Understand the key concepts of storage and take advantage of recent APIs to improve both your developer productivity and users’ privacy.
Photo Credits: Susan Q Yin |
Discover the benefits of Android shared storage and simplify your file management. Learn about the Android file system, how to easily share files on Android, and optimize your data management. Unlock the potential of seamless file sharing and efficient data organization on your Android device.
Storage Architecture
Android provides different APIs to access or expose files with different
tradeoffs. You can use app data to store user personal info only accessible to
the app or can use shared storage for user data that can or should be
accessible to other apps and saved even if the user uninstalls your app.
Credits: Android Dev Summit |
History of Storage Permissions
Up to Android 9, files in the shared storage were readable and writable by
apps that requested the proper permissions which are
WRITE_EXTERNAL_STORAGE
and READ_EXTERNAL_STORAGE.
On Android 10, Google released a privacy upgrade regarding shared file
access named
Scoped Storage
.
So from Android 10 onwards,
the app will give limited access (scoped access) only to media files
like photos, videos, and audio by requesting
READ_EXTERNAL_STORAGE.
WRITE_EXTERNAL_STORAGE
is now deprecated and no longer required
to add files to shared storage anymore.
PDF, ZIP, and DOCX files are accessible through the
Storage Access Framework (SAF) via document picker. Document picker
allows users to retain complete control over which document files they give
access to the app.
As per privacy concerns, Android has removed location metadata from the
media files i.e. photos, videos, and audio unless the app has asked for
ACCESS_MEDIA_LOCATION
permission.
Common Storage Use Cases
Let’s explore some of the common use cases for Android Storage and which API
to use.
Downloading a file to internal storage
Let’s say you want to download a file from API response and store it in
internal storage only accessible to your app. We will use
filesDir
which allows us to store files in an internal
directory for the application.
// create client and request val client = OkHttpClient() val request = Request.Builder().url(CONFIG_URL).build() /** /* By using .use() method, it will close any underlying network socket /* automatically at the end of lambdas to avoid memory leaks */ client.newCall(request).execute().use { response -> response.body?.byteStream()?.use { input -> // using context.filesDir data will be stored into app's internal storage val target = File(context.filesDir, "user-config.json") target.outputStream().use { output -> input.copyTo(output) } } }
Store Files based on available location
Let’s imagine you want to download a big file/asset in our app that is not
confidential but meaningful only to our app.
val fileSize = 500_000_000L // 500MB // check if filesDir has usable space bigger than our file size, // if not we can check into app's external storage directory. val target= if(context.filesDir.usableSpace > fileSize) { context.filesDir } else { context.getExternalFilesDir(null).find { externalStorage -> externalStorage.usableSpace > fileSize } } ?: throw IOException("Not Enough Space") // create and save the file based on the target val file = File(target, "big-file.asset")
Add image to shared storage
Now, let’s look into how we can add a media file to shared storage. Please
note that if we save files to shared storage, users can access them through
other apps.
To save the image, we require to ask for
WRITE_EXTERNAL_STORAGE
permission up to Android 9. From Android
10 onwards, we don’t require to ask this permission anymore.
fun saveMediaToStorage(context: Context, bitmap: Bitmap) { // Generating a file name val filename = BILL_FILE_NAME + "_${System.currentTimeMillis()}.jpg" // Output stream var fos: OutputStream? = null // For devices running android >= Q if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // getting the contentResolver context.contentResolver?.also { resolver -> // Content resolver will process the content values val contentValues = ContentValues().apply { // putting file information in content values put(MediaStore.MediaColumns.DISPLAY_NAME, filename) put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg") put( MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM + BILL_FILE_DIR ) } // Inserting the contentValues to contentResolver // and getting the Uri val imageUri: Uri? = resolver.insert( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues ) // Opening an output stream with the Uri that we got fos = imageUri?.let { resolver.openOutputStream(it) } } } else { // These for devices running on android < Q val imagesDir = Environment .getExternalStoragePublicDirectory( Environment.DIRECTORY_DCIM + BILL_FILE_DIR ) // check if the imagesDir exist, if not make a one if (!imagesDir.exists()) { imagesDir.mkdir() } val image = File(imagesDir, filename) fos = FileOutputStream(image) // request the media scanner to scan the files // at the specified path with a callback MediaScannerConnection.scanFile( context, arrayOf(image.toString()), arrayOf("image/jpeg") ) { path, uri -> Log.d("Media Scanner", "New Image - $path || $uri") } } fos?.use { // Finally writing the bitmap to the output stream that we opened bitmap.compress(Bitmap.CompressFormat.JPEG, QUALITY, it) } }
Select a file with the document picker
Now, let’s say we need to access document files, so we will rely on the
document picker via the action
For that, I’m using Jetpack Activity dependency in the project.
OpenDocument
Intent.
// add the Jetpack Activity dependency first // create documentPicker object by registering for OpenDocument activity result // which will handle the intent-resolve logic val documentPicker = rememberLauncherForActivityResult(OpenDocument()) { uri -> if(uri == null) return context.contentResolver.openInputStream(uri)?.use { // we can copy the file content, you can refer to above code // to save that content to file or use it other way. } } // usage: launch our intent-handler with MIME type of PDF documentPicker.launch(arrayOf("application/pdf"))
The action
OpenDocument
Intent is available on devices running
4.4 and higher.
Conclusion
Android is working on improving privacy and transparency for Android users
along with the latest releases with event UX enhancements like the photo
picker.
Android is also working on adding more Permission-less APIs that keep the
user in control of giving access without the need of requesting permissions
on the app side.
For more detailed information, please read the documentation for Scoped
Storage.
thanks for sharing this with us
ReplyDelete