Android 9.0 適配指南

又到了我一年一度寫Android適配文章的時間,本身這篇應該會早幾個月發出來,但是前兩三個月主要忙於Flutter的項目,所以這篇文章才姍姍來遲。不過畢竟是9.0的適配,還不算太晚哈!

1.前言

國內從去年開始就有消息說,應用上架或者更新要求TargetSdkVersion最低要爲26以上,也就是最低也要適配到8.0。今年來也都逐步地開始落實。比如下圖的小米應用商店公告
在這裏插入圖片描述
當然Google Play的要求更爲嚴格:
在這裏插入圖片描述

還包括從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.權限

首先是權限組的變更:

上圖可以看到,在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的適配工作需要改動和注意的點相比較以前版本的適配來說並不多,從本篇的篇幅就可以看出來,詳細的變化可以參看文末的鏈接。後面如果遇到什麼坑,我也會及時補充進來。感謝你的閱讀!!

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章