适配从 Android10 升级至 Android 13的问题

本文介绍了在将项目target版本升级到Android13时,开发者需要注意的几个关键适配点:新权限管理(如细化权限取代READ_EXTERNAL_STORAGE),WebView的API调整,POST_NOTIFICATIONS通知权限,以及组件注册时对android:exported属性的处理和PendingIntent的可变性要求。

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

前言

项目需求,将target版本从 30 升级至 33,也就是 Android 13 版本。翻阅了 Android Developer 官方文档,
结合自己升级过程中遇到的问题,旨在帮助有需要的朋友们。

适配点

READ_EXTERNAL_STORAGE 权限

Android 13 开始,新增了运行时权限 READ_MEDIA_IAMGESREAD_MEDIA_VIDEOREAD_MEDIA_AUDIO
来代替 READ_EXTERNAL_STORAGE 权限。

当应用需要升级到 target>=33,会出现两种情况:

  1. 已授权 READ_EXTERNAL_STORAGE 权限,系统自动赋予对应的细化权限。
  2. 未授权 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调整

  1. setAppCacheEnabled 和 setAppCachePath 废弃
    setAppCacheEnabled 和 setAppCachePath 是为了实现 H5 的离线缓存功能而添加的API,
    33版本将这两个API废弃且没有提供代替的API,猜测是内部实现了改功能。
  2. 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;
    }

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值