Android Q 適配,讓你少走些彎路!

原文地址: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,必須對應用進行存儲權限改動的適配。

      在我的測試中,當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_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的唯一性持久性

      經測試上述方法完全相同的手機有可能重複,網上還有其他方案比如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>=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接口數量統計,例如:

     

     

    • 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接口

 

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