又到了我一年一度写Android适配文章的时间,本身这篇应该会早几个月发出来,但是前两三个月主要忙于Flutter的项目,所以这篇文章才姗姗来迟。不过毕竟是9.0的适配,还不算太晚哈!
国内从去年开始就有消息说,应用上架或者更新要求TargetSdkVersion最低要为26以上,也就是最低也要适配到8.0。今年来也都逐步地开始落实。比如下图的小米应用商店公告:
当然Google Play的要求更为严格:
还包括从8月份开始在Google Play上发布的应用必须支持64位架构。可以看到适配工作真的不能像以前一样随心所欲了。好在我之前也有写过相关的适配攻略,Android适配系列:
Android 6.0 的动态权限管理
Android 7.0脱坑指南
Android 8.0适配指北
进入正题,首先将我们项目中的targetSdkVersion
改为 28。接下来运行你的项目,看有没中枪。
在9.0中默认情况下启用网络传输层安全协议 (TLS),默认情况下已停用明文支持。也就是不允许使用http请求,要求使用https。
比如我使用的是okhttp,会报错:
java.net.UnknownServiceException: CLEARTEXT communication to xxxx not permitted by network security policy
解决方法是需要我们添加网络安全配置。首先在 res
目录下新建xml
文件夹,添加network_security_config.xml
文件:
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
network-security-config>
AndroidManifest.xml
中的application
添加:
<manifest ... >
<application android:networkSecurityConfig="@xml/network_security_config">
...
application>
manifest>
以上这是一种简单粗暴的配置方法,要么支持http,要么不支持http。为了安全灵活,我们可以指定支持的http域名:
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">secure.example.comdomain>
<domain includeSubdomains="true">cdn.example1.comdomain>
domain-config>
network-security-config>
当然不止这些配置,还有抓包配置、设置自定义CA以及各种场景下灵活的配置,详细的方法可以查看官方文档。
在 Android 6.0 时,就已经取消了对 Apache HTTP
客户端的支持。 从 Android 9.0 开始,默认情况下该库已从 bootclasspath
中移除。但是耐不住有些SDK中还在使用,比如我见到的友盟QQ分享报错问题。
所以要想继续使用Apache HTTP
,需要在应用的 AndroidManifest.xml 文件中添加:
<manifest ... >
<application>
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
...
application>
manifest>
可以试着搜索一下你的代码,看是否有调用startForegroundService
方法来启动一个前台服务。
startForegroundService
主要来源估计都是8.0适配时候加上的:
Intent intentService = new Intent(this, MyService.class);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
startForegroundService(intentService);
} else {
startService(intentService);
}
9.0 要求创建一个前台服务需要请求 FOREGROUND_SERVICE
权限,否则系统会引发 SecurityException
。
java.lang.RuntimeException: Unable to start service com.weilu.test.MyService@81795be with Intent { cmp=com.weilu.test/.MyService }:
java.lang.SecurityException: Permission Denial: startForeground from pid=28631, uid=10626 requires android.permission.FOREGROUND_SERVICE
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3723)
at android.app.ActivityThread.access$1700(ActivityThread.java:201)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1705)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:6820)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:876)
解决方法就是AndroidManifest.xml
中添加FOREGROUND_SERVICE
权限:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
在9.0 中,不能直接非 Activity
环境中(比如Service
,Application
)启动 Activity
,否则会崩溃报错:
java.lang.RuntimeException: Unable to create service com.weilu.test.MyService: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
at android.app.ActivityThread.handleCreateService(ActivityThread.java:3578)
at android.app.ActivityThread.access$1400(ActivityThread.java:201)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1690)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:6820)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:876)
这类问题一般会在点击推送消息跳转页面这类场景,解决方法就是 Intent 中添加标志FLAG_ACTIVITY_NEW_TASK
,
Intent intent = new Intent(this, TestActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
这类异形屏叫法很多,刘海屏、水滴屏、挖孔屏、美人尖。。。
其实如果你的页面不需要全屏显示,那么不需要额外的适配工作。
如果页面是全屏显示(比如启动页)。为了防止你的内容被遮挡,大部分场景下都是可以使用获取状态栏高度来处理遮挡的适配问题。因为状态栏的高度都是大于等于刘海的高度。
当然,如果你想利用起来刘海区域,就需要获取刘海位置等信息进行适配。在Android 9.0中官方提供了DisplayCutout
类,可以确定刘海区域的位置,国内的部分厂商在8.0就有了自己的适配方案。
具体的我就不过多介绍了,推荐大家看以下文章:
Android P 刘海屏适配全攻略
Android刘海屏、水滴屏全面屏适配方案
上图可以看到,在9.0 中新增权限组CALL_LOG
并将 READ_CALL_LOG
、WRITE_CALL_LOG
和 PROCESS_OUTGOING_CALLS
权限从PHONE
中移入该组。
如果应用需要访问通话记录或者需要处理去电,则您必须向 CALL_LOG
权限组明确请求这些权限。 否则会发生 SecurityException
。
PHONE_STATE
Intent 操作读取电话号码,同时需要 READ_CALL_LOG
权限和 READ_PHONE_STATE
权限。PhoneStateListener的onCallStateChanged()
中读取电话号码,只需要 READ_CALL_LOG
权限。 不需要 READ_PHONE_STATE
权限。在 Android 9 中,调用Build.SERIAL
会始终返回 UNKNOWN
以保护用户的隐私。如果你的应用需要访问设备的硬件序列号,那么需要先请求 READ_PHONE_STATE
权限,然后调用 Build.getSerial()
。
注意非 SDK 接口的限制。主要是一些热修复、插件化框架涉及比较多,注意及时升级新版本。
多进程使用WebView
注意无法共用同一数据目录。 详细点击查看
总的来说,9.0的适配工作需要改动和注意的点相比较以前版本的适配来说并不多,从本篇的篇幅就可以看出来,详细的变化可以参看文末的链接。后面如果遇到什么坑,我也会及时补充进来。感谢你的阅读!!
Android 9.0 行为变更
targetSdkVersion升级28