1. 程式人生 > >適配Android7.0應用間檔案共享FileProvider

適配Android7.0應用間檔案共享FileProvider

android編譯版本升級到7.0以後,會出現很多適配方面的工作,從android官方文件對於android7.0行為變更可以瞭解到,android7.0的應用禁止傳遞類似file:// URI這樣的連結,否則應用會丟擲FileUriExposedException異常,比較典型的場景就是我們專案中呼叫攝像頭拍照,如果不對這個進行適配,我們按照以前的程式碼呼叫攝像頭拍照的時候,會出現以下錯誤:

android.os.FileUriExposedException: file:///storage/emulated/0/photoTest/photo.jpeg exposed beyond app through ClipData.Item.getUri()

接下來開始對這個進行適配,適配的方案主要參考了 鴻洋大神 Android 7.0 行為變更 通過FileProvider在應用間共享檔案吧 這篇部落格

1.首先在專案res目錄下新建xml目錄,並新建file_paths.xml,這個檔案主要用來配置應用共享檔案的路徑
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <root-path
        name="root"
        path="" />
    <files-path
        name="files"
        path="" />

    <cache-path
        name="cache"
        path="" />

    <external-path
        name="external"
        path="" />

    <external-files-path
        name="external_file_path"
        path="" />
    <external-cache-path
        name="external_cache_path"
        path="" />

</paths>

在paths節點下支援以下幾個子節點:

  • :代表裝置的根目錄new File("/")
  • : 代表context.getFilesDir()
  • : 代表context.getCacheDir()
  • : 代表Environment.getExternalStorageDirectory()
  • : 代表context.getExternalFilesDirs()
  • : 代表getExternalCacheDirs()
    path節點支援name和path兩個屬性,配置了path屬性就相當於在相應路徑下子目錄,例如:
<external-path
        name="external"
        path="phototest" />

這樣配置就代表應用可以使用Environment.getExternalStorageDirectory()/phototest 目錄以及其子目錄的檔案

2. 在AndroidManifest.xml的application節點下增加FileProvider的宣告
<application>
        ...
        ...

        <!--適配android 7.0檔案訪問
              com.hua.phototest是應用的包名
        -->
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.hua.phototest.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
    </application>
3.FileProvider工具類,參考自鴻洋大神部落格
public class FileProvider7 {

    public static Uri getUriForFile(Context context, File file) {
        Uri fileUri = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            fileUri = getUriForFile24(context, file);
        } else {
            fileUri = Uri.fromFile(file);
        }
        return fileUri;
    }



    private static Uri getUriForFile24(Context context, File file) {
        Uri fileUri = android.support.v4.content.FileProvider.getUriForFile(context,
                context.getPackageName() + ".fileprovider",
                file);
        return fileUri;
    }


    public static void setIntentDataAndType(Context context,
                                            Intent intent,
                                            String type,
                                            File file,
                                            boolean writeAble) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            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);
            chmod("777", file.getAbsolutePath());//apk放在cache檔案中,需要獲取讀寫許可權
        }
    }

    public static   void chmod(String permission, String path) {
        try {
            String command = "chmod " + permission + " " + path;
            Runtime runtime = Runtime.getRuntime();
            runtime.exec(command);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    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);
        }
    }
}
4.最後在傳遞URI的時候呼叫相應的方法獲取URI即可,例如下面程式碼是呼叫攝像頭拍照:
private String mTempPhotoPath;
    private Uri imageUri;

    private void takePhoto() {
        Intent intentToTakePhoto = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        File fileDir = new File(Environment.getExternalStorageDirectory() + File.separator + "photoTest" + File.separator);
        if (!fileDir.exists()) {
            fileDir.mkdirs();
        }

        File photoFile = new File(fileDir, "photo.jpeg");
        mTempPhotoPath = photoFile.getAbsolutePath();
        //適配android7.0應用間檔案共享
        imageUri = FileProvider7.getUriForFile(this, photoFile);
        intentToTakePhoto.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
        startActivityForResult(intentToTakePhoto, RC_TAKE_PHOTO);
    }