安卓11以正确的姿势读取Android/data目录

Updated on with 3,050 views

Android 11禁止了普通用户对Android/data目录的访问,所有采用之前的方法已经无法访问Android/data目录了.

正确的方式是使用Google官方提供的SAF框架来获取Android/data目录的访问.

具体可参照GitHub上我的工具类Utils

代码如下

package cn.yanjingtp.utils.utils

import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.provider.DocumentsContract
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.documentfile.provider.DocumentFile
import cn.yanjingtp.utils.base.CxtUtil
import java.net.URLEncoder
import java.nio.charset.StandardCharsets

/**
 * 存储访问框架 (SAF)相关
 * 1.在onCreate中注册registerForActivityResult
 * 2.调用getSAFGrant判断目标目录是否有权限
 * 3.没有权限调用getSAFGrant
 * 4.当有权限的时候调用getDocumentFilePath获取name,uri等信息
 */

/**
 * 判断是否已经获取了指定Data目录权限
 * @param targetPath 目标目录 如:Android/data/com.tencent.mm/MicroMsg/Download
 */
fun getSAFGrant(targetPath: String): Boolean {
    for (persistedUriPermission in CxtUtil.getCxt().contentResolver.persistedUriPermissions) {
        if (persistedUriPermission.isReadPermission && persistedUriPermission.uri.toString() == "content://com.android.externalstorage.documents/tree/primary%3A${
                    URLEncoder.encode(targetPath, StandardCharsets.UTF_8.name())
                }") {
            return true
        }
    }
    return false
}

/**
 * 获取指定Data目录权限
 * @param targetPath 目标目录 如:Android/data/com.tencent.mm/MicroMsg/Download
 */
fun getSAFGrant(targetPath: String, startActivity: ActivityResultLauncher<Intent>?) {
    val uri: String =  changeToUri(targetPath)
    val parse = Uri.parse(uri)
    val intent = Intent("android.intent.action.OPEN_DOCUMENT_TREE")
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, parse)
    }
    startActivity?.launch(intent)
}

/**
 * 在onActivityResult中的回调,并保存这个目录的访问权限
 */
@SuppressLint("WrongConstant")
fun registerForActivityResult(activity: AppCompatActivity): ActivityResultLauncher<Intent> {
    return activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
        //保存这个目录的访问权限
        activityResult.data?.data?.let {
            CxtUtil.getCxt().contentResolver.takePersistableUriPermission(it,
                (Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) and activityResult.data!!.flags)
        }
    }
}

/**
 * 直接返回DocumentFile,可通过DocumentFile获取到name,uri等信息
 * @param targetPath 目标目录 如:Android/data/com.tencent.mm/MicroMsg/Download
 */
fun getDocumentFilePath(targetPath: String): DocumentFile? {
    return DocumentFile.fromTreeUri(CxtUtil.getCxt(), Uri.parse(changeToUri(targetPath)))
}

/**
 * 转换至uriTree的路径
 */
private fun changeToUri(path: String):String {
    val dealPath =   if (path.startsWith("/storage/emulated/0/")) path.replace("/storage/emulated/0/","") else path
    return "content://com.android.externalstorage.documents/tree/primary%3A${URLEncoder.encode(dealPath, StandardCharsets.UTF_8.name())}"
}


标题:安卓11以正确的姿势读取Android/data目录
作者:yanjing
地址:https://yanjingtp.cn/articles/2022/06/25/1656147951279.html