Android 10-11适配外部存储方案

Android Api 29 对文件和文件夹进行了重大更改。不允许使用外部存储,如下方法:

Environment.getExternalStorageDirectory() = /mnt/sdcard

Environment.getExternalStoragePublicDirectory("test") = /mnt/sdcard/test

只能使用内部存储

getExternalFilesDir("test") = /mnt/sdcard/Android/data/com.my.app/files/test

getExternalFilesDir(null) = /mnt/sdcard/Android/data/com.my.app/files

但谷歌官方给了一个后门,在AndroidManifest.xml文件中application节点中加上android:requestLegacyExternalStorage="true"属性才可以访问沙盒路径下的数据

原来的项目就要重新适配

Android开发:filePath放在哪个文件夹

Environment.getDataDirectory() = /data

Environment.getDownloadCacheDirectory() = /cache

外部存储

Environment.getExternalStorageDirectory() = /mnt/sdcard

Environment.getExternalStoragePublicDirectory("test") = /mnt/sdcard/test

Environment.getRootDirectory() = /system

getPackageCodePath() = /data/app/com.my.app-1.apk

getPackageResourcePath() = /data/app/com.my.app-1.apk

getCacheDir() = /data/data/com.my.app/cache

getDatabasePath("test") = /data/data/com.my.app/databases/test

getDir("test", Context.MODE_PRIVATE) = /data/data/com.my.app/app_test

getExternalCacheDir() = /mnt/sdcard/Android/data/com.my.app/cache

应用内部存储

getExternalFilesDir("test") = /mnt/sdcard/Android/data/com.my.app/files/test

getExternalFilesDir(null) = /mnt/sdcard/Android/data/com.my.app/files

getFilesDir() = /data/data/com.my.app/files

参考文章:

安卓Enviroment类的详解-CSDN博客

权限申请

11以上添加如下权限:

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

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

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

代码动态申请

public final class PermissionActivity extends AppCompatActivity {
 
    private static final int REQUEST_CODE = 1024;
 
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestPermission();
    }
 
    private void requestPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            // 先判断有没有权限
            if (Environment.isExternalStorageManager()) {
                writeFile();
            } else {
                Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
                intent.setData(Uri.parse("package:" + context.getPackageName()));
                startActivityForResult(intent, REQUEST_CODE);
            }
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // 先判断有没有权限
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
                    ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
                writeFile();
            } else {
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE);
            }
        } else {
            writeFile();
        }
    }
 
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_CODE) {
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
                    ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
                writeFile();
            } else {
                ToastUtils.show("存储权限获取失败");
            }
        }
    }
 
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_CODE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            if (Environment.isExternalStorageManager()) {
                writeFile();
            } else {
                ToastUtils.show("存储权限获取失败");
            }
        }
    }
 
    /**
     * 模拟文件写入
     */
    private void writeFile() {
        ToastUtils.show("写入文件成功");
    }
}

动态申请代码三方框架XXPermissions

XXPermissions.with(this)
        // 不适配 Android 11 可以这样写
        //.permission(Permission.Group.STORAGE)
        // 适配 Android 11 需要这样写,这里无需再写 Permission.Group.STORAGE
        .permission(Permission.MANAGE_EXTERNAL_STORAGE)
        .request(new OnPermissionCallback() {
 
            @Override
            public void onGranted(List<String> permissions, boolean all) {
                if (all) {
                    toast("获取存储权限成功");
                }
            }
 
            @Override
            public void onDenied(List<String> permissions, boolean never) {
                if (never) {
                    toast("被永久拒绝授权,请手动授予存储权限");
                    // 如果是被永久拒绝就跳转到应用权限系统设置页面
                    XXPermissions.startPermissionActivity(MainActivity.this, permissions);
                } else {
                    toast("获取存储权限失败");
                }
            }
        });

项目地址:GitHub - getActivity/XXPermissions: Android 权限请求框架,已适配 Android 14

复制代码
 // JitPack 远程仓库:https://jitpack.io
        maven { url 'https://jitpack.io' }
复制代码
   // 权限请求框架:https://github.com/getActivity/XXPermissions
    implementation 'com.github.getActivity:XXPermissions:18.5

表示将第三方库迁移到 AndroidX

android.enableJetifier = true

<manifest>

<application>

<!-- 告知 XXPermissions 当前项目已经适配了分区存储特性 -->

<meta-data

android:name="ScopedStorage"

android:value="true" />

</application>

</manifest>

框架扩展

XXPermissions.with(this)
        // 申请单个权限
        .permission(Permission.RECORD_AUDIO)
        // 申请多个权限
        .permission(Permission.Group.CALENDAR)
        // 设置权限请求拦截器(局部设置)
        //.interceptor(new PermissionInterceptor())
        // 设置不触发错误检测机制(局部设置)
        //.unchecked()
        .request(new OnPermissionCallback() {

            @Override
            public void onGranted(@NonNull List<String> permissions, boolean allGranted) {
                if (!allGranted) {
                    toast("获取部分权限成功,但部分权限未正常授予");
                    return;
                }
                toast("获取录音和日历权限成功");
            }

            @Override
            public void onDenied(@NonNull List<String> permissions, boolean doNotAskAgain) {
                if (doNotAskAgain) {
                    toast("被永久拒绝授权,请手动授予录音和日历权限");
                    // 如果是被永久拒绝就跳转到应用权限系统设置页面
                    XXPermissions.startPermissionActivity(context, permissions);
                } else {
                    toast("获取录音和日历权限失败");
                }
            }
        });

其他API介绍

// 判断一个或多个权限是否全部授予了
XXPermissions.isGranted(Context context, String... permissions);

// 获取没有授予的权限
XXPermissions.getDenied(Context context, String... permissions);

// 判断某个权限是否为特殊权限
XXPermissions.isSpecial(String permission);

// 判断一个或多个权限是否被勾选了《不再询问》的选项(一定要在权限申请的回调方法中调用才有效果)
XXPermissions.isDoNotAskAgainPermissions(Activity activity, String... permissions);

// 跳转到应用权限设置页
XXPermissions.startPermissionActivity(Context context, String... permissions);
XXPermissions.startPermissionActivity(Activity activity, String... permissions);
XXPermissions.startPermissionActivity(Activity activity, String... permission, OnPermissionPageCallback callback);
XXPermissions.startPermissionActivity(Fragment fragment, String... permissions);
XXPermissions.startPermissionActivity(Fragment fragment, String... permissions, OnPermissionPageCallback callback);

// 设置不触发错误检测机制(全局设置)
XXPermissions.setCheckMode(false);
// 设置权限申请拦截器(全局设置)
XXPermissions.setInterceptor(new IPermissionInterceptor() {});
相关推荐
深海呐5 小时前
Android AlertDialog圆角背景不生效的问题
android
ljl_jiaLiang5 小时前
android10 系统定制:增加应用使用数据埋点,应用使用时长统计
android·系统定制
花花鱼5 小时前
android 删除系统原有的debug.keystore,系统运行的时候,重新生成新的debug.keystore,来完成App的运行。
android
落落落sss6 小时前
sharding-jdbc分库分表
android·java·开发语言·数据库·servlet·oracle
消失的旧时光-19439 小时前
kotlin的密封类
android·开发语言·kotlin
服装学院的IT男10 小时前
【Android 13源码分析】WindowContainer窗口层级-4-Layer树
android
CCTV果冻爽11 小时前
Android 源码集成可卸载 APP
android
码农明明11 小时前
Android源码分析:从源头分析View事件的传递
android·操作系统·源码阅读
秋月霜风12 小时前
mariadb主从配置步骤
android·adb·mariadb
Python私教13 小时前
Python ORM 框架 SQLModel 快速入门教程
android·java·python