Android最佳實踐之分享文件

原文鏈接:Sharing a File

本課程教你:

  • 接收文件請求
  • 創建選取文件的Activity
  • 響應文件選取
  • 對選取的文件授權
  • 請求應用分享這個文件

一旦你設置了你的應用使用content URIs來分享文件,你就能夠響應其他應用對這些文件的請求。響應這些請求的一種方式是從服務方應用提供一個文件選擇接口供其他應用來調用。這種方式允許客戶端應用用戶從服務端應用選取文件,然后接收選中文件的content URI。

本課程教你如何在你的應用中根據請求的文件創建一個文件選取的 Activity

接收文件請求

為了接收來自客戶端應用的請求以及返回一個content URI,你的應用需要提供一個文件選取 Activity。客戶端應用使用包含 ACTION_PICK 動作的 Intent 調用 [startActivityForResult()](https://developer.android.com/reference/android/app/Activity.html#startActivityForResult(android.content.Intent, int)) 啟動這個 Activity ,你的應用將根據用戶選取的文件將對應的 content URI返回給客戶端應用。

關于使用客戶端應用發起請求的更多內容,請參見 Requesting a Shared File(中文鏈接:請求文件分享)。

創建一個文件選擇Activity

為了設置一個選取文件的 Activity,在manifest文件中指定這個 Activity 名稱,然后在intent filter中為起添加 ACTION_PICK 動作、 CATEGORY_DEFAULT策略以及 CATEGORY_OPENABLE 策略來匹配指定的選擇事件。同樣,添加MIME類型的過濾器來指定你的應用能提供給其他應用的文件類型。下面這段代碼顯示了怎樣指定一個新的 Activity 和 intent filter。

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
...
    <application>
    ...
        <activity
            android:name=".FileSelectActivity"
            android:label="@File Selector" >
            <intent-filter>
                <action
                    android:name="android.intent.action.PICK"/>
                <category
                    android:name="android.intent.category.DEFAULT"/>
                <category
                    android:name="android.intent.category.OPENABLE"/>
                <data android:mimeType="text/plain"/>
                <data android:mimeType="image/*"/>
            </intent-filter>
        </activity>

在代碼中定義選取文件的Acitivity

下一步,定義一個 Activity 的子類,這個子類用來顯示你的應用內部存儲器files/images/目錄下的可用文件。在這個目錄下,用戶可以選擇他們想要的文件。下面這段代碼顯示了如何定義這個 Activity,以及如何根據用戶選擇做出相對應的響應:

public class MainActivity extends Activity {
    // The path to the root of this app's internal storage
    private File mPrivateRootDir;
    // The path to the "images" subdirectory
    private File mImagesDir;
    // Array of files in the images subdirectory
    File[] mImageFiles;
    // Array of filenames corresponding to mImageFiles
    String[] mImageFilenames;
    // Initialize the Activity
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
       // Set up an Intent to send back to apps that request a file
       mResultIntent =
            new Intent("com.example.myapp.ACTION_RETURN_FILE");
      // Get the files/ subdirectory of internal storage
      mPrivateRootDir = getFilesDir();
      // Get the files/images subdirectory;
       mImagesDir = new File(mPrivateRootDir, "images");
      // Get the files in the images subdirectory
      mImageFiles = mImagesDir.listFiles();
      // Set the Activity's result to null to begin with
      setResult(Activity.RESULT_CANCELED, null);
      /*
       * Display the file names in the ListView mFileListView.
       * Back the ListView with the array mImageFilenames, which
       * you can create by iterating through mImageFiles and
       * calling File.getAbsolutePath() for each File
       */
       ...
    }
    ...
}

響應文件選擇

一旦用戶選擇了需要共享的文件,你的應用必須能夠確定究竟是哪個文件被選擇了,然后為這個文件產生一個content URI。因為 ActivityListView 控件中顯示了所有可能的文件,當一個用戶選擇了一個文件時,系統就會調用 [onItemClick()](https://developer.android.com/reference/android/widget/AdapterView.OnItemClickListener.html#onItemClick(android.widget.AdapterView<?>, android.view.View, int, long)) 方法,通過這個方法你能得到用戶選擇的文件。

在 [onItemClick()](https://developer.android.com/reference/android/widget/AdapterView.OnItemClickListener.html#onItemClick(android.widget.AdapterView<?>, android.view.View, int, long)) 中,獲取一個當前選中文件的 File 對象,將它與在 FileProvider<provider> 中指定的授權一起傳遞給 [getUriForFile()](https://developer.android.com/reference/android/support/v4/content/FileProvider.html#getUriForFile(android.content.Context, java.lang.String, java.io.File))。最終生成的content URI會包含這個授權、根據文件目錄生成的路徑段、以及文件的名字和擴展名。至于 FileProvider 怎樣根據路徑段映射到對應的文件目錄,取決于在XML meta-data中的描述。詳情請參考 Specify Sharable Directories(中文鏈接:Android最佳實踐之設置文件分享——指定可分享的目錄)。

以下的代碼段教你怎樣監測選中的文件以及如何獲取它的content URI:

 protected void onCreate(Bundle savedInstanceState) {
    ...
    // Define a listener that responds to clicks on a file in the ListView
    mFileListView.setOnItemClickListener(
            new AdapterView.OnItemClickListener() {
        @Override
        /*
         * When a filename in the ListView is clicked, get its
         * content URI and send it to the requesting app
         */
        public void onItemClick(AdapterView<?> adapterView,
                View view,
                int position,
                long rowId) {
            /*
             * Get a File for the selected file name.
             * Assume that the file names are in the
             * mImageFilename array.
             */
            File requestFile = new File(mImageFilename[position]);
            /*
             * Most file-related method calls need to be in
             * try-catch blocks.
             */
            // Use the FileProvider to get a content URI
            try {
                fileUri = FileProvider.getUriForFile(
                        MainActivity.this,
                        "com.example.myapp.fileprovider",
                        requestFile);
            } catch (IllegalArgumentException e) {
                Log.e("File Selector",
                      "The selected file can't be shared: " +
                      clickedFilename);
            }
            ...
        }
    });
    ...
}

注意你只能對providermeta-data文件下<paths>元素包含的文件路徑生成相對應的 content URIs,正如 Specify Sharable Directories (中文鏈接:Android最佳實踐之設置文件分享——指定可分享的目錄)章節所說的那樣。如果你調用 [getUriForFile()](https://developer.android.com/reference/android/support/v4/content/FileProvider.html#getUriForFile(android.content.Context, java.lang.String, java.io.File)) 傳入一個未定義的文件路徑,你會收到 IllegalArgumentException 異常。

為文件授權

現在你已經有了希望分享給其他應用的content URI,你需要允許客戶端應用訪問這個文件。為了允許訪問這個文件,我們在 Intent 文件中添加這個文件的content URI并設置訪問權限標志。你允許的權限是臨時的,當接收方的應用任務棧結束的時候這個權限會自動到期。

下面的代碼教你對這個文件如何設置讀權限:

protected void onCreate(Bundle savedInstanceState) {
    ...
    // Define a listener that responds to clicks in the ListView
    mFileListView.setOnItemClickListener(
            new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView,
                View view,
                int position,
                long rowId) {
            ...
            if (fileUri != null) {
                // Grant temporary read permission to the content URI
                mResultIntent.addFlags(
                    Intent.FLAG_GRANT_READ_URI_PERMISSION);
            }
            ...
         }
         ...
    });
...
}

注意:在使用臨時訪問權限中,調用 setFlags() 是安全訪問你的文件的唯一方式。避免調用 [Context.grantUriPermission()](https://developer.android.com/reference/android/content/Context.html#grantUriPermission(java.lang.String, android.net.Uri, int)) 方法來獲取一個文件的content URI,因為這個方法會一直允許訪問,直到你調用 [Context.revokeUriPermission()](https://developer.android.com/reference/android/content/Context.html#revokeUriPermission(android.net.Uri, int)) 方法移除這個權限為止。

使用請求應用共享這個文件

為了分享這個文件到請求的應用,在setResult() 中傳送一個包含content URI和權限的 Intent。當你定義的這個 Activity 結束的時候,系統會發送這個包含content URI的 Intent 給客戶端應用。 下面這段代碼教你怎樣完成這些操作:

protected void onCreate(Bundle savedInstanceState) {
    ...
    // Define a listener that responds to clicks on a file in the ListView
    mFileListView.setOnItemClickListener(
            new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView,
                View view,
                int position,
                long rowId) {
            ...
            if (fileUri != null) {
                ...
                // Put the Uri and MIME type in the result Intent
                mResultIntent.setDataAndType(
                        fileUri,
                        getContentResolver().getType(fileUri));
                // Set the result
                MainActivity.this.setResult(Activity.RESULT_OK,
                        mResultIntent);
                } else {
                    mResultIntent.setDataAndType(null, "");
                    MainActivity.this.setResult(RESULT_CANCELED,
                            mResultIntent);
                }
            }
    });

一旦用戶選擇一個文件提供給用戶一種立即返回客戶端應用的方式。一種方法是提供一個復選標記或者完成按鈕。在按鈕上使用 android:onClick 屬性綁定一個方法,在這個方法里調用 finish()。例如:

public void onDoneClick(View v) {
    // Associate a method with the Done button
    finish();
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容