63

Android 9.0 适配指南

 4 years ago
source link: https://www.tuicool.com/articles/3UzUJfv
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

又到了我一年一度写Android适配文章的时间,本身这篇应该会早几个月发出来,但是前两三个月主要忙于Flutter的项目,所以这篇文章才姗姗来迟。不过毕竟是9.0的适配,还不算太晚哈!

1.前言

国内从去年开始就有消息说,应用上架或者更新要求TargetSdkVersion最低要为26以上,也就是最低也要适配到8.0。今年来也都逐步地开始落实。比如下图的 小米应用商店公告

eE3MRzB.png!web 当然 Google Play的要求

更为严格:

yQryMrA.png!web

还包括从8月份开始在Google Play上发布的应用必须支持64位架构。可以看到适配工作真的不能像以前一样随心所欲了。好在我之前也有写过相关的适配攻略,Android适配系列:

2.准备工作

进入正题,首先将我们项目中的 targetSdkVersion 改为 28。接下来运行你的项目,看有没中枪。

3.网络

1.Http请求失败

在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 文件:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>
复制代码

AndroidManifest.xml 中的 application 添加:

<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
    <application android:networkSecurityConfig="@xml/network_security_config">
            ...
    </application>
</manifest>
复制代码

以上这是一种简单粗暴的配置方法,要么支持http,要么不支持http。为了安全灵活,我们可以指定支持的http域名:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
	<!-- Android 9.0 上部分域名时使用 http -->
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">secure.example.com</domain>
        <domain includeSubdomains="true">cdn.example1.com</domain>
    </domain-config>
</network-security-config>
复制代码

当然不止这些配置,还有抓包配置、设置自定义CA以及各种场景下灵活的配置,详细的方法可以查看 官方文档

2.Apache HTTP 客户端弃用

在 Android 6.0 时,就已经取消了对 Apache HTTP 客户端的支持。 从 Android 9.0 开始,默认情况下该库已从 bootclasspath 中移除。但是耐不住有些SDK中还在使用,比如我见到的 友盟QQ分享报错问题

所以要想继续使用 Apache HTTP ,需要在应用的 AndroidManifest.xml 文件中添加:

<uses-library android:name="org.apache.http.legacy" android:required="false"/>
复制代码

4.前台服务

可以试着搜索一下你的代码,看是否有调用 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" />
复制代码

5.启动Activity

在9.0 中,不能直接非 Activity 环境中(比如 ServiceApplication )启动 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);
复制代码

6.异形屏适配

这类异形屏叫法很多,刘海屏、水滴屏、挖孔屏、美人尖。。。

  1. 其实如果你的页面不需要全屏显示,那么不需要额外的适配工作。

  2. 如果页面是全屏显示(比如启动页)。为了防止你的内容被遮挡,大部分场景下都是可以使用获取状态栏高度来处理遮挡的适配问题。因为状态栏的高度都是大于等于刘海的高度。

当然,如果你想利用起来刘海区域,就需要获取刘海位置等信息进行适配。在Android 9.0中官方提供了 DisplayCutout 类,可以确定刘海区域的位置,国内的部分厂商在8.0就有了自己的适配方案。

具体的我就不过多介绍了,推荐大家看以下文章:

7.权限

首先是权限组的变更:

7FvqMbV.png!webjmeMFvY.png!web

上图可以看到,在9.0 中新增权限组 CALL_LOG 并将 READ_CALL_LOGWRITE_CALL_LOGPROCESS_OUTGOING_CALLS 权限从 PHONE 中移入该组。

1.限制访问通话记录

如果应用需要访问通话记录或者需要处理去电,则您必须向 CALL_LOG 权限组明确请求这些权限。 否则会发生 SecurityException

2.限制访问电话号码

  • 要通过 PHONE_STATE Intent 操作读取电话号码,同时需要 READ_CALL_LOG 权限和 READ_PHONE_STATE 权限。
  • 要从 PhoneStateListener的onCallStateChanged() 中读取电话号码,只需要 READ_CALL_LOG 权限。 不需要 READ_PHONE_STATE 权限。

8.其他

  • 在 Android 9 中,调用 Build.SERIAL 会始终返回 UNKNOWN 以保护用户的隐私。如果你的应用需要访问设备的硬件序列号,那么需要先请求 READ_PHONE_STATE 权限,然后调用 Build.getSerial()

  • 注意非 SDK 接口的限制。主要是一些热修复、插件化框架涉及比较多,注意及时升级新版本。

总的来说,9.0的适配工作需要改动和注意的点相比较以前版本的适配来说并不多,从本篇的篇幅就可以看出来,详细的变化可以参看文末的链接。后面如果遇到什么坑,我也会及时补充进来。感谢你的阅读!!

参考


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK