歡迎光臨
每天分享高質量文章

Android Q (10) 適配指南  讓你少走一堆彎路

作者:吃貓貓的魚

鏈接:https://juejin.im/post/5cad5b7ce51d456e5a0728b0

導讀

Android Q Beta 1剛出,講道理國內是不到下半年不用理睬Q的,但是上月末的一封華為要求適配Q的郵件要求我們在5月底之前完成相關適配,不然應用會被下架。

一開始還心生奇怪,為什麼這次華為的郵件來的那麼早以及嚴格。當我仔細閱讀了官方文件之後發現Q的更新特別多,且不適配應用可能無法正常運行(不管targetSDK是否為Q)

 

國內相關的文章還比較少,本文將總結歸納AndroidQ官方文件並將自己所踩過的坑記錄下來,以便大家少走彎路。

本文將從三個角度介紹Android Q的部分適配問題,也是大家開發適配過程中大概率會遇到的問題:

  • Q 行為變更:所有應用 (不管targetSdk是多少,對所有跑在Q設備上的應用均有影響)

  • Q 行為變更:以 Android Q 為標的平臺的應用(targetSDK == Q 才有影響)

  • 專案升級遇到的問題

至於Q的新功能及SDK,我粗略掃了一眼,專案中並沒有涉及,故暫不介紹,只放出鏈接AndroidQ新API及功能。

https://developer.android.com/preview/features

Q 行為變更:所有應用

用戶隱私權限變更

 

AndroidQ引入了大量更改和限制以增強對用戶隱私的保護。

官方文件將這一部分內容獨立於Q 行為變更:所有應用來介紹,是因為這一部分內容龐大且重要,個人認為Q的最大更新就是用戶隱私權限變更。具體變更的權限如下:

權限 受影響應用 如何啟用(影響範圍)
儲存權限 訪問和共享外部儲存設備中的檔案的應用 adb shell sm set-isolated-storage on(下文詳述)
定位權限 在後臺時請求訪問用戶位置信息的應用 這種權限策略在 Android Q 上始終處於啟用狀態
從後臺啟動 Activity 不需要用戶互動就啟動 Activity 的應用 關閉允許系統執行後臺活動開發者選項即可啟用限制
設備識別符號(deviceId) 訪問設備序列號或 IMEI 的應用 在搭載 Android Q 的設備上安裝應用
無線掃描權限 使用 WLAN API 和 Bluetooth API 的應用 以 Android Q 為標的平臺

因為從後臺啟動Activity權限和無線掃描權限兩種權限的變更影響較少。本文不作詳述,如有涉及請查閱官方文件。

https://developer.android.com/preview/privacy/background-activity-starts

從後臺啟動Activity權限變更僅針對與用戶毫無交互就啟動一個Activity的情況,(比如微信登陸授權)

以下會著重介紹儲存權限,定位權限和設備識別符號三種權限的變更與適配。

 

儲存權限

 

Android Q 在外部儲存設備中為每個應用提供了一個“隔離儲存沙盒”(例如 /sdcard)。任何其他應用都無法直接訪問您應用的沙盒檔案。由於檔案是您應用的私有檔案,因此您不再需要任何權限即可在外部儲存設備中訪問和儲存自己的檔案。此變更可讓您更輕鬆地保證用戶檔案的隱私性,並有助於減少應用所需的權限數量。

 

沙盒,簡單而言就是應用專屬檔案夾,並且訪問這個檔案夾無需權限。谷歌官方推薦應用在沙盒記憶體儲檔案的地址為Context.getExternalFilesDir()下的檔案夾。比如要儲存一張圖片,則應放在Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)中。

以下將按訪問的標的檔案的地址介紹如何適配。

 

1、訪問自己檔案:Q中用更精細的媒體特定權限替換並取消了 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE權限,並且無需特定權限,應用即可訪問自己沙盒中的檔案。

 

2、訪問系統媒體檔案:Q中引入了一個新定義媒體檔案的共享集合,如果要訪問沙盒外的媒體共享檔案,比如照片,音樂,視頻等,需要申請新的媒體權限:READ_MEDIA_IMAGES,READ_MEDIA_VIDEO,READ_MEDIA_AUDIO,申請方法同原來的儲存權限。

 

3、訪問系統下載檔案:對於系統下載檔案夾的訪問,暫時沒做限制,但是,要訪問其中其他應用的檔案,必須允許用戶使用系統的檔案選擇器應用來選擇檔案。

 

4、訪問其他應用沙盒檔案:如果你的應用需要使用其他應用在沙盒內創建的檔案,請點擊使用其他應用的檔案,本文不做介紹。

 

所以請判斷當應用運行在Q平臺上時,取消對READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE兩個權限的申請。並替換為新的媒體特定權限。

 

關於儲存權限的(如何啟用)影響範圍

 

模擬器

 

在Android Q Beat1中,谷歌暫未開放儲存權限的改動。我們需要使用adb命令

 

adb shell sm set-isolated-storage on

來開啟模擬器對於儲存權限的變更來進行適配。

 

真機

 

當滿足以下每個條件時,將開啟兼容樣式,即不開啟Q設備中的儲存權限改動:

 

  1. 應用targetSDK<=P。

  2. 應用安裝在從 Android P 升級到 Android Q 的設備上。

 

但是當應用重新安裝(更新)時,不會重新開啟兼容樣式,儲存權限改動將生效。

 

所以按官方文件所說,無論targetSDK是否為Q,必須對應用進行儲存權限改動的適配。

 

在我的測試中,當targetSDK<=P,在Q Beat1版上申請兩個舊權限時會自動改成申請三個新權限,不會影響應用正常使用,但當targetSDK==Q時,申請舊權限將失敗並影響應用正常使用。

 

定位權限

 

為了讓用戶更好地控制應用對位置信息的訪問權限,Android Q 引入了新的位置權限 ACCESS_BACKGROUND_LOCATION

與現有的 ACCESS_FINE_LOCATION 和 ACCESS_COARSE_LOCATION 權限不同,新權限僅會影響應用在後臺運行時對位置信息的訪問權。除非應用的某個 Activity 可見或應用正在運行前臺服務,否則應用將被視為在後臺運行。

 

與iOS系統一樣,Q中也加入了後臺位置權限ACCESS_BACKGROUND_LOCATION,如果應用需要在後臺時也獲得用戶位置(比如滴滴),就需要動態申請ACCESS_BACKGROUND_LOCATION權限。

當然如果不需要的話,應用就無需任何改動,且谷歌會按照應用的targetSDK作出不同處理:

 

targetSDK <= P 應用如果請求了ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION權限,Q設備會自動幫你申請ACCESS_BACKGROUND_LOCATION權限。

 

設備唯一識別符號

 

從 Android Q 開始,應用必須具有 READ_PRIVILEGED_PHONE_STATE 簽名權限才能訪問設備的不可重置識別符號(包含 IMEI 和序列號)。

許多用例不需要不可重置的設備識別符號。如果您的應用沒有該權限,但您仍嘗試查詢識別符號的相關信息。會傳回空值或報錯。

 

設備唯一識別符號需要特別註意,原來的READ_PHONE_STATE權限已經不能獲得IMEI和序列號,如果想在Q設備上通過

 

((TelephonyManagergetActivity()      .getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId()

獲得設備ID,會傳回空值(targetSDK<=P)或者報錯(targetSDK==Q)。且官方所說的READ_PRIVILEGED_PHONE_STATE權限只提供給系統app,所以這個方法算是廢了。

 

谷歌官方給予了設備唯一ID最佳做法,但是此方法給出的ID可變,可以按照具體需求具體解決。

本文給出一個不變和基本不重覆的UUID方法。

 

public static String getUUID() {

String serial = null;

String m_szDevIDShort = "35" +
        Build.BOARD.length() % 10 + Build.BRAND.length() % 10 +

        Build.CPU_ABI.length() % 10 + Build.DEVICE.length() % 10 +

        Build.DISPLAY.length() % 10 + Build.HOST.length() % 10 +

        Build.ID.length() % 10 + Build.MANUFACTURER.length() % 10 +

        Build.MODEL.length() % 10 + Build.PRODUCT.length() % 10 +

        Build.TAGS.length() % 10 + Build.TYPE.length() % 10 +

        Build.USER.length() % 10//13 位

try {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        serial = android.os.Build.getSerial();
    } else {
        serial = Build.SERIAL;
    }
    //API>=9 使用serial號
    return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
} catch (Exception exception) {
    //serial需要一個初始化
    serial = "serial"// 隨便一個初始化
}
    //使用硬體信息拼湊出來的15位號碼
    return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
}

然由於唯一識別符號權限的更改會導致android.os.Build.getSerial()傳回unknown,但是由於m_szDevIDShort是由硬體信息拼出來的,所以仍然保證了UUID的唯一性和持久性。

 

經測試上述方法完全相同的手機有可能重覆,網上還有其他方案比如androidID,但是androidID可能由於機型原因傳回null,所以個人任務兩種方法半斤八兩。設備ID的獲取一個版本比一個版本艱難,如果有好的方法歡迎指出。

 

minSDK警告

 

在 Android Q 中,當用戶首次運行以 Android 6.0(API 級別 23)以下的版本為標的平臺的任何應用時,Android平臺會向用戶發出警告。

如果此應用要求用戶授予權限,則系統會先向用戶提供調整應用權限的機會,然後才會允許此應用首次運行。

 

谷歌要求運行在Q設備上的應用targetSDK>=23,不然會向用戶發出警告。

Q 行為變更:以 Android Q 為標的平臺的應用

非 SDK 接口限制

 

非SDK接口限制在Android P中就已提出,但是在Q中,被限制的接口的分類有較大變化。

 

非SDK接口介紹

 

為了確保應用穩定性和兼容性,Android 平臺開始限制您的應用可在 Android 9(API 級別 28)中使用哪些非 SDK 接口。Android Q 包含更新後的受限非 SDK 接口串列(基於與 Android 開發者之間的協作以及最新的內部測試)。

 

非SDK接口限制就是某些SDK中的私用方法,如private方法,你通過Java反射等方法獲取並呼叫了。那麼這些呼叫將在target>=P或target>=Q的設備上被限制使用,當你使用了這些方法後,會報錯:

 

獲取方法 報錯信息
Dalvik instruction referencing a field NoSuchFieldError thrown
Dalvik instruction referencing a method NoSuchMethodError thrown
Reflection via Class.getDeclaredField() or Class.getField() NoSuchFieldException thrown
Reflection via Class.getDeclaredMethod(), Class.getMethod() NoSuchMethodException thrown
Reflection via Class.getDeclaredFields(), Class.getFields() Non-SDK members not in results
Reflection via Class.getDeclaredMethods(), Class.getMethods() Non-SDK members not in results
JNI via env->GetFieldID() NULL returned, NoSuchFieldError thrown
JNI via env->GetMethodID() NULL returned, NoSuchMethodError thrown

 

非SDK接口查找

 

如果您不確定自己的應用是否使用了非 SDK 接口,則可以測試該應用進行確認。

 

當你呼叫了非SDK接口時,會有類似Accessing hidden XXX的日誌:

 

Accessing hidden field Landroid/os/Message;->flags:I (light greylist, JNI)

但是一個大專案到底哪裡使用了這些方法,靠review代碼和看日誌肯定是不現實的,谷歌官方也提供了官方檢查器veridex用來檢測一個apk中哪裡使用了非SDK接口。veridex下載。

 

https://android.googlesource.com/platform/prebuilts/runtime/+/master/appcompat

其中有windows,linux和mac版本,對應下載即可。

下載解壓後命令列cd到veridex目錄下使用./appcompat.sh –dex-file=Q.apk即可自動掃描。Q.apk為包的絕對路徑,如果包與veridex在相同目錄下直接輸入包檔案名即可。

 

掃描結果分為兩部分,一部分為被呼叫的非SDK接口的位置,另一部分為非SDK接口數量統計,例如:

 

 

  1. greylist: 灰名單,即當前版本仍能使用的非SDK接口,但在下一版本中可能變成被限制的非SDK接口

  2. blacklist:黑名單,使用了就會報錯。也是我們專案中必須解決的非SDK接口

  3. greylist-max-o:在targetSDK<=O中能使用,但是在targetSDK>=P中被限制的非SDK接口

  4. greylist-max-p:在targetSDK<=P中能使用,但是在targetSDK>=Q中被限制的非SDK接口

 

所以從適配Q的角度出發,除了greylist我們可以暫時不解決以外,其餘三種型別的非SDK接口需要我們進行適配。

 

非SDK接口適配

 

如果您的應用依賴於非 SDK 接口,則應該開始計劃遷移到 SDK 替代方案。如果您無法為應用中的某項功能找到使用非 SDK 接口的替代方案,則應該請求新的公共 API。

 

官方要求targetSDK>=P的應用不使用這些方法,並尋找其他的公共API去替代這些非SDK接口,如果找不到,則可以向谷歌申請,請求一個新的公共API https://developer.android.com/distribute/best-practices/develop/restrictions-non-sdk-interfaces#feature-request (一般不需要)。

 

就我個人掃描並定位的結果來看,專案中使用非SDK接口大概率有以下兩種情況:

 

  1. 在自定義View的過程中為了方便,使用反射修改某個引數。

  2. 三方SDK中使用了非SDK接口(這種情況比較多)。

 

第一種是好解決的,畢竟是我們自己寫的代碼。

 

第二種就頭疼了,只能更新到最新的三方SDK版本,或者提工單、換庫(也是整個適配過程中工作量最龐大的部分)。

專案升級遇到的問題

模擬器X86,專案中SO庫為v7

 

  • 找到so庫原始碼,編譯成x86

  • 如果so庫只是某個功能點使用,對APP整體沒大影響,就可以屏蔽特定so庫功能或略過測試

  • 如果so庫是專案核心庫必須加載,也可使用騰訊雲測,上面有谷歌親兒子Q版本。騰訊雲測有adb遠程連接除錯功能(我沒成功過)。adb連不上也沒關係,直接安裝就行,雲測上也可以直接看日誌。

  • 至於inter的houdini我嘗試研究過,理論上能安裝在x86模擬器上讓它編譯v7的so庫,但是由於關於houdini的介紹比較少也比較舊,建議大家時間不充裕的話就別研究了。

 

Requires development platform Q but this is a release platform.

 

由於目前Q是preview版,所以targetSDK==Q 的應用只能在Q設備上跑。

 

INSTALL_FAILED_INVALID_APK: Failed to extract native libraries, res=-2

 

這個錯誤是由於打包壓縮so庫時造成的,具體原因可見

https://issuetracker.google.com/issues/37045367

 

在AndroidManifest.xml的application節點下加入android:extractNativeLibs="true"

可能有人加了上面代碼還是不行,在app/build.gradle中的defaultConfig節點下加入

packagingOptions{ 
      doNotStrip "/armeabi/.so" doNotStrip "/armeabi-v7a/.so" doNotStrip "/x86/.so" }

 

Didn’t find class “org.apache.http.client.methods.HttpPost”

 

在AndroidManifest.xml的application節點下加入

 

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

 

如果你的專案沒有適配過android O或P,那麼你需要註意:

 

  1. android O的讀取已安裝應用權限(對應用內自動更新有影響)

  2. android P的預設禁止訪問http的API

 

這兩個版本的適配問題本文就不做詳述,網上有很多詳細的介紹。

總結

適配還是不能拉下,如果你一下子從6.0升級到Q,你真的會哭的。

 

平時也多註意三方庫的更新,因為安卓版本的更新勢必導致了需要更新三方庫。

 

官方文件的永遠是最準確的。

參考文獻

官方文件

https://developer.android.com/preview

非SDK接口

https://juejin.im/post/5afe50eef265da0b70262463

已同步到看一看
赞(0)

分享創造快樂