分享檔案

將應用程式設定成使用內容 URI 共用檔案後,您就可以回應其他應用程式 針對這些檔案提出請求回應這些要求的方法之一就是提供檔案選取項目 設定介面,供其他應用程式叫用。這個方法 應用程式可讓使用者從伺服器應用程式選取檔案,然後接收所選檔案的 內容 URI

本課程將說明如何在應用程式中建立檔案選取 Activity 回應檔案要求

接收檔案要求

如要從用戶端應用程式接收檔案請求並以內容 URI 做出回應,您的應用程式 提供檔案選項 Activity。用戶端應用程式啟動 Activity,方法是使用內含動作的 Intent 呼叫 startActivityForResult() ACTION_PICK。當用戶端應用程式呼叫時 startActivityForResult(),您的應用程式可 將結果以使用者所選檔案的內容 URI 格式傳回用戶端應用程式。

如要瞭解如何在用戶端應用程式中實作檔案要求,請參閱課程 要求分享檔案

建立檔案選取活動

如要設定檔案選取功能 Activity,請先指定 資訊清單中的 Activity 和意圖篩選器 動作符合 ACTION_PICKCATEGORY_DEFAULTCATEGORY_OPENABLE。同時新增 MIME 類型篩選器 適合應用程式提供給其他應用程式的檔案下列程式碼片段說明如何指定 新的 Activity 和意圖篩選器:

<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>

定義程式碼中的檔案選取活動

接著,定義 Activity 子類別,以顯示下列來源中的可用檔案: 在內部儲存空間中應用程式的 files/images/ 目錄,讓使用者 指定要上傳的檔案以下程式碼片段說明如何定義 Activity,並回應使用者選擇的內容:

Kotlin

class MainActivity : Activity() {

    // The path to the root of this app's internal storage
    private lateinit var privateRootDir: File
    // The path to the "images" subdirectory
    private lateinit var imagesDir: File
    // Array of files in the images subdirectory
    private lateinit var imageFiles: Array<File>
    // Array of filenames corresponding to imageFiles
    private lateinit var imageFilenames: Array<String>

    // Initialize the Activity
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Set up an Intent to send back to apps that request a file
        resultIntent = Intent("com.example.myapp.ACTION_RETURN_FILE")
        // Get the files/ subdirectory of internal storage
        privateRootDir = filesDir
        // Get the files/images subdirectory;
        imagesDir = File(privateRootDir, "images")
        // Get the files in the images subdirectory
        imageFiles = imagesDir.listFiles()
        // Set the Activity's result to null to begin with
        setResult(Activity.RESULT_CANCELED, null)
        /*
         * Display the file names in the ListView fileListView.
         * Back the ListView with the array imageFilenames, which
         * you can create by iterating through imageFiles and
         * calling File.getAbsolutePath() for each File
         */
        ...
    }
    ...
}

Java

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

回覆選取的檔案

使用者選取共用檔案後,應用程式必須判斷您選取的檔案 然後產生檔案的內容 URI由於 Activity 會顯示 當使用者點選檔案名稱時,ListView 中可用的檔案清單 系統會呼叫 onItemClick() 方法,您可以在此取得所選檔案。

使用意圖在應用程式之間傳送檔案 URI 時 您必須謹慎取得 應用程式可讀取。在搭載 Android 6.0 (API 級別 23) 以上版本的裝置上執行此作業 需要特殊 因為該 Android 版本的權限模型有所變更 READ_EXTERNAL_STORAGE的 成為 危險權限,接收端應用程式可能缺少這類權限。

考慮到這些因素後,建議您避免使用 Uri.fromFile(),需要 存在幾個缺點方法:

  • 不允許在不同設定檔之間共用檔案。
  • 規定應用程式必須符合下列條件: WRITE_EXTERNAL_STORAGE 權限。
  • 接收應用程式必須具有 READ_EXTERNAL_STORAGE 權限, 可能無法對沒有該權限的重要共用目標 (例如 Gmail) 執行。

不使用 Uri.fromFile(), 您可以使用 URI 權限授予其他應用程式 存取特定 URI雖然 URI 權限不適用於 file:// URI 產生的是 Uri.fromFile(),而他們 屬於與內容供應器相關聯的 URI FileProvider API 可 協助您建立這類 URI這個方法也適用於非 於外部儲存空間,但在傳送意圖的應用程式本機儲存空間中。

onItemClick()內, 所選檔案名稱的 File 物件,並將該物件做為引數傳送至 getUriForFile() 和 您在 FileProvider<provider> 元素。 產生的內容 URI 包含授權,也就是與 目錄 (如 XML 中繼資料所指定),以及包含其 。FileProvider 如何將目錄對應至路徑 本節將說明以 XML 中繼資料為基礎的區隔 指定可共用的目錄

下列程式碼片段說明如何偵測所選檔案,並取得該檔案的內容 URI:

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
            /*
             * Get a File for the selected file name.
             * Assume that the file names are in the
             * imageFilename array.
             */
            val requestFile = File(imageFilenames[position])
            /*
             * Most file-related method calls need to be in
             * try-catch blocks.
             */
            // Use the FileProvider to get a content URI
            val fileUri: Uri? = try {
                FileProvider.getUriForFile(
                        this@MainActivity,
                        "com.example.myapp.fileprovider",
                        requestFile)
            } catch (e: IllegalArgumentException) {
                Log.e("File Selector",
                        "The selected file can't be shared: $requestFile")
                null
            }
            ...
        }
        ...
    }

Java

    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.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
                 * imageFilename array.
                 */
                File requestFile = new File(imageFilename[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: " + requestFile.toString());
                }
                ...
            }
        });
        ...
    }

請記住,您只可以為目錄內檔案產生內容 URI 您已在包含 <paths> 元素的中繼資料檔案中指定 ,例如 ,請參閱「指定可分享的目錄」一節。如果你打電話 getUriForFile() 代表 File,您會收到 IllegalArgumentException

授予檔案權限

您現在已有要與其他應用程式共用的檔案內容 URI,接下來您需要 允許用戶端應用程式存取檔案。若要允許存取,請透過下列方式將權限授予用戶端應用程式: 將內容 URI 新增至 Intent,然後對 Intent。您授予的權限屬於暫時性性質,而且會過期 接收應用程式的工作堆疊完成時。

下列程式碼片段顯示如何設定檔案的讀取權限:

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
            ...
            if (fileUri != null) {
                // Grant temporary read permission to the content URI
                resultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                ...
            }
            ...
        }
        ...
    }

Java

    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Define a listener that responds to clicks in the ListView
        fileListView.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
                    resultIntent.addFlags(
                        Intent.FLAG_GRANT_READ_URI_PERMISSION);
                }
                ...
             }
             ...
        });
    ...
    }

注意:僅呼叫 setFlags() 可讓您使用臨時存取權限安全地授予檔案存取權。避免撥打電話 Context.grantUriPermission() 方法, 檔案的內容 URI,因為這個方法授予使用者存取權時, 正在呼叫 Context.revokeUriPermission()

請勿使用 Uri.fromFile()。強制接收應用程式 具備 READ_EXTERNAL_STORAGE 權限 如果希望在所有使用者之間分享資訊, Android 4.4 (API 級別 19) 以下版本,需要您的 應用程式,一律具備 WRITE_EXTERNAL_STORAGE 權限。 而且重要的共用目標 (例如 Gmail 應用程式) 也沒有 READ_EXTERNAL_STORAGE,造成 呼叫失敗 您可以改用 URI 權限,將特定 URI 的存取權授予其他應用程式。 雖然 URI 權限不適用於 file:// URI,因為 Uri.fromFile(),他們可以 與內容供應器相關的 URI 作業您不必自行實作這項功能 您可以和應該使用 FileProvider ,詳情請參閱「檔案共用」一文。

將檔案與要求的應用程式共用

如要將檔案提供給提出要求的應用程式,請傳遞 Intent 包含內容 URI 和 setResult() 權限您剛定義的 Activity 完成後, 系統會將包含內容 URI 的 Intent 傳送至用戶端應用程式。 下列程式碼片段說明如何執行這項作業:

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
            ...
            if (fileUri != null) {
                ...
                // Put the Uri and MIME type in the result Intent
                resultIntent.setDataAndType(fileUri, contentResolver.getType(fileUri))
                // Set the result
                setResult(Activity.RESULT_OK, resultIntent)
            } else {
                resultIntent.setDataAndType(null, "")
                setResult(RESULT_CANCELED, resultIntent)
            }
        }
    }

Java

    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.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
                    resultIntent.setDataAndType(
                            fileUri,
                            getContentResolver().getType(fileUri));
                    // Set the result
                    MainActivity.this.setResult(Activity.RESULT_OK,
                            resultIntent);
                    } else {
                        resultIntent.setDataAndType(null, "");
                        MainActivity.this.setResult(RESULT_CANCELED,
                                resultIntent);
                    }
                }
        });

讓使用者在選取檔案後,可立即返回用戶端應用程式。 其中一個方法是顯示勾號或「完成」按鈕。將方法與 按下按鈕旁的 android:onClick 屬性。在方法中,呼叫 finish()。例如:

Kotlin

    fun onDoneClick(v: View) {
        // Associate a method with the Done button
        finish()
    }

Java

    public void onDoneClick(View v) {
        // Associate a method with the Done button
        finish();
    }

如需其他相關資訊,請參閱: