In a previous article, using the native API of the Android system to realize the sharing function , I mainly talked about the implementation process, but there are still many pits to face in the specific implementation. Then this article is to provide a packaged Share2 library for your reference.
GitHub project address: Share2
Students who have read the previous article should know that there are three main steps to call the built-in sharing function of the Android system:
-
Create one
Intent
, specifying itAction
asIntent.ACTION_SEND
, means to create an implicit intent that sends the specified content. -
Then specify the content and type to be sent, that is, set the shared text content or file
Uri
, and declare the type of the file, so that applications that support this type of content can be opened. -
Finally, an implicit intent is sent to the system, the system sharing selector is enabled, and the result is returned after the sharing is completed.
For more related content, please refer to the previous article , which will not be repeated here.
After knowing the general implementation process, you can actually implement it as long as you solve the following problems.
Decide what type of content to share
The type of content to be shared actually directly determines the final implementation form. We know that in common usage scenarios, it is to share pictures and some files between applications, but for those products that only share text, the issues to be considered in the implementation of the two are completely different.
So in order to solve this problem, we can pre-determine the supported shared content types, and different types can be handled differently.
@StringDef({ShareContentType.TEXT, ShareContentType.IMAGE, ShareContentType.AUDIO, ShareContentType.VIDEO, ShareContentType.File})
@Retention(RetentionPolicy.SOURCE)
@interface ShareContentType {
/**
* Share Text
*/
final String TEXT = "text/plain";
/**
* Share Image
*/
final String IMAGE = "image/*";
/**
* Share Audio
*/
final String AUDIO = "audio/*";
/**
* Share Video
*/
final String VIDEO = "video/*";
/**
* Share File
*/
final String File = "*/*";
}`
In Share2, a total of 5 types of shared content are defined, which can basically cover common usage scenarios. When calling the sharing interface, you can directly specify the content type, such as text, pictures, audio and video, and various other types of files.
Identify the source of the content to share
There may be different sources for different types of content. For example text might just be a string object. For sharing pictures or other files, we usually need a Uri
to identify a resource. This actually leads to a key question in the specific implementation: how to obtain the shared file Uri
, and this Uri
can be processed by the receiving application?
When this problem is further refined and transformed into a specific problem that needs to be solved, it is:
- How to get the content file to share
Uri
? - How can the recipient also be able to
Uri
obtain the file according to ?
To answer these questions, let's first look at the source of the shared file. Usually, the specific way we obtain a file in an application is as follows:
- The user obtains a specified file by opening the file selector or image selector;
- The user obtains a media file by taking pictures or recording audio and video;
- The user obtains a file by downloading it or directly through the local file path.
Then we divide the file Uri
into :
1. Uri returned by the system
Common scenario : Get the Uri of a file through the file selector
private static final int REQUEST_FILE_SELECT_CODE = 100;
private @ShareContentType String fileType = ShareContentType. File;
/**
* 打开文件管理选择文件
*/
private void openFileChooser() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
try {
startActivityForResult(Intent.createChooser(intent, "Choose File"), REQUEST_FILE_SELECT_CODE);
} catch (Exception ex) {
// not install file manager.
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == FILE_SELECT_CODE && resultCode == RESULT_OK) {
// 获取到的系统返回的 Uri
Uri shareFileUrl = data.getData();
}
}
The obtained in this way Uri
is ContentProvider
returned by the system. There is a big difference between the Android 4.4
previous version and the later version. We will talk about how to deal with it later. Uri
Just remember what the system returns to us first .
Uri
Some common styles in files returned by the system : content://com.android.providers.media.documents.. content://com.android.providers.downloads... content://media/external/images/media /...content://com.android.externalstorage.documents..
2. Customize the Uri returned by FileProvider
Common scenarios : For example, calling the system camera to take pictures or record audio and video, to pass in a generated target file , we need to use to achieve Uri
it from the Android 7.0
beginning .FileProvider
private static final int REQUEST_FILE_SELECT_CODE = 100;
/**
* 打开系统相机进行拍照
*/
private void openSystemCamera() {
//调用系统相机
Intent takePhotoIntent = new Intent();
takePhotoIntent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePhotoIntent.resolveActivity(getPackageManager()) == null) {
Toast.makeText(this, "当前系统没有可用的相机应用", Toast.LENGTH_SHORT).show();
return;
}
String fileName = "TEMP_" + System.currentTimeMillis() + ".jpg";
File photoFile = new File(FileUtil.getPhotoCacheFolder(), fileName);
// 7.0 和以上版本的系统要通过 FileProvider 创建一个 content 类型的 Uri
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
currentTakePhotoUri = FileProvider.getUriForFile(this, getPackageName() + ".fileProvider", photoFile);
takePhotoIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION|);
} else {
currentTakePhotoUri = Uri.fromFile(photoFile);
}
//将拍照结果保存至 outputFile 的Uri中,不保留在相册中
takePhotoIntent.putExtra(MediaStore.EXTRA_OUTPUT, currentTakePhotoUri);
startActivityForResult(takePhotoIntent, TAKE_PHOTO_REQUEST_CODE);
}
// 调用系统相机进行拍照与上面通过文件选择器获得文件 uri 的方式类似
// 在 onActivityResult 进行回调处理,此时 Uri 是自定义 FileProvider 中指定的,注意与文件选择器获取的系统返回 Uri 的区别。
If you use it, you FileProvider
should pay attention ContentProvider
to Uri
the . For example, if we configureManifest
the attribute in , then the format becomes: , for this type of Uri, let's call it the Uri returned by the custom FileProvider .FileProvider
android:authorities="com.xx.xxx.fileProvider"
Uri
content://com.xx.xxx.fileProvider...
Uri
3. Uri obtained from the path of the file
In fact, this cannot be regarded as a file Uri
type by itself, but this is a very common calling scenario, so it is explained separately.
When we call new File(String path)
, need to pass in the specified file path. This absolute path is usually: /storage/emulated/0/...
this style, so how to turn a file path into Uri
a form? To answer this question, you actually need to deal with the shared files.
Handling of shared file Uri
Handling access rights
Uri
The three sources of files mentioned above have different processing methods for different types. Otherwise, the first problem you will encounter is:
java.lang.SecurityException: Uid xxx does not have permission to uri 0 @ content://com.android.providers...
This is due to the Uri
lack , so it is necessary to Uri
authorize temporary access to the application, otherwise it will prompt the lack of permissions.
For the Uri returned by the sharing system, we can process it like this:
// 1. 可以对发起分享的 Intent 添加临时访问授权
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// 2. 也可以这样:由于不知道最终用户会选择哪个app,所以授予所有应用临时访问权限
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
List<ResolveInfo> resInfoList = activity.getPackageManager().queryIntentActivities(shareIntent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
activity.grantUriPermission(packageName, shareFileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
Handling FileProvider returns Uri
It should be noted that for the processing of custom FileProvider
return Uri
, even if temporary access permissions are set, the Uri will not be recognized when shared to third-party applications
A typical scenario is that if we share FileProvider
the returned Uri
settings to a third-party application such as WeChat or QQ, it prompts that the file does not exist, because they cannot identify it Uri
.
The handling of this problem is actually the same as changing the file path into the system returned Uri
as . We only need to change the custom FileProvider
returned to Uri
a third-party application that can recognize the system returned Uri
.
When creating FileProvider
, you need to pass in an File
object, so you can directly know the file path, so the problem is transformed into: how to get the Uri returned by the system through the file path
Get the Uri returned by the system through the file path
Answering this question is simple for the Android 7.0
following versions of the system:
Uri uri = Uri.fromFile(file);
However, it is much more cumbersome to process in Android 7.0
and above systems. Let's talk about how to adapt under different system versions. The following getFileUri
method implements the method File
of querying the system ContentProvider
by obtain the corresponding file Uri
.
public static Uri getFileUri (Context context, @ShareContentType String shareContentType, File file){
if (context == null) {
Log.e(TAG,"getFileUri current activity is null.");
return null;
}
if (file == null || !file.exists()) {
Log.e(TAG,"getFileUri file is null or not exists.");
return null;
}
Uri uri = null;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
uri = Uri.fromFile(file);
} else {
if (TextUtils.isEmpty(shareContentType)) {
shareContentType = "*/*";
}
switch (shareContentType) {
case ShareContentType.IMAGE :
uri = getImageContentUri(context, file);
break;
case ShareContentType.VIDEO :
uri = getVideoContentUri(context, file);
break;
case ShareContentType.AUDIO :
uri = getAudioContentUri(context, file);
break;
case ShareContentType.File :
uri = getFileContentUri(context, file);
break;
default: break;
}
}
if (uri == null) {
uri = forceGetFileUri(file);
}
return uri;
}
private static Uri getFileContentUri(Context context, File file) {
String volumeName = "external";
String filePath = file.getAbsolutePath();
String[] projection = new String[]{MediaStore.Files.FileColumns._ID};
Uri uri = null;
Cursor cursor = context.getContentResolver().query(MediaStore.Files.getContentUri(volumeName), projection,
MediaStore.Images.Media.DATA + "=? ", new String[] { filePath }, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
int id = cursor.getInt(cursor.getColumnIndex(MediaStore.Files.FileColumns._ID));
uri = MediaStore.Files.getContentUri(volumeName, id);
}
cursor.close();
}
return uri;
}
private static Uri getImageContentUri(Context context, File imageFile) {
String filePath = imageFile.getAbsolutePath();
Cursor cursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[] { MediaStore.Images.Media._ID }, MediaStore.Images.Media.DATA + "=? ",
new String[] { filePath }, null);
Uri uri = null;
if (cursor != null) {
if (cursor.moveToFirst()) {
int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
Uri baseUri = Uri.parse("content://media/external/images/media");
uri = Uri.withAppendedPath(baseUri, "" + id);
}
cursor.close();
}
if (uri == null) {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DATA, filePath);
uri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
}
return uri;
}
private static Uri getVideoContentUri(Context context, File videoFile) {
Uri uri = null;
String filePath = videoFile.getAbsolutePath();
Cursor cursor = context.getContentResolver().query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
new String[] { MediaStore.Video.Media._ID }, MediaStore.Video.Media.DATA + "=? ",
new String[] { filePath }, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
Uri baseUri = Uri.parse("content://media/external/video/media");
uri = Uri.withAppendedPath(baseUri, "" + id);
}
cursor.close();
}
if (uri == null) {
ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.DATA, filePath);
uri = context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
}
return uri;
}
private static Uri getAudioContentUri(Context context, File audioFile) {
Uri uri = null;
String filePath = audioFile.getAbsolutePath();
Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
new String[] { MediaStore.Audio.Media._ID }, MediaStore.Audio.Media.DATA + "=? ",
new String[] { filePath }, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
Uri baseUri = Uri.parse("content://media/external/audio/media");
uri = Uri.withAppendedPath(baseUri, "" + id);
}
cursor.close();
}
if (uri == null) {
ContentValues values = new ContentValues();
values.put(MediaStore.Audio.Media.DATA, filePath);
uri = context.getContentResolver().insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
}
return uri;
}
private static Uri forceGetFileUri(File shareFile) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
try {
@SuppressLint("PrivateApi")
Method rMethod = StrictMode.class.getDeclaredMethod("disableDeathOnFileUriExposure");
rMethod.invoke(null);
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
}
}
return Uri.parse("file://" + shareFile.getAbsolutePath());
}
Among them, the
forceGetFileUri
method is implemented through reflection, andAndroid 7.0
it is not allowedfile://
Uri
toApp
share files between different s at first, but ifFileProvider
the is still invalid, we can kill the detection through reflection.
Through File Path
the conversion Uri
method, we finally unified Uri
the , and finally converted all of them into the returned content of the delivery system Uri
, so that third-party applications can obtain the shared content normally.
finally realized
Share2
The specific implementation is carried out according to the above method, and the integration can be carried out in the following ways:
// 添加依赖
compile 'gdut.bsx:share2:0.9.0'
Get Uri according to FilePath
public Uri getShareFileUri() {
return FileUtil.getFileUri(this, ShareContentType.FILE, new File(filePath));;
}
share text
new Share2.Builder(this)
.setContentType(ShareContentType.TEXT)
.setTextContent("This is a test message.")
.setTitle("Share Text")
.build()
.shareBySystem();
share pictures
new Share2.Builder(this)
.setContentType(ShareContentType.IMAGE)
.setShareFileUri(getShareFileUri())
.setTitle("Share Image")
.build()
.shareBySystem();
Share pictures to the designated interface, such as sharing to WeChat Moments
new Share2.Builder(this)
.setContentType(ShareContentType.IMAGE)
.setShareFileUri(getShareFileUri())
.setShareToComponent("com.tencent.mm", "com.tencent.mm.ui.tools.ShareToTimeLineUI")
.setTitle("Share Image To WeChat")
.build()
.shareBySystem();
share files
new Share2.Builder(this)
.setContentType(ShareContentType.FILE)
.setShareFileUri(getShareFileUri())
.setTitle("Share File")
.build()
.shareBySystem();