博客
关于我
Android9外置SD卡的读写权限报错
阅读量:268 次
发布时间:2019-02-28

本文共 31007 字,大约阅读时间需要 103 分钟。

外置sd卡相关功能的时候遇到了在9.0上外置SD卡写入权限的问题,在 9.0 之前的平台,申请了 WRITE_MEDIA_STORAGE 的权限后,平台签名的应用就可以通过 java.io.File 接口写入外置 SD 卡了。但之后,想要写入外置 SD 卡,就需要像第三方应用一样,使用 DocumentFile 的接口,

可以阅读 API 文档 存储访问框架 和 使用作用域目录访问 。

android 9加入权限和动态申请都无法对外置SD卡创建文件夹

   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

如果是APP开发,可以做兼容 , 以下是对DocumentFile 类调用的封装

贴代码

 

package com.mw.test;import androidx.appcompat.app.AlertDialog;import androidx.appcompat.app.AppCompatActivity;import androidx.core.app.ActivityCompat;import androidx.core.content.ContextCompat;import android.Manifest;import android.app.Activity;import android.content.Context;import android.content.DialogInterface;import android.content.Intent;import android.content.pm.PackageManager;import android.net.Uri;import android.os.Bundle;import android.os.storage.StorageManager;import android.os.storage.StorageVolume;import android.provider.Settings;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.Toast;import java.io.File;import java.io.IOException;import java.io.OutputStream;import java.lang.reflect.Array;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.ArrayList;import java.util.List;import java.util.Set;public class MainActivity extends AppCompatActivity implements View.OnClickListener{    private static final String TAG = "MainActivity-Test";    String rootPath = "";  //外置 SD 卡路径    private List
unPermissionList = new ArrayList
(); //申请未得到授权的权限列表 private final int RequestCode = 100;//权限请求码 private AlertDialog mPermissionDialog; private String mPackName ; //获取 a'p'k 包名 private String[] permissionList = new String[]{ //申请的权限列表 Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, }; private Button mButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); checkPermission(); mPackName = getPackageName(); rootPath = getStoragePath(this,true); //获取 可以插拔的 sd 卡 路径 Log.i(TAG," rootPath: " + rootPath); if (DocumentsUtils.checkWritableRootPath(this, rootPath)) { //检查sd卡路径是否有 权限 没有显示dialog showOpenDocumentTree(); } mButton = (Button) findViewById(R.id.creatDir); mButton.setOnClickListener(this); } @Override public void onClick(View view) { switch (view.getId()){ case R.id.creatDir: Log.i(TAG,"点击"); try { writeDataToSD(); } catch (IOException e) { e.printStackTrace(); } } } private void showOpenDocumentTree() { Intent intent = null; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { StorageManager sm = 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(this, rootPath, uri); } break; default: break; } } //权限判断和申请 public void checkPermission() { unPermissionList.clear();//清空申请的没有通过的权限 //逐个判断是否还有未通过的权限 for (int i = 0; i < permissionList.length; i++) { if (ContextCompat.checkSelfPermission(this, permissionList[i]) != PackageManager.PERMISSION_GRANTED) { unPermissionList.add(permissionList[i]);//添加还未授予的权限到unPermissionList中 } } //有权限没有通过,需要申请 if (unPermissionList.size() > 0) { ActivityCompat.requestPermissions( this,permissionList, 100); Log.i(TAG, "check 有权限未通过"); } else { //权限已经都通过了,可以将程序继续打开了 Log.i(TAG, "check 权限都已经申请通过"); } } /** * 5.请求权限后回调的方法 * * @param requestCode 是我们自己定义的权限请求码 * @param permissions 是我们请求的权限名称数组 * @param grantResults 是我们在弹出页面后是否允许权限的标识数组,数组的长度对应的是权限 * 名称数组的长度,数组的数据0表示允许权限,-1表示我们点击了禁止权限 */ @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); Log.i(TAG,"申请结果反馈"); boolean hasPermissionDismiss = false; if (100 == requestCode) { for (int i = 0; i < grantResults.length; i++) { if (grantResults[i] == -1) { hasPermissionDismiss = true; //有权限没有通过 Log.i(TAG,"有权限没有被通过"); break; } } } if (hasPermissionDismiss) {//如果有没有被允许的权限 showPermissionDialog(); } else { //权限已经都通过了,可以将程序继续打开了 Log.i(TAG, "onRequestPermissionsResult 权限都已经申请通过"); } } /** * 不再提示权限时的展示对话框 */ private void showPermissionDialog() { Log.i(TAG,"mPackName: " + mPackName); if (mPermissionDialog == null) { mPermissionDialog = new AlertDialog.Builder(this) .setMessage("已禁用权限,请手动授予") .setPositiveButton("设置", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { cancelPermissionDialog(); Uri packageURI = Uri.parse("package:" + mPackName); //去设置里面设置 Intent intent = new Intent(Settings. ACTION_APPLICATION_DETAILS_SETTINGS, packageURI); startActivity(intent); } }) .setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { //关闭页面或者做其他操作 cancelPermissionDialog(); } }) .create(); } mPermissionDialog.show(); } private void cancelPermissionDialog() { mPermissionDialog.cancel(); } /** * 通过反射调用获取内置存储和外置sd卡根路径(通用) * * @param mContext 上下文 * @param is_removale 是否可移除,false返回内部存储路径,true返回外置SD卡路径 * @return */ private static String getStoragePath(Context mContext, boolean is_removale) { String path = ""; //使用getSystemService(String)检索一个StorageManager用于访问系统存储功能。 StorageManager mStorageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE); Class
storageVolumeClazz = null; try { storageVolumeClazz = Class.forName("android.os.storage.StorageVolume"); Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList"); Method getPath = storageVolumeClazz.getMethod("getPath"); Method isRemovable = storageVolumeClazz.getMethod("isRemovable"); Object result = getVolumeList.invoke(mStorageManager); for (int i = 0; i < Array.getLength(result); i++) { Object storageVolumeElement = Array.get(result, i); path = (String) getPath.invoke(storageVolumeElement); boolean removable = (Boolean) isRemovable.invoke(storageVolumeElement); if (is_removale == removable) { return path; } } } catch (Exception e) { e.printStackTrace(); } return path; } public void writeDataToSD() throws IOException { //写文件的例子 文件不存在,会创建 String str = "just a test\n"; String strRead = ""; String sdkOut = getStoragePath(this,true); //获取 可以插拔的 sd 卡 路径 Log.i(TAG,"sdkOut:"+sdkOut); String filePath = sdkOut + "/test"; Log.i(TAG," sdkOut: " + filePath); File file = new File(filePath); if (!file.exists()){ if(DocumentsUtils.mkdirs(this,file)){ Log.i(TAG,"创建文件夹:" + filePath); }else{ Log.i(TAG,"创建文件夹失败:" + filePath); } } String fileWritePath = filePath + "/test.txt"; File fileWrite = new File(fileWritePath); Log.i(TAG," 准备写入" ); try { OutputStream outputStream = DocumentsUtils.getOutputStream(this,fileWrite); //获取输出流 // OutputStream outputStream = new FileOutputStream(fileWrite); outputStream.write(str.getBytes()); outputStream.close(); Log.i(TAG," 写入成功" ); Toast.makeText(this,"路径:" + fileWritePath + "成功",Toast.LENGTH_SHORT ).show(); } catch (IOException e) { e.printStackTrace(); Log.i(TAG," 写入失败" ); Toast.makeText(this,"路径:" + fileWritePath + "失败",Toast.LENGTH_SHORT ).show(); } }}
package com.test.multicamera.utils;import android.annotation.TargetApi;import android.content.Context;import android.content.SharedPreferences;import android.net.Uri;import android.os.Build;import android.preference.PreferenceManager;import android.provider.DocumentsContract;import android.util.Log;import androidx.documentfile.provider.DocumentFile;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.util.ArrayList;import java.util.List;public class DocumentsUtils {    private static final String TAG = DocumentsUtils.class.getSimpleName();    public static final int OPEN_DOCUMENT_TREE_CODE = 8000;    private static List
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); } } } 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); Log.i(TAG," baseFolder " + baseFolder); 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(); Log.e(TAG, "save uri" + rootPath); 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)) { Log.i(TAG,"isOnExtSdCard"); DocumentFile documentFile = DocumentsUtils.getDocumentFile(root, true, context); return documentFile == null || !documentFile.canWrite(); } else { Log.i(TAG," get perf"); 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; }}

 

 

 

 

 

 

对于系统定制开发,为了不想改所有的APP做兼容,所以直接修改系统文件

 

Android 从6.0 开始引入了Runtime permission,应用对于storage 进行读取、存储的时候,需要注册、申请对应的权限。

Android 8.0中对于sdcard 读写只需要申请权限即可使用,可以在Android 9.0 中同样的应用执行同样的步骤,却提示了Permission denied。

详情可以看一下

 

APP应用外置SD卡读写失败selinux造成的报avc权限拒绝,

我这边试着关闭selinux 没有成功,只好再想其它办法

adb logcat | grep avc

01-01 02:21:28.269  6995  6995 W zsj.multicamera: type=1400 audit(0.0:44): avc: denied { setattr } for name="base.vdex" dev="mmcblk0p56" ino=3885 scontext=u:r:system_app:s0 tcontext=u:object_r:apk_data_file:s0 tclass=file permissive=0

01-01 00:05:57.110  4047  4047 W tel.multicamera: type=1400 audit(0.0:24): avc: denied { unlink } for name="base.apk.arm64.flock" dev="mmcblk0p56" ino=2151 scontext=u:r:system_app:s0 tcontext=u:object_r:apk_data_file:s0 tclass=file permissive=0

修改系统文件-以下两个地址的内容要一致

system\sepolicy\private\system_app.te

system\sepolicy\prebuilts\api\28.0\private\system_app.te

直接新增了好几样权限(也可以根接日志avc 提示来增加)

allow system_app apk_data_file:file {create write unlink setattr};

修改完成后,make 系统,然后只用推送相应的包就可以了

adb push SC200Y\out\target\product\msm8953_64\system\etc\selinux /system/etc/ 

adb reboot  

重启后 重试 还是不行

// Copyright (C) 2016 The Android Open Source Project//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at////      http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.#define LOG_TAG "sdcard"#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PROP_SDCARDFS_DEVICE "ro.sys.sdcardfs"#define PROP_SDCARDFS_USER "persist.sys.sdcardfs"static bool supports_esdfs(void) { std::string filesystems; if (!android::base::ReadFileToString("/proc/filesystems", &filesystems)) { PLOG(ERROR) << "Could not read /proc/filesystems"; return false; } for (const auto& fs : android::base::Split(filesystems, "\n")) { if (fs.find("esdfs") != std::string::npos) return true; } return false;}static bool should_use_sdcardfs(void) { char property[PROPERTY_VALUE_MAX]; // Allow user to have a strong opinion about state property_get(PROP_SDCARDFS_USER, property, ""); if (!strcmp(property, "force_on")) { LOG(WARNING) << "User explicitly enabled sdcardfs"; return true; } else if (!strcmp(property, "force_off")) { LOG(WARNING) << "User explicitly disabled sdcardfs"; return !supports_esdfs(); } // Fall back to device opinion about state if (property_get_bool(PROP_SDCARDFS_DEVICE, true)) { LOG(WARNING) << "Device explicitly enabled sdcardfs"; return true; } else { LOG(WARNING) << "Device explicitly disabled sdcardfs"; return !supports_esdfs(); }}// NOTE: This is a vestigial program that simply exists to mount the in-kernel// sdcardfs filesystem. The older FUSE-based design that used to live here has// been completely removed to avoid confusion./* Supplementary groups to execute with. */static const gid_t kGroups[1] = { AID_PACKAGE_INFO };static void drop_privs(uid_t uid, gid_t gid) { ScopedMinijail j(minijail_new()); minijail_set_supplementary_gids(j.get(), arraysize(kGroups), kGroups); minijail_change_gid(j.get(), gid); minijail_change_uid(j.get(), uid); /* minijail_enter() will abort if priv-dropping fails. */ minijail_enter(j.get());}static bool sdcardfs_setup(const std::string& source_path, const std::string& dest_path, uid_t fsuid, gid_t fsgid, bool multi_user, userid_t userid, gid_t gid, mode_t mask, bool derive_gid, bool default_normal, bool use_esdfs) { // Try several attempts, each time with one less option, to gracefully // handle older kernels that aren't updated yet. for (int i = 0; i < 4; i++) { std::string new_opts; if (multi_user && i < 3) new_opts += "multiuser,"; if (derive_gid && i < 2) new_opts += "derive_gid,"; if (default_normal && i < 1) new_opts += "default_normal,"; auto opts = android::base::StringPrintf("fsuid=%d,fsgid=%d,%smask=%d,userid=%d,gid=%d", fsuid, fsgid, new_opts.c_str(), mask, userid, gid); if (mount(source_path.c_str(), dest_path.c_str(), use_esdfs ? "esdfs" : "sdcardfs", MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME, opts.c_str()) == -1) { PLOG(WARNING) << "Failed to mount sdcardfs with options " << opts; } else { return true; } } return false;}static bool sdcardfs_setup_bind_remount(const std::string& source_path, const std::string& dest_path, gid_t gid, mode_t mask) { std::string opts = android::base::StringPrintf("mask=%d,gid=%d", mask, gid); if (mount(source_path.c_str(), dest_path.c_str(), nullptr, MS_BIND, nullptr) != 0) { PLOG(ERROR) << "failed to bind mount sdcardfs filesystem"; return false; } if (mount(source_path.c_str(), dest_path.c_str(), "none", MS_REMOUNT | MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME, opts.c_str()) != 0) { PLOG(ERROR) << "failed to mount sdcardfs filesystem"; if (umount2(dest_path.c_str(), MNT_DETACH)) PLOG(WARNING) << "Failed to unmount bind"; return false; } return true;}static bool sdcardfs_setup_secondary(const std::string& default_path, const std::string& source_path, const std::string& dest_path, uid_t fsuid, gid_t fsgid, bool multi_user, userid_t userid, gid_t gid, mode_t mask, bool derive_gid, bool default_normal, bool use_esdfs) { if (use_esdfs) { return sdcardfs_setup(source_path, dest_path, fsuid, fsgid, multi_user, userid, gid, mask, derive_gid, default_normal, use_esdfs); } else { return sdcardfs_setup_bind_remount(default_path, dest_path, gid, mask); }}static void run_sdcardfs(const std::string& source_path, const std::string& label, uid_t uid, gid_t gid, userid_t userid, bool multi_user, bool full_write, bool derive_gid, bool default_normal, bool use_esdfs) { std::string dest_path_default = "/mnt/runtime/default/" + label; std::string dest_path_read = "/mnt/runtime/read/" + label; std::string dest_path_write = "/mnt/runtime/write/" + label; umask(0); //alex 2021-03-20 modify sdcard avc denied [full_write set true and multi_user set false; full_write =true; multi_user = false; if (multi_user) { // Multi-user storage is fully isolated per user, so "other" // permissions are completely masked off. if (!sdcardfs_setup(source_path, dest_path_default, uid, gid, multi_user, userid, AID_SDCARD_RW, 0006, derive_gid, default_normal, use_esdfs) || !sdcardfs_setup_secondary(dest_path_default, source_path, dest_path_read, uid, gid, multi_user, userid, AID_EVERYBODY, 0027, derive_gid, default_normal, use_esdfs) || !sdcardfs_setup_secondary(dest_path_default, source_path, dest_path_write, uid, gid, multi_user, userid, AID_EVERYBODY, full_write ? 0007 : 0027, derive_gid, default_normal, use_esdfs)) { LOG(FATAL) << "failed to sdcardfs_setup"; } } else { // Physical storage is readable by all users on device, but // the Android directories are masked off to a single user // deep inside attr_from_stat(). if (!sdcardfs_setup(source_path, dest_path_default, uid, gid, multi_user, userid, AID_SDCARD_RW, 0007, derive_gid, default_normal, use_esdfs) || !sdcardfs_setup_secondary(dest_path_default, source_path, dest_path_read, uid, gid, multi_user, userid, AID_EVERYBODY, full_write ? 0007 : 0022, derive_gid, default_normal, use_esdfs) || !sdcardfs_setup_secondary(dest_path_default, source_path, dest_path_write, uid, gid, multi_user, userid, AID_EVERYBODY, full_write ? 0007 : 0022, derive_gid, default_normal, use_esdfs)) { LOG(FATAL) << "failed to sdcardfs_setup"; } } // Will abort if priv-dropping fails. drop_privs(uid, gid); if (multi_user) { std::string obb_path = source_path + "/obb"; fs_prepare_dir(obb_path.c_str(), 0775, uid, gid); } exit(0);}static int usage() { LOG(ERROR) << "usage: sdcard [OPTIONS]

标红的为被修改的部分

 

只需要sdcard打包

mmm system/core/sdcard/
只需要推送
adb push  SC200Y\out\target\product\msm8953_64\system\bin\sdcard /system/bin
然后杀进程 或 重启系统
ps -A |grep sdcard

然后就可以正常读写了

 

 

 

转载地址:http://xxqn.baihongyu.com/

你可能感兴趣的文章
mysql 主从 lock_mysql 主从同步权限mysql 行锁的实现
查看>>
mysql 主从互备份_mysql互为主从实战设置详解及自动化备份(Centos7.2)
查看>>
mysql 主从关系切换
查看>>
MYSQL 主从同步文档的大坑
查看>>
mysql 主键重复则覆盖_数据库主键不能重复
查看>>
Mysql 事务知识点与优化建议
查看>>
Mysql 优化 or
查看>>
mysql 优化器 key_mysql – 选择*和查询优化器
查看>>
MySQL 优化:Explain 执行计划详解
查看>>
Mysql 会导致锁表的语法
查看>>
mysql 使用sql文件恢复数据库
查看>>
mysql 修改默认字符集为utf8
查看>>
Mysql 共享锁
查看>>
MySQL 内核深度优化
查看>>
mysql 内连接、自然连接、外连接的区别
查看>>
mysql 写入慢优化
查看>>
mysql 分组统计SQL语句
查看>>
Mysql 分页
查看>>
Mysql 分页语句 Limit原理
查看>>
MySql 创建函数 Error Code : 1418
查看>>