- Sharing Content between Android apps
- Sharing text
- Sharing HTML text
- Receiving text
- Sharing files and images
- Receiving files
- The Support Library is your friend
- Data and file storage overview
- Categories of storage locations
- Permissions and access to external storage
- Scoped storage
- View files on a device
- Additional resources
- Videos
- Sharing Content with Intents
Sharing Content between Android apps
Sharing is caring, as they say, but sharing on Android means something perhaps slightly different. ‘Sharing’ is really shorthand for sending content such as text, formatted text, files, or images between apps.
So if ‘sharing’ == sending content, it makes slightly more sense that it is implemented using ACTION_SEND (or ACTION_SEND_MULTIPLE) Intents and its dozen extras.
While that approach is perfectly valid, I prefer to use ShareCompat, a set of classes in the v4 Support Library designed to make it easy to build intents for sharing content.
Sharing text
Sharing plain text is, as you might imagine, a good place to start. In fact, there’s not a whole lot to it:
ShareCompat.IntentBuilder uses a fluent API where you can chain together multiple method calls, using only the ones you need. For sharing, one of the most important parts is picking the right mime type — this is how apps filter what type of content they can receive. By using text/plain, we signify that our Intent will only contain plain text. Then, of course, setText() is how we actually add the CharSequence to the Intent to send. And while you can certainly send styled text using setText(), there’s no guarantee that the receiving app will honor that styling, so you should ensure that the text is legible with or without styling.
You’ll note we then use resolveActivity() before calling startActivity(). As mentioned in Protecting Implicit Intents with Runtime Checks, this is critical to prevent an ActivityNotFoundException when there is no Activity available to handle the mime type you have selected. While probably not as much of a concern with text/plain, it may be much more common with other types.
Note: when you use startActivity(shareIntent), that respects any default apps the user has set (i.e., if they’ve previously selected sharing all “text/plain” items to a certain app). If you’d like to instead always show a disambiguation chooser, use the intent generated from IntentBuilder.createChooserIntent() as explained in the ACTION_CHOOSER documentation.
Sharing HTML text
Some apps, most notably email clients, also support formatting with HTML. The changes, compared to plain text, are fairly minor:
The differences here are that we use of setHtmlText() in place of setText() and a mime type of text/html replacing text/plain. Here ShareCompat actually does a little bit extra: setHtmlText() also uses Html.fromHtml() to create a fallback formatted text to pass along to the receiving app if you haven’t previously called setText() yourself.
Given that many of the apps that can receive HTML text are email clients, there’s a number of helper methods to set the subject, to:, cc:, and bcc: email addresses as well — consider adding at least a subject to any share intent for best compatibility with email apps.
Of course, you’ll still want to call resolveActivity() just as before — nothing changes there.
Receiving text
While the focus so far has been on the sending side, it is helpful to know exactly what is happening on the other side (if not just to build a simple receiving app to install on your emulator for testing purposes). Receiving Activities add an intent filter to the Activity:
The action is obviously the more critical part — without that there’s nothing that would denote this as an ACTION_SEND (the action behind sharing). The mime type, same as with our sending code, is also present here. What isn’t as obvious are the two categories. From the element documentation:
Note: In order to receive implicit intents, you must include the CATEGORY_DEFAULT category in the intent filter. The methods startActivity() and startActivityForResult() treat all intents as if they declared the CATEGORY_DEFAULT category. If you do not declare it in your intent filter, no implicit intents will resolve to your activity.
So CATEGORY_DEFAULT is required for our use case. Then, CATEGORY_BROWSABLE allows web pages to natively share into apps without any extra effort required on the receiving side.
And to actually extract the information from the Intent, the useful ShareCompat.IntentReader can be used:
Similar to IntentBuilder, IntentReader is just a simple wrapper that make it easy to extract information.
Sharing files and images
While sending and receiving text is straightforward enough (create text, include it in Intent), sending files (and particularly images — the most common type by far) has an additional wrinkle: file permissions.
The simplest code you might try might look like
And that almost works — the tricky part is in getting a Uri to the File that other apps can actually read, particularly when it comes to Android 6.0 Marshmallow devices and runtime permissions (which include the now dangerous READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions).
My plea: don’t use Uri.fromFile(). It forces receiving apps to have the READ_EXTERNAL_STORAGE permission, won’t work at all if you are trying to share across users, and prior to KitKat, would require your app to have WRITE_EXTERNAL_STORAGE. And really important share targets, like Gmail, won’t have the READ_EXTERNAL_STORAGE permission — so it’ll just fail.
Instead, you can use URI permissions to grant other apps access to specific Uris. While URI permissions don’t work on file:// URIs as is generated by Uri.fromFile(), they do work on Uris associated with Content Providers. Rather than implement your own just for this, you can and should use FileProvider as explained in the File Sharing Training.
Once you have it set up, our code becomes:
Using FileProvider.getUriForFile(), you’ll get a Uri actually suitable for sending to another app — they’ll be able to read it without any storage permissions — instead, you are specifically granting them read permission with FLAG_GRANT_READ_URI_PERMISSION.
Note: we don’t call setType() anywhere when building our ShareCompat (even though in the video I did set it). As explained in the setDataAndType() Javadoc, the type is automatically inferred from the data URI using getContentResolver().getType(uriToImage). Since FileProvider returns the correct mime type automatically, we don’t need to manually specify a mime type at all.
If you’re interested in learning more about avoiding the storage permission, consider watching my Forget the Storage Permission talk or at least go through the slides, which covers this topic in depth at 14:55 (slide 11).
Receiving files
Receiving files isn’t too different from text because you’re still going to use ShareCompat.IntentReader. For example, to make a Bitmap out of an incoming file, it would look like:
Of course, you’re free to do whatever you want with the InputStream — watch out for images that are so large you hit an OutOfMemoryException. All of the things you know about loading Bitmaps still apply.
The Support Library is your friend
With both ShareCompat (and its IntentBuilder and IntentReader) and FileProvider in the v4 Support Library, you’ll be able to include sharing text, HTML text, and files in your app with the best practices by default.
Источник
Data and file storage overview
Android uses a file system that’s similar to disk-based file systems on other platforms. The system provides several options for you to save your app data:
- App-specific storage: Store files that are meant for your app’s use only, either in dedicated directories within an internal storage volume or different dedicated directories within external storage. Use the directories within internal storage to save sensitive information that other apps shouldn’t access.
- Shared storage: Store files that your app intends to share with other apps, including media, documents, and other files.
- Preferences: Store private, primitive data in key-value pairs.
- Databases: Store structured data in a private database using the Room persistence library.
The characteristics of these options are summarized in the following table:
Type of content | Access method | Permissions needed | Can other apps access? | Files removed on app uninstall? | |
---|---|---|---|---|---|
App-specific files | Files meant for your app’s use only | From internal storage, getFilesDir() or getCacheDir() From external storage, getExternalFilesDir() or getExternalCacheDir() | Never needed for internal storage Not needed for external storage when your app is used on devices that run Android 4.4 (API level 19) or higher | No | Yes |
Media | Shareable media files (images, audio files, videos) | MediaStore API | READ_EXTERNAL_STORAGE when accessing other apps’ files on Android 11 (API level 30) or higher READ_EXTERNAL_STORAGE or WRITE_EXTERNAL_STORAGE when accessing other apps’ files on Android 10 (API level 29) Permissions are required for all files on Android 9 (API level 28) or lower | Yes, though the other app needs the READ_EXTERNAL_STORAGE permission | No |
Documents and other files | Other types of shareable content, including downloaded files | Storage Access Framework | None | Yes, through the system file picker | No |
App preferences | Key-value pairs | Jetpack Preferences library | None | No | Yes |
Database | Structured data | Room persistence library | None | No | Yes |
The solution you choose depends on your specific needs:
How much space does your data require? Internal storage has limited space for app-specific data. Use other types of storage if you need to save a substantial amount of data. How reliable does data access need to be? If your app’s basic functionality requires certain data, such as when your app is starting up, place the data within internal storage directory or a database. App-specific files that are stored in external storage aren’t always accessible because some devices allow users to remove a physical device that corresponds to external storage. What kind of data do you need to store? If you have data that’s only meaningful for your app, use app-specific storage. For shareable media content, use shared storage so that other apps can access the content. For structured data, use either preferences (for key-value data) or a database (for data that contains more than 2 columns). Should the data be private to your app? When storing sensitive data—data that shouldn’t be accessible from any other app—use internal storage, preferences, or a database. Internal storage has the added benefit of the data being hidden from users.
Categories of storage locations
Android provides two types of physical storage locations: internal storage and external storage. On most devices, internal storage is smaller than external storage. However, internal storage is always available on all devices, making it a more reliable place to put data on which your app depends.
Removable volumes, such as an SD card, appear in the file system as part of external storage. Android represents these devices using a path, such as /sdcard .
Apps themselves are stored within internal storage by default. If your APK size is very large, however, you can indicate a preference within your app’s manifest file to install your app on external storage instead:
Permissions and access to external storage
On earlier versions of Android, apps needed to declare the READ_EXTERNAL_STORAGE permission to access any file outside the app-specific directories on external storage. Also, apps needed to declare the WRITE_EXTERNAL_STORAGE permission to write to any file outside the app-specific directory.
More recent versions of Android rely more on a file’s purpose than its location for determining an app’s ability to access, and write to, a given file. In particular, if your app targets Android 11 (API level 30) or higher, the WRITE_EXTERNAL_STORAGE permission doesn’t have any effect on your app’s access to storage. This purpose-based storage model improves user privacy because apps are given access only to the areas of the device’s file system that they actually use.
Android 11 introduces the MANAGE_EXTERNAL_STORAGE permission, which provides write access to files outside the app-specific directory and MediaStore . To learn more about this permission, and why most apps don’t need to declare it to fulfill their use cases, see the guide on how to manage all files on a storage device.
Scoped storage
To give users more control over their files and to limit file clutter, apps that target Android 10 (API level 29) and higher are given scoped access into external storage, or scoped storage, by default. Such apps have access only to the app-specific directory on external storage, as well as specific types of media that the app has created.
Use scoped storage unless your app needs access to a file that’s stored outside of an app-specific directory and outside of a directory that the MediaStore APIs can access. If you store app-specific files on external storage, you can make it easier to adopt scoped storage by placing these files in an app-specific directory on external storage. That way, your app maintains access to these files when scoped storage is enabled.
To prepare your app for scoped storage, view the storage use cases and best practices guide. If your app has another use case that isn’t covered by scoped storage, file a feature request. You can temporarily opt-out of using scoped storage.
View files on a device
To view the files stored on a device, use Android Studio’s Device File Explorer.
Additional resources
For more information about data storage, consult the following resources.
Videos
Content and code samples on this page are subject to the licenses described in the Content License. Java is a registered trademark of Oracle and/or its affiliates.
Источник
Sharing Content with Intents
Intents allow us to communicate data between Android apps and implicit intents can also accept actions. One of those actions is the ACTION_SEND command which indicates we want to send data across apps. To send data, all you need to do is specify the data and its type, and the system will identify compatible receiving activities and display them to the user.
Sending and receiving data between applications with intents is most commonly used for social sharing of content. Intents allow users to share information quickly and easily, using their favorite applications.
You can send content by invoking an implicit intent with ACTION_SEND .
To send images or binary data:
Sending URL links should simply use text/plain type:
In certain cases, we might want to send an image along with text. This can be done with:
Sharing multiple images can be done with:
See this stackoverflow post for more details.
Note: Facebook does not properly recognize multiple shared elements. See this facebook specific bug for more details and share using their SDK.
Facebook doesn’t work well with normal sharing intents when sharing multiple content elements as discussed in this bug. To share posts with facebook, we need to:
- Create a new Facebook app here (follow the instructions)
- Add the Facebook SDK to your Android project
- Share using this code snippet:
You may want to send an image that were loaded from a remote URL. Assuming you are using a third party library like Glide, here is how you might share an image that came from the network and was loaded into an ImageView. There are two ways to accomplish this. The first way, shown below, takes the bitmap from the view and loads it into a file.
and then later assuming after the image has completed loading, this is how you can trigger a share:
Make sure to setup the «SD Card» within the emulator device settings:
Note that if you are using API 24 or above, see the section below on using a FileProvider to work around new file restrictions.
If you are targeting Android API 24 or higher, private File URI resources (file:///) cannot be shared. You must instead wrap the File object as a content provider (content://) using the FileProvider class.
First, you must declare this FileProvider in your AndroidManifest.xml file within the tag:
Next, create a resource directory called xml and create a fileprovider.xml . Assuming you wish to grant access to the application’s specific external storage directory, which requires requesting no additional permissions, you can declare this line as follows:
Finally, you will convert the File object into a content provider using the FileProvider class:
If you see a INSTALL_FAILED_CONFLICTING_PROVIDER error when attempting to run the app, change the string com.codepath.fileprovider in your Java and XML files to something more unique, such as com.codepath.fileprovider.YOUR_APP_NAME_HERE .
Note that there are other XML tags you can use in the fileprovider.xml , which map to the File directory specified. In the example above, we use Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) , which corresponded to the XML tag in the declaration with the Pictures path explicitly specified. Here are all the options you can use too:
XML tag | Corresponding storage call | When to use |
---|---|---|
Context.getFilesDir() | data can only be viewed by app, deleted when uninstalled ( /data/data/[packagename]/files ) | |
Context.getExternalFilesDir() | data can be read/write by the app, any apps granted with READ_STORAGE permission can read too, deleted when uninstalled ( /Android/data/[packagename]/files ) | |
Context.getCacheDir() | temporary file storage | |
Environment.getExternalStoragePublicDirectory() | data can be read/write by the app, any apps can view, files not deleted when uninstalled | |
Context.getExternalCacheDir() | temporary file storage with usually larger space |
If you are using API 23 or above, then you’ll need to request runtime permissions for Manifest.permission.READ_EXTERNAL_STORAGE and Manifest.permission.WRITE_EXTERNAL_STORAGE in order to share the image as shown above since newer versions require explicit permisions at runtime for accessing external storage.
Note: There is a common bug on emulators that will cause MediaStore.Images.Media.insertImage to fail with E/MediaStore﹕ Failed to insert image unless the media directory is first initialized as described in the link.
This is how you can easily use an ActionBar share icon to activate a ShareIntent. The below focuses on the support ShareActionProvider for use with AppCompatActivity .
Note: This is an alternative to using a sharing intent as described in the previous section. You either can use a sharing intent or the provider as described below.
First, we need to add an ActionBar menu item in res/menu/ in the XML specifying the ShareActionProvider class.
Next, get access to share provider menu item in the Activity so we can attach the share intent later:
Note: ShareActionProvider does not respond to onOptionsItemSelected() events, so you set the share action provider as soon as it is possible.
Now, once you’ve setup the ShareActionProvider menu item, construct and attach the share intent for the provider but only after image has been loaded as shown below using the RequestListener for Glide .
Note: Be sure to call attachShareIntentAction method both inside onCreateOptionsMenu AND inside the onResourceReady for Glide to ensure that the share attaches properly.
We can use a similar approach if we wish to create a share action for the current URL that is being loaded in a WebView:
Check out the official guide for easy sharing for more information.
Источник