FileProvider

基礎(chǔ)

官網(wǎng)鏈接

為了安全地共享本 app 的文件,您需要配置您的應(yīng)用程序,以 Content URI 形式為文件提供安全的句柄。 FileProvider 會(huì)根據(jù)你在 XML 中提供的規(guī)范生成文件的 Content URI 。

  1. FileProvider 繼承于 ContentProvider ,它只是內(nèi)容提供者的一個(gè)特殊子類。

  2. 也可以不使用 FileProvider ,而自己定義一個(gè) ContentProvider 。

幾個(gè)方法的返回路徑

方法名 返回路徑
getExternalCacheDir() sd 卡中 Android/data/包名/cache 目錄
getExternalFilesDir(String) sd 卡中 Android/data/包名/files/xx 目錄,xx為方法參數(shù)
getCacheDir() 內(nèi)部存儲(chǔ)目錄下的 cache 目錄
getDir() 內(nèi)部存儲(chǔ)目錄下的 app_xx 目錄 ,其中 xx 為方法參數(shù)
getFilesDir() 內(nèi)部存儲(chǔ)目錄下 files 目錄

Manifest 配置

在清單文件中進(jìn)行如下配置:

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

<meta-data> 中 resource 屬性指定了 xml 文件名,該文件位于 res/xml 文件夾下。
authorities 指定了 FileProvider 生成 URI 的authorities。

xml

在 res/xml 文件夾下建立以 resource 屬性值為名的 xml 文件,在該文件中指定本 app 可以共享的文件目錄,所有可以共享的目錄只能在該 xml 中指定,無法通過代碼動(dòng)態(tài)完成

可以參考如下示例:

<paths>
    <files-path path="images/" name="url_name" />
</paths>
  1. path 屬性指定了共享的子目錄, name 屬性指定了共享目錄在 content URI 中的別名。子目錄與別名是一一對(duì)應(yīng)關(guān)系。

  2. <paths> 中可以有多個(gè)子標(biāo)簽。

  3. 父目錄名通過子標(biāo)簽名指定。如上面的 files-path 指定的父目錄就是 getFilesDir() 返回的目錄。

  4. 在上面例子中,如果共享的是 images 目錄下的 default_image.jpg ,其對(duì)應(yīng)的 content URI 如下:

content://com.example.myapp.fileprovider/url_name/default_image.jpg

其文件的真實(shí)路徑

Context.getFilesDir() +"/images/default_image.jpg"

可以發(fā)現(xiàn),轉(zhuǎn)成 URI 后,images 目錄下的路徑會(huì)全部真實(shí)地保留。保留的部分叫剩余路徑(為后期分析源碼方便,自己取的名字)。

子標(biāo)簽名

官網(wǎng)鏈接

標(biāo)簽名 對(duì)應(yīng)的目錄
files-path Context#getFilesDir()
cache-path Context#getCacheDir()
external-path Environment.getExternalStorageDirectory()
external-files-path Context#getExternalFilesDir
external-cache-path Context#getExternalCacheDir()

源碼分析

因?yàn)槭?ContentProvider ,所以先看其 attachInfo 方法。

attachInfo()

public void attachInfo(Context context, ProviderInfo info) {
        super.attachInfo(context, info);
        //對(duì) exported 與 grantUriPermissions 屬性值進(jìn)行判斷,其要求兩者值必須分別為 false 與 true,否則就會(huì)扔異常。
        ……
        // authority 的值就是 authorities 屬性的值
        mStrategy = getPathStrategy(context, info.authority);
    }

 private static PathStrategy getPathStrategy(Context context, String authority) {
        PathStrategy strat;
        synchronized (sCache) {
           //以 authority 值為 key 對(duì) PathStrategy 對(duì)象進(jìn)行緩存
            strat = sCache.get(authority);
            if (strat == null) {
                try {
                    strat = parsePathStrategy(context, authority);
                } 
                // 省略異常處理代碼
                sCache.put(authority, strat);
            }
        }
        return strat;
    }

對(duì) strat 進(jìn)行初始化時(shí),會(huì)調(diào)用 parsePathStrategy 方法,其主要就是解析在清單文件中指定的 xml 文件。

private static PathStrategy parsePathStrategy(Context context, String authority)
            throws IOException, XmlPullParserException {
        final SimplePathStrategy strat = new SimplePathStrategy(authority);
        //讀取清單文件中配置的 xml 文件
        final ProviderInfo info = context.getPackageManager()
                .resolveContentProvider(authority, PackageManager.GET_META_DATA);
        final XmlResourceParser in = info.loadXmlMetaData(
                context.getPackageManager(), META_DATA_FILE_PROVIDER_PATHS);
        //對(duì) in 進(jìn)行非空判斷 ,忽略。下面是對(duì) in 進(jìn)行 xml 解析
        int type;
        while ((type = in.next()) != END_DOCUMENT) {
            if (type == START_TAG) {
                final String tag = in.getName();
                //分別獲取 name 屬性與 path 屬性
                final String name = in.getAttributeValue(null, ATTR_NAME);
                String path = in.getAttributeValue(null, ATTR_PATH);

                File target = null;
                if (TAG_ROOT_PATH.equals(tag)) {
                    target = DEVICE_ROOT;
                } else if (TAG_FILES_PATH.equals(tag)) {// 標(biāo)簽名為 files-path
                    target = context.getFilesDir();
                } else if (TAG_CACHE_PATH.equals(tag)) {// 標(biāo)簽名為 cache-path
                    target = context.getCacheDir();
                } else if (TAG_EXTERNAL.equals(tag)) {// 標(biāo)簽名為 external-path
                    target = Environment.getExternalStorageDirectory();
                } else if (TAG_EXTERNAL_FILES.equals(tag)) {// 標(biāo)簽名為 external-files-path
                    //這段代碼的作用就是將 target 賦值為 getExternalFilesDir() 的返回值
                } else if (TAG_EXTERNAL_CACHE.equals(tag)) {// 標(biāo)簽名為 external-cache-path
                    //這段代碼的作用就是將 target 賦值為 getExternalCacheDir() 的返回值
                }

                if (target != null) {
                    // buildPath 會(huì)在 target 目錄下建立一個(gè)以 path 值為名的目錄
                    strat.addRoot(name, buildPath(target, path));
                }
            }
        }

        return strat;
    }

    private static File buildPath(File base, String... segments) {
        File cur = base;
        for (String segment : segments) {
            if (segment != null) {
                cur = new File(cur, segment);
            }
        }
        return cur;
    }

其余方法

其他幾個(gè)方法的實(shí)現(xiàn),基本上都是通過 PathStrategy 完成的。如

    public static Uri getUriForFile(Context context, String authority, File file) {
        final PathStrategy strategy = getPathStrategy(context, authority);
        return strategy.getUriForFile(file);
    }

PathStrategy 與 SimplePathStrategy

PathStrategy 的唯一子類是 SimplePathStrategy。它會(huì)在 parsePathStrategy() 中被創(chuàng)建。

  static class SimplePathStrategy implements PathStrategy {
        private final String mAuthority;
        //以 name 屬性值為 key ,以標(biāo)簽名與 path 值聯(lián)合指定的目錄 File 為 value
        private final HashMap<String, File> mRoots = new HashMap<String, File>();

        public SimplePathStrategy(String authority) {
            mAuthority = authority;
        }

        // name 是 xml 中定義 name 屬性值,而 root 是根據(jù) xml 中標(biāo)簽名與 path 屬性值聯(lián)合指定的一個(gè)本地文件目錄,name 與 root 是一一對(duì)應(yīng)關(guān)系
        public void addRoot(String name, File root) {
            ……
            mRoots.put(name, root);
        }

        @Override
        public Uri getUriForFile(File file) {
            String path;// path 指代 file 的路徑。
            
            Map.Entry<String, File> mostSpecific = null;
            for (Map.Entry<String, File> root : mRoots.entrySet()) {
                final String rootPath = root.getValue().getPath();

               // 查找最接近 file 路徑的 Map.Entry<String, File> 對(duì)象。

                if (path.startsWith(rootPath) && (mostSpecific == null
                        || rootPath.length() > mostSpecific.getValue().getPath().length())) {
                    mostSpecific = root;
                }
            }

            // mostSpecific  非空判斷,略
            
            // 以下對(duì) path 進(jìn)行處理,并最終轉(zhuǎn)成 content://authority/name 形式的 URI
            // 通過該種方式處理,最終會(huì)將指定的文件根據(jù)清單文件中的 authority 以及 xml 中指定的 name 轉(zhuǎn)換成 URI

            final String rootPath = mostSpecific.getValue().getPath();
            // 截取指定 file 的除 標(biāo)簽名及 path 屬性指定的剩余部分
            if (rootPath.endsWith("/")) {
                path = path.substring(rootPath.length());
            } else {
                path = path.substring(rootPath.length() + 1);
            }
            // 將 path 轉(zhuǎn)換成  name/剩余路徑  形式
            path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/");
            return new Uri.Builder().scheme("content")
                    .authority(mAuthority).encodedPath(path).build();
        }

        @Override
        public File getFileForUri(Uri uri) {
            // 獲取到的內(nèi)容形式為:/name/剩余路徑
            String path = uri.getEncodedPath();

            final int splitIndex = path.indexOf('/', 1);
            // xml 中指定的 name 屬性值
            final String tag = Uri.decode(path.substring(1, splitIndex));
            // 文件的剩余路徑
            path = Uri.decode(path.substring(splitIndex + 1));
            // 獲取文件的根路徑
            final File root = mRoots.get(tag);
            if (root == null) {
                throw new IllegalArgumentException("Unable to find configured root for " + uri);
            }

            File file = new File(root, path);
            try {
                file = file.getCanonicalFile();
            } catch (IOException e) {
                throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
            }

            if (!file.getPath().startsWith(root.getPath())) {
                throw new SecurityException("Resolved path jumped beyond configured root");
            }

            return file;
        }
    }

總結(jié)

  1. 指定 path 后,該目錄下的所有文件都可以共享,無論是文件還是文件夾
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 上周,寫了個(gè)小demo,正好同事使用的小米手機(jī)系統(tǒng)內(nèi)核更新到7.0,遂拿來測試了一番。其中遇到的小問題,現(xiàn)在來跟大...
    monkey_who閱讀 4,891評(píng)論 0 13
  • 自己簡單的翻譯了一下整篇文檔,然后和網(wǎng)絡(luò)上對(duì)比了一下,感覺還是太菜,所以轉(zhuǎn)了其他人的翻譯,感覺更適合。稍微修改了一...
    Arnold_J閱讀 1,335評(píng)論 0 1
  • 由于 Android 7.0 或更高版本的系統(tǒng)在國內(nèi)手機(jī)市場上的占比不是很高,很多 Android 開發(fā)人員并沒有...
    亦楓閱讀 4,426評(píng)論 1 39
  • 01 今天跟朋友抱怨,最近感覺身體出現(xiàn)了一點(diǎn)小狀況,眼睛干澀,視力衰退,皮膚變差。 朋友問了一句,“你是不是經(jīng)常熬...
    竹葉騰1閱讀 608評(píng)論 0 5
  • 微笑,是我們最寶貴的故事’海之冬6 2011-2-24 22:24海之冬17 平時(shí)的她.這樣的天氣起來特別的晚.可...
    野蠻開心冬閱讀 176評(píng)論 0 0

友情鏈接更多精彩內(nèi)容