文章目录
前言
项目需求,将target版本从 30 升级至 33,也就是 Android 13 版本。翻阅了 Android Developer 官方文档,
结合自己升级过程中遇到的问题,旨在帮助有需要的朋友们。
适配点
READ_EXTERNAL_STORAGE 权限
Android 13 开始,新增了运行时权限 READ_MEDIA_IAMGES、READ_MEDIA_VIDEO、READ_MEDIA_AUDIO ,
来代替 READ_EXTERNAL_STORAGE 权限。
当应用需要升级到 target>=33,会出现两种情况:
- 已授权 READ_EXTERNAL_STORAGE 权限,系统自动赋予对应的细化权限。
- 未授权 READ_EXTERNAL_STORAGE 权限,即使再请求 READ_EXTERNAL_STORAGE 权限,也不会有任何效果,
需要申请相应的细化权限。
代码示例:
public static boolean hasPermission(Context context, String permission) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
&& (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission))) {
return true;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (context.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
public static void requestStoragePermissionIfNeed(Activity activity, int requestCode) {
String[] permissions;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissions = new String[]{Manifest.permission.READ_MEDIA_VIDEO, Manifest.permission.READ_MEDIA_IAMGES};
} else {
permissions = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE};
}
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
activity.requestPermissions(permissions, requestCode);
}
} catch (Exception e) {
e.printStackTrace();
}
}
WebView调整
- setAppCacheEnabled 和 setAppCachePath 废弃
setAppCacheEnabled 和 setAppCachePath 是为了实现 H5 的离线缓存功能而添加的API,
33版本将这两个API废弃且没有提供代替的API,猜测是内部实现了改功能。 - setForceDark 废弃
Android 13 开始,系统会根据应用的主题属性isLightTheme,自动设置WebView的浅色或深色主题样式。
所以,WebView 中设置深色主题的API setForceDark 被废弃了。
不过,如果没有设置应用主题,如何将 WebView 设置为深色主题那?可以使用:
WebSettings.setAlgorithmicDarkeningAllowed()或WebSettingsCompat.setAlgorithmicDarkeningAllowed()方法。
通知权限 POST_NOTIFICATIONS
Android 13 引入了一个新的运行时权限:POST_NOTIFICATIONS。在发送通知时,需要动态申请。
<!-- AndroidManifest权限声明 -->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.xiaxl.test">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
</manifest>
// Java代码动态申请POST_NOTIFICATIONS权限
if (Build.VERSION.SDK_INT >= 33) {
int checkPermission =
ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.POST_NOTIFICATIONS);
if (checkPermission != PackageManager.PERMISSION_GRANTED) {
//动态申请
ActivityCompat.requestPermissions(MainActivity.this, new String[]{
Manifest.permission.POST_NOTIFICATIONS}, PERMISSION_REQUEST_CODE);
} else {
//showRecordNotification();
}
} else {
//showRecordNotification();
}
剪切板内容隐藏
Android 13(API 33)开始,Android剪切板新增了一项新API:
PersistableBundle#(ClipDescription.EXTRA_IS_SENSITIVE, true): 根据需要隐藏要复制到剪切板的用户账户、密码登敏感信息。
private void addData2Clipboard() {
ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = ClipData.newPlainText("111111", "我是密码");
ClipDescription description = clipData.getDescription();
// 隐私内容:剪切板加密
PersistableBundle persistableBundle = new PersistableBundle();
if (Build.VERSION.SDK_INT >= 33) {
persistableBundle.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
} else {
persistableBundle.putBoolean("android.content.extra.IS_SENSITIVE", true);
}
description.setExtras(persistableBundle);
// 剪切板添加加密内容
clipboardManager.setPrimaryClip(clipData);
}
静态注册组件
Android 11(target=31)开始,组件在注册时,必须明确指定 android:exported属性。
如果组件使用了intent-filter标签,那么它大概率是要对外暴露的,此时需要将export设置为true即可,其他情况设置为false。
由于清单文件中,组件注册比较多,手动添加太过麻烦,所以,写了一个python脚本,自动添加 android:exported 属性。
需要的自取:
import xml.etree.ElementTree as ET
import os
format_str = '{http://schemas.android.com/apk/res/android}'
def _append_exported(nodes, target_tag):
for node in nodes:
if format_str+'exported' in node.attrib:
node_string = ET.tostring(node, encoding='unicode')
# print(node_string)
else :
had_append_export = False
if 'activity' == target_tag:
action_nodes = node.findall('.//action')
for action_node in action_nodes:
if format_str+'name' in action_node.attrib:
value = action_node.get(format_str+'name')
if value == 'android.intent.action.VIEW':
node.set('android:exported', "true")
had_append_export = True
break
if not had_append_export:
node.set('android:exported', "false")
def parse_xml_and_add_node(ori_path):
etree = ET.parse(source=ori_path)
root = etree.getroot()
# 命名空间映射
namespace_mapping = {
'android': 'http://schemas.android.com/apk/res/android',
'tools': 'http://schemas.android.com/tools'
}
# 注册命名空间前缀和URI的映射关系
for prefix, uri in namespace_mapping.items():
ET.register_namespace(prefix, uri)
# 为组件添加 exported 属性
activity_nodes = root.findall('.//activity')
_append_exported(activity_nodes, 'activity')
service_nodes = root.findall('.//service')
_append_exported(service_nodes, 'service')
receiver_nodes = root.findall('.//receiver')
_append_exported(receiver_nodes, 'receiver')
provider_nodes = root.findall('.//provider')
_append_exported(provider_nodes, 'provider')
# 更新文件
etree.write(ori_peth, encoding='utf-8', xml_declaration=True)
if __name__ == '__main__':
# 获取当前目录中的manifest文件, 根据自己需要修改
ori_peth = os.path.join(os.getcwd(),'test.xml')
print(ori_peth)
parse_xml_and_add_node(ori_path=ori_peth)
PendingIntent 必须指定可变性
Android 11(target=31)开始,在创建 PendingIntent 时,需要明确指定 FLAG_MUTABLE or FLAG_IMMUTABLE。
代码示例:
private PendingIntent getPiOfBroadcast(Context context, int requestCode,
Intent intent, int flags) {
PendingIntent pi;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
pi = PendingIntent.getBroadcast(context, requestCode, intent, flags | PendingIntent.FLAG_IMMUTABLE);
} else {
pi = PendingIntent.getBroadcast(context, requestCode, intent, flags);
}
return pi;
}
private PendingIntent getPiOfActivity(Context context, int requestCode,
Intent intent, int flags) {
PendingIntent pi;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
pi = PendingIntent.getActivity(context, requestCode, intent, flags | PendingIntent.FLAG_IMMUTABLE);
} else {
pi = PendingIntent.getActivity(context, requestCode, intent, flags);
}
return pi;
}