总结系列-Android10适配
Posted ZhangQiang-
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了总结系列-Android10适配相关的知识,希望对你有一定的参考价值。
android10,即TargetSDK29于2019 年 9 月上线正式版, google play store要求TargetSDK29的适配,要求新产品在8月1号前完成,已有产品在11月1号前完成适配,记录. 在Android 10 版本中,某些改动较大,有一定的开发适配成本,本文主要记录一些相关适配点及部分调研情况- 关于Androidx
相关目录:
限制非 SDK 接口 ( 官方指导) Android 10 中的隐私权变更 ( google文档) 存储权限 定位权限 从后台启动 Activity 设备唯一标识符 深色主题 设置面板 其他相关 Android10升级可能的问题限制非 SDK 接口 (官方指导)
目前,开发者对于非SDK API的调用,一般是采取反射或JNI间接调用的方法进行的。由于Android是开源的,所以开发者对非公有SDK的调用十分混乱, Google为了进一步防止碎片化,规范开发者的API使用行为,于是开始限制开发者 通过反射或JNI间接调用非Android SDK提供的api。 这个变更会影响所有运行在9.0及以上的Android系统的APP,无论该APP是否已经升级了Target SDK。 几乎所有第三方SDK(包括Android support库!!)都有大量通过反射调用非SDK api的情况。通常反射调用时都有try-catch,能够保证应用不崩,但实际功能受影响的程度有待评估。 针对非 SDK 接口的限制 / Android 10 的名单更改名单类型 | 影响 |
greylist | 灰名单,即当前版本仍能使用的非SDK接口,但在下一版本中可能变成被限制的非SDK接口 , 不用关注 |
greylist-max-o | 受限制的灰名单。APP运行在 版本<=8.0的系统里 可以正常访问,targetSDK>8.0且运行在>8.0的手机会抛出异常。 |
greylist-max-p | 受限制的灰名单。APP运行在 版本<=9.0的系统里 可以正常访问,targetSDK>9.0且运行在>9.0的手机会抛出异常。 |
blacklist | 所有三方应用不允许调用。 黑名单,使用了就会报错。项目中必须解决的非SDK接口 |
访问方式 | 结果 |
Dalvik 指令引用某个字段 | 抛出 NoSuchFieldError |
Dalvik 指令引用某个方法 | 抛出 NoSuchMethodError |
通过 Class.getDeclaredField() 或 Class.getField() 进行反射 | 抛出 NoSuchFieldException |
通过 Class.getDeclaredMethod()、Class.getMethod() 进行反射 | 抛出 NoSuchMethodException |
通过 Class.getDeclaredFields()、Class.getFields() 进行反射 | 结果中未获取到非 SDK 成员 |
通过 Class.getDeclaredMethods()、Class.getMethods() 进行反射 | 结果中未获取到非 SDK 成员 |
通过 env->GetFieldID() 进行 JNI 调用 | 返回 NULL,抛出 NoSuchFieldError |
通过 env->GetMethodID() 进行 JNI 调用 | 返回 NULL,抛出 NoSuchMethodError |
怎么检测出非SDK接口的使用?
方法一 veridex 工具扫描 APK
google官方也提供了官方检查器veridex用来检测一个apk中哪里使用了非SDK接口 使用 veridex 工具命令:appcompat.sh --dex-file=**.apk --imprecise ( 目前该工具推荐支持mac和linux运行,Windows须先安装WLS) 扫描示例如下:#51: Reflection greylist-max-o Landroid/animation/TimeAnimator;->mListener potential use(s):
Landroid/support/v4/app/FragmentManagerImpl;->getAnimationListener(Landroid/view/animation/Animation;)Landroid/view/animation/Animation$AnimationListener;
#94: Reflection greylist Lcom/android/internal/R$dimen;->status_bar_height use(s):
Lcom/demo/common/utils/SystemUtils;->getStatusBarHeight(Landroid/app/Activity;)I
#1340: Reflection blacklist Lcom/android/org/conscrypt/SSLParametersImpl;->setApplicationProtocols potential use(s):
Lokhttp3/internal/platform/Jdk9Platform;->buildIfSupported()Lokhttp3/internal/platform/Jdk9Platform;
107 hidden API(s) used: 24 linked against, 83 through reflection
91 in greylist
0 in blacklist
1 in greylist-max-o
15 in greylist-max-p
use(s):左边是被调用的非SDK的api,并非常详细地列出了包名、类名、函数名(or变量名),以及其所属的名单级别(greylist or blacklist)。
use(s):右边就是调用了非SDK api的项目代码的位置。 比如上面扫描结果告诉我们:- #51:谷歌的Android-support库中的FragmentManagerImpl通过反射调用了greylist-max-o灰名单中的android/animation/TimeAnimator的mListener变量。 很明显,我们无法修改support库中的源码, 只能等官方自己修复。
- #94:项目里的demo/common/utils/SystemUtils的getStatusBarHeight方法 通过反射获取了com.android.internal.R$dimen类中的status_bar_height变量,属于通过反射调用灰名单的非SDK api, 暂时是没有问题。
- #1340:项目里的okhttp3/internal/platform/Jdk9Platform的buildIfSupported方法通过反射调用了com/android/org/conscrypt/SSLParametersImpl的setApplicationProtocols方法,属于通过反射调用 黑名单里的非SDK api。 很明显,这里我们要注意评估项目中的okhttp通过反射调用黑名单api的行为,需要评估是否会引起崩溃(有无try-catch),是否在9.0以上机型造成功能异常,这个就需要开发者 根据具体的业务情况去做具体分析了。
方法二 全局搜索业务代码
在veridex扫描结果中排查项目业务代码对非SDK接口的调用情况比较困难。所以可以通过最原始的办法————全局搜索 JAVA反射相关的api(搜索上面的表格里的getField、getMethod等关键字),与官方给出的hidden api list(上面提到的hiddenapi-flags.csv)做比对,就很容易排查中业务代码里对非SDK接口的调用情况。 全局搜索getField,发现项目通过反射找到对应类的对应成员变量,在官方提供的 hiddenapi-flags.csv中可以发现该成员变量是否非SDK接口,且属于受限制的灰名单,由于业务代码对反射调用进行了try-catch,所以实际业务中拿到的是一个null,这个时候我们就可以感受到谷歌官方对非SDK接口的限制,并评估具体的业务影响。 谷歌官方的建议:如果您的应用依赖于非 SDK 接口,则应该开始计划迁移到 SDK 接口或其他替代方案。如果您无法为应用中的功能找到使用非 SDK 接口的替代方案,可以向谷歌官方 申请新的公共 API。
Android 10 中的隐私权变更 (google文档)
存储权限
Android 10 在外部存储设备中为每个应用提供了一个“隔离存储沙盒”,其访问权限范围限定为外部存储,即分区存储。 此类应用可以查看外部存储设备内* 特定于应用的目录中的文件(使用 getExternalFilesDir() 访问)。 * 应用创建的照片、视频和音频片段(通过 媒体库访问),无需请求任何与存储相关的用户权限。 任何其他应用都无法直接访问您应用的沙盒文件。此变更可让您更轻松地保证用户文件的隐私性,并有助于减少应用所需的权限数量。 简单而言就是应用专属文件夹,并且 访问这个文件夹无需权限。 谷歌官方推荐应用在沙盒内存储文件的地址为Context. getExternalFilesDir()下的文件夹。比如要存储一张图片,则应放在Context. getExternalFilesDir(Environment.DIRECTORY_PICTURES)中。 target=29时,谷歌临时允许使用老版本的权限来解决储存分区问题 在manifest application下添加 android:requestLegacyExternalStorage ="true" (注:在Android11 即targetSdkVersion = 30中不行了, 强制开启分区存储。) 处理外部存储中的媒体文件 将文件保存到外部存储 本次适配升级暂用兼容模式 :(若需具体适配看另一篇分区存储适配) android10的时候在 targetSdkVersion = 29 应用中,设置 android:requestLegacyExternalStorage="true" ,就可以不启动分区存储,让以前的文件读取正常使用。但是 targetSdkVersion = 30 中不行了, 强制开启分区存储 。 当然,如果是覆盖安装呢,可以增加 android:preserveLegacyExternalStorage="true" ,暂时关闭分区存储,好让开发者完成数据迁移的工作。为什么是暂时呢?因为只要 卸载重装 ,就会失效了。 以下是关于分区存储会遇到的 所有情况 , 分情况运行: 1) targetSdkVersion = 28,运行后正常读写。 2) targetSdkVersion = 29,不删除应用,targetSdkVersion 由28修改到29,覆盖安装,运行后正常读写。 3) targetSdkVersion = 29,删除应用,重新运行,读写报错,程序崩溃(open failed: EACCES (Permission denied)) 4) targetSdkVersion = 29,添加android:requestLegacyExternalStorage="true"(不启用分区存储),读写正常不报错 5) targetSdkVersion = 30,不删除应用,targetSdkVersion 由29修改到30,读写报错,程序崩溃(open failed: EACCES (Permission denied)) 6) targetSdkVersion = 30,不删除应用,targetSdkVersion 由29修改到30,增加android:preserveLegacyExternalStorage="true",读写正常不报错 7) targetSdkVersion = 30,删除应用,重新运行,读写报错,程序崩溃(open failed: EACCES (Permission denied))定位权限
为了让用户更好地控制应用对位置信息的访问权限 Android 10 引入了 ACCESS_BACKGROUND_LOCATION 权限(危险权限)。 与现有的 ACCESS_FINE_LOCATION 和 ACCESS_COARSE_LOCATION 权限不同,新权限仅会影响应用在 后台运行时对位置信息的访问权。除非应用的某个 Activity 可见或应用正在运行前台服务(类型为 location),否则应用将被视为在后台运行。 如果应用需要在后台时也获得用户位置(比如滴滴),就需要动态申请ACCESS_BACKGROUND_LOCATION权限。 该权限允许应用程序在后台访问位置。如果请求此权限,则还必须请求 ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION 权限。只请求此权限无效果。 当然如果不需要的话,应用就无需任何改动,且谷歌会按照应用的targetSDK作出不同处理: 在Android 10的设备上,如果你的应用的 targetSdkVersion < 29,则在请求 ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION 权限时, 系统会自动同时请求 ACCESS_BACKGROUND_LOCATION 。在请求弹框中,选择“始终允许”表示同意后台获取位置信息,选择“仅在应用使用过程中允许”或"拒绝"选项表示拒绝授权 如果你的应用的 targetSdkVersion >= 29,则请求 ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION 权限表示在前台时拥有访问设备位置信息的权限。 在请求弹框中,选择“始终允许”表示前后台都可以获取位置信息,选择“仅在应用使用过程中允许”只表示拥有前台的权限。 对应关系:目标平台版本 | 是否授予了 粗略或精确位置信息使用权限? | 清单中是否 定义了后台权限? | 更新后的默认权限状态 |
Android 10 | 是 | 是 | 前台和后台访问权 |
Android 10 | 是 | 否 | 仅前台访问权 |
Android 10 | 否 | (被系统忽略) | 无访问权 |
Android 9 或更低版本 | 是 | 在设备升级时由系统自动添加 | 前台和后台访问权 |
Android 9 或更低版本 | 否 | (被系统忽略) | 无访问权 |
一些电话、蓝牙和WLAN的API需要精确位置权限
下面列举了Android 10中必须具有 ACCESS_FINE_LOCATION 权限才能使用类和方法: 电话 * TelephonyManager * getCellLocation() * getAllCellInfo() * requestNetworkScan() * requestCellInfoUpdate() * getAvailableNetworks() * getServiceState() * TelephonyScanManager * requestNetworkScan() * TelephonyScanManager.NetworkScanCallback * onResults() * PhoneStateListener * onCellLocationChanged() * onCellInfoChanged() * onServiceStateChanged() WLAN * WifiManager * startScan() * getScanResults() * getConnectionInfo() * getConfiguredNetworks() * WifiAwareManager * WifiP2pManager * WifiRttManager 蓝牙 * BluetoothAdapter * startDiscovery() * startLeScan() * BluetoothAdapter.LeScanCallback * BluetoothLeScanner * startScan()从后台启动 Activity
从 Android 10 开始,系统会增加针对从后台启动 Activity 的限制, 简单解释就是 应用处于后台时,无法启动Activity 。比如点开一个应用会进入启动页或者广告页,一般会有几秒的延时再跳转至首页。如果这期间你退到后台,那么你将无法看到跳转过程。 而在之前的版本中,会强制弹出页面至前台。但下情景除外 * 应用具有可见窗口,例如前台 Activity。 * 应用在前台任务的返回栈中已有的 Activity。 * 应用在 Recents 上现有任务的返回栈中已有的 Activity。Recents 就是我们的任务管理列表。 * 应用收到系统的 PendingIntent 通知。 * 应用收到它应该在其中启动界面的系统广播。示例包括 ACTION_NEW_OUTGOING_CALL 和 SECRET_CODE_ACTION。应用可在广播发送几秒钟后启动 Activity。 * 用户已向应用授予 SYSTEM_ALERT_WINDOW 权限,或是在应用权限页开启后台弹出页面的开关。 因为此项行为变更适用于在 Android 10 上运行的所有应用,所以这一限制导致最明显的问题就是点击推送信息时,有些应用无法进行正常的跳转(具体的实现问题导致) 针对这类问题,可以采取 PendingIntent 的方式,发送通知时使用 setContentIntent 方法。 对于全屏 intent ,注意设置最高优先级和添加 USE_FULL_SCREEN_INTENT 权限,这是一个普通权限。比如微信来语音或者视频通话时,弹出的接听页面就是使用这一功能。 权限需清单文件注册,系统自动授权,调用全屏通知会有以下两种场景: 1.如果用户设备被锁定,会显示全屏 Activity,覆盖锁屏。 2.如果用户设备处于解锁状态,通知以展开形式显示,其中包含用于处理或关闭通知的选项。设备唯一标识符
从Android 10开始已经无法完全标识一个设备,曾经用mac地址、IMEI等设备信息标识设备的方法,从Android 10开始统统失效。而且无论你的APP是否适配过Android 10。 从Android10开 始普通应用不再允许请求权限 android.permission.READ_PHONE_STATE。而且,无论你的App是否适配过Android 10(既targetSdkVersion是否大于等于29),均无法再获取到设备IMEI等设备信息。 受影响的API:IMEI等设备信息
Build.getSerial(); TelephonyManager.getImei(); TelephonyManager.getMeid() TelephonyManager.getDeviceId(); TelephonyManager.getSubscriberId(); TelephonyManager.getSimSerialNumber();- targetSdkVersion<29 的应用,其在获取设备ID时,会直接返回null
- targetSdkVersion>=29 的应用,其在获取设备ID时,会直接抛出异常SecurityException
Mac地址随机分配
从Android10开始,默认情况下,在搭载 Android 10 或更高版本的设备上,系统会传输随机分配的 MAC 地址。(即从Android 10开始,普通应用已经无法获取设备的真正mac地址,标识设备已经无法使用mac地址)如何标识设备唯一性
1 Google解决方案:(google- 唯一标识符最佳做法 ) 如果您的应用有追踪非登录用户的需求,可用ANDROID_ID来标识设备。- ANDROID_ID生成规则:签名+设备信息+设备用户
- ANDROID_ID重置规则:设备恢复出厂设置时,ANDROID_ID将被重置
- 如果应用以 Android 10 或更高版本为目标平台,则会发生 SecurityException 。
- 如果应用以 Android 9(API 级别 28)或更低版本为目标平台,则相应方法会返回 null 或占位符数据(如果应用具有 READ_PHONE_STATE 权限)。否则,会发生 SecurityException 。
无线扫描权限
启用和停用 WLAN :
无法启用或停用 WLAN , WifiManager.setWifiEnabled() 在Android10 始终返回false ,如果需要操作,需引导开启 设置界面 对 直接访问已配置的 WLAN 网络实施了限制 : 应用不是系统应用或 DPC,则下列方法不会返回有用数据: * getConfiguredNetworks() 方法始终返回空列表。 注意:如果运营商应用调用 getConfiguredNetworks(),则系统返回的列表仅包含运营商配置的网络。 * 每个返回整数值的网络操作方法(addNetwork() 和 updateNetwork())始终返回 -1。 * 每个返回布尔值的网络操作(removeNetwork()、reassociate()、enableNetwork()、disableNetwork()、reconnect() 和 disconnect())始终返回 false。深色主题
Android 10 新增了一个系统级的深色主题(在系统设置中开启)。虽然深色主题并 不是强制适配项 ,但是它可以带给用户更好的体验:1.手动适配(资源替换)
官方文档中提到的继承Theme.AppCompat.DayNight 或者 Theme.MaterialComponents.DayNight的方法,但这只是将我们使用的各种View的 默认样式进行了适配,并不太适用于实际项目的适配。 配的方法很简单,类似屏幕适配、国际化的操作,并不需要继承上面的主题。比如你要修改颜色,就在 res 下新建 values-night 目录,创建对应的 colors.xml 文件 。将具体要修改的色值定义在里面。图标之类的也是一个思路,创建对应的 drawable-night 目录。2.自动适配(Force Dark)
应用必须选择启用 Force Dark,方法是在其主题背景中设置 android:forceDarkAllowed="true" 。 您可以通过 android:forceDarkAllowed 布局属性或 setForceDarkAllowed(boolean) 在特定视图上控制 Force Dark。( 如果使用的是 DayNight 或 Dark Theme 主题,则设置 forceDarkAllowed 不生效 ) 设置的色值会自动取反。但也因此颜色不受控制,能否达到预期效果是个需要注意的问题。追求快速适配可以采取此方案。 判断深色主题是否开启 int currentNightMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;设置面板
Android 10 引入了“设置面板”,让应用能够在自身环境中向用户显示设置。这样一来,用户无需通过转到 设置 来更改 NFC 或 移动数据 等设置,而能继续留在原来的应用中。 要显示设置面板,请发出具有以下一个 Settings.Panel 操作的 intent: Intent panelIntent = new Intent(Settings.Panel.settings_panel_type); startActivityForResult(panelIntent); ACTION_INTERNET_CONNECTIVITY 显示与互联网连接相关的设置,例如飞行模式、WLAN 和移动数据。 ACTION_WIFI 显示 WLAN 设置,但不显示其他连接设置。这对于需要 WLAN 连接以执行大容量上传或下载的应用非常有用。 ACTION_NFC 显示与近距离无线通信 (NFC) 相关的所有设置。 ACTION_VOLUME 显示所有音频流的音量设置。其他相关
webview相关 webview loadData不展示,通过loadUrl的方式可以,10以后 loadData需要base64数据处理 Glide 加载本地图片 直接加载file出问题,须转换为URI方式 明文HTTP限制 当SDK版本大于API 28时,默认限制了HTTP请求 在AndroidManifest.xml中Application节点添加如下代码 <application android:usesCleartextTraffic="true"> 限制了对剪贴板数据的访问权限 : 除非您的应用是默认输入法 (IME) 或是目前处于焦点的应用,否则它无法访问 Android 10 或更高版本平台上的剪贴板数据。暂无解决方案 手势导航: 您需要将应用内容扩展到屏幕边缘,并适当地处理存在冲突的手势( 屏幕边缘向内-返回) 手势导航 文档 应用使用: 灰度模式 ; 干扰模式(禁止显示其通知,并且不会将其显示为推荐的应用) 启用和停用 WLAN : 无法启用或停用 WLAN ,若需要 请使用 设置面板 。WLAN 直连广播不生效 Android10上对折叠屏设备有了更好的支持, 有折叠屏适配的需求,可以参看 为可折叠设备构建应用 和 华为折叠屏应用开发指导 。 暂停和播放: 在 Android 10 中,暂停的应用无法播放音频。Android10升级可能的问题(收集)
问题:一个页面通过webview展示的图片不展示 解决过程:发现通过loadData不展示,通过loadUrl的方式可以,后来发现10以后loadData需要base64数据处理 if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.Q) String newhtml_code = Base64.encodeToString(htmlStr.getBytes(), Base64.NO_PADDING); webview.loadData(newhtml_code,"text/html", "base64"); else webview.loadData(htmlStr, "text/html; charset=UTF-8", null); Activity透明相关,windowIsTranslucent属性 如果你要显示一个半透明的Activity,这在android10之前普通样式Activity只需要设置windowIsTranslucent=true即可,但是到了Android10,它没有效果了,而且如果动态设置View.setVisibility(),界面还会出现残影... 解决办法:使用Dialog样式Activity,且设置windowIsFloating=true,此时问题又来了,如果Activity根布局没有设置fitsSystemWindow=true,默认是没有侵入状态栏的,使界面看上去正常。 第三方分享图片等操作 直接使用文件路径的,图片分享,都需要注意,这是不可行的,都只能通过MediaStore等API,拿到Uri来操作相关参考链接:
Google 文档 Android10 行为变更 / 迁移指南 Android 10 适配攻略 Android10 文件存储相关 记录项目升级androidX+API29的各种坑 SAF(Storage Access Framework)使用攻略 携程Android 10适配踩坑指南 / 简书 // 华为文档 Android 10分区存储介绍及 百度APP适配实践 Android10填坑适配指南,实际经验代码 Android 10 更新内容与适配( 关于Android 10.0适配,看这篇就够了 适配Android P之非SDK接口限制的排查方法 拖不得了,Android11 最全 适配实践 Android11(30)/Android10(29)分区存储-适配方案以上是关于总结系列-Android10适配的主要内容,如果未能解决你的问题,请参考以下文章