Android读取手机内部存储JSON文件遇权限及路径获取问题
问题修复方案
一、解决「Error getting file path」错误
你的getPathFromUri函数存在核心问题:使用MediaStore.Images.Media.DATA列查询JSON文件,这是图片媒体库专属列,无法匹配JSON这类通用文件,导致返回null触发错误提示。此外,Android 10+的Scoped Storage限制了直接获取文件路径的能力,正确做法是直接通过Uri读取文件内容,无需转换为路径。
修复步骤:
- 删除
getPathFromUri函数,修改onActivityResult直接传递Uri给ViewModel:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == FILE_PICK_REQUEST_CODE && resultCode == Activity.RESULT_OK) { data?.data?.let { uri -> view?.findViewById<TextView>(R.id.selectedItemTv)?.text = "Selected item: ${uri.toString()}" selectedFileUri = uri // 直接保留Uri,无需转换为file://路径 // 调用ViewModel的处理函数 viewModel.handleFileSelection(requireActivity().application, requireContext(), uri) { msg -> Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show() } } } }
二、解决testPush函数的FileNotFoundException(权限拒绝)
Environment.getExternalStoragePublicDirectory在Android 10(API 29)已被废弃,且Scoped Storage限制了对公共目录的直接访问,即使申请了MANAGE_EXTERNAL_STORAGE,也需要用户手动在系统设置中为应用开启「所有文件访问权限」,该权限无法通过常规权限申请流程获取。
替代方案:
放弃硬编码路径读取文件,改用文件选择器让用户手动选择ads_interests.json,或者通过MediaStore查询该文件:
// 通过MediaStore查询Downloads目录下的ads_interests.json fun queryAdsInterestsFile(application: Application): Uri? { val projection = arrayOf(MediaStore.Files.FileColumns._ID) val selection = "${MediaStore.Files.FileColumns.DISPLAY_NAME} = ? AND ${MediaStore.Files.FileColumns.RELATIVE_PATH} = ?" val selectionArgs = arrayOf("ads_interests.json", "${Environment.DIRECTORY_DOWNLOADS}/") val cursor = application.contentResolver.query( MediaStore.Files.getContentUri("external"), projection, selection, selectionArgs, null ) cursor?.use { if (it.moveToFirst()) { val id = it.getLong(it.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)) return MediaStore.Files.getContentUri("external").buildUpon().appendPath(id.toString()).build() } } return null } // 使用查询到的Uri读取文件 fun testPush(application: Application) { val fileUri = queryAdsInterestsFile(application) ?: return try { application.contentResolver.openInputStream(fileUri)?.use { inputStream -> val result = inputStream.bufferedReader().readText() myDataReference.setValue(result) } } catch (e: Exception) { e.printStackTrace() } }
若必须使用MANAGE_EXTERNAL_STORAGE,需添加代码引导用户开启权限:
fun requestManageStoragePermission(context: Context) { if (!Environment.isExternalStorageManager()) { val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) intent.data = Uri.parse("package:${context.packageName}") context.startActivity(intent) } }
三、修复handleFileSelection函数的逻辑错误
该函数错误地尝试从assets文件夹读取文件,而用户选择的是外部存储文件,需改为通过ContentResolver读取Uri对应的文件内容:
fun handleFileSelection( application: Application, appContext: Context, fileUri: Uri, callback: (String) -> Unit ) { viewModelScope.launch(Dispatchers.IO) { try { // 通过ContentResolver读取外部文件 val inputStream = application.contentResolver.openInputStream(fileUri) inputStream?.use { stream -> val jsonString = stream.bufferedReader().readText() // Parse JSON using Gson val gson = Gson() val jsonData = gson.fromJson(jsonString, jsonData::class.java) // Upload interests to Firebase uploadInterests(jsonData) callback("File uploaded successfully") } ?: callback("Failed to open file") } catch (e: Exception) { e.printStackTrace() callback("Error: ${e.message}") } } }
四、其他注意事项
- 移除
openFilePicker中不必要的MediaStore.EXTRA_OUTPUT参数,ACTION_GET_CONTENT无需该参数,它用于拍照等场景输出文件。 - 确保
FileProvider配置正确(在AndroidManifest.xml中声明,且paths.xml包含对应路径),不过文件选择器返回的Uri通常是系统提供的,无需自行处理。
内容的提问来源于stack exchange,提问作者Giga Shubitidze
相关产品推荐
相关产品推荐

