search
分发文档
分发文档/应用分发/生态政策/控件适配指南/文件选择控件
文件选择控件更新时间:2025-12-26 11:14:00

一、背景

传统上,应用需读取文件信息时,通常通过申请存储权限或所有文件访问权限,由用户主动授权后,应用才可以读取手机存储的文件。该方案存在诸多痛点,例如:

  • 应用一般不需要读取手机上的所有文件数据,但申请并获得对应的权限,将获得读取存储空间上所有文件数据的能力,存在申请数据范围过大的问题;
  • 与此同时,用户出于隐私考虑,可能会拒绝授权,导致应用相关功能不可用,影响应用的用户体验;
  • 此外,用户如果多次拒绝授权,应用则需引导用户,跳转到设置中,手动授权,才能继续正常使用应用相关功能,影响应用用户体验。

为解决上述问题,文件Picker应运而生。应用可通过接入文件Picker,弹出系统级的文件选择界面,由用户手动选择需要的文件,授权给三方应用读取,具有如下显著优势:

  • 应用不再需要申请完整的存储权限,解决用户拒绝授权导致应用功能不可用问题;
  • 应用只能读取用户选择的文件数据,满足用户最小化授权的要求。

二、功能介绍

应用通过接入文件Picker,在用户使用相关功能时,弹出系统级的文件Picker选择界面,由用户手动选择需要授权的文件授权给三方应用,三方应用读取到用户选择文件数据,继续使用应用功能,文件Picker支持应用进行单选和多选,最多支持选择100个文件。

三、适用范围

当前支持最新视觉效果文件Picker的操作系统版本如下:

  • ColorOS 16(基于Android 16)及以上
  • HyperOS 3(基于Android 16)及以上
  • OriginOS 6(基于Android 16)及以上
  • MagicOS 10(基于Android 16)及以上

四、使用说明

以下详细说明文件Picker的接口。

1. 文件Intent参数构造

应用可按下述参数说明,构造Intent,使用startActivityForResult(Intent intent, int requestCode),拉起文件Picker界面。

1.1 action

构造Intent,应用需传入action参数,必选。

构建参数action
参数类型String
构造APInew Intent(String actionName)
取值Intent.ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT"

调用示例:

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);

1.2 type

使用type参数,用于指定文件的数据类型,必须指定。

构建参数type
参数类型String
构造APIIntent.setType(String type)
取值范围文件类型字符串,如"text/plain","application/pdf","*/*"等

调用示例:

intent.setType("text/plain");

1.3 Extras:EXTRA_MIME_TYPES

应用可选传入MIME 类型数组,限制用户只能选择这些类型的文件,配合type使用

构建参数EXTRA_MIME_TYPES
参数类型String
构造APIIntent.putExtra(String name, String[] filterFileType)
取值范围name参数:Intent.EXTRA_MIME_TYPES
name参数说明:限制用户选择的文件类型
filterFileType参数取值范围:字符串数组
filterFileType参数说明:文件类型,如new String[] {"image/*", // 所有图片
"application/pdf" // PDF文件 }

调用示例:

intent.setType("*/*");
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{
"text/*",
"application/pdf"});

1.4. Extras:EXTRA_ALLOW_MULTIPLE

应用可选传入是否多选的EXTRA_ALLOW_MULTIPLE参数,如不传入,则默认为单选。

构造参数EXTRA_ALLOW_MULTIPLE
参数类型boolean
构造APIIntent.putExtra(String name, boolean value)
取值范围name参数:Intent.EXTRA_ALLOW_MULTIPLE
name参数说明:是否多选
value参数取值范围:true/false
value参数说明:true对应多选,false对应单选

备注:文件Picker多选最大支持可选100个文件。

调用示例:

intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)

2. Intent返回值

2.1. requestCode

onActivityResult 回调中,requestCode 值就是传入 startActivityForResult requestCode,关联回调的结果与对应的请求。

返回值requestCode
参数类型int
取值与startActivityForResult(Intent intent, int requestCode)时传入的requestCode一致,代表对应请求的回调。

2.2. resultCode

返回值resultCode
参数类型int
取值范围Activity.RESULT_OK,成功
Activity.RESULT_CANCELED,被取消

2.3. data

返回值data
参数类型intent
数据处理data.getData()拿到Uri,之后可通过ContentResolver.openInputStream读取文件数据

3. 持久化授权

持久化授权takePersistableUriPermission 是 Android ContentResolver 类中的一个方法,用于获取针对给定 URI 的长期持久权限授予,例如通过 ACTION_OPEN_DOCUMENT以选择特定文件,使应用能够在设备重启和应用重新启动后保留对用户所选文档的访问权限。

// 在onActivityResult中
final int takeFlags = intent.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);  
// Check for the freshest data.
getContentResolver().takePersistableUriPermission(uri, takeFlags);  

4. androidX库接口

应用接入时,可以使用androidX库的封装接口:

打开单个文件:
https://developer.android.com/reference/kotlin/androidx/activity/result/contract/ActivityResultContracts.OpenDocument

打开多个文件:
https://developer.android.com/reference/kotlin/androidx/activity/result/contract/ActivityResultContracts.OpenMultipleDocuments

详细实现代码:
https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContracts.kt;l=456

五、使用示例

1. 兼容性适配

1.1 移除AndroidManifest权限配置

在支持文件Picker的系统版本,应用不再需要申请存储相关权限时:

  • 应用可移除 AndroidManiest.xml 的读取存储相关权限申请
<!--<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />-->
<!--<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />-->
  • 应用也可在AndroidManifest.xml中配置读取存储相关权限最高可用的Android版本,以最高支持在Android 15(SDK版本号35)申请存储相关权限。
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="35" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:maxSdkVersion="35" />

1.2 判断系统是否支持

判断系统级文件Picker是否可用参考代码

public boolean isFilePickerAvailable(Intent intent) {
return getPackageManager().resolveActivity(intent, 0) != null;
}

基于Android 16及以上的国内机型,为厂商优化后的新UI风格,体验更好。

应用可在不支持文件Picker的操作系统,继续申请读取文件相关权限,参考兼容代码如下:

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.setType("text/plain")
if (isFilePickerAvaiable(intent)) {    
// 支持,拉起文件Picker
startActivityForResult(intent, REQUEST_CODE);
} else {
// 不支持,申请权限等方式
}

2. 打开文件

Java用法示例

import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;

private static final String LOG_TAG = "PickerDemo";

public void pickOpenFile() {
// 构造intent,设置action
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"text/plain"});
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false) // 是否支持多选,true支持,false不支持
openActivityResultLauncher.launch(intent);
}

ActivityResultLauncher<Intent> openActivityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
        @Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent data = result.getData();
Uri uri = data.getData();
if (uri != null) { // 单选
try (InputStream inputStream = getContext().getContentResolver().openInputStream(uri)) {
if (inputStream != null) {
byte[] bytes = inputStream.readAllBytes();
Log.d(LOG_TAG, "Selected URI: " + uri + ", read success:" + new String(bytes));
                        }
                    } catch (IOException e) {
Log.d(LOG_TAG, "Selected URI: " + uri+", read failed");
                    }
                } else { // 多选
ClipData clipData = data.getClipData();
if (clipData != null) {
for (int i= 0; i < clipData.getItemCount(); i++) {
Log.d(LOG_TAG, "Selected URI is " + clipData.getItemAt(i).getUri());
                        }
                    }                  
                }                
            }
        }
    });

Kotlin用法示例

import androidx.activity.result.contract.ActivityResultContracts

private val LOG_TAG = "PickerDemo"

fun pickOpenFile() {
    // 构造intent,设置action
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
    intent.addCategory(Intent.CATEGORY_OPENABLE)
    intent.setType("text/plain")
    intent.putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("text/plain"))
    intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false) // 是否支持多选,true支持,false不支持
    openActivityResultLauncher.launch(intent)
}

private val openActivityResultLauncher = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()) { result ->
    if (result.resultCode == Activity.RESULT_OK) {
        val data: Intent? = result.data
        if (data != null) {
            val uri = data.data
            try {
                if (uri != null) { // 单选
                    getContentResolver().openInputStream(uri).use { inputStream ->
                        if (inputStream != null) {
                            val bytes: ByteArray = inputStream.readAllBytes()
                            Log.d(LOG_TAG, "Selected URI: " + uri + ", read success:" + String(bytes))
                        }
                    }
                } else { // 多选
                    val clipData = data.clipData
                    if (clipData != null) {
                        for (i in 0..<clipData!!.itemCount) {
                            Log.d(LOG_TAG,"Selected URI is " + clipData.getItemAt(i).uri)
                        }
                    }
                }
            } catch (e: IOException) {
                Log.e(LOG_TAG, "Selected URI: $uri, read failed")
            }
        }
    }
}

六、参考

文件类型含义
image/*所有图片
audio/*所有音频
video/*所有视频
text/* 所有文本
text/plain纯文本
application/pdf PDF文档
application/zipZIP压缩包
text/htmlHTML 文档
application/xhtml+xmlXHTML 文档
image/gifGIF 图像
image/jpegJPEG 图像
image/bmpBMP 图像
image/pngPNG 图像
video/mpegMPEG 动画
application/octet-stream任意二进制数据
application/mswordMicrosoft Word 文件
video/3gpp3GP 视频
application/vnd.android.package-archiveAPK 文件
audio/mpegMP3 音频
video/mp4 MP4 视频
application/vnd.ms-powerpointPPT 文件
application/vnd.ms-excelExcel 文件
application/jsonJSON 数据
application/xmlXML 数据
\*/\* 所有文件类型

该适配指南与金标联盟官网适配指南一致,您也可查阅金标联盟官网中公示适配指南。

金标联盟官方文档地址:https://www.itgsa.com/doc/6631953378231296

上一篇:联系人选择控件
下一篇:照片视频选择控件
文档内容是否有帮助?
有帮助
无帮助