将应用设置为使用内容 URI 共享文件后,您可以响应其他应用的 对这些文件的请求响应这些请求的一种方法是提供文件选择 服务器应用可调用的接口。通过这种方法, 应用,让用户从服务器应用中选择文件,然后接收所选文件的 内容 URI。
本课介绍了如何在应用中创建文件选择 Activity
用于响应文件请求
接收文件请求
如需接收来自客户端应用的文件请求并使用内容 URI 进行响应,您的应用应
提供文件选择 Activity。客户端应用启动此操作
通过使用包含该操作的 Intent 调用 startActivityForResult() 来实现 Activity
ACTION_PICK。当客户端应用调用
startActivityForResult(),您的应用可以
以用户所选文件的内容 URI 的形式将结果返回给客户端应用。
如需了解如何在客户端应用中实现文件请求,请参阅 请求共享文件。
创建文件选择 Activity
如需设置文件选择 Activity,请先指定
Activity,以及一个 intent 过滤器
与操作ACTION_PICK和
类别CATEGORY_DEFAULT和
CATEGORY_OPENABLE。同时添加 MIME 类型过滤器
向其他应用提供的文件。以下代码段展示了如何指定
新的 Activity 和 intent 过滤器:
<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
接下来,定义一个 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() 方法,您可以在其中获取所选文件。
在使用 intent 将文件的 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()生成,它们的
处理与 Content Provider 关联的 URI。通过
FileProvider API 可以
可帮助您创建此类 URI。此方法也适用于
存储在外部存储空间中,但在发送 intent 的应用的本地存储空间中。
在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 权限对
Uri.fromFile(),他们确实
处理与 Content Provider 关联的 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(); }
如需了解其他相关信息,请参阅: