將應用程式設定成使用內容 URI 共用檔案後,您就可以回應其他應用程式 針對這些檔案提出請求回應這些要求的方法之一就是提供檔案選取項目 設定介面,供其他應用程式叫用。這個方法 應用程式可讓使用者從伺服器應用程式選取檔案,然後接收所選檔案的 內容 URI
本課程將說明如何在應用程式中建立檔案選取 Activity
回應檔案要求
接收檔案要求
如要從用戶端應用程式接收檔案請求並以內容 URI 做出回應,您的應用程式
提供檔案選項 Activity
。用戶端應用程式啟動
Activity
,方法是使用內含動作的 Intent
呼叫 startActivityForResult()
ACTION_PICK
。當用戶端應用程式呼叫時
startActivityForResult()
,您的應用程式可
將結果以使用者所選檔案的內容 URI 格式傳回用戶端應用程式。
如要瞭解如何在用戶端應用程式中實作檔案要求,請參閱課程 要求分享檔案。
建立檔案選取活動
如要設定檔案選取功能 Activity
,請先指定
資訊清單中的 Activity
和意圖篩選器
動作符合 ACTION_PICK
和
CATEGORY_DEFAULT
和
CATEGORY_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(); }
如需其他相關資訊,請參閱: