Cause things out

I use file manager a lot. I need to import and export files, and every time I am faced with a messy list of files in external storage, as a Virgoan OBSessive-compulsive disorder, I feel very uncomfortable

Why?

As we all know, Android storage permissions have been criticized for allowing developers to do whatever they want with read and write permissions, but some developers are stupid enough to mess with user storage!

Let’s take a look at some of the directories some apps create externally:

Type 1: Create folders to save your Pictures and videos.

Type 2: cache files directly outside the user has no idea who built it and what you do

Ultimate idiot: Create package name folder directly, you who ah, penguin amazing ah

Some people may think this is ok, how many users look at file manager every day, but I need to correct these bad habits when I encounter ocD!

Talk about read and write

Read and write permission is to obtain the external storage permission of the mobile phone, that is, the external storage space in the entire directory you can read and write. Except for saving pictures, videos and some large files, most files can be placed in the App private directory, which does not require permissions for reading and writing. Most of our temporary files, cache, KV and database are put here.

So let’s consider this: Do we really need read and write permissions when we’re not reading or writing large files or other app data?

Android File System

Android file permissions are changing all the time, but it still doesn’t work. I talked about the changes in Android storage behavior before – juejin (cn) said partition storage and permissions changes, I suggest you can take a look.

Due to the existence of SdCard in the early days, Android has always followed this tradition. Although most mobile phones do not have SdCard now, it is still visible in the phone manager. In fact, this is the virtual address mapped by the system, which is generally called external storage space.

APP Private Space

Internal storage is divided into internal private and external private, that is, this part of the space is private app, theoretically other apps can not access. Arrange such a space for your app on the internal storage space and external storage space of the phone. We usually use it to cache files and store sensitive files.

Memory space private directory:

It is usually named by the package name. Where is it? Look at the picture…

Path accessed through the system API: /data/user/0/app_packageName/… /data/date/app_packageName/…

App private directory in external storage space:

This directory is still not secure until Android10, other apps can still read and write after obtaining external read and write permissions. On Android11, the introduction of partitioned storage is secure and belongs to a true app private directory.

So how do we solve this

This depends on the level of awareness of the developers, some projects are old, many people are not willing to pay the price of modification.

Here’s how I did it:

  • Resources such as Pictures, videos, and documents can be used in the DCIM, Pictures, Video, Download, and Document directoriesMediaStoreandFileProviderInsert.
  • Memory private space stores sensitive files
  • External private space stores temporary caches

Utility class

Here share my common file management tools class, I wish you no longer file chaos

FilePath: FilePath management tool class

Object FilePath {/*---------------------- External: Partition storage directory -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * * * * / / partition storage - the Cache directory * / fun getAppExternalCachePath (subDir: String? =null):String{ val path = StringBuilder(getContext().externalCacheDir? .absolutePath) subDir? .let { path.append(File.separator).append(it).append(File.separator) } val dir = File(path.toString()) if (! Dir.exists () dir.mkdir() return path.tostring ()} /** * Partition storage -File directory */ fun getAppExternalFilePath(subDir: String? =null):String{ val path = getContext().getExternalFilesDir(subDir)? .absolutePath val dir = File(path.toString()) if (! dir.exists()) dir.mkdir() return path.toString() } /*--------------------------------------------------*/ / * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- internal: private directory -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * * * * / / private directory - files * / fun getAppFilePath (subDir: String? =null): String { val path = StringBuilder(getContext().filesDir.absolutePath) subDir? .let { path.append(File.separator).append(it).append(File.separator) } val dir = File(path.toString()) if (! Dir.exists () dir.mkdir() return path.tostring ()} /** * Private directory -cache */ fun getAppCachePath(subDir:String? =null):String{ val path = StringBuilder(getContext().cacheDir.absolutePath) subDir? .let { path.append(File.separator).append(it).append(File.separator) } val dir = File(path.toString()) if (! Dir. The exists ()) dir. The mkdir () return path. The toString ()} / * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the cache directory -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * / fun getAudioPathEndWithSeparator(): String { return getAppCachePath("audio") } fun getTxtPathEndWithSeparator(): String { return getAppCachePath("txt") } fun getMp3PathEndWithSeparator(): String { return getAppCachePath("mp3") } fun getTempPathEndWithSeparator(): String {return getAppCachePath (" temp ")} / * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * / / * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- external: Public directory (permission) -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * / / * * * * / fun Pictures getExternalPicturesPath (subDir: String? =null): String{ val path = StringBuilder(Environment.getExternalStorageDirectory().absolutePath) .append(File.separator) .append(Environment.DIRECTORY_PICTURES) subDir? .let { path.append(File.separator).append(it).append(File.separator) } val dir = File(path.toString()) if (! dir.exists()) dir.mkdir() return path.toString() } /** * Download */ fun getExternalDownloadPath(subDir:String? =null): String{ val path = StringBuilder(Environment.getExternalStorageDirectory().absolutePath) .append(File.separator) .append(Environment.DIRECTORY_DOWNLOADS) subDir? .let { path.append(File.separator).append(it).append(File.separator) } val dir = File(path.toString()) if (! dir.exists()) dir.mkdir() return path.toString() } /** * DCIM */ fun getExternalCameraPath( subDir:String? =null): String{ val path = StringBuilder(Environment.getExternalStorageDirectory().absolutePath) .append(File.separator) .append(Environment.DIRECTORY_DCIM) subDir? .let { path.append(File.separator).append(it).append(File.separator) } val dir = File(path.toString()) if (! dir.exists()) dir.mkdir() return path.toString() } /** * Music */ fun getExternalMusicPath(subDir:String? =null): String{ val path = StringBuilder(Environment.getExternalStorageDirectory().absolutePath) .append(File.separator) .append(Environment.DIRECTORY_MUSIC) subDir? .let { path.append(File.separator).append(it).append(File.separator) } val dir = File(path.toString()) if (! dir.exists()) dir.mkdir() return path.toString() } /*---------------------------------------------------------*/ }Copy the code

FileUtil file manipulation tools:

Object FileUtil{/** * File to Uri */ fun file2Uri(File: File?) : Uri? {if (file==null) return null return if (build.version.sdk_int >= build.version_codes.n) {// Adaptation of Android 7.0 file permissions, Create a content type Uri through FileProvider FileProvider. GetUriForFile (getContext (), "${getContext (). The packageName}. FileProvider", File)} else {uri.fromfile (file)}} /** * Convert file to byte array */ fun file2Byte(file: file?): ByteArray? { if (file==null) return null var buffer: ByteArray? = null try { val fis = FileInputStream(file) val bos = ByteArrayOutputStream() val b = ByteArray(1024) var n:  Int while (fis.read(b).also { n = it } ! = -1) { bos.write(b, 0, n) } fis.close() bos.close() buffer = bos.toByteArray() } catch (e: FileNotFoundException) { e.printStackTrace() } catch (e: IOException) {e.printStackTrace()} return buffer} /** * Uri to File */ fun uri2File(Uri: Uri?) : File? { if (uri==null) return null var file:File ? = File(uri.toString()) if (file! =null && file.exists()) return file if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { when (uri.scheme) { Contentresolver.scheme_file -> {file = file (requireNotNull(uri.path))} contentresolver.scheme_content -> {// Save the file to the sandbox val contentResolver = getContext().contentResolver val displayName = "${TimeUtil.getCurrentTime()}.${ MimeTypeMap.getSingleton().getExtensionFromMimeType( contentResolver.getType(uri) ) }".replace(".bin","") val ios = contentResolver.openInputStream(uri) if (ios ! = null) { file = File(FilePath.getTxtPathEndWithSeparator(), displayName).apply { val fos = FileOutputStream(this) FileUtils.copy(ios, fos) fos.close() ios.close() } } } else -> { } } return file }else{ var path: String? = null when(uri.scheme){ "file" -> { path = uri.encodedPath if (path ! = null) { path = Uri.decode(path) val cr = getContext().contentResolver val buff = StringBuffer() buff.append("(").append(MediaStore.Images.ImageColumns.DATA).append("=") .append("'$path'").append(")") val cur: Cursor? = cr.query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, arrayOf( MediaStore.Images.ImageColumns._ID, MediaStore.Images.ImageColumns.DATA ), buff.toString(), null, null ) var index = 0 var dataIdx = 0 cur?.let { cur.moveToFirst() while (! cur.isAfterLast()) { index = cur.getColumnIndex(MediaStore.Images.ImageColumns._ID) index = cur.getInt(index) dataIdx = cur.getColumnIndex(MediaStore.Images.ImageColumns.DATA) path = cur.getString(dataIdx) cur.moveToNext() } cur.close() } if (index == 0) { } else { val u = Uri.parse("content://media/external/images/media/$index") println("temp uri is :$u") "Content"}}} - > {/ / val after 4.2.2 proj = arrayOf (MediaStore. Images. Media. DATA) val cursor: cursor? = getContext().contentResolver.query(uri, proj, null, null, null) cursor? .let { if (cursor.moveToFirst()) { val columnIndex: Int = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA) path = cursor.getString(columnIndex) } cursor.close() }  } else -> { //Log.i(TAG, "Uri Scheme:" + uri.getScheme()); }} return File(path)}} /** * Delete folder */ fun deleteRecursive(fileOrDirectory: File) { if (fileOrDirectory.isDirectory) for (child in fileOrDirectory.listFiles()) deleteRecursive( child ) fileOrDirectory.delete() } fun deleteCacheDir() = thread{ File(FilePath.getTempPathEndWithSeparator()).deleteRecursively() File(FilePath.getMp3PathEndWithSeparator()).deleteRecursively() File(FilePath.getTxtPathEndWithSeparator()).deleteRecursively() File(FilePath.getAudioPathEndWithSeparator()).deleteRecursively() } }Copy the code

There are also some media manipulation tools, such as save pictures, videos, create PDF files, TXT files. All of these are posted on GitHub, if you are interested, please check them out.

AndroidStorageDemo

Android storage behavior changes – Juejin (cn)