如何从SAF文件选择器请求获取文件路径与实例并避免大文件复制卡顿
问题根因
当前的整屏卡顿是因为在主线程执行大文件IO操作,1~2GB的视频全量复制到本地缓存会长时间阻塞主线程,且该复制操作完全没有必要,SAF返回的URI本身就支持直接读取内容,适配Android 4.4到Android 12+全版本无需使用legacy storage相关标志位。
最优实现方案(无需复制文件)
- 首先要持久化URI权限:你请求时已经添加了
FLAG_GRANT_PERSISTABLE_URI_PERMISSION,拿到返回的URI后要主动调用持久化权限方法,即使应用重启也能正常访问该URI,无需用户重复授权。 - 直接通过URI读取内容上传:不要强行将URI转为本地文件路径,直接通过
ContentResolver打开输入流喂给上传服务即可,全程不会产生额外存储占用,也没有大文件复制的耗时开销。 - 若上传服务仅支持本地文件路径:则必须把复制操作放到后台线程执行(比如协程IO调度器、子线程),复制过程可搭配进度提示,不会阻塞主界面。
代码修改参考
1. URI权限持久化(拿到返回URI后第一时间执行)
uri?.let { val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION context.contentResolver.takePersistableUriPermission(it, takeFlags) }
2. 直接通过URI构造上传请求示例(以OkHttp为例)
不需要生成本地缓存文件,直接读取URI内容上传:
fun buildRequestBodyFromUri(uri: Uri, context: Context): RequestBody { val contentResolver = context.contentResolver val mimeType = contentResolver.getType(uri) ?: "video/*" val fileName = getFileNameFromUriOrNull(uri, contentResolver) ?: "unknown_video" return object : RequestBody() { override fun contentType() = MediaType.parse(mimeType) override fun contentLength() = contentResolver.openFileDescriptor(uri, "r")?.statSize ?: -1 override fun writeTo(sink: BufferedSink) { contentResolver.openInputStream(uri)?.use { input -> sink.outputStream().use { output -> input.copyTo(output) } } } } }
注意事项
- 不要强制把SAF的URI转成本地路径,Android 10+之后第三方应用无权限访问其他应用的私有目录路径,转出来的路径本身无效,强制使用会引发崩溃,通过
ContentResolver操作URI是官方唯一推荐的全版本适配方案。 - 若使用第三方上传SDK,优先查找SDK是否支持传入
ContentResolver和URI的接口,目前主流上传SDK都已适配SAF场景。 - 确实需要本地缓存的场景,必须在后台线程执行文件复制操作,绝对不能在主线程执行IO操作。
内容的提问来源于stack exchange,提问作者ansh sachdeva
相关产品推荐
相关产品推荐

