此限制不只仅局限于sdk层 ( 直接引用 或者 反射 ),而触及到了 JNI 层,其实早在 android N 的时候就限制了 C / C++ 使用的符号集合,一旦NDK有没法通知的变动,毫无疑问会引发程序的 crash 。html
SDK 接口指在 Android 框架 软件包索 中记录的接口, Google为了让开发者有过渡的时间而且起到警示的做用, 针对 non-sdk 接口设定了不一样级别的名单类型:前端
咱们平时开发须要注意的也就是 深灰名单 和 黑名单,不用太在乎 浅灰名单 ,由于前面说到过能够直接引用 non-sdk 接口,这里基本上是指直接引用 浅灰名单的接口。java
这边先举个例子:在官方的浅灰名单中,其中列举可不少咱们平时用的接口,例如 Intent 获取资源:linux
Landroid/content/Intent;->FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT:I
Landroid/content/Intent;->getExtra(Ljava/lang/String;)Ljava/lang/Object;
Landroid/content/Intent;->getExtra(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;
复制代码
然而这正是咱们平时用的不少的 Api,因此没必要太在乎,浅灰名单属于很是低级别的警告, 客户端适配迫切须要解决的是 黑名单 的列表: 黑名单 基本上是咱们平时应用级开发不会用到的Api,例如:android
Lsun/util/calendar/BaseCalendar;->getMonthLength(II)I
复制代码
对应sdk中代码:web
//推荐使用的方法,官方不限制
public int getMonthLength(CalendarDate var1) {
BaseCalendar.Date var2 = (BaseCalendar.Date)var1;
int var3 = var2.getMonth();
if (var3 >= 1 && var3 <= 12) {
return this.getMonthLength(var2.getNormalizedYear(), var3);
} else {
throw new IllegalArgumentException("Illegal month value: " + var3);
}
}
//官方黑名单方法,运行在 P 设备上直接crash
private int getMonthLength(int var1, int var2) {
int var3 = DAYS_IN_MONTH[var2];
if (var2 == 2 && this.isLeapYear(var1)) {
++var3;
}
return var3;
}
复制代码
因此呢,黑名单 虽然听起来 骇人听闻,可是对于存量app的影响倒不是很大,由于基本上都是一些私有的,罕见的方法。算法
影响范围最大的当属 深灰名单, 由于官方强烈不推荐使用,可是为了给开发者缓冲时间,只有 Target Api 28+ 才会出现异常,表明性的 Api 有 DexFile 类:shell
Ldalvik/system/DexFile;-><init>(Ljava/io/File;Ljava/lang/ClassLoader;[Ldalvik/system/DexPathList$Element;)V
Ldalvik/system/DexFile;-><init>(Ljava/lang/String;Ljava/lang/ClassLoader;[Ldalvik/system/DexPathList$Element;)V
Ldalvik/system/DexFile;-><init>(Ljava/lang/String;Ljava/lang/String;ILjava/lang/ClassLoader;[Ldalvik/system/DexPathList$Element;)V
Ldalvik/system/DexFile;-><init>(Ljava/nio/ByteBuffer;)V
Ldalvik/system/DexFile;->DEX2OAT_FOR_BOOT_IMAGE:I
Ldalvik/system/DexFile;->DEX2OAT_FOR_FILTER:I
Ldalvik/system/DexFile;->DEX2OAT_FOR_RELOCATION:I
Ldalvik/system/DexFile;->DEX2OAT_FROM_SCRATCH:I
Ldalvik/system/DexFile;->NO_DEXOPT_NEEDED:I
Ldalvik/system/DexFile;->closeDexFile(Ljava/lang/Object;)Z
Ldalvik/system/DexFile;->createCookieWithArray([BII)Ljava/lang/Object;
Ldalvik/system/DexFile;->createCookieWithDirectBuffer(Ljava/nio/ByteBuffer;II)Ljava/lang/Object;
Ldalvik/system/DexFile;->defineClass(Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/lang/Object;Ldalvik/system/DexFile;Ljava/util/List;)Ljava/lang/Class;
Ldalvik/system/DexFile;->defineClassNative(Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/lang/Object;Ldalvik/system/DexFile;)Ljava/lang/Class;
复制代码
以及 AssetManager 相关:apache
Landroid/content/res/AssetManager;->TAG:Ljava/lang/String;
Landroid/content/res/AssetManager;->addAssetPathInternal(Ljava/lang/String;Z)I
Landroid/content/res/AssetManager;->addAssetPathNative(Ljava/lang/String;Z)I
Landroid/content/res/AssetManager;->addAssetPaths([Ljava/lang/String;)[I
Landroid/content/res/AssetManager;->addOverlayPathNative(Ljava/lang/String;)I
Landroid/content/res/AssetManager;->applyStyle(JIIJ[IIJJ)V
复制代码
这两个类被列入深灰名单基本上就打翻了市面上一众 热修复 框架, 这意味着若是您的App 是以 28+为目标版本,而且运行在 android P 之上,则这些热修复框架可能没法正常运行。api
在今年6月份 GMTC(全球大前端技术大会) 的时候,京东架构师发表了演讲 《当插件化赶上android P》 中就提到了,去黑科技化,目前 Android P 的 non-sdk 限制已经影响到京东的 插件框架。
有两种方法:
若是您的本地有 AOSP 项目的话,在根目录运行
make hiddenapi-aosp-blacklist
复制代码
而后,能够在如下位置找到文件:
out/target/common/obj/PACKAGING/hiddenapi-aosp-blacklist.txt
复制代码
你大能够在 相应名单中 查找你想查找的类,不过官方提供了自动扫描工具 veridex。
下载到本地目录, 找到你的系统对应的脚本目录:
使用命令扫描:
appcompat.sh --dex-file=apk路径
复制代码
能够看到咱们的项目中只有一个 深灰名单的警告。
下图是利用各类途径使用 non-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 |
相似写法,将会发生 NoSuchProviderException:
SecureRandom.getInstance("SHA1PRNG", "Crypto")
复制代码
在android P 以前的设备上,使用 Crypto 提供商,若是 target < 24 (N) 可以正常使用,若是target 24+ 则会失败
在adnrodi P 设备上 因为完全移除了 Crypto, 所以不管 target 是何值 都会抛出异常 NoSuchProviderException
加密功能的 BC 提供者被移除,官方博客中这样说:
Starting in Android P, we plan to deprecate some functionality from the BC provider that's duplicated by the AndroidOpenSSL (also known as Conscrypt) provider
android P开始,BC 提供者变成不推荐,若是targetApi < P 会有日志警告 targetApi >=p (28+) 将会抛出 NoSuchAlgorithmException ,如下写法将会受影响:
Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC") or
Cipher.getInstance("AES/CBC/PKCS7PADDING", Security.getProvider("BC"))
复制代码
所以,建议不要再指定 provider 而使用默认实现。
若是您的应用须要在运行 Android 9 的设备上检测传感器事件,请使用前台服务。
Android P 引入 CALL_LOG 权限组并将 READ_CALL_LOG、WRITE_CALL_LOG 和 PROCESS_OUTGOING_CALLS 权限移入该组。 在以前的 Android 版本中,这些权限位于 PHONE 权限组
Android 8.0和Android 8.1:
成功调用 WifiManager.getScanResults() 须要如下任何一项权限:
若是调用应用程序没有任何这些权限,则调用将失败并显示 SecurityException。
Android 9及更高版本:
成功调用 WifiManager.startScan() 须要知足如下全部条件:
成功调用 WifiManager.getScanResults() 须要知足如下全部条件:
若是调用应用程序不知足全部这些要求,则调用将失败并显示 SecurityException。
相似的限制也适用于 getConnectionInfo() 函数,该函数返回描述当前 Wi-Fi 链接的 WifiInfo 对象。 若是调用应用具备如下权限,则只能使用该对象的函数来检索 SSID 和 BSSID 值:
在 RFC 2818
中,回退到 CN 已被弃用。所以,Android 再也不回退到使用 CN。 要验证主机名,服务器必须出示具备匹配 SAN 的证书。 不包含与主机名匹配的 SAN 的证书再也不被信任
其实,自 Android 6 发布,就移除了对 Apache HTTP 客户端的支持,而推荐改用 HttpURLConnection 类,由于它能够经过透明压缩和响应缓存减小网络使用,并可最大限度下降耗电量, 今后咱们变习惯这样使用 Apache HTTP API,即在 build.geadle
添加:
android {
useLibrary 'org.apache.http.legacy'
}
复制代码
androd P 开始,默认状况下该内容库已从 bootclasspath 中移除且不可用于应用。
这句话怎么理解,也就是说默认 Apache HTTP API 不可用,即便在build.geadle
申明了该库。 这种说法分两种状况: 运行在 android P 设备上的应用:
Target 28
,默认会报 NoClassDefFoundError,由于此库被禁止使用,要继续使用 Apache HTTP 客户端,以 Android 9 及更高版本为目标的应用能够向其 AndroidManifest.xml
添加如下内容:
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
复制代码
Target < 28
能够和 android 6.0
一致。
bootclasspath 是 linux 系统变量,是系统在启动时会预先加载的类,以提升系统性能,这是 小米 MIX(7.0)
上的 bootclasspath 变量:
/system/bin/sh: /system/framework/core-oj.jar:/system/framework/core-libart.jar:/system/framework/conscrypt.jar:/system/framework/okhttp.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/apache-xml.jar:/system/framework/org.apache.http.legacy.boot.jar:/system/framework/vivo-framework.jar:/system/framework/tcmiface.jar:/system/framework/telephony-ext.jar:/system/framework/vivo-media.jar:/system/framework/qcrilhook.jar:/system/framework/WfdCommon.jar:/system/framework/com.qti.location.sdk.jar:/system/framework/oem-services.jar:/system/framework/qcom.fmradio.jar: not found
复制代码
变量中有:/system/framework/org.apache.http.legacy.boot.jar
,所以系统会帮咱们加载,默认容许使用。
这是 android P
上的 bootclasspath 变量:
/system/framework/core-oj.jar:/system/framework/core-libart.jar:/system/framework/conscrypt.jar:/system/framework/okhttp.jar:/system/framework/bouncycastle.jar:/system/framework/apache-xml.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/android.hidl.base-V1.0-java.jar:/system/framework/android.hidl.manager-V1.0-java.jar:/system/framework/framework-oahl-backward-compatibility.jar:/system/framework/android.test.base.jar
generic_x86_64:/ $
复制代码
是没有 apache 的 http 库的, 可是 他们有一个共同特色,就是系统内置了 apache 包, 在 /system/framework/
目录下:
,可是我有一个困惑的地方,就是 一样 是运行在 android P 设备上 和 运行在 低版本上(>M) DexPathList
值确不同:
android P
:PathClassLoader// 这是httpClient的 ClassLoader
DexPathList[[zip file "/system/framework/org.apache.http.legacy.boot.jar",
zip file "/data/app/com.example.leixiang.demoapp-hOOUC7E0LuRvgmYC38vd5w==/base.apk"
],nativeLibraryDirectories=[/data/app/com.example.leixiang.demoapp-hOOUC7E0LuRvgmYC38vd5w==/lib/x86_64, /system/lib64]]
复制代码
android N
:dalvik.system.PathClassLoader //这是httpClient的 ClassLoader
[DexPathList[[zip file "/data/app/com.example.leixiang.demoapp-1/base.apk"
],nativeLibraryDirectories=[/data/app/com.example.leixiang.demoapp-1/lib/arm64, /system/lib64, /vendor/lib64]]]
复制代码
他们不一样之处在于,android N 设备上的 DexPathList
里面多了 apache
的包,可是他们的加载器却都仍是 PathClassLoader
,我想多是 P 系统上再也不预先加载 apache http 相关类,因此把他加入 DexPathList
? , 而且 P 以前的系统加载 bootclasspath 中类也是用的 PathClassLoader
?这个有待研究。
Android 9 引入了一项新的电池管理功能,即应用待机群组。 应用待机群组能够基于应用最近使用时间和使用频率,帮助系统排定应用请求资源的优先级。 根据使用模式,每一个应用都会归类到 五个 优先级群组之一中。 系统将根据应用所属的群组限制每一个应用能够访问的设备资源:
若是用户当前正在使用应用,应用将被归到“活跃”群组中,例如:
FCM是指google推送啦,国内不要想了,至于长链接和心跳包是否会限制要看国内厂商具体操做了。
若是应用常常运行,但当前未处于活跃状态,它将被归到“工做集”群组中。 例如,用户在大部分时间都启动的某个社交媒体应用可能就属于“工做集”群组。 若是应用被间接使用,它们也会被升级到“工做集”群组中 。
若是应用会按期使用,但不是天天都必须使用,它将被归到“经常使用”群组中。 例如,用户在健身房运行的某个锻炼跟踪应用可能就属于“经常使用”群组。
若是应用不常用,那么它属于“极少使用”群组。 例如,用户仅在入住酒店期间运行的酒店应用就可能属于“极少使用”群组。
安装可是从未运行过的应用会被归到“从未使用”群组中。 系统会对这些应用施加极强的限制。
咱们能够利用 UsageStatsManager.getAppStandbyBucket() 查看咱们处于哪个分组,此 api 是 21 添加。
不过用户能够经过配置 低电耗 白名单来摆脱分组的限制,具体配置方法看这里。
如下是各分组对应的活动限制:
Setting | Jobs * | Alarms | Network | Firebase Cloud Messaging § |
---|---|---|---|---|
User Restricts Background Activity | ||||
Restrictions enabled: | Never | Never | Never | No restriction |
Doze | ||||
Doze active: | Deferred to window | Regular alarms: Deferred to window While-idle alarms: Deferred up to 9 minutes | Deferred to window | High priority: No restriction Normal priority: Deferred to window |
App Standby Buckets (by bucket) | ||||
Active: | No restriction | No restriction | No restriction | No restriction |
Working set: | Deferred up to 2 hours | Deferred up to 6 minute | No restriction | No restriction |
Frequent: | Deferred up to 8 hours | Deferred up to 30 minutes | No restriction | High priority: 10/day |
Rare: | Deferred up to 24 hours | Deferred up to 2 hours | Deferred up to 24 hours | High priority: 5/day |
咱们能够经过 adb命令 让咱们的调试设备处于特定分组来测试相关的行为。
$ adb shell am set-standby-bucket packagename active|working_set|frequent|rare
复制代码
前台服务 可让你应用处于活跃状态,上面提到过 前台服务 可让你的应用分组处于 活跃分组。
Target 28+ 并使用前台服务的应用必须请求 FOREGROUND_SERVICE
权限。 这是 普通权限,所以,系统会自动为请求权限的应用授予此权限。
在 Android 9 中,Build.SERIAL
始终设置为 "UNKNOWN"
以保护用户的隐私。
若是您的应用须要访问设备的硬件序列号,您应改成请求 READ_PHONE_STATE
权限,而后调用 getSerial()。
android P SDK
api:
/**
* A hardware serial number, if available. Alphanumeric only, case-insensitive.
* For apps targeting SDK higher than {@link Build.VERSION_CODES#O_MR1} this
* field is set to {@link Build#UNKNOWN}.
*
* @deprecated Use {@link #getSerial()} instead.
**/
@Deprecated
// IMPORTANT: This field should be initialized via a function call to
// prevent its value being inlined in the app during compilation because
// we will later set it to the value based on the app's target SDK. public static final String SERIAL = getString("no.such.thing"); 复制代码
以 Android 9 为目标平台的应用应采用私有 DNS API
。 具体而言,当系统解析程序正在执行 DNS-over-TLS
时,应用应确保任何内置 DNS 客户端均使用加密的 DNS 查找与系统相同的主机名,或停用它而改用系统解析程序。 停用路径: Settings -> private dns
:
若是您的应用 Target 28+,则默认状况下 isCleartextTrafficPermitted() 函数返回 false。 若是您的应用须要为特定域名启用明文,您必须在应用的网络安全性配置中针对这些域名将 cleartextTrafficPermitted
显式设置为 true
。
否定会出现如下日志错误输出:
W/Glide: Load failed for http://xxx-99billxx.ufile.ucloud.cn/online/image/A1707101417703-349-0xxhdpi
java.io.IOException: Cleartext HTTP traffic to xxx-99billxx.ufile.ucloud.cn not permitted
复制代码
注意:该api是 api 23 才引入的。 因此,因为一些历史缘由没法及时把服务器变动为 https 的应用,应该经过配置文件针对特定域名容许使用明文传输,也就是 http 服务。
定义配置文件 res/xml/network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">secure.example.com</domain>
</domain-config>
</network-security-config>
复制代码
而后在manifest.xml 中申明
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
<application android:networkSecurityConfig="@xml/network_security_config"
... >
...
</application>
</manifest>
复制代码
为改善 Android 9 中的应用稳定性和数据完整性,应用没法再让多个进程共用同一 WebView 数据目录。 此类数据目录通常存储 Cookie、HTTP 缓存以及其余与网络浏览有关的持久性和临时性存储
若是您的应用必须在多个进程中使用 WebView 的实例,则必须先利用 WebView.setDataDirectorySuffix() 函数为每一个进程指定惟一的数据目录后缀,而后再在该进程中使用 WebView 的给定实例。 该函数会将每一个进程的网络数据放入其在应用数据目录内本身的目录中。
注:即便您使用 setDataDirectorySuffix(),系统也不会跨应用的进程界限共享 Cookie 以及其余网络数据。 若是应用中的多个进程须要访问同一网络数据,您须要自行在这些进程之间复制数据。 例如,您能够调用 getCookie() 和 setCookie(),在不一样进程之间手动传输 Cookie 数
改进了 dexer 以及协调Jetbranins改进 Kotlin Compier 使用 kotlin 编写的App在android P 上运行得更快,而且和 Jetbrains 团队沟通,提供了新的 kotlin 插件。
替代 BitmapFactory 能够从 流、file、byte buffer、 uRL 加载 Bitmap 和 Drawable 支持精确尺寸缩放, 而且支持加载 gif 、 Webp, 以及圆角等样式设置。
在室内,IEEE 802.11 MC WI-FI protocol 测量与附近wifi 连接点的距离(2~3个),经过 RTT,来测量距离, 能精确到 1~2米 在提供硬件支持的 Android P 设备上,应用可使用全新的 RTT API 来测量与附近支持 RTT 的 Wi-Fi 接入点 (AP) 的距离,设备不须要链接至 AP 便可使用 RTT
使用uses-feature来标注:
<uses-feature android:name="android.hardware.wifi.rtt" />
复制代码
显示文本可能很复杂,包含多种字体,行间距,字母间距,文本方向,换行符,连字符等功能。TextView必须作不少工做来测量和布置给定的文本:读取字体文件,查找字形,肯定形状,测量边界框以及在内部字缓存中缓存单词。更重要的是,全部这些工做都发生在 UI线程 上,它可能会致使您的应用程序 丢帧 测量文本可能占用设置文本所需时间的 90%
android P 正式引入, 对于 android P 以前经过 Jetpack 的PrecomputedTextCompat使用.
// UI thread
val params: PrecomputedText.Params = textView.getTextMetricsParams()
val ref = WeakReference(textView)
executor.execute {
// background thread
val text = PrecomputedText.create("Hello", params)
val textView = ref.get()
textView?.post {
// UI thread
val textViewRef = ref.get()
textViewRef?.text = text
}
}
复制代码
android P 引入了文本放大镜,以改善用户选择文本的体验。放大镜经过能够在文本上拖动的窗格查看放大文本,帮助用户精肯定位光标或文本选择手柄,只须要覆写视图的 **OnTouchEvent()**方法:
fun onTouchEvent(event:MotionEvent):Boolean {
when(event.actionMasked){
MotionEvent.ACTION_DOWN - >
magnifier.show(event.x,event.y)
MotionEvent.ACTION_MOVE - >
magnifier.show(event.x,event.y)
MotionEvent.ACTION_UP - >
magnifier.dismiss()
}
}
复制代码
可是惋惜的是:目前低版本设备(<P)没有相关兼容类使用。
在运行 Android 9 或更高版本的设备上,Android 运行时 (ART) 提早编译器经过将应用软件包中的 DEX 文件转换为更紧凑的表示形式,进一步优化了压缩的 Dalvik Executable 格式 (DEX) 文件。 此项变动可以让您的应用启动更快并消耗更少的磁盘空间和内存。
这种改进特别有利于磁盘 I/O 速度较慢的低端设备。