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

吃貓貓的魚發表於2019-04-11

原文地址:juejin.im/post/5cad5b…

導讀


文中連結請自行科學上網

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及功能

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許可權無線掃描許可權兩種許可權的變更影響較少。本文不作詳述,如有涉及請查閱官方文件

      從後臺啟動Activity許可權變更僅針對與使用者毫無互動就啟動一個Activity的情況,(比如微信登陸授權)
    複製程式碼

    以下會著重介紹儲存許可權,定位許可權裝置識別符號三種許可權的變更與適配

    • 儲存許可權

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

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

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

      • 訪問自己檔案:Q中用更精細的媒體特定許可權替換並取消READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE許可權,並且無需特定許可權,應用即可訪問自己沙盒中的檔案。

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

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

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

      所以請判斷當應用執行在Q平臺上時,取消READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE兩個許可權的申請。並替換為新的媒體特定許可權。

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

      • 模擬器

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

        adb shell sm set-isolated-storage on
        複製程式碼

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

      • 真機

        當滿足以下每個條件時,將開啟相容模式,即不開啟Q裝置中的儲存許可權改動:

          應用targetSDK<=P。
          應用安裝在從 Android P 升級到 Android Q 的裝置上。
        複製程式碼

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

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

      (在我的測試中,在Q Beat1版上申請兩個舊許可權時會自動改成申請三個新許可權,不會影響應用正常使用,但還是建議適配)

    • 定位許可權

      為了讓使用者更好地控制應用對位置資訊的訪問許可權,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_LOCATIONACCESS_COARSE_LOCATION許可權,Q裝置會自動幫你申請ACCESS_BACKGROUND_LOCATION許可權。

      裝置唯一識別符號

      從 Android Q 開始,應用必須具有 READ_PRIVILEGED_PHONE_STATE 簽名許可權才能訪問裝置的不可重置識別符號(包含 IMEI 和序列號)。許多用例不需要不可重置的裝置識別符號。如果您的應用沒有該許可權,但您仍嘗試查詢識別符號的相關資訊。

      裝置唯一識別符號需要特別注意,原來的READ_PHONE_STATE許可權已經不能獲得IMEI和序列號,如果想在Q裝置上通過

      ((TelephonyManager) getActivity().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的唯一性永續性

  • 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>=Ptarget>=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下載

    其中有windows,linuxmac版本,對應下載即可。下載解壓後命令列cdveridex目錄下使用./appcompat.sh --dex-file=Q.apk即可自動掃描。Q.apk為包的絕對路徑,如果包與veridex在相同目錄下直接輸入包檔名即可。

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

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

    • greylist: 灰名單,即當前版本仍能使用的非SDK介面,但在下一版本中可能變成被限制的非SDK介面
    • blacklist:黑名單,使用了就會報錯。也是我們專案中必須解決的非SDK介面
    • greylist-max-o: 在targetSDK<=O中能使用,但是在targetSDK>=P中被限制的非SDK介面
    • greylist-max-p: 在targetSDK<=P中能使用,但是在targetSDK>=Q中被限制的非SDK介面

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

  • 非SDK介面適配

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

    官方要求targetSDK>=P的應用不使用這些方法,並尋找其他的公共API去替代這些非SDK介面,如果找不到,則可以向谷歌申請,請求一個新的公共API(一般不需要)。

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

    • 在自定義View的過程中為了方便,使用反射修改某個引數。
    • 三方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庫時造成的,具體原因可見 issuetracker.google.com/issues/3704…

    在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,那麼你需要注意:

    • android O的讀取已安裝應用許可權(對應用內自動更新有影響)
    • android P的預設禁止訪問http的API

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


總結

  • 適配還是不能拉下,如果你一下子從6.0升級到Q,你真的會哭的。
  • 平時也多注意三方庫的更新,因為安卓版本的更新勢必導致了需要更新三方庫。
  • 官方文件的永遠是最準確的。

參考文獻

官方文件

非SDK介面

相關文章