Android 版本適配:9.0 Pie(API 級別 28)

版權聲明:本文爲博主原創文章,未經博主允許不得轉載
文章分類:Android知識體系 - 版本適配

一、前言

本文主要是從官方文檔中篩選出一些常見的適配項,若有任何紕漏或需要補充的,歡迎大家在評論區指出。

二、版本適配

1. 限制 HTTP 網絡請求

Android 9.0 中限制了 HTTP(明文傳輸)網絡請求,若仍繼續使用HTTP請求,則會在日誌中提示以下異常(只是無法正常發出請求,不會導致應用崩潰):

java.net.UnknownServiceException: CLEARTEXT communication to xxx not permitted by network security policy

適配的方法如下:

  • 在資源目錄中新建一個 xml 文件,例如 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
        ...
        android:networkSecurityConfig="@xml/network_security_config">
        ...
    </application>
    

2. 棄用 Apache HTTP Client

由於官方在 Android 9.0 中移除了所有 Apache HTTP Client 相關的類,因此我們的應用或是一些第三方庫如果使用了這些類,就會拋出找不到類的異常:

java.lang.NoClassDefFoundError: Failed resolution of: Lorg/apache/http/conn/scheme/SchemeRegistry;

若需要繼續使用 Apache HTTP Client ,可通過以下方法進行適配:

  • 在 AndroidManifest.xml 中添加以下內容:

    <uses-library android:name="org.apache.http.legacy" android:required="false"/>
    
  • 或者在應用中直接將 Apache HTTP Client 相關的類打包並進行引用

3. 限制非 SDK 接口的調用

3.1 簡述

一直以來,官方提供的接口分爲了 SDK 接口和非 SDK 接口。SDK 接口即官方支持的接口,開發者可以直接調用不會有任何限制。一般而言,SDK 接口都記錄在官方的接口索引中,沒有記錄的就視爲非 SDK 接口,例如一些使用了 @hide 標註的方法。

以往開發者對於非 SDK 接口的調用通常是利用反射或者JNI間接調用的方式進行,但這樣的調用方式如果處理不當會比較容易出現一些未知的錯誤。爲了提升用戶體驗和降低應用發生崩潰的風險,Android 9.0 對應用能使用的非 SDK 接口實施了限制,具體的限制手段請見下表:

此外,爲了開發者能夠順利過渡到 Android 9.0,官方對非 SDK 接口進行了分類,共分爲三類,light-greylist(淺灰名單)、dark-greylist(深灰名單)以及blacklist(黑名單):

  • light-greylist(淺灰名單):對於此名單中的非 SDK 接口,官方暫未找到可替代的 SDK 接口,因此開發者仍可繼續訪問(如果 targetSdkVersion 大於等於28時會出現警告)。
  • dark-greylist(深灰名單):targetSdkVersion 小於28時仍可繼續使用此名單中的接口,但會出現警告提示;大於等於28時,這些接口將會限制訪問。
  • blacklist(黑名單):無論 targetSdkVersion 爲多少,只要應用運行在 Android 9.0 平臺上,訪問此名單中的接口都會受限

3.2 如何測試應用是否使用非 SDK 接口

可以通過以下方式進行測試(詳情請至官方文檔):

  • 使用 Android 9.0 或更高版本的設備調試應用
  • 使用 StrictMode API 進行測試
  • 使用 veridex 工具對應用進行掃描

建議使用第三種方式,該工具的掃描結果會列出應用對於三個限制名單中的接口的調用細節。

4. 前臺服務權限

在 Android 9.0 中,應用在使用前臺服務之前必須先申請 FOREGROUND_SERVICE 權限,否則就會拋出 SecurityException 異常。

此外,由於 FOREGROUND_SERVICE 權限只是普通權限,因此開發者只需在 AndroidManifest.xml 中註冊此權限即可,系統會自動對此權限進行授權:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

5. 強制執行 FLAG_ACTIVITY_NEW_TASK 要求

在 Android 7.0(API 級別 24)之前,若開發者需要通過非 Activity context 啓動 Activity,就必須設置 Intent 標誌 FLAG_ACTIVITY_NEW_TASK,否則會啓動失敗並拋出以下異常

android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity  context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?

但這個要求在更新 Android 7.0 以後由於系統問題被臨時取消了,開發者即使不設置標誌也可以正常啓動 Activity。而在 Android 9.0 中官方修復了這個問題,這個要求重新開始強制執行,因此開發者在適配 Android 9.0 時需要注意這個問題。

6. 不允許共享 WebView 數據目錄

Android 9.0 中爲了改善應用穩定性和數據完整性,應用無法再讓多個進程共用同一 WebView 數據目錄。此類數據目錄一般存儲 Cookie、HTTP 緩存以及其他與網絡瀏覽有關的持久性和臨時性存儲。

如果開發者需要在多進程中使用 WebView,則必須先調用 WebView.setDataDirectorySuffix() 方法爲每個進程設置用於存儲 WebView 數據的目錄。若多進程 WebView 之間需要共享數據,開發者需自己通過 IPC 的方式實現。

此外,若開發者只想在一個進程中使用 WebView,並且希望嚴格執行這個規則,可以通過在其他進程中調用 WebView.disableWebView() 方法,這樣其他進程創建 WebView 實例就會拋出異常。

7. 其他 API 方面的修改

7.1 Region.Op 相關

Android 9.0 中如果在使用繪圖裁剪功能時設置了除 Region.Op.INTERSECTRegion.Op.DIFFERENCE 以外的類型,就會拋出以下異常:

 java.lang.IllegalArgumentException: Invalid Region.Op - only INTERSECT and DIFFERENCE are allowed

具體原因是官方廢棄了那幾個具有 Region.Op 參數的裁剪方法,如 clipRect(@NonNull RectF rect, @NonNull Region.Op op)

/**
 * Modify the current clip with the specified rectangle.
 *
 * @param rect The rect to intersect with the current clip
 * @param op How the clip is modified
 * @return true if the resulting clip is non-empty
 *
 * @deprecated Region.Op values other than {@link Region.Op#INTERSECT} and
 * {@link Region.Op#DIFFERENCE} have the ability to expand the clip. The canvas clipping APIs
 * are intended to only expand the clip as a result of a restore operation. This enables a view
 * parent to clip a canvas to clearly define the maximal drawing area of its children. The
 * recommended alternative calls are {@link #clipRect(RectF)} and {@link #clipOutRect(RectF)};
 *
 * As of API Level API level {@value Build.VERSION_CODES#P} only {@link Region.Op#INTERSECT} and
 * {@link Region.Op#DIFFERENCE} are valid Region.Op parameters.
 */
@Deprecated
public boolean clipRect(@NonNull RectF rect, @NonNull Region.Op op) {
    checkValidClipOp(op);
    return nClipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
            op.nativeInt);
}

private static void checkValidClipOp(@NonNull Region.Op op) {
    if (sCompatiblityVersion >= Build.VERSION_CODES.P
            && op != Region.Op.INTERSECT && op != Region.Op.DIFFERENCE) {
        throw new IllegalArgumentException(
                "Invalid Region.Op - only INTERSECT and DIFFERENCE are allowed");
    }
}

對於這個問題,可以通過以下方法進行適配:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    canvas.clipPath(path);
} else {
    canvas.clipPath(path, Region.Op.XOR);// REPLACE、UNION 等類型
}

7.2 Build.SERIAL 被棄用

Android 9.0 之前,開發者可以使用 Build.SERIAL 獲取設備的序列號。現在這個方法被棄用了,Build.SERIAL 將始終設置爲 “UNKNOWN” 以保護用戶的隱私。

適配的方法爲先請求 READ_PHONE_STATE 權限,然後調用 Build.getSerial() 方法。

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