In a previous article using Android system native API to achieve sharing function mainly said the implementation process, but there are still many pits to face in the concrete implementation. Then this article is to provide a packaged Share2 library for your reference.

GitHub project address: Share2


For those of you who read the previous article, there are three main steps to invoke the built-in sharing function of the Android system:

  • ACTION_SEND creates an implicit Intent to send the specified content, specifying the Action as intent.action_send.

  • Then specify the content and type to be sent, that is, set the Uri of the text content or file to be shared, and declare the type of the file so that applications that support that type of content can open it.

  • Finally, the implicit intention is sent to the system, the system sharing selector is opened, and the result is returned after the sharing is completed.

Please refer to the previous article for more relevant content, and I will not repeat it here.


Know the general implementation process, in fact, as long as the following problems can be specific implementation.

Determine the type of content to share

The type of content shared, in fact, directly determines the final form of implementation. We know that a common usage scenario is to share images and files between applications, but for products that just share text, the two implementation concerns are completely different.

Therefore, in order to solve this problem, we can set the supported shared content types in advance, and different processing can be carried out for different types.

@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 = "* / *"; } `Copy the code

In Share2, a total of five types of sharing content are defined, which basically covers common usage scenarios. Content types, such as text, images, audio and video, and various other types of files, can be specified directly when the sharing interface is invoked.

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 images or other files, we usually need a Uri to identify a resource. This actually leads to a key implementation problem: how do you get the Uri of the shared file that can be processed by the receiving application?

When this problem is further refined and translated into specific problems to be solved, it is:

  1. How to obtain the content file to shareUri
  2. How can the recipient also be able to baseUriGot the file?

To answer these questions, let’s take a look at the source of the file sharing. The specific ways we usually get a file in an application are:

  • The user obtains a specified file by opening the file selector or picture selector.
  • Users take photos or record audio and video to get a media file;
  • Users can obtain a file either by downloading it or directly from the local file path.

File URIs are divided into the following types according to the source of the file:

1. The Uri returned by the system

Common scenario: Obtain the Uri of a file through the file selector

private static final int REQUEST_FILE_SELECT_CODE = 100; private @ShareContentType String fileType = ShareContentType. File; /** * Open file management select file */ private voidopenFileChooser() {
        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) {// Obtain the Uri returned by the system Uri shareFileUrl = data.getData(); }}Copy the code

The Uri retrieved in this way is returned by the system ContentProvider. There are major differences between Android versions before and after 4.4, which we’ll discuss later. Just remember the Uri that the system returns to us.

The file Uri of the system to return to some common styles: the content: / / com. Android. Will. Media. The documents.. content://com.android.providers.downloads… content://media/external/images/media/… content://com.android.externalstorage.documents..

2. Customize the Uri returned by the FileProvider

Common scenarios: for example, to call the system camera to take photos or record audio and video, we need to pass a Uri to generate the object file. Since Android 7.0, we need to use the FileProvider to achieve this.

private static final int REQUEST_FILE_SELECT_CODE = 100; /** * Open the system camera to take photos */ private voidopenSystemCameraIntent takePhotoIntent = new Intent(); takePhotoIntent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);if (takePhotoIntent.resolveActivity(getPackageManager()) == null) {
            Toast.makeText(this, "There are no camera apps available on the current system.", Toast.LENGTH_SHORT).show();
            return;
        }

        String fileName = "TEMP_" + System.currentTimeMillis() + ".jpg"; File photoFile = new File(FileUtil.getPhotoCacheFolder(), fileName); Systems 7.0 and later use FileProvider to create a Uri of type Contentif (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); PutExtra (mediastore. EXTRA_OUTPUT, currentTakePhotoUri); startActivityForResult(takePhotoIntent, TAKE_PHOTO_REQUEST_CODE); } // Call the system camera to take a photo similar to the above method of getting the file URI through the file selector // call the onActivityResult callback, in which case the URI is specified in the custom FileProvider, Note the difference with the system return Uri retrieved by the file selector.Copy the code

Note the difference between the Uri returned by a FileProvider and the Uri returned by a ContentProvider. Android :authorities=”com.xx.xxx. FileProvider “android:authorities=”com.xx.xxx. FileProvider” content://com.xx.xxx.fileProvider… For this type of Uri, let’s call it the Uri returned by the custom FileProvider.

3. Obtain the Uri from the file path

This is not really a file Uri type in its own right, but it is a very common invocation scenario, so it is highlighted separately.

New File(String path) : /storage/emulated/0/… With this style, how do you turn a file path into a file Uri? To answer this question, you actually need to deal with sharing files.

Share file Uri processing

Processing access rights

The three sources of file URIs mentioned above are handled differently depending on the type, otherwise the first problem you will encounter is:

java.lang.SecurityException: Uid xxx does not have permission to uri 0 @ content://com.android.providers...
Copy the code

This problem is caused by the lack of access permission to the Uri returned by the system. Therefore, grant temporary access permission to the Uri to the application. Otherwise, a message will be displayed indicating that the permission is missing.

To share the Uri returned by the system, we can do this:

Shareinten. addFlags(intent.flag_grant_read_uri_permission); // 1. // 2. You can also grant temporary access to all apps because you don't know which app the end user will chooseif (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); }}Copy the code

The FileProvider returns the Uri

One thing to note is for customizationsFileProviderreturnUriThe Uri will not be recognized when shared to a third-party application, even if temporary access permission is set

A typical scenario is if we share the Uri Settings returned by a custom FileProvider to a third-party application such as wechat or QQ and the file does not exist because they cannot recognize the Uri.

The solution to this problem is the same as changing the file path to the Uri returned by the system. We only need to change the Uri returned by the custom FileProvider to the Uri returned by the system that can be recognized by third-party applications.

When creating a FileProvider, you need to pass in a File object, so you can directly know the path to the File. This turns the question into: How do I get the Uri returned by the system from the File path

Obtain the Uri returned by the system based on the file path

For Android versions below 7.0, the answer to this question is simple:

Uri uri = Uri.fromFile(file);
Copy the code

Android 7.0 and above is much more complicated to handle, so here’s how to adapt to different versions of the system. The following getFileUri method retrieves the FileUri by querying the system ContentProvider for the File object and type passed in.

   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());
    }
Copy the code

The forceGetFileUri method is implemented by reflection. Android 7.0 does not allow the file:// Uri method to share files between different apps, but the FileProvider method is still invalid. We can kill this detection with reflection.

By converting File Path into Uri, we finally unified three different scenarios of content Uri passed in when calling system sharing, and finally converted all of them into the Uri returned by the system, so that third-party applications can normally obtain the shared content.

Finally realize

Share2 is implemented in accordance with the above methods and can be integrated in the following ways:

// Add dependency compile'gdut. BSX: share2:0.9.0'
Copy the code

Get the Uri according to FilePath

 public Uri getShareFileUri() {
       return FileUtil.getFileUri(this, ShareContentType.FILE, new File(filePath));;
 }
Copy the code

Share the text

new Share2.Builder(this)
    .setContentType(ShareContentType.TEXT)
    .setTextContent("This is a test message.")
    .setTitle("Share Text")
    .build()
    .shareBySystem();
Copy the code

Share photos

new Share2.Builder(this)
    .setContentType(ShareContentType.IMAGE)
    .setShareFileUri(getShareFileUri())
    .setTitle("Share Image")
    .build()
    .shareBySystem();
Copy the code

Share pictures to a specified interface, such as 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();
Copy the code

Share files

new Share2.Builder(this)
    .setContentType(ShareContentType.FILE)
    .setShareFileUri(getShareFileUri())
    .setTitle("Share File")
    .build()
    .shareBySystem();
Copy the code

The final result

GitHub project address: Share2