美文网首页
安卓7.0适配之应用间共享文件

安卓7.0适配之应用间共享文件

作者: bruce1990 | 来源:发表于2018-04-18 13:58 被阅读0次

    问题的引出是在一次版本更新的时候,我把编译版本上升到了API26,于是对于安卓7.0的手机在更新后安装apk的过程中出现闪退,查看错误提示FileUriExposedException。查阅资料得知原来是在Android7.0系统上,Android 框架强制执行了 StrictMode API 政策禁止向你的应用外公开 file:// URI。 如果一项包含文件 file:// URI类型 的 Intent 离开你的应用,应用失败,并出现 FileUriExposedException 异常,如调用系统相机拍照,或裁切照片等。那么,怎么解决这个问题呢?
    谷歌给出的方案是使用FileProvider

    step1.在AndroidManifest.xml文件的application节点下面注册provider

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.jph.takephoto.fileprovider"
        android:grantUriPermissions="true"
        android:exported="false">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>
    

    step2.指定共享目录

    具体做法是:在res目录下新建名为xml的目录,然后新建一个资源文件。如上面的文件名就叫“file_paths” 可以随便去。但是要和注册文件里的一致

    <?xml version="1.0" encoding="utf-8"?>
    <paths>
        <!--外置SD卡-->
        <root-path name="root" path="" />
    
        <!--Context.getFilesDir()-->
        <files-path name="files" path="" />
    
        <!--Context.getCacheDir()-->
        <cache-path name="cache" path="" />
    
        <!--Environment.getExternalStorageDirectory()-->
        <external-path name="external" path="" />
    
        <!--Context.getExternalFilesDir(null)-->
        <external-files-path name="external_file_path" path="" />
    
        <!--Context.getExternalCacheDir()-->   <!--直到support-v4:25.0.0才支持-->
        <external-cache-path name="external_cache_path" path="" />
    
    </paths>
    

    step3.使用FileProvider

    File file=new File(Environment.getExternalStorageDirectory(), "/temp/"+System.currentTimeMillis() + ".jpg");
    if (!file.getParentFile().exists())file.getParentFile().mkdirs();
    Uri imageUri = FileProvider.getUriForFile(context, "com.jph.takephoto.fileprovider", file);//通过FileProvider创建一个content类型的Uri
    Intent intent = new Intent();
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //添加这一句表示对目标应用临时授权该Uri所代表的文件
    intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);//设置Action为拍照
    intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);//将拍取的照片保存到指定URI
    startActivityForResult(intent,1006);
    

    正常情况下,这么做就可以了,实际开发中难免会使用第三方,很多第三方也对这个做了兼容,并且他们往往会这么写

    <provider
                android:name="android.support.v4.content.FileProvider"
                android:authorities="${applicationId}.fileprovider"
                android:grantUriPermissions="true"
                android:exported="false">
                <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/file_paths" />
    </provider>
    

    这样会造成多个provider的冲突,可能你看见log日志里面提示你使用 tools:replace这个属性,然而当你这么干了之后,灾难性的的事情发生了,第三方的provider出问题了。那么怎么做呢,我的解决方案要求不需要去改动第三方,对于远程依赖的第三方这种做法无疑是最完美的。

    写一个provider继承FileProvider

    public class MyProvider extends FileProvider {
    }
    

    改写主项目中注册的provider

    <provider
                android:name="com.androidquanjiakan.base.MyProvider"
                android:authorities="${applicationId}.android24.fileprovider"
                android:exported="false"
                android:grantUriPermissions="true">
    
                <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/file_paths1" />
     </provider>
    

    我把name,authorities以及resource改变了,这样解决了与第三方的冲突问题

    最后,封装一个获取uri的工具类兼容7.0的特性

    public class FileProviderUtil {
    
        public static Uri getUriForFile(Context context, File file) {
            Uri fileUri = null;
            if (Build.VERSION.SDK_INT >= 24) {
                fileUri = getUriForFile24(context, file);
            } else {
                fileUri = Uri.fromFile(file);
            }
            return fileUri;
        }
    
    
    
        public static Uri getUriForFile24(Context context, File file) {
            Uri fileUri = android.support.v4.content.FileProvider.getUriForFile(context,
                    context.getPackageName() + ".android24.fileprovider",
                    file);
            return fileUri;
        }
    
        public static void setIntentDataAndType(Context context,
                                                Intent intent,
                                                String type,
                                                File file,
                                                boolean writeAble) {
            if (Build.VERSION.SDK_INT >= 24) {
                intent.setDataAndType(getUriForFile(context, file), type);
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                if (writeAble) {
                    intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                }
            } else {
                intent.setDataAndType(Uri.fromFile(file), type);
            }
        }
    
    
        public static void setIntentData(Context context,
                                         Intent intent,
                                         File file,
                                         boolean writeAble) {
            if (Build.VERSION.SDK_INT >= 24) {
                intent.setData(getUriForFile(context, file));
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                if (writeAble) {
                    intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                }
            } else {
                intent.setData(Uri.fromFile(file));
            }
        }
    
    
        public static void grantPermissions(Context context, Intent intent, Uri uri, boolean writeAble) {
    
            int flag = Intent.FLAG_GRANT_READ_URI_PERMISSION;
            if (writeAble) {
                flag |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
            }
            intent.addFlags(flag);
            List<ResolveInfo> resInfoList = context.getPackageManager()
                    .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
            for (ResolveInfo resolveInfo : resInfoList) {
                String packageName = resolveInfo.activityInfo.packageName;
                context.grantUriPermission(packageName, uri, flag);
            }
        }
    
    }
    

    相关文章

      网友评论

          本文标题:安卓7.0适配之应用间共享文件

          本文链接:https://www.haomeiwen.com/subject/mrdvkftx.html