将应用设置为使用内容 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(); }
如需了解其他相关信息,请参阅: