在Android9.0开发过程中,我们想往TF卡里面写入数据的时候,我们在代码中和资源文件中都申请了权限,但是一直会报如下权限问题 一、错误日志
saveBytes Exception e /storage/A466-13E5/DCIM/test/1627287905384.yuv (Permission denied)
解决办法: 代码路径:system\vold\model\PublicVolume.cpp
if (!(mFusePid = fork())) {
if (getMountFlags() & MountFlags::kPrimary) {
if (execl(kFusePath, kFusePath,
"-u", "1023", // AID_MEDIA_RW
"-g", "1023", // AID_MEDIA_RW
"-U", std::to_string(getMountUserId()).c_str(),
"-w",
mRawPath.c_str(),
stableName.c_str(),
NULL)) {
PLOG(ERROR) << "Failed to exec";
}
} else {
if (execl(kFusePath, kFusePath,
"-u", "1023", // AID_MEDIA_RW
"-g", "1023", // AID_MEDIA_RW
"-U", std::to_string(getMountUserId()).c_str(),
"-w", // 添加 写 权限就是这样
mRawPath.c_str(),
stableName.c_str(),
NULL)) {
PLOG(ERROR) << "Failed to exec";
}
}
LOG(ERROR) << "FUSE exiting";
_exit(1);
}
备注说明:修改上面文件后,我们进入到system/vold目录下面执行mm命令,然后把编译生成的文件推到system/bin文件,然后重启设备后可以执行adb shell命令,然后执行mount命令查看挂载设备
/mnt/media_rw/A466-13E5 on /mnt/runtime/default/A466-13E5 type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,mask=6)
/mnt/media_rw/A466-13E5 on /storage/A466-13E5 type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,mask=6)
/mnt/media_rw/A466-13E5 on /mnt/runtime/read/A466-13E5 type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,mask=23)
/mnt/media_rw/A466-13E5 on /mnt/runtime/write/A466-13E5 type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,mask=7)
代码示例测试: 1、在AndroidManifest.xml文件中添加权限
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
2、代码中动态申请权限
public class Permission {
public static final int REQUEST_CODE = 5;
private static final String[] PERMISSIONS = new String[]{
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
};
public static boolean checkPermission(Activity activity) {
if (isPermissionGranted(activity)) {
return true;
} else {
ActivityCompat.requestPermissions(activity, PERMISSIONS, REQUEST_CODE);
return false;
}
}
public static boolean isPermissionGranted(Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
for (int i = 0; i < PERMISSIONS.length; i++) {
int checkPermission = ContextCompat.checkSelfPermission(activity, PERMISSIONS[i]);
if (checkPermission != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
}
return true;
}
}
3、在Activity.java中检测权限,如果有权限就执行下一步的处理
@Override
protected void onStart() {
super.onStart();
Permission.checkPermission(this);
}
@Override
protected void onResume() {
super.onResume();
if (Permission.isPermissionGranted(this)) {
initCameraFragment();
}
}
按照上面的修改后,我们就可以正常汪TF卡里面读写数据。 二、在Android 9.0 ,发现文件管理器在写入外置 SD 卡时出现了写入失败的问题,定位到 File.canWrite() 方法,发现返回了 false。经过讨论追踪定位,发现是由于 Google 的一个更改导致的: 代码路径
framework/base/services/core/java/com/android/server/pm/PackageManagerService.java
framework/base/data/etc/platform.xml
源代码中把framework/base/data/etc/platform.xml中的sdcard_rw权限去掉了
<permission name="android.permission.WRITE_MEDIA_STORAGE" >
<group gid="media_rw" />
- <group gid="sdcard_rw" />
</permission>
在PackageManagerService.java文件中systemReady()中把这些写的权限去掉
if (Process.isIsolated(uid)) {
return Zygote.MOUNT_EXTERNAL_NONE;
}
- if (checkUidPermission(WRITE_MEDIA_STORAGE, uid) == PERMISSION_GRANTED) {
- return Zygote.MOUNT_EXTERNAL_DEFAULT;
- }
if (checkUidPermission(READ_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
return Zygote.MOUNT_EXTERNAL_DEFAULT;
}
这里的修改移除了 WRITE_MEDIA_STORAGE 权限相关权限,导致了外部 SD 卡存储不可写的问题 三、DocumentFile 适配方案 1、DocumentFile 权限请求及处理 权限请求需要在 Activity 或者 Fragment 中发起,同时在 onActivityResult 中捕获返回的 Uri,这个 Uri 可以保存在本地存储中,方便再次调用。请求的代码封装如下:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
if (DocumentsUtils.checkWritableRootPath(getActivity(), rootPath)) {
showOpenDocumentTree();
}
// ...
}
private void showOpenDocumentTree() {
Intent intent = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
StorageManager sm = getActivity().getSystemService(StorageManager.class);
StorageVolume volume = sm.getStorageVolume(new File(rootPath));
if (volume != null) {
intent = volume.createAccessIntent(null);
}
}
if (intent == null) {
intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
}
startActivityForResult(intent, DocumentsUtils.OPEN_DOCUMENT_TREE_CODE);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case DocumentsUtils.OPEN_DOCUMENT_TREE_CODE:
if (data != null && data.getData() != null) {
Uri uri = data.getData();
DocumentsUtils.saveTreeUri(getActivity(), rootPath, uri);
}
break;
default:
break;
}
}
2、DocumentFile 文件操作封装
public class DocumentsUtils {
private static final String TAG = DocumentsUtils.class.getSimpleName();
public static final int OPEN_DOCUMENT_TREE_CODE = 8000;
private static List<String> sExtSdCardPaths = new ArrayList<>();
private DocumentsUtils() {
}
public static void cleanCache() {
sExtSdCardPaths.clear();
}
/**
* Get a list of external SD card paths. (Kitkat or higher.)
*
* @return A list of external SD card paths.
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
private static String[] getExtSdCardPaths(Context context) {
if (sExtSdCardPaths.size() > 0) {
return sExtSdCardPaths.toArray(new String[0]);
}
for (File file : context.getExternalFilesDirs("external")) {
if (file != null && !file.equals(context.getExternalFilesDir("external"))) {
int index = file.getAbsolutePath().lastIndexOf("/Android/data");
if (index < 0) {
Log.w(TAG, "Unexpected external file dir: " + file.getAbsolutePath());
} else {
String path = file.getAbsolutePath().substring(0, index);
try {
path = new File(path).getCanonicalPath();
} catch (IOException e) {
// Keep non-canonical path.
}
sExtSdCardPaths.add(path);
}
}
}
Log.v(TAG, "getExtSdCardPaths sExtSdCardPaths.isEmpty() "+sExtSdCardPaths.isEmpty());
Log.v(TAG, "getExtSdCardPaths sExtSdCardPaths.get(0) "+sExtSdCardPaths.get(0));
if (sExtSdCardPaths.isEmpty()) sExtSdCardPaths.add("/storage/sdcard1");
return sExtSdCardPaths.toArray(new String[0]);
}
/**
* Determine the main folder of the external SD card containing the given file.
*
* @param file the file.
* @return The main folder of the external SD card containing this file, if the file is on an SD
* card. Otherwise,
* null is returned.
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
private static String getExtSdCardFolder(final File file, Context context) {
String[] extSdPaths = getExtSdCardPaths(context);
try {
for (int i = 0; i < extSdPaths.length; i++) {
if (file.getCanonicalPath().startsWith(extSdPaths[i])) {
return extSdPaths[i];
}
}
} catch (IOException e) {
return null;
}
return null;
}
/**
* Determine if a file is on external sd card. (Kitkat or higher.)
*
* @param file The file.
* @return true if on external sd card.
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
public static boolean isOnExtSdCard(final File file, Context c) {
return getExtSdCardFolder(file, c) != null;
}
/**
* Get a DocumentFile corresponding to the given file (for writing on ExtSdCard on Android 5).
* If the file is not
* existing, it is created.
*
* @param file The file.
* @param isDirectory flag indicating if the file should be a directory.
* @return The DocumentFile
*/
public static DocumentFile getDocumentFile(final File file, final boolean isDirectory,
Context context) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
return DocumentFile.fromFile(file);
}
String baseFolder = getExtSdCardFolder(file, context);
boolean originalDirectory = false;
if (baseFolder == null) {
return null;
}
String relativePath = null;
try {
String fullPath = file.getCanonicalPath();
if (!baseFolder.equals(fullPath)) {
relativePath = fullPath.substring(baseFolder.length() + 1);
} else {
originalDirectory = true;
}
} catch (IOException e) {
return null;
} catch (Exception f) {
originalDirectory = true;
//continue
}
String as = PreferenceManager.getDefaultSharedPreferences(context).getString(baseFolder,
null);
Uri treeUri = null;
if (as != null) treeUri = Uri.parse(as);
if (treeUri == null) {
return null;
}
// start with root of SD card and then parse through document tree.
DocumentFile document = DocumentFile.fromTreeUri(context, treeUri);
if (originalDirectory) return document;
String[] parts = relativePath.split("/");
for (int i = 0; i < parts.length; i++) {
DocumentFile nextDocument = document.findFile(parts[i]);
if (nextDocument == null) {
if ((i < parts.length - 1) || isDirectory) {
nextDocument = document.createDirectory(parts[i]);
} else {
nextDocument = document.createFile("image", parts[i]);
}
}
document = nextDocument;
}
return document;
}
public static boolean mkdirs(Context context, File dir) {
boolean res = dir.mkdirs();
if (!res) {
if (DocumentsUtils.isOnExtSdCard(dir, context)) {
DocumentFile documentFile = DocumentsUtils.getDocumentFile(dir, true, context);
res = documentFile != null && documentFile.canWrite();
}
}
return res;
}
public static boolean delete(Context context, File file) {
boolean ret = file.delete();
if (!ret && DocumentsUtils.isOnExtSdCard(file, context)) {
DocumentFile f = DocumentsUtils.getDocumentFile(file, false, context);
if (f != null) {
ret = f.delete();
}
}
return ret;
}
public static boolean canWrite(File file) {
boolean res = file.exists() && file.canWrite();
if (!res && !file.exists()) {
try {
if (!file.isDirectory()) {
res = file.createNewFile() && file.delete();
} else {
res = file.mkdirs() && file.delete();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return res;
}
public static boolean canWrite(Context context, File file) {
boolean res = canWrite(file);
if (!res && DocumentsUtils.isOnExtSdCard(file, context)) {
DocumentFile documentFile = DocumentsUtils.getDocumentFile(file, true, context);
res = documentFile != null && documentFile.canWrite();
}
return res;
}
public static boolean renameTo(Context context, File src, File dest) {
boolean res = src.renameTo(dest);
if (!res && isOnExtSdCard(dest, context)) {
DocumentFile srcDoc;
if (isOnExtSdCard(src, context)) {
srcDoc = getDocumentFile(src, false, context);
} else {
srcDoc = DocumentFile.fromFile(src);
}
DocumentFile destDoc = getDocumentFile(dest.getParentFile(), true, context);
if (srcDoc != null && destDoc != null) {
try {
if (src.getParent().equals(dest.getParent())) {
res = srcDoc.renameTo(dest.getName());
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
res = DocumentsContract.moveDocument(context.getContentResolver(),
srcDoc.getUri(),
srcDoc.getParentFile().getUri(),
destDoc.getUri()) != null;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
return res;
}
public static InputStream getInputStream(Context context, File destFile) {
InputStream in = null;
try {
if (!canWrite(destFile) && isOnExtSdCard(destFile, context)) {
DocumentFile file = DocumentsUtils.getDocumentFile(destFile, false, context);
if (file != null && file.canWrite()) {
in = context.getContentResolver().openInputStream(file.getUri());
}
} else {
in = new FileInputStream(destFile);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return in;
}
public static OutputStream getOutputStream(Context context, File destFile) {
OutputStream out = null;
try {
if (!canWrite(destFile) && isOnExtSdCard(destFile, context)) {
DocumentFile file = DocumentsUtils.getDocumentFile(destFile, false, context);
if (file != null && file.canWrite()) {
out = context.getContentResolver().openOutputStream(file.getUri());
}
} else {
out = new FileOutputStream(destFile);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return out;
}
public static boolean saveTreeUri(Context context, String rootPath, Uri uri) {
DocumentFile file = DocumentFile.fromTreeUri(context, uri);
if (file != null && file.canWrite()) {
SharedPreferences perf = PreferenceManager.getDefaultSharedPreferences(context);
perf.edit().putString(rootPath, uri.toString()).apply();
return true;
} else {
Log.e(TAG, "no write permission: " + rootPath);
}
return false;
}
public static boolean checkWritableRootPath(Context context, String rootPath) {
File root = new File(rootPath);
if (!root.canWrite()) {
if (DocumentsUtils.isOnExtSdCard(root, context)) {
DocumentFile documentFile = DocumentsUtils.getDocumentFile(root, true, context);
return documentFile == null || !documentFile.canWrite();
} else {
SharedPreferences perf = PreferenceManager.getDefaultSharedPreferences(context);
String documentUri = perf.getString(rootPath, "");
if (documentUri == null || documentUri.isEmpty()) {
return true;
} else {
DocumentFile file = DocumentFile.fromTreeUri(context, Uri.parse(documentUri));
return !(file != null && file.canWrite());
}
}
}
return false;
}
}
备注说明:我测试发现platform.xml和PackageManagerService.java文件中把权限加上,同时结合DocumentFile 适配方案测试后发现还是不行 文件参考:
Android 9.0 SD卡写入权限问题
https://blog.csdn.net/qq_36467463/article/details/88691726
【Android笔记】Android 9.0 SD卡读写权限问题
https://www.jianshu.com/p/f1329c001fd9
Android 9.0中sdcard 的权限和挂载问题
https://blog.csdn.net/shift_wwx/article/details/85633801
安卓内外部存储完全解析 – 别再弄混了
https://www.jianshu.com/p/116025bf51f7
Android 获取外置SD卡
https://www.2cto.com/kf/201502/377175.html
【译】如何在 Android 5.0 上获取 SD卡 的读写权限
https://blog.csdn.net/I_wait_for_you/article/details/70240114
Android 5.1 修改第三方APP读写外置SD卡权限
https://blog.csdn.net/yhyqf/article/details/85317777
Android P 外置 SD 卡写入权限问题
https://blog.csdn.net/ch853199769/article/details/88052886
|