Android targetSdkVersion 原理

本文深入解析Android系统中targetSdkVersion的概念,解释其作为Android提供前向兼容性主要手段的原理,通过具体实例说明如何通过targetSdkVersion确保应用在不同Android系统版本间的兼容性,同时探讨了修改targetSdkVersion可能带来的影响和测试需求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前几天 Google 官方发布文章解析 compileSdkVersion、minSdkVersion 以及 targetSdkVersion 的含义,以及合理设置各个值的意义,原文 Picking your compileSdkVersion, minSdkVersion, and targetSdkVersion(后面简称 “原文”),还有翻译版

其中,compileSdkVersion 和 minSdkVersion 都非常好理解,前者表示编译的 SDK 版本,后者表示应用兼容的最低 SDK 版本。但是对于 targetSdkVersion 其实很难一句话解析清楚,原文用了“万能”的词 —— interesting 来描述。以前我也有一些迷糊,看到有些人和我有同样的困惑,本文试图彻底解决这个问题。

原文是这么说的:

targetSdkVersion is the main way Android provides forward compatibility

targetSdkVersion 是 Android 系统提供前向兼容的主要手段。这是什么意思呢?随着 Android 系统的升级,某个系统的 API 或者模块的行为可能会发生改变,但是为了保证老 APK 的行为还是和以前兼容。只要 APK 的 targetSdkVersion 不变,即使这个 APK 安装在新 Android 系统上,其行为还是保持老的系统上的行为,这样就保证了系统对老应用的前向兼容性。

这里还是用原文的例子,在 Android 4.4 (API 19)以后,AlarmManager 的 set() 和setRepeat() 这两个 API 的行为发生了变化。在 Android 4.4 以前,这两个 API 设置的都是精确的时间,系统能保证在 API 设置的时间点上唤醒 Alarm。因为省电原因 Android 4.4 系统实现了 AlarmManager 的对齐唤醒,这两个 API 设置唤醒的时间,系统都对待成不精确的时间,系统只能保证在你设置的时间点之后某个时间唤醒。

这时,虽然 API 没有任何变化,但是实际上 API 的行为却发生了变化,如果老的 APK 中使用了此 API,并且在应用中的行为非常依赖 AlarmManager 在精确的时间唤醒,例如闹钟应用。如果 Android 系统不能保证兼容,老的 APK 安装在新的系统上,就会出现问题。

Android 系统是怎么保证这种兼容性的呢?这时候 targetSdkVersion 就起作用了。APK 在调用系统 AlarmManager 的 set() 或者 setRepeat() 的时候,系统首先会查一下调用的 APK 的 targetSdkVersion 信息,如果小于 19,就还是按照老的行为,即精确设置唤醒时间,否者执行新的行为。

我们来看一下 Android 4.4 上 AlarmManger 的一部分源代码:

1
2
3
4
5
6
7
private final boolean mAlwaysExact;  
AlarmManager(IAlarmManager service, Context ctx) {  
     mService = service;
 
     final int sdkVersion = ctx.getApplicationInfo().targetSdkVersion;
     mAlwaysExact = (sdkVersion < Build.VERSION_CODES.KITKAT);
}

看到这里,首选获取应用的 targetSdkVersion,判断是否是小于 Build.VERSION_CODES.KITKAT (即 API Level 19),来设置 mAlwaysExact 变量,表示是否使用精确时间模式。

1
2
3
4
5
6
7
8
9
10
public static final long WINDOW_EXACT = 0;  
public static final long WINDOW_HEURISTIC = -1;
 
private long legacyExactLength() {  
     return  (mAlwaysExact ? WINDOW_EXACT : WINDOW_HEURISTIC);
}
 
public void set(int type, long triggerAtMillis, PendingIntent operation) {  
     setImpl(type, triggerAtMillis, legacyExactLength(), 0, operation,  null );
}

这里看到,直接影响到 set() 方法给 setImpl() 传入不同的参数,从而影响到了 set() 的执行行为。具体的实现在 AlarmManagerService.java,这里就不往下深究了。

看到这里,发现其实 Android 的 targetSdkVersion 并没有什么特别的,系统使用它也非常直接,甚至很“粗糙”。仅仅是用过下面的 API 来获取 targetSdkVersion,来判断是否执行哪种行为:

1
getApplicationInfo().targetSdkVersion;

所以,我们可以猜测到,如果 Android 系统升级,发生这种兼容行为的变化时,一般都会在原来的保存新旧两种逻辑,并通过 if-else 方法来判断执行哪种逻辑。果然,在源码中搜索,我们会发现不少类似 getApplicationInfo().targetSdkVersion < Buid.XXXX 这样的代码,相对于浩瀚的 Android 源码量来说,这些还是相对较少了。其实原则上,这种会导致兼容性问题的修改还是越少越好,所以每次发布新的 Android 版本的时候,Android 开发者网站都会列出做了哪些改变,在这里,开发者需要特别注意。

最后,我们也可以理解原文中说的那句话的含义,明白了为什么修改了 APK 的 targetSdkVersion 行为会发生变化,也明白了为什么修改 targetSdkVersion 需要做完整的测试了。

写完这篇文章,再回头去看一下原文的 targetSdkVersion 那一段,发现作者是说的多么“滴水不漏”。


原文出处:http://www.race604.com/android-targetsdkversion/ 


<think>我们正在讨论的是在UniApp中处理Android 11(API级别30)及以上版本的通知权限问题,特别是当`targetSdkVersion`设置为33时。用户报告说使用`plus.android.requestPermissions`请求`android.permission.POST_NOTIFICATIONS`权限没有响应,并且希望实现当用户授予权限后跳转到应用设置页面。 首先,我们需要明确几个关键点: 1. 从Android 12(API级别31)开始,引入了`POST_NOTIFICATIONS`权限,该权限在`targetSdkVersion`为33时成为必要权限。 2. 在Android 11(API级别30)及以下,通知权限是默认授予的,不需要运行时请求。 3. 在Android 12及以上,如果`targetSdkVersion`>=31,则必须请求`POST_NOTIFICATIONS`权限。 但是,用户提到的是Android 11设备,并且`targetSdkVersion`为33。这里需要注意: - 在Android 11设备上,即使`targetSdkVersion`为33,系统仍然不会强制要求`POST_NOTIFICATIONS`权限,因为该权限在Android 11上不存在。因此,在Android 11上请求这个权限可能不会有任何反应(系统会忽略它)。 所以,问题可能在于:在Android 11设备上请求一个不存在的权限(`POST_NOTIFICATIONS`)导致没有响应。那么,正确的做法应该是: - 对于Android 12及以上设备,请求`POST_NOTIFICATIONS`权限。 - 对于Android 11及以下设备,不需要请求通知权限(因为默认已授予)。 但是,用户还提到想要实现“当用户授予权限后跳转到应用设置页面”。这里可能有些误解,因为通常我们是在用户拒绝权限后才引导用户去设置页面手动开启权限。在用户授予权限后,我们不需要跳转到设置页面。 因此,我推测用户的实际需求可能是: 1. 正确请求通知权限(在Android 12及以上)。 2. 如果用户拒绝了权限,则提示用户去应用设置页面手动开启权限。 另外,关于跳转到应用设置页面,我们可以使用以下方法: 在UniApp中,可以使用以下代码跳转到应用设置页面: ```javascript plus.runtime.openSettings(); // 打开应用设置页面 ``` 现在,我们需要一个完整的解决方案: 1. 判断设备版本:如果是Android 12及以上,则请求`POST_NOTIFICATIONS`权限;如果是Android 11及以下,则不需要请求(但可以检查通知是否开启?注意:在低版本Android上,通知权限是默认开启的,但用户可以在应用设置中关闭通知,所以你可能需要检查通知通道的状态?不过,在Android 8.0及以上,通知是通过通知通道管理的,但运行时权限只存在于Android 12及以上)。 2. 请求权限:使用`plus.android.requestPermissions`,但注意这个API在HBuilderX 3.1.0+推荐使用`uni.authorize`或`uni.requestAuthSetting`,但通知权限在uni-app的API中可能没有直接封装。因此,可能需要使用原生方式(Android权限请求)来处理。 3. 处理权限请求结果:如果用户拒绝,则跳转到应用设置页面。 但是,用户提到`plus.android.requestPermissions`没有响应。这可能是由于在Android 11上请求一个不存在的权限,或者是在Android 12+上请求权限的方式有问题。 让我们重新考虑权限请求的方法: 在UniApp中,我们可以通过条件编译(区分平台)和条件判断(区分Android版本)来执行不同的操作。 具体步骤: 1. 判断平台:如果是Android,再判断系统版本。 2. 如果是Android 12及以上(即API级别>=31),则请求`POST_NOTIFICATIONS`权限。 3. 如果是Android 11及以下,则跳过请求(因为不需要运行时请求)。 4. 无论何种情况,如果我们需要确保通知权限开启,可以检查应用的通知设置(但注意,在Android 8.0以上,通知通道可以单独关闭,这并不属于运行时权限,而是应用设置的一部分)。因此,跳转到设置页面可能是为了打开通知通道的设置。 然而,用户的需求是“当用户授予权限后跳转到应用设置页面”,这并不常见。通常,我们只在拒绝时引导用户去设置。但如果是用户主动要求去设置(比如应用需要用户进行一些其他设置),那么可以在任何时候调用`plus.runtime.openSettings()`。 所以,我猜测用户可能是想:在请求权限后,无论用户是否授予,都跳转到设置页面?或者是在用户授予权限后,再跳转到设置页面进行其他设置?这不太合理。 因此,我建议用户明确跳转设置页面的时机。通常,我们这样处理权限请求: - 请求权限。 - 如果用户授予,则执行需要权限的操作(例如发送通知)。 - 如果用户拒绝,则显示提示,告诉用户可以在设置中开启,并提供一个按钮跳转到设置页面。 现在,针对通知权限,在Android 12+上的处理: 在UniApp中,我们可以使用以下方式请求权限(使用原生Android权限请求): ```javascript // 判断平台 if (uni.getSystemInfoSync().platform === 'android') { // 获取系统版本 const version = uni.getSystemInfoSync().osVersion; // 返回字符串,如"10"、"11"、"12" const majorVersion = parseInt(version.split('.')[0]); if (majorVersion >= 12) { // Android 12及以上 // 请求通知权限 const main = plus.android.runtimeMainActivity(); const Permission = plus.android.importClass("android.Manifest"); const permission = Permission.permission.POST_NOTIFICATIONS; plus.android.requestPermissions(main, [permission], 0x1000, function(e) { // 回调函数 if (e.requestCode === 0x1000) { // 获取权限结果 let grantResults = e.grantResults; if (grantResults && grantResults.length > 0) { // 检查是否授予权限 if (grantResults[0] === 0) { // 0表示授予 console.log("用户授予通知权限"); // 这里可以执行发送通知等操作 } else { console.log("用户拒绝通知权限"); // 可以提示用户,并跳转到应用设置页面 uni.showModal({ title: '提示', content: '您拒绝了通知权限,是否去设置页面开启?', success: function(res) { if (res.confirm) { plus.runtime.openSettings(); } } }); } } } }); } else { // Android 11及以下,不需要请求通知权限 console.log("当前系统版本不需要请求通知权限"); // 但是,我们可以检查通知是否被用户关闭(在应用设置中)?这需要另外的方法 // 在低版本,我们可以直接发送通知,如果用户关闭了通知,则通知不会显示,但不会有运行时权限问题。 } } ``` 注意:上述代码中,我们使用了`plus.android.requestPermissions`,并且指定了一个请求码(0x1000)。在回调中,我们根据请求码来判断,然后检查授权结果数组(`grantResults`)中的第一个结果(因为我们只请求了一个权限)。 但是,用户提到在Android 11上使用`plus.android.requestPermissions`请求`POST_NOTIFICATIONS`没有响应。这是因为在Android 11上,这个权限不存在,所以请求一个不存在的权限,系统不会弹出对话框,也不会回调结果。因此,我们需要避免在Android 11及以下请求这个权限。 上面的代码已经通过系统版本判断避免了在Android 12以下请求该权限,所以应该不会出现没有响应的问题。 另外,关于跳转到设置页面,我们使用了`plus.runtime.openSettings()`,它会打开当前应用的系统设置页面,用户可以在那里开启通知权限(对于Android 12以下,通知权限在设置页面中通常以开关形式存在,而不是运行时权限)。 但是,在Android 12以下,通知权限并不是运行时权限,而是应用设置的一部分。因此,我们无法通过运行时权限API来获取其状态。如果我们想检查在Android 12以下的通知是否被关闭,可能需要使用其他方法,比如通过通知管理器检查通知通道的状态(这比较复杂,且不同系统版本方法不同)。 因此,如果用户希望在低版本Android上也引导用户开启通知(如果用户关闭了通知),我们可以直接跳转到设置页面,而不需要先请求权限(因为请求权限在低版本无效)。 总结实现步骤: 1. 判断平台和系统版本。 2. 如果是Android 12+,请求`POST_NOTIFICATIONS`权限,并根据结果决定是否跳转到设置页面(仅在拒绝时引导)。 3. 如果是Android 11及以下,我们可以直接引导用户去设置页面开启通知(因为不需要请求权限,但用户可能关闭了通知),或者先检查通知是否开启(需要额外代码)?但是,检查通知是否开启在低版本比较复杂,所以一种简单的做法是:在低版本也提供一个按钮,让用户跳转到设置页面去开启通知(如果应用认为通知很重要)。 因此,我们可以这样设计: - 在应用启动时,检查是否已经开启通知(对于Android 12+,通过权限状态;对于Android 8.0+,可以检查通知通道是否被关闭,但跨平台方式复杂)。 - 如果发现通知没有开启(无论是因为权限还是通知被关闭),则弹窗提示用户,并提供跳转设置页面的按钮。 但是,由于用户的需求是“点击提示后直接跳转到权限设置页面”,我们可以提供一个统一的入口,比如一个按钮,点击后: - 在Android 12+,先请求权限,如果用户拒绝则跳转到设置页面;如果用户授予,则不需要跳转(但用户可能希望跳转去设置其他通知选项?)。 - 在Android 11及以下,直接跳转到设置页面(因为不需要请求权限,但需要用户手动开启通知开关)。 这里,我们假设用户希望的是:用户点击某个按钮(比如“开启通知”),然后我们根据系统版本执行不同的操作。 代码示例: ```javascript function openNotificationSetting() { const systemInfo = uni.getSystemInfoSync(); if (systemInfo.platform === 'android') { const version = systemInfo.osVersion; const majorVersion = parseInt(version.split('.')[0]); if (majorVersion >= 12) { // Android 12+ const main = plus.android.runtimeMainActivity(); const Permission = plus.android.importClass("android.Manifest"); const permission = Permission.permission.POST_NOTIFICATIONS; plus.android.requestPermissions(main, [permission], 0x1000, function(e) { if (e.requestCode === 0x1000) { let grantResults = e.grantResults; if (grantResults && grantResults.length > 0) { if (grantResults[0] === 0) { console.log("权限已授予"); // 这里可以发送测试通知,或者什么都不做 } else { // 拒绝后跳转到设置页面 uni.showModal({ title: '提示', content: '请在设置中开启通知权限', success: (res) => { if (res.confirm) { plus.runtime.openSettings(); } } }); } } } }); } else { // Android 11及以下,直接跳转到设置页面 plus.runtime.openSettings(); } } else { // iOS平台处理,这里不展开 } } ``` 注意:在Android 11及以下,我们直接跳转到设置页面,因为不需要请求权限,但是用户可能关闭了通知开关,所以让用户去设置页面开启。 另外,关于`plus.android.requestPermissions`在Android 12+上是否有效,需要确保HBuilderX的基座和打包环境支持。如果使用自定义基座,请确保使用最新的HBuilderX。 最后,如果用户希望在授予权限后也跳转到设置页面(比如用户可能想设置通知渠道),那么可以在授予权限后也调用`plus.runtime.openSettings()`,但通常这不是必要的。 根据用户的需求调整:在用户授予权限后跳转到设置页面,那么修改回调: ```javascript if (grantResults[0] === 0) { // 用户授予权限,跳转到设置页面(用户可能进一步设置通知渠道等) plus.runtime.openSettings(); } ``` 但是,这样会使用户在授予权限后立即跳转到设置页面,可能会干扰用户。所以,请根据实际需求决定。 综上所述,我们提供了一个函数`openNotificationSetting`,它根据系统版本执行不同的操作,实现了在Android 12+上请求通知权限,并在拒绝时引导跳转设置;在Android 11及以下直接跳转设置页面。 注意:由于UniApp的权限API并未封装通知权限,因此我们使用了原生Android的方式。在iOS上,通知权限的请求方式不同,需要另外处理(使用`uni.requestPushPermission`或其他方法),这里仅处理Android。 最后,关于用户提到的`plus.android.requestPermissions`没有响应的问题,通过避免在Android 11及以下请求不存在的权限,应该可以解决。 希望这个回答能帮助用户解决问题。</think>在 UniApp 中处理 Android 11+ 设备通知权限问题(`targetSdkVersion=33`)需要结合权限请求和设置页面跳转逻辑,以下是完整解决方案: ### 解决方案步骤 #### 1. 检测通知权限状态 ```javascript function checkNotificationPermission() { // Android 13+ (API 33+) 使用 POST_NOTIFICATIONS 权限 if (plus.os.name === 'Android' && parseInt(plus.os.version) >= 13) { const main = plus.android.runtimeMainActivity(); const Context = plus.android.importClass('android.content.Context'); const NotificationManager = plus.android.importClass('android.app.NotificationManager'); const nm = main.getSystemService(Context.NOTIFICATION_SERVICE); return nm.areNotificationsEnabled(); } // Android 12 及以下默认视为有权限 return true; } ``` #### 2. 请求通知权限并处理结果 ```javascript function requestNotificationPermission() { return new Promise((resolve) => { const main = plus.android.runtimeMainActivity(); // Android 13+ 使用标准权限请求 if (plus.os.name === 'Android' && parseInt(plus.os.version) >= 13) { const Permission = plus.android.importClass('android.Manifest$permission'); const permissions = [Permission.POST_NOTIFICATIONS]; main.requestPermissions(permissions, 0, { onRequestPermissionsResult: (code, perms, results) => { resolve(results.length > 0 && results[0] === 0); } }); } else { // 低版本直接返回 true resolve(true); } }); } ``` #### 3. 跳转到应用设置页面 ```javascript function openAppSettings() { const Intent = plus.android.importClass('android.content.Intent'); const Settings = plus.android.importClass('android.provider.Settings'); const Uri = plus.android.importClass('android.net.Uri'); const main = plus.android.runtimeMainActivity(); const intent = new Intent(); intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.parse("package:" + main.getPackageName())); main.startActivity(intent); } ``` #### 4. 整合到业务逻辑 ```javascript async function handleNotificationPermission() { if (!checkNotificationPermission()) { const granted = await requestNotificationPermission(); if (!granted) { uni.showModal({ title: '权限提示', content: '需要通知权限才能接收消息,是否前往设置?', success: (res) => { if (res.confirm) openAppSettings(); } }); } } else { // 已有权限,执行通知相关操作 } } // 在页面中调用 export default { onLoad() { handleNotificationPermission(); } } ``` ### 关键问题说明 1. **`requestPermissions` 无响应问题** - Android 13+ 必须使用 `android.permission.POST_NOTIFICATIONS` - 低于 Android 13 的设备无需请求(系统默认授予) - 确保在 `AndroidManifest.xml` 声明权限: ```xml <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> ``` 2. **权限请求结果处理** - 通过 `onRequestPermissionsResult` 回调获取结果 - `results[0] === 0` 表示授权成功 3. **设置页面跳转原理** - 使用 `ACTION_APPLICATION_DETAILS_SETTINGS` 打开应用详情页 - 通过 `package:[应用包名]` 定位到当前应用设置 ### 适配注意事项 1. **Android 版本差异** - `targetSdkVersion=33` 仅影响 Android 13+ 设备 - 低于 Android 13 的设备不受影响[^1] 2. **UniApp 原生插件** ```json // manifest.json 配置 "app-plus": { "android": { "targetSdkVersion": 33 } } ``` 3. **动态权限请求** - 需在 `pages.json` 配置 `"androidPermission"` 节点 - 或使用 `uni.request` 动态获取权限 [^1]: Android 13 通知权限变更官方文档 ### 相关问题 1. **如何检测用户永久拒绝了权限?** 在 `onRequestPermissionsResult` 中检查 `shouldShowRequestPermissionRationale()` 方法,若返回 `false` 表示用户勾选了"不再询问"。 2. **Android 12 及以下设备如何处理通知权限?** 低于 Android 13 的设备无需运行时请求,但需在应用设置中检查通知开关状态,可通过 `NotificationManagerCompat.areNotificationsEnabled()` 检测。 3. **UniApp 云打包时需要注意什么?** 需在 HBuilderX 中配置: ```json "distribute": { "android": { "targetSdkVersion": "33" } } ``` 并勾选 manifest.json 中的 `"targetSdkVersion": "33"` 4. **iOS 平台如何实现同等逻辑?** 使用 `uni.requestPushPermission()` 统一接口,iOS 无需跳转设置页面,系统会自动弹出权限请求窗口。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值