[Android] 透過FileProvider分享檔案

今天要來介紹如何"安全地"分享檔案。

首先要提到的是application sandbox: Android使用Linux user-based protection的方法來保護/隔離每一個application (UID)的目錄/檔案/程序等等。所以基於安全考量,基本上Google也都會建議所有的檔案/資料,請放到application自己的資料夾下(/data/data/[PACKAGE_NAME]/files)。這個資料夾只有該application (UID)讀得到。因為如果你放在external storage (ex: SD卡),那是所有人可讀/可寫的到的地方,不儘導致機敏資料洩漏,也有被人竄改的風險。

但這也衍生出一個問題。直接舉個例子來說:假設有一個相機軟體,主張可以保護你的個人隱私,所以把你照的照片都放在只有這個相機軟體才讀得到的地方。但你有天想要利用LINE分享給朋友,你該怎麼辦?

其實Android有提供一個叫FileProvider的東西,可以讓你使用content://Uri並安全地分享檔案。那你會問,前一段才講說被保護住了,別人無法讀取,那他如何解決權限的問題。關鍵點就在於FileProvider使用content URI。Content URI允許你"暫時"授於讀或寫的權限給你指定的application。你當然也可以說,那我把要分享的檔案放到SD卡或把檔案權限開成o+rw也可以,當然,不過這樣也門戶大開,所有人都可以來讀你的資料了。

以下就讓我們一步一步來看要如何使用FileProvider來分享檔案:

1. 指定你想分享的檔案位置:
在res/下新增一個xml/files_path.xml,並指定你想透過FileProvider分享的檔案位置和名稱。如下:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 後面的path="images/",實際上會對應到 -->
    <!-- /data/data/com.example.app/files/images/ -->
    <!-- (com.example.app為package name) -->
    <files-path name="shared_files" path="share/"/>
</paths>

當然,你也可以用<external-path>, <cache-path>,只是他對應到的路徑不同(Context.getExternalFilesDir()  / Context.getCacheDir() )。

2. 定義你的FileProvider:
必需在AndroidManifest.xml中,定義FileProvider,如下:
<provider
    android:name="android.support.v4.content.FileProvider"
    <!-- 這邊需要自行定義這個provider的authorities -->
    android:authorities="com.example.myfileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        <!-- 這邊則是對應到剛剛我們定義的files_path.xml -->
        android:resource="@xml/files_path" />
</provider>

3. 取得檔案URI:
到這邊,你已經算是完了大部分的事先準備工作。但為了要分享檔案,你還需要準備好檔案的content URI:
// 如果剛剛在files_path.xml中我們是用<files-path>,
// 那我們就使用Context.getFilesDir()來取得files/的路徑。
// 別忘了在files_path.xml中後面還有一個path="share/",
// 所以這邊我們也必需接上"share"。
File sharePath = new File(Context.getFilesDir(), "share");
// 這邊就是明確指定是要取得哪個檔案的content URI了
File file = new File(sharePath, "image.jpg");
Uri contentUri = getUriForFile(getContext(), "com.example.myfileprovider", file);

4. 授予權限:
這邊算是最重要的一步了,前面這些動作,只是讓你準備好一個資料夾,並定義一個FileProvider來管理它,然後準備好其中某個檔案的content URI。可是別忘了最前面所說的,這個資料夾只有你自己讀得到,其他人還是沒有權限喔!

所以還必需暫時幫你指定的application開權限。這邊我自己有用過兩種方式:
 i. 指定要分享的application:
Intent i = new Intent();
i.setAction(ACTION);
i.setClassName("com.example.app", "com.example.another_app.MainActivity");
i.setData(contentUri);
// *** 使用 FLAG_GRANT_READ_URI_PERMISSION or 
// FLAG_GRANT_WRITE_URI_PERMISSION or both 來授予權限。
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(i);

ii. 使用Android "share via" 功能,讓使用者自行選擇想分享到哪個application:
Intent i = new Intent();
i.setAction(Intent.ACTION_SEND);
i.putExtra(Intent.EXTRA_STREAM, contentUri);
i.setType("image/jpeg");
// *** 使用 FLAG_GRANT_READ_URI_PERMISSION or 
// FLAG_GRANT_WRITE_URI_PERMISSION or both 來授予權限。
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(i, getResources().getText(R.string.send_to)));

註: 我們暫時授予的權限,會在被授予者的stack結束時自動撤銷。

Ref:
FileProvider | Android Developers
Sharing Files
Sharing Simple Data

留言

這個網誌中的熱門文章

逃得了一時 逃不了一世

Google Hangouts

我老闆是真男人!