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

Anlia發表於2019-04-21
版權宣告:本文為博主原創文章,未經博主允許不得轉載
文章分類: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 Pie(API 級別 28)

此外,為了開發者能夠順利過渡到 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() 方法。

相關文章