- Saving Files
- This lesson teaches you to
- You should also read
- Choose Internal or External Storage
- Obtain Permissions for External Storage
- Save a File on Internal Storage
- Save a File on External Storage
- Query Free Space
- Delete a File
- How to get away with READ/WRITE permissions on Android
- Create a file
- Write to internal storage
- Write to cache
- Write to external storage
- Read a file
- Reading from external storage
- Share a file
- Setup FileProvider
- Specify available files
- Share files
- Epilogue
- Redux architecture and Android
Saving Files
This lesson teaches you to
You should also read
Android uses a file system that’s similar to disk-based file systems on other platforms. This lesson describes how to work with the Android file system to read and write files with the File APIs.
A File object is suited to reading or writing large amounts of data in start-to-finish order without skipping around. For example, it’s good for image files or anything exchanged over a network.
This lesson shows how to perform basic file-related tasks in your app. The lesson assumes that you are familiar with the basics of the Linux file system and the standard file input/output APIs in java.io .
Choose Internal or External Storage
All Android devices have two file storage areas: «internal» and «external» storage. These names come from the early days of Android, when most devices offered built-in non-volatile memory (internal storage), plus a removable storage medium such as a micro SD card (external storage). Some devices divide the permanent storage space into «internal» and «external» partitions, so even without a removable storage medium, there are always two storage spaces and the API behavior is the same whether the external storage is removable or not. The following lists summarize the facts about each storage space.
- It’s always available.
- Files saved here are accessible by only your app by default.
- When the user uninstalls your app, the system removes all your app’s files from internal storage.
Internal storage is best when you want to be sure that neither the user nor other apps can access your files.
- It’s not always available, because the user can mount the external storage as USB storage and in some cases remove it from the device.
- It’s world-readable, so files saved here may be read outside of your control.
- When the user uninstalls your app, the system removes your app’s files from here only if you save them in the directory from getExternalFilesDir() .
External storage is the best place for files that don’t require access restrictions and for files that you want to share with other apps or allow the user to access with a computer.
Tip: Although apps are installed onto the internal storage by default, you can specify the android:installLocation attribute in your manifest so your app may be installed on external storage. Users appreciate this option when the APK size is very large and they have an external storage space that’s larger than the internal storage. For more information, see App Install Location.
Obtain Permissions for External Storage
To write to the external storage, you must request the WRITE_EXTERNAL_STORAGE permission in your manifest file:
Caution: Currently, all apps have the ability to read the external storage without a special permission. However, this will change in a future release. If your app needs to read the external storage (but not write to it), then you will need to declare the READ_EXTERNAL_STORAGE permission. To ensure that your app continues to work as expected, you should declare this permission now, before the change takes effect.
However, if your app uses the WRITE_EXTERNAL_STORAGE permission, then it implicitly has permission to read the external storage as well.
You don’t need any permissions to save files on the internal storage. Your application always has permission to read and write files in its internal storage directory.
Save a File on Internal Storage
When saving a file to internal storage, you can acquire the appropriate directory as a File by calling one of two methods:
getFilesDir() Returns a File representing an internal directory for your app. getCacheDir() Returns a File representing an internal directory for your app’s temporary cache files. Be sure to delete each file once it is no longer needed and implement a reasonable size limit for the amount of memory you use at any given time, such as 1MB. If the system begins running low on storage, it may delete your cache files without warning.
To create a new file in one of these directories, you can use the File() constructor, passing the File provided by one of the above methods that specifies your internal storage directory. For example:
Alternatively, you can call openFileOutput() to get a FileOutputStream that writes to a file in your internal directory. For example, here’s how to write some text to a file:
Or, if you need to cache some files, you should instead use createTempFile() . For example, the following method extracts the file name from a URL and creates a file with that name in your app’s internal cache directory:
Note: Your app’s internal storage directory is specified by your app’s package name in a special location of the Android file system. Technically, another app can read your internal files if you set the file mode to be readable. However, the other app would also need to know your app package name and file names. Other apps cannot browse your internal directories and do not have read or write access unless you explicitly set the files to be readable or writable. So as long as you use MODE_PRIVATE for your files on the internal storage, they are never accessible to other apps.
Save a File on External Storage
Because the external storage may be unavailable—such as when the user has mounted the storage to a PC or has removed the SD card that provides the external storage—you should always verify that the volume is available before accessing it. You can query the external storage state by calling getExternalStorageState() . If the returned state is equal to MEDIA_MOUNTED , then you can read and write your files. For example, the following methods are useful to determine the storage availability:
Although the external storage is modifiable by the user and other apps, there are two categories of files you might save here:
Public files Files that should be freely available to other apps and to the user. When the user uninstalls your app, these files should remain available to the user.
For example, photos captured by your app or other downloaded files.
Private files Files that rightfully belong to your app and should be deleted when the user uninstalls your app. Although these files are technically accessible by the user and other apps because they are on the external storage, they are files that realistically don’t provide value to the user outside your app. When the user uninstalls your app, the system deletes all files in your app’s external private directory.
For example, additional resources downloaded by your app or temporary media files.
If you want to save public files on the external storage, use the getExternalStoragePublicDirectory() method to get a File representing the appropriate directory on the external storage. The method takes an argument specifying the type of file you want to save so that they can be logically organized with other public files, such as DIRECTORY_MUSIC or DIRECTORY_PICTURES . For example:
If you want to save files that are private to your app, you can acquire the appropriate directory by calling getExternalFilesDir() and passing it a name indicating the type of directory you’d like. Each directory created this way is added to a parent directory that encapsulates all your app’s external storage files, which the system deletes when the user uninstalls your app.
For example, here’s a method you can use to create a directory for an individual photo album:
If none of the pre-defined sub-directory names suit your files, you can instead call getExternalFilesDir() and pass null . This returns the root directory for your app’s private directory on the external storage.
Remember that getExternalFilesDir() creates a directory inside a directory that is deleted when the user uninstalls your app. If the files you’re saving should remain available after the user uninstalls your app—such as when your app is a camera and the user will want to keep the photos—you should instead use getExternalStoragePublicDirectory() .
Regardless of whether you use getExternalStoragePublicDirectory() for files that are shared or getExternalFilesDir() for files that are private to your app, it’s important that you use directory names provided by API constants like DIRECTORY_PICTURES . These directory names ensure that the files are treated properly by the system. For instance, files saved in DIRECTORY_RINGTONES are categorized by the system media scanner as ringtones instead of music.
Query Free Space
If you know ahead of time how much data you’re saving, you can find out whether sufficient space is available without causing an IOException by calling getFreeSpace() or getTotalSpace() . These methods provide the current available space and the total space in the storage volume, respectively. This information is also useful to avoid filling the storage volume above a certain threshold.
However, the system does not guarantee that you can write as many bytes as are indicated by getFreeSpace() . If the number returned is a few MB more than the size of the data you want to save, or if the file system is less than 90% full, then it’s probably safe to proceed. Otherwise, you probably shouldn’t write to storage.
Note: You aren’t required to check the amount of available space before you save your file. You can instead try writing the file right away, then catch an IOException if one occurs. You may need to do this if you don’t know exactly how much space you need. For example, if you change the file’s encoding before you save it by converting a PNG image to JPEG, you won’t know the file’s size beforehand.
Delete a File
You should always delete files that you no longer need. The most straightforward way to delete a file is to have the opened file reference call delete() on itself.
If the file is saved on internal storage, you can also ask the Context to locate and delete a file by calling deleteFile() :
Note: When the user uninstalls your app, the Android system deletes the following:
- All files you saved on internal storage
- All files you saved on external storage using getExternalFilesDir() .
However, you should manually delete all cached files created with getCacheDir() on a regular basis and also regularly delete other files you no longer need.
Источник
How to get away with READ/WRITE permissions on Android
Android has been using permissions since its beginning but never really enforced the correct usage until Marshmallow and developers took advantage of it and went rampant. Since Marshmallow (API 23, Android 6.0), android ecosystem introduced permission requests and now we have to ask user to explicitly provide permissions which is really good in the sense of privacy for the users, but adds a lot of work for developers and provides kind of bad user experience.
This article has a bit of a clickbait title and does not focus on any hacks but leverages Android’s ecosystem to show how an app can read, write and share files without requiring READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE . Each app in Android runs in its own system, like a sandbox. These sandboxes are not connected from one another which provides security. Now, to work with other apps, Android system has provided certain tools and one of them is FileProvider. FileProvider is a special subclass of ContentProvider that facilitates secure file sharing with other apps by creating content:// uri. We will come back to this later.
Note: This post is not intended towards Read/Write heavy files such as a photo gallery or a music player. If your app occasionally write a file, eg. export some file or save an image once in a while, you may find this interesting.
Let’s begin by checking what different methods and storage options we have to create a file.
Create a file
As the title says, you don’t need to have WRITE_EXTERNAL_STORAGE permission to create a file and write to it. If you use app’s internal storage to write a file, you don’t need write permission.
Write to internal storage
Files in your app’s internal storage are private and secure from other apps. On Android 6.0 and lower, other apps can read your app’s internal files if you set the file mode to world readable. This mode has been deprecated and will throw a SecurityException from Android 7.0. To share an internal file, you need to use FileProvider .
Create a file in internal storage — context.getFilesDir() returns a File representing an internal directory of your app.
You may also use context.openFileOutput() to get a FileOutputStream to write.
Read more about writing to internal storage on Android docs — Write to internal storage
Write to cache
App’s cache directory is uniquely associated with the app. This directory is meant for temporary storage and the system may delete the files if running low on storage.
Write to external storage
Now, coming to the most important part. We can write to external storage without asking for WRITE_EXTERNAL_STORAGE permission. Android 4.4 (API 19) introduced Storage Access Framework (SAF) which makes it simple for users to browse and open documents, images, and other files across all of their their preferred document storage providers. We’ll use SAF to create a new file and write to it.
This intent will trigger Storage Access Framework and the user will see a document browser prompting user to save the file. Once user saves the file, the app gets callback with uri of the file. mimeType will depend on what type of file you’re going to write. eg. txt/plain for a text file, image/png for a png image, application/pdf for a PDF, etc.
The Storage Access Framework creates a new file with the given name and provides your app with a uri generated from FileProvider. Your app gets temporary access to write to this file (via uri). We can use FileDescription to write to this file.
That’s it! You have created a file in external storage and written to it without asking user for storage permissions. Read more about SAF on android docs — Document Provider.
Read a file
To read a file from external storage, your app needs to have READ_EXTERNAL_STORAGE permission (or WRITE_EXTERNAL_STORAGE permission). But we can leverage Storage Access Framework, other apps (gallery, file explorer, etc) to let user a pick a file that they want to use. Reading from internal storage or cache directory does not require any system permissions.
Reading from external storage
mimeType is based on what type of file you want user to select. eg. txt/plain for a text file, image/png for a png image, application/pdf for a PDF, etc. This intent will trigger an application (or an app chooser) using which user can pick a file. This will return your app an uri generated from FileProvider and your app gets temporary access to read the file.
If it’s a text file, reading the contents is very easy. context.contentResolver.openInputStream(uri).reader().readText()
If it’s an image file, you can use FileDescriptior to convert to Bitmap.
So yes, if you have an app which seldom reads from external storage, you may use this method to read files without requiring explicit permissions. Read more about Document Provider.
Share a file
Sharing some information from the app has become one of the most basic requirements and users want to share a lot of things with their friends, eg. a note, an image, something that they drew, etc. To improve security and also to make sure that other apps with which the content is being shared with has the correct permissions, Android introduced FileProvider. As mentioned above, FileProvider is a special subclass of ContentProvider that facilitates secure file sharing with other apps by creating content:// uri.
Above we used other apps to create and read files from external storage. This is only possible because of FileProvider. Android does not allow sharing file:// uri and will immediately throw FileUriExposedExceptio which will crash the app. So when an app wants to share a file with other apps, the app creates a content uri using FileProvider, grants temporary read or write access to the other app (via intent) so that the other apps can have access to the file regardless if they have READ/WRITE permission or not.
I think FileProvider is the single most useful feature in android ecosystem. FileProvider comes with support library so we don’t have to worry about OEM updates.
Setup FileProvider
Setting up FileProvider takes about 5 mins and once it’s done, you don’t have to worry about it at all.
In your app’s AndroidManifest declare a provider.
android:authorities attributes to a URI authority based on a domain you control; for example, if you control the domain mydomain.com you should use the authority com.mydomain.fileprovider. Or you can just set it based on your app id. If you use $
Keep android:exported attribute as false as the FileProvider does not need to be public. Set the android:grantUriPermissions attribute to true, to allow you to grant temporary access to files.
Specify available files
In the meta-data you have to define what files are available through fileprovider for other apps to use. This does not mean that other apps have access to those files. Other apps will have access only when your app generates a content uri using FileProvider, shares it with the other apps via intent and grants uri permissions.
Create a folder named xml in your app’s res directory and create a file named fileproviderpaths.xml . You may keep any name, just make sure that it’s reflected in the manifest.
name provides path segment to uri which increases security as this value hides the name of the subdirectory that your app is sharing. path provides subdirectory that you are sharing. path value has to be a subdirectory. You can not share a file by its file name or using wildcards.
There are different tags available for different types of storage.
- is for internal storage subdirectory.
- is for external storage subdirectory.
- is for cache storage subdirectory.
You can find more types of tags to specify files on Android docs — Specifying available files.
Once this setup is done, we are ready to share files with other apps.
Share files
So, yeah. You have shared your file securely with other app and the other app has temporary access to your file via content uri.
You should definitely read more about FileProvider and how cool it is on Android docs — FileProvider.
As FileProvider often likes to say and I quote
FileProvider is a really great concept and it works so well. You can request longer access to files using it and so much more.
Epilogue
I was adding support for offline export and import of data in my app — Flutter: Instant Movie Ratings (written in Kotlin, not Flutter), I added permission to manifest, did the whole shebang of asking permissions to write files. I really don’t like permissions so I google-fu’ed and found out about Storage Access Framework, DocumentProvider, FileProvider and re-did the whole import/export functionality and removed the permissions. The app request permission just for internet access.
Redux architecture and Android
Learn the Redux architecture and implement the clean architecture it in your Android codebase. This series of posts explores the depth of Redux and guides you through implementing the architecture in Kotlin for your android app.
Источник