原文链接: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();
}