原文鏈接: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。因為 Activity 在 ListView 控件中顯示了所有可能的文件,當一個用戶選擇了一個文件時,系統就會調用 [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);
}
...
}
});
...
}
注意你只能對provider
meta-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();
}