描述

时间点:

2022年8月我换了红米K50,它当时是MIUI13(基于Android12)。

项目演示的录制时间是2022年5月

Android13发布是2022年8月

然后我就更新了MIUI14,从Android12 -> Android13

做一个音乐APP的时候,需要扫描本地音乐。

需要扫本地资源仓库,然后找mp3音乐文件。

但是代码写好后,一直扫不到。

分析

锁定代码问题:

手机自带音乐播放器可以扫描到这些文件 -》说明已经由外存文件 变为系统资源库文件

然后准备看数据库,但是因为手机没有root,没权限看。

虚拟机有权限,app运虚拟机,ok。

image-20230509211216881

然后问老师,是本来就有的Bug,还是我的代码有问题。

然后直接下成品的app,发现这个功能可以。

说明是我自己的代码有问题。

  1. 刚开始以为代码写错了

  2. 然后定位到本地音乐模块

  3. 再定位到ScanLocalMusicAsyncTask这个【扫描本地音乐异步任务】类

  4. 再定位到doInBackground方法(没有那个扫目录的动画)image-20230509211247169

  5. 再定位到某块代码没有执行

    1
    while (cursor != null && cursor.moveToNext()) {
  6. 通过打日志,发现cursor不为null,但是没有数据。image-20230509211303680

  7. 然后查cursor的实例化方法

    1
    2
    Cursor cursor = contentResolver.query(
    ...
  8. 代码没问题

  9. 后来被指点是权限问题,然后对着AndroidManifest。xml以及那个动态申请权限的框架和动态申请代码一顿实验。

  10. 偶然间,看手机app后台的时候发现:我出Bug的app相对成品app少了一个访问音频的访问权限。image-20230509211645975

  11. 问题就到:为什么有访问资源库的权限就访问音频没了?

  12. 还是在那两个文件死磕了很久,不断的搜索和尝试

  13. 最后在这样搜:

    READ_EXTERNAL_STORAGE 不能访问音频

    image-20230509211812026

    找到答案:是Android13的新特性。

  14. image-20230509211920193

实现

1, 配置文件中申明

(13只用下面那个,但是低于13只要上面那个)

image-20230509212039067

2,然后在动态申请加上这个

image-20230509212202065

总结

  • Android新版版特性对Android开发很重要,搞不好就是一个卡好几天的坑

  • 要根据Bug样子,尽量定位到关键所在。

  • 多用日志和一些辅助信息去判断问题到底出在哪里

  1. Android13新特性:行为变更:以 Android 13 或更高版本为目标平台的应用 | Android 开发者 | Android Developers
  2. 访问音频权限:https://developer.android.com/reference/android/Manifest.permission#READ_MEDIA_AUDIO

后续

当时改了之后,在Android13的手机上可以运行了。

但是在13版本一下的手机就进入不了界面。

是这里的权限处理。

就是13:用新权限

12以及一下:用老权限

所以这里要加权限判断:

image-20230524110841550

先简单处理了一下。

纠正2

这个地方我还是没有搞懂,他的本质是Android不同版本之间的权限处理问题。

从【总结】开始处理错了。

没有弄明白这个动态处理权限框架的用法。

正确的bug修复是这样的:

我们应该根据当前手机版本的不同去申请不同的权限。

Android13申请:

android.permission.READ_MEDIA_IMAGES

android.permission.READ_MEDIA_VIDEO

android.permission.READ_MEDIA_AUDIO

Android12以及一下用:

android.permission.READ_EXTERNAL_STORAGE

权限申明

AndroidManifest 里面加上所有要用的权限

1
2
3
4
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

不管老版新版。都要写

具体代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/**
* 启动界面
*/
//声明当前界面有动态获取权限的逻辑
@RuntimePermissions
public class SplashActivity extends BaseViewModelActivity<ActivitySplashBinding> {


//region AboutPermission

/**
* 检查是否有需要的权限
*/
private void checkPermission() {
//让动态框架检查是否授权了
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// 请求权限new
SplashActivityPermissionsDispatcher.onPermissionGrantedNewWithPermissionCheck(this);
} else {
// 请求权限old
SplashActivityPermissionsDispatcher.onPermissionGrantedOldWithPermissionCheck(this);
}
}


@NeedsPermission({
Manifest.permission.CAMERA,
Manifest.permission.READ_MEDIA_IMAGES,
Manifest.permission.READ_MEDIA_VIDEO,
Manifest.permission.READ_MEDIA_AUDIO
})
/**
* 权限授权了就会调用该方法(专供Android13)
*/
void onPermissionGrantedNew() {
Log.d("标注","onPermissionGranted");
//如果有权限就进入下一步
prepareNext();
}

@NeedsPermission({
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE
})
void onPermissionGrantedOld() {
Log.d("标注","onPermissionGrantedOld");
//如果有权限就进入下一步
prepareNext();
}

/**
* 授权后回调
*
* @param requestCode
* @param permissions
* @param grantResults
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//将授权结果传递到框架
SplashActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}

/**
* 显示权限授权对话框
* 目的是提示用户
*/
@OnShowRationale({
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.READ_MEDIA_IMAGES,
Manifest.permission.READ_MEDIA_VIDEO,
Manifest.permission.READ_MEDIA_AUDIO
})
void showRequestPermission(PermissionRequest request) {
new AlertDialog.Builder(getHostActivity())
.setMessage(R.string.permission_hint)
.setPositiveButton(R.string.allow, (dialog, which) -> request.proceed())
.setNegativeButton(R.string.deny, (dialog, which) -> request.cancel()).show();
}

/**
* 拒绝了权限调用
*/
@OnPermissionDenied({
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.READ_MEDIA_IMAGES,
Manifest.permission.READ_MEDIA_VIDEO,
Manifest.permission.READ_MEDIA_AUDIO
})
void showDenied() {
//退出应用
finish();
}

/**
* 再次获取权限的提示
*/
@OnNeverAskAgain({
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.READ_MEDIA_IMAGES,
Manifest.permission.READ_MEDIA_VIDEO,
Manifest.permission.READ_MEDIA_AUDIO
})
void showNeverAsk() {
//继续请求权限
checkPermission();
}

//endregion

}

注意,如果之前这个已经用过一次的话,会生成一个SplashActivityPermissionsDispatcher类。

要去这个类的磁盘目录删了,然后出来先把带有这个类的代码注释掉。

然后运行以下,就生成了新的这个类。

然后再把之前注释的代码打开就可以了。

我的坑经验:

这个动态请求权限的框架,如果想实现:

Android13申请权限A,Andorid14申请权限B。

就要用两个@NeedsPermission,然后下面写两个onPermissionGranted来处理不同版本的权限申请结果。

比如一个 onPermissionGrantedOld 一个 onPermissionGrantedNew

这样就会生成两个权限请求方法。

然后我们在checkPermission中判断:

1
2
3
4
5
6
7
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// 请求权限new
SplashActivityPermissionsDispatcher.onPermissionGrantedNewWithPermissionCheck(this);
} else {
// 请求权限old
SplashActivityPermissionsDispatcher.onPermissionGrantedOldWithPermissionCheck(this);
}

如果是Android13以及高版本,就用SplashActivityPermissionsDispatcher.onPermissionGrantedNewWithPermissionCheck

如果是Android12以及低版本,就用

SplashActivityPermissionsDispatcher.onPermissionGrantedOldWithPermissionCheck

二次总结

用别人的框架一定要弄懂到底应该怎么用。