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

2018-04-18  本文已影响0人  bruce1990

问题的引出是在一次版本更新的时候,我把编译版本上升到了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);
        }
    }

}
上一篇下一篇

猜你喜欢

热点阅读