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() {});
相关推荐
是店小二呀3 分钟前
【优选算法 | 字符串】字符串模拟题精选:思维+实现解析
android·c++·算法
奔跑吧 android1 小时前
【android bluetooth 协议分析 12】【A2DP详解 1】【车机侧蓝牙音乐免切源介绍】
android·bluetooth·bt·gd·a2dpsink·免切源·aosp14
飞猿_SIR2 小时前
Android Exoplayer多路不同时长音视频混合播放
android·音视频
前端懒猫3 小时前
android实现USB通讯
android
jiet_h4 小时前
Android锁
android
teacher伟大光荣且正确12 小时前
Qt Creator 配置 Android 编译环境
android·开发语言·qt
飞猿_SIR14 小时前
Android Exoplayer 实现多个音视频文件混合播放以及音轨切换
android·音视频
HumoChen9915 小时前
GZip+Base64压缩字符串在ios上解压报错问题解决(安卓、PC模拟器正常)
android·小程序·uniapp·base64·gzip
沙振宇19 小时前
【HarmonyOS】ArkTS开发应用的横竖屏切换
android·华为·harmonyos
橙子1991101620 小时前
Kotlin 中的作用域函数
android·开发语言·kotlin