又到了我一年一度寫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
環境中(比如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);
6.異形屏適配
這類異形屏叫法很多,劉海屏、水滴屏、挖孔屏、美人尖。。。
-
其實如果你的頁面不需要全屏顯示,那麼不需要額外的適配工作。
-
如果頁面是全屏顯示(比如啓動頁)。爲了防止你的內容被遮擋,大部分場景下都是可以使用獲取狀態欄高度來處理遮擋的適配問題。因爲狀態欄的高度都是大於等於劉海的高度。
當然,如果你想利用起來劉海區域,就需要獲取劉海位置等信息進行適配。在Android 9.0中官方提供了DisplayCutout
類,可以確定劉海區域的位置,國內的部分廠商在8.0就有了自己的適配方案。
具體的我就不過多介紹了,推薦大家看以下文章:
7.權限
首先是權限組的變更:
上圖可以看到,在9.0 中新增權限組CALL_LOG
並將 READ_CALL_LOG
、WRITE_CALL_LOG
和 PROCESS_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的適配工作需要改動和注意的點相比較以前版本的適配來說並不多,從本篇的篇幅就可以看出來,詳細的變化可以參看文末的鏈接。後面如果遇到什麼坑,我也會及時補充進來。感謝你的閱讀!!