在Android 16即将到来的之际。也就是targetSdkVersion即将出现36,而30已然会成为历史。那么我的项目已经停留在30很久了。是时候要适配一下适用市场的主流机型了。正常来查找资料的,无非就是已经升级和准备升级targetSdkVersion开发版本。所以你是哪一种情况呢?其实最有效的解决方法就是去查询官方文档,无一例外。因为规则都是由他们决定的,他们有主导权,我在文章中会插入一些链接文档,需要有想深入了解的,最好还是去官网了解一下。那么现在就开始你的升级之路吧!现在按升级顺序来讲讲可能遇到的问题吧!
1).targetSdkVersion (31-32) Android 12
https://developer.android.google.cn/about/versions/12/behavior-changes-12?hl=zh-cn
1、在Android 12及以上版本中,如果创建PendingIntent时没有指定FLAG_IMMUTABLE或FLAG_MUTABLE,应用将抛出异常并崩溃。这是因为系统无法确定PendingIntent的预期用途和行为,从而无法确保其安全性。FLAG_IMMUTABLE 通常是最安全的选择,适用于绝大多数情况。
报错内容:PACKAGE_NAME: Targeting S+ (version 10000 and above) requires that one
of \FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a
PendingIntent.Strongly consider using FLAG_IMMUTABLE, only use
FLAG_MUTABLE if \some functionality depends on the PendingIntent being
mutable, e.g. if \it needs to be used with inline replies or bubbles.
解决方法:注释掉的代码就是修改的位置
private Notification createNotification() {
Notification.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(id, name, NotificationManager.IMPORTANCE_LOW);
getNotifyMgr(MQTTService.this.getApplicationContext()).createNotificationChannel(channel);
builder = new Notification.Builder(MQTTService.this.getApplicationContext(), id);
} else {
builder = new Notification.Builder(MQTTService.this.getApplicationContext());
}
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setContentTitle(PackageUtils.getAppName(MQTTService.this.getApplicationContext()));
builder.setContentText("订单消息接收中…");
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.mipmap.ic_launcher));
builder.setWhen(System.currentTimeMillis());
Intent activityIntent = MainActivity.getIntent(MQTTService.this.getApplicationContext());
@SuppressLint("UnspecifiedImmutableFlag") PendingIntent pendingIntent = PendingIntent.getActivity(MQTTService.this.getApplicationContext(),
1,
activityIntent,
android.os.Build.VERSION.SDK_INT >= 31?PendingIntent.FLAG_IMMUTABLE:PendingIntent.FLAG_UPDATE_CURRENT);
//PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(pendingIntent);
return builder.build();
}
2、(精确的闹钟权限)为了鼓励应用节省系统资源,以 Android 12 及更高版本为目标平台且设置了精确的闹钟的应用必须能够访问“闹钟和提醒”功能,该功能显示在系统设置的特殊应用访问权限屏幕中。
解决方法:如果你的项目中有引用到类似功能的就会出现这个问题,解决的方案就是动态申请一下权限就行
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
requestPermissions(granted -> {
if (!granted) {}
}, Manifest.permission.SCHEDULE_EXACT_ALARM);
}
3、(针对从后台启动前台服务的限制) 以 Android 12(API 级别 31)或更高版本为目标平台的应用无法在后台运行时启动前台服务,但少数特殊情况除外。如果应用在后台运行时尝试启动前台服务,并且前台服务不符合任何特殊情况,则系统会抛出 ForegroundServiceStartNotAllowedException。如果你在项目有引用service 且开启是前台服务。如果切换到后台。就大概率可能出现这个bug
解决方法:就是如下的这个链接,最下面的是示例代码:定位类型 foregroundServiceType 跟用户权限是一 一对应的,豁免的条件可以是继承系统服务。或者是申请通知权限。通知交互获取豁免条件。
查阅的相关博主的博客说是推荐WorkerManger来替代。但是好像还是不能有效的解决问题。因为归根结底还是因为系统拦截导致的。官网介绍说WorkerManger的内容如下:
https://zhuanlan.zhihu.com/p/712108383
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE_LOCATION"
android:minSdkVersion="34" /> <!-- 启动类型为location的前台服务 -->
<application>
<service
android:name=".MainService"
android:directBootAware="true"
android:enabled="true"
android:exported="true"
android:foregroundServiceType="connectedDevice|location"
android:permission="android.permission.FOREGROUND_SERVICE" />
</application>
4、(蓝牙权限) Android 12 引入了 BLUETOOTH_SCAN、BLUETOOTH_ADVERTISE 和 BLUETOOTH_CONNECT 权限。这些权限可让以 Android 12 为目标平台的应用更轻松地与蓝牙设备互动,尤其是不需要访问设备位置信息的应用。为了让您的设备做好准备以 Android 12 或更高版本为目标平台,请更新应用的逻辑。请声明一组更现代的蓝牙权限,而不是声明一组旧版蓝牙权限。
解决方法:判断当前版本是否大于Android 12 动态申请权限
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" /> <!-- 申明该权限不适用于安卓12及以上 -->
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" /> <!-- 申明该权限不适用于安卓12及以上 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Android 11+ 新增的蓝牙扫描权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
String[] permission = new String[Build.VERSION.SDK_INT >= Build.VERSION_CODES.S?4:2];
permission[0] = Manifest.permission.ACCESS_FINE_LOCATION;// 精确位置
permission[1] = Manifest.permission.ACCESS_COARSE_LOCATION;// 粗略位置
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Android 12及以上
permission[2] = Manifest.permission.BLUETOOTH_CONNECT;
permission[3] = Manifest.permission.BLUETOOTH_SCAN;
}
requestMustPermissions(granted -> {
if(!granted) showMessage("请打开申请相关的权限");
else PrintBlueActivity.startActivity(requireActivity());
}, permission));// 搜索蓝牙操作
2).targetSdkVersion (33) Android 13
1、(细化的媒体权限) 如果您的应用以 Android 13 或更高版本为目标平台,并且需要访问其他应用已经创建的媒体文件,您必须请求以下一项或多项细化的媒体权限,而不是READ_EXTERNAL_STORAGE 权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32"
tools:ignore="ScopedStorage" />
<!--Android 13版本适配,细化存储权限-->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
String[] permission = new String[(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) ? 4 : 3];
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permission[0] = Manifest.permission.CAMERA;
permission[1] = Manifest.permission.READ_MEDIA_AUDIO;
permission[2] = Manifest.permission.READ_MEDIA_IMAGES;
permission[3] = Manifest.permission.READ_MEDIA_VIDEO;
}else {
permission[0] = Manifest.permission.CAMERA;
permission[1] = Manifest.permission.READ_EXTERNAL_STORAGE;
permission[2] = Manifest.permission.WRITE_EXTERNAL_STORAGE;
}
requestMustPermissions(granted -> {
if (!granted) {
Tip.toast(getString(R.string.tip_permission));
} else {
// 操作图片动作。。。
}
}, permission);
2、(蓝牙连接) 废弃了 BluetoothAdapter#enable() 和 BluetoothAdapter#disable()对于以 Android 13(API 级别 33)或更高版本为目标平台的应用,BluetoothAdapter#enable() 和 BluetoothAdapter#disable() 方法已废弃,并且始终返回 false。
3、(registerReceiver 需要指定导出行为)
-
谷歌在 Android 12 (API 31)新增了四大组件需要指定
android:exported属性的特性,这次在 Android 13 上面做了一些变动,因为谷歌之前只考虑到静态注册四大组件的情况,但是遗漏了一种情况,BroadcastReceiver 不仅可以静态注册,还可以动态注册,动态注册的广播不需要额外在AndroidManifest.xml中再进行静态注册,所以这次谷歌将这个规则漏洞补上了,并且要求开发者在动态注册广播的时候,能够指定 BroadcastReceiver 是否能支持导出,由此来保护应用免受安全漏洞的影响。 -
到此,大家心中可能有一个疑惑,这里的支持导出是什么意思?有产生什么作用?可以先看一下谷歌官方的原话
为了帮助提高运行时接收器的安全性,Android 13 允许您指定您应用中的特定广播接收器是否应被导出以及是否对设备上的其他应用可见。 如果导出广播接收器,其他应用将可以向您的应用发送不受保护的广播。 此导出配置在以 Android 13 或更高版本为目标平台的应用中可用,有助于防止一个主要的应用漏洞来源。 在以前的 Android 版本中,设备上的任何应用都可以向动态注册的接收器发送不受保护的广播,除非该接收器受签名权限的保护。
-
谷歌的解释很明了,如果广播支持导出,那么其他应用可以通过发送这个广播触发我们应用的逻辑,这可能会发生程序安全漏洞的问题。
-
那么该如何适配这一特性呢?谷歌官方提供了一个
registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags)API,flags参数传入Context.RECEIVER_EXPORTED(支持导出) 或Context.RECEIVER_NOT_EXPORTED(不支持导出),具体的代码适配代码如下:
String action = "xxxxxx";
IntentFilter filter = new IntentFilter(action);
if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
context.registerReceiver(new LocaleChangeReceiver(), filter, Context.RECEIVER_EXPORTED);
} else {
context.registerReceiver(new LocaleChangeReceiver(), filter);
}
4、(更安全的动态代码加载 )
File jar = new File("xxxx.jar");
try (FileOutputStream os = new FileOutputStream(jar)) {
jar.setReadOnly();
} catch (IOException e) { ... }
PathClassLoader cl = new PathClassLoader(jar, parentClassLoader);
-
谷歌在 Android 12 (API 31)新增了四大组件需要指定
android:exported属性的特性,这次在 Android 13 上面做了一些变动,因为谷歌之前只考虑到静态注册四大组件的情况,但是遗漏了一种情况,BroadcastReceiver 不仅可以静态注册,还可以动态注册,动态注册的广播不需要额外在AndroidManifest.xml中再进行静态注册,所以这次谷歌将这个规则漏洞补上了,并且要求开发者在动态注册广播的时候,能够指定 BroadcastReceiver 是否能支持导出,由此来保护应用免受安全漏洞的影响。 -
到此,大家心中可能有一个疑惑,这里的支持导出是什么意思?有产生什么作用?可以先看一下谷歌官方的原话
-
为了帮助提高运行时接收器的安全性,Android 13 允许您指定您应用中的特定广播接收器是否应被导出以及是否对设备上的其他应用可见。 如果导出广播接收器,其他应用将可以向您的应用发送不受保护的广播。 此导出配置在以 Android 13 或更高版本为目标平台的应用中可用,有助于防止一个主要的应用漏洞来源。 在以前的 Android 版本中,设备上的任何应用都可以向动态注册的接收器发送不受保护的广播,除非该接收器受签名权限的保护。 -
谷歌的解释很明了,如果广播支持导出,那么其他应用可以通过发送这个广播触发我们应用的逻辑,这可能会发生程序安全漏洞的问题。
-
那么该如何适配这一特性呢?谷歌官方提供了一个
registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags)API,flags参数传入Context.RECEIVER_EXPORTED(支持导出) 或Context.RECEIVER_NOT_EXPORTED(不支持导出),具体的代码适配代码如下: - 还有一种情况,不需要指定
flag参数,就是当要注册的广播 action 隶属系统的 action 时候,这个时候可以不需要指定导出行为。 - 如果我们应用有动态加载代码的需求,并且此时
targetSdk升级到了 API 34(即 Android 14),那么需要注意一个点,动态加载的文件(Jar、Dex、Apk 格式)需要设置成可读的,具体案例的代码如下: - 至于谷歌这样做的原因,我觉得十分简单,是为了程序的安全,防止有人抢先在动态加载之前先把动态文件替换了,那么会导致执行到一些恶意的代码,间接导致应用被入侵或者篡改。
-
另外需要注意的一个点的是,如果你的应用
targetSdk大于等于 API 34(即 Android 14),如果不去适配这一特性,那么运行在 Android 14 的手机上面系统会抛出异常。




5735

被折叠的 条评论
为什么被折叠?



