Use Android system native API to realize sharing function (2)

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 it Actionas Intent.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 Urito identify a resource. This actually leads to a key question in the specific implementation: how to obtain the shared file Uri, and this Urican 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:

  1. How to get the content file to share Uri?
  2. How can the recipient also be able to Uriobtain 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 Uriinto :

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 Uriis ContentProviderreturned by the system. There is a big difference between the Android 4.4previous version and the later version. We will talk about how to deal with it later. UriJust remember what the system returns to us first .

UriSome 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 Uriit from the Android 7.0beginning .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 FileProvidershould pay attention ContentProviderto Urithe . 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 .FileProviderandroid:authorities="com.xx.xxx.fileProvider"Uricontent://com.xx.xxx.fileProvider...Uri

3. Uri obtained from the path of the file

In fact, this cannot be regarded as a file Uritype 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 Uria form? To answer this question, you actually need to deal with the shared files.

Handling of shared file Uri

Handling access rights

UriThe 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 Urilack , so it is necessary to Uriauthorize 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 FileProviderreturn 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 FileProviderthe returned Urisettings 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 Urias . We only need to change the custom FileProviderreturned to Uria third-party application that can recognize the system returned Uri.

When creating FileProvider, you need to pass in an Fileobject, 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.0following versions of the system:

Uri uri = Uri.fromFile(file);

However, it is much more cumbersome to process in Android 7.0and above systems. Let's talk about how to adapt under different system versions. The following getFileUrimethod implements the method Fileof querying the system ContentProviderby 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 forceGetFileUrimethod is implemented through reflection, and Android 7.0it is not allowed file:// Urito Appshare files between different s at first, but if FileProviderthe is still invalid, we can kill the detection through reflection.

Through File Paththe conversion Urimethod, we finally unified Urithe , 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

Share2The 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();

final effect

GitHub project address: Share2

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325966983&siteId=291194637