Android 9 添加了对 IEEE 802.11mc Wi-Fi 协议(也称为 Wi-Fi Round-Trip-Time (RTT))的平台支持,从而支持室内定位功能。
https://developer.android.com/about/versions/pie/android-9.0#rtt
Android 9 增加了对刘海屏的适配支持,在管理员功能->绘制(drawing)->刘海(cutout)设置里可以调整刘海样式,用以测试
窗口属性layoutInDisplayCutoutMode设置页面对刘海屏的支持模式:
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT:默认模式,页面非全屏模式,刘海区域正常展示(状态栏颜色等);页面全屏模式,不使用刘海区域,内容区域移动以避开刘海区域
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER:页面无论是否全屏模式,刘海区域都不使用(避开)
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES:页面无论是否全屏模式,刘海区域都可以使用,但需要页面使用SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN(沉浸式布局),才可以使layout从刘海区域开始布局
设置代码示例:
//页面全屏
requestWindowFeature(Window.FEATURE_NO_TITLE)
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
//沉浸式布局
val decorView = window.decorView
var systemUiVisibility = decorView.systemUiVisibility
val flags = (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_FULLSCREEN)
systemUiVisibility = systemUiVisibility or flags
window.decorView.systemUiVisibility = systemUiVisibility
//页面刘海使用模式
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
this.window.attributes = this.window.attributes.apply {
layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
}
}
有时我们内容扩展到了刘海区域,但是也要适当的避开刘海区域,就需要知道刘海具体的区域了,Android 9 提供了相应的API:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val displayCutout = window.decorView.rootWindowInsets?.displayCutout
if (displayCutout == null) {//为空则没有刘海区域
...
} else {
//每个刘海区域的位置(Rect对象)
val cutouts:List<Rect> = displayCutout.boundingRects
//避开所有刘海的安全区域
val safeRect = Rect(displayCutout.safeInsetLeft,displayCutout.safeInsetTop,displayCutout.safeInsetRight,SCREEN_HEIGHT - displayCutout.safeInsetBottom)
}
}
DisplayCutout对象:设备刘海信息对象
DisplayCutout.boundingRects:获取所有刘海区域的区域,每个Rect表示一个刘海区域相对于屏幕的ltrb值
DisplayCutout.safeInsetLeft/T/R/B方法:获取屏幕中间-避开所有刘海区域的最大区域,可以在这个区域内显示不想被刘海遮挡内容,如下图:
注意:
这个区域只是避开了刘海区域,不一定避开了状态栏,因为官方规定是刘海高度小于状态高度,所以可能会造成状态栏遮挡部分内容,需要做适配
当切换为横屏时,原有的顶部刘海会到左边,相应的safeTop变为safeLeft,其他值同理;但是在onConfigurationChanged()方法内部,无法及时获取最新的safeLTRB,很尴尬。。。
不进行适配,都默认采用LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT模式也可以,只不过对于全屏模式的页面,UI效果不好
进行适配,可以将全屏模式页面设置为LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES模式,然后利用沉浸式布局,将内容绘制在刘海区域,不过要注意重要内容不要被遮挡,如果遮挡,可以用刘海相关API算出刘海区域,将内容避开刘海区域展示
最佳实践:https://developer.android.com/guide/topics/display-cutout/
Android 9 提供ImageDecoder类来代替BitmapFactory和BitmapFactory.Options类,可以通过API实现图片的加载、裁剪、圆角、包括Gif图等处理,对于Gif图(AnimatedImageDrawable)的处理会应用到每一帧
val source = ImageDecoder.createSource(file/byteArray...)
val drawable = ImageDecoder.decodeDrawable(source,{decoder,_,_->
decoder.setTargetSampleSize(2)//设置裁剪粒度
//图层绘制
decoder.setPostProcessor{canvas->
...
}
})
https://developer.android.com/about/versions/pie/android-9.0#decoding-images
Android 9 引入了AnimatedImageDrawable类,用于绘制和显示 GIF 和 WebP 动画图像
@Throws(IOException::class)
private fun decodeImage() {
val decodedAnimation = ImageDecoder.decodeDrawable(
ImageDecoder.createSource(resources, R.drawable.my_drawable))
//start()后,第一帧开始播放
(decodedAnimation as? AnimatedImageDrawable)?.start()
}
https://developer.android.com/about/versions/pie/android-9.0#animation
1.1后台访问限制
应用处在后台时,不可访问麦克风、摄像头、传感器
如需使用,可以使用前台服务
1.2通话记录访问限制
READ_CALL_LOG、WRITE_CALL_LOG、PROCESS_OUTGOING_CALLS移到了新的CALL_LOGS权限组
适配时,解决好权限(组)适配即可,参照6.0权限适配Android 动态权限机制
通过intent读取电话号时,需要READ_CALL_LOG和READ_PHONE_STATE权限
通过PhoneStateListener获取电话号时,仅需要READ_CALL_LOG权限
1.3Wifi访问限制
WifiManager的getConnectionInfo()方法的调用,需要ACCESS_WIFI_STATE权限;Android 9 开始,如果要通过返回的WifiInfo对象,获取BSSID和SSID的值,还需要满足:
app有ACCESS_FINE_LOCATION或者ACCESS_COARSE_LOCATION权限
开启设备定位服务
不满足任意一条则得不到正确的id:BSSID:02:00:00:00:00:00 SSID:< unknown ssid>
NETWORK_STATE_CHANGED_ACTION的广播,不再包含SSID、BSSID、连接信息等数据,需要的话使用WifiManager的getConnectionInfo()
1.4电话信息访问限制
Android 9 开始设备定位服务关闭时,TelephonyManager的这些方法不返回结果:
getAllCellInfo()、listen()、getCellLocation()、getNeighboringCellInfo()
对非SDK方法的调用,无论是通过直接引用、反射引用、JNI引用,都有以下限制:
浅灰名单中的接口,仍可以调用,debug环境给予logcat警告
深灰名单中的接口,如果target为28,则与黑名单行为一致;target小于28,仍可以调用,并会在debug环境给予logcat警告
使用黑名单中的SDK接口,无论target为多少,会有如下异常(崩溃)行为
https://developer.android.com/about/versions/pie/restrictions-non-sdk-interfaces#results-of-keeping-non-sdk
经测试,发现部分深灰名单的部分接口并没有异常,比如SystemProperties.getXxx()相关方法
静态分析工具veridex,可以检测apk中的非SDK接口违规调用
运行时检测,可以通过观察logcat来发现:Accessing hidden field|method …
veridex和各种名单: https://android.googlesource.com/platform/prebuilts/runtime/+/master/appcompat
百度网盘下载地址:https://pan.baidu.com/s/1NojiqPjvJMzJ_AuHd5jukQ?errno=0&errmsg=Auth Login Sucess&&bduss=&ssnerror=0&traceid=
适配时,要确保没有调用黑名单的接口,如果target升为28,那么也要确保没有调用深灰名单的接口,其余的仍可以调用,不过为了向前兼容,能改的最好改了
AES、DESEDE、OAEP、EC和https://www.bouncycastle.org/里的算法参数在Android 9 被废弃,target小于28时给予警告,大于等于28时时抛出NoSuchAlgorithmException
Crypto-Java提供程序Android 9 平台已移除,调用SecureRandom.getInstance(“SHA1PRNG”, “Crypto”),将会发生 NoSuchProviderException
在Android 9 或更高版本中,解码修改后的UTF-8/CESU-8序列,请使用DataInputStream.readUTF()函数或NewStringUTF()-JNI函数
Android 9 开始,不允许访问/proc/net/xt_qtaguid文件夹下的文件,与使有没有该文件夹的设备保持一致
依赖这些文件的TrafficStats 和 NetworkStatsManager API继续工作,其他API不受支持,在不同设备上行为可能不一致
Android 9 开始,从非Activity环境启动Activity需要FLAG_ACTIVITY_NEW_TASK的flag,否则不会启动,并在系统中输出日志
Android 9 之前是强制执行,7.0之前如果没有flag则抛出异常,7.0和8.0不传也能启动,是一个系统bug,Android 9 修复了
Android 9 之前,可以通过通知栏面板或者设置页面,设置屏幕旋转模式:自动旋转模式、纵向模式
Android 9 开始,将纵向模式改为:旋转锁定模式;自动旋转模式行为没有改变
旋转锁定模式时,用户可以通过底部导航栏的旋转提示按钮,将当前页面旋转为其可以旋转(screenOrientation设置)的方向,而不是永远固定为纵向
sensor(传感器)相关模式和固定方向(如portrait)模式,都会忽略旋转锁定模式:sensor可以任意旋转、固定则不能旋转
Android 9 可以通过调用CameraManager.getCameraIdList()方法,检查每个可用摄像头,而不是假设只有一个
App应该合理的决定,向用户展示哪些摄像头
target为28时,创建前台服务Service.startForeground(),需要有FOREGROUND_SERVICE权限;不加该权限则SecurityException崩溃
FOREGROUND_SERVICE权限为普通静态权限,在Manifest里声明即可
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
target为28时,Build.SERIAL会返回UNKNOWN,可以在申请READ_PHONE_STATE权限后,调用Build.getSerial()获取
target为28时,NetworkSecurityPolicy.isCleartextTrafficPermitted()方法默认返回false,如需明文要在网络安全性配置中显示声明为true
target为28时,多个进程不能共享一个Webview数据目录(存放cookie、缓存等持久性数据),需要通过调用WebView.setDataDirectorySuffix()方法为不同进程的WebView设置单独的数据目录
如需多个进程想访问同一份数据,可以手动在进程间复制数据,如cookie
6.0之前,Apache Http客户端作为标准SDK的一部分,放在bootclasspath中,开发者可以直接使用
6.0开始,标准SDK不支持Apache Http客户端,从bootclasspath移除,放入系统的可选库中,需要在gradle中使用useLibrary来添加可选库使用
android {
compileSdkVersion 28
useLibrary 'org.apache.http.legacy'
...
}
注意:useLibrary使用同时,最好按照规范,在manifest里配置uses-library,如3所说
Android 9 开始,如果target为28,若想继续使用Apache Http客户端,除了gradle的useLibrary声明外,还必须要在manifest中声明(此项target小于28时,不声明也可):
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
required为true:设备系统可选库必须含有此类库,否则PackageManager不允许安装apk;
required为false:系统可以安装apk,不保证有此类库;此时需要开发者进行类库存在性保证,如使用反射方式
minSdkVersion < 23时,required必须为false,因为6.0以前虽然是没有类库的,但是bootclasspath中有相关类,可以使用Apache Http相关类,如果为true的话6.0以前就安装不了了
target为28时,0面积View不能再被聚焦(获取焦点)
target为28时,Activity触摸模式下,不再分配初始焦点,需要手动请求requestFocus()
channel的改动是在8.0系统的,不过android在8.1系统上又有改动,由于很多app都是从targetApi=26直接升到targetApi28,所以可能会直接引发27(8.1)的改动带来的问题,所以channel问题在这里需要被关注一下。
具体问题解析可以参照这篇文章:Android 8.0/8.1channel适配
8.0开始,要求每个notification需要有channel,且channel应该先创建好,否则没有channel时,notification不会显示,并在debug时会弹toast警告,release时忽略(会报系统logcat)
Android 8.1 开始(target27),如果没有已经存在的channel,则会抛异常(target>=27时直接崩溃);当target是26时,则会在系统进程log-error
适配时,target>=27时,需要检查所有的notification要有channel设置,且channel必须先创建;target<27时,在8.0及以上的设备,也应该这样做
和上一点一样,也是由于8.1的改动,导致了可能直到9.0适配才会发现的问题。
具体问题解析可以参照这篇文章:Android 8.0/8.1screenOrientation适配