Android 9 適配怎麼作? 「QQ音樂」優化實錄

做者:趙澤鵬 騰訊社交網絡開發工程師 html

商業轉載請聯繫騰訊WeTest得到受權,非商業轉載請註明出處。
java

原文連接:wetest.qq.com/lab/view/40…android




WeTest 導讀

2018年8月7日,Google對外發布最新 Android 9.0 正式版系統,並宣佈系統版本Android P 被正式命名爲代號「Pie」,最新系統已經正式推送包括谷歌Pixel、Pixel2系列以及Essential Phone。web

騰訊WeTest平臺第一時間進行了系統升級,實現Android 9.0機型兼容測試和遠程調試服務。apache


爲了讓用戶第一時間瞭解Android 9.0系統的兼容性狀況,咱們基於谷歌Pixel、Pixel 2機型的Android 9 Pie系統,對市面TOP86款應用進行「深度兼容測試」,發佈了《騰訊WeTest Android 9.0兼容性測試報告》(點擊閱讀原文前往Android 9 專區下載)。canvas


Android P 行爲變動適配



1、全面屏檢測


在 Android 8.0 時代各個手機廠商就開始發佈本身的全面屏手機,可是此時 Android 官方並未支持到該功能,因此各個廠商都各自實現了一套全面屏判斷邏輯,對於開發者來講甚是麻煩。終於在 Android P 裏官方收歸了該功能的判斷邏輯,Android P 和以後的版本徹底可使用官方 API 來判斷全面屏,固然前提是第三方廠商按照 google 官方接口去實現。Android P 版本判斷全面屏代碼很簡單,可是在適配過程當中你可能會在網上發現以下判斷代碼:segmentfault


if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    decorView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {        
    @RequiresApi(api = 28)
        @Override
        public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {            
            if (windowInsets != null) {
                DisplayCutout cutout = windowInsets.getDisplayCutout();                
                if (cutout != null) {
                    List<Rect> rects = cutout.getBoundingRects();                    
                    //經過判斷是否存在rects來肯定是否全面屏手機
                    if (rects != null && rects.size() > 0) {
                        isNotchScreen = true;
                    }
                }
            }            
            return windowInsets;
        }
    });
}複製代碼

這段代碼確實能夠判斷出全面屏與否,可是會形成一個很嚴重的後果,就是在某些手機(pixel 和 vivo x21 均出現該狀況)上底部導航欄會透明,致使應用內容會透到導航欄從而被遮擋,大大影響內容展現。最後通過仔細排查發現僅僅由於在上面那段代碼中調用了 setOnApplyWindowInsetsListener 函數,該函數在 Android 官網有詳細介紹,是用來在 Android 21 版本以後代替 fitSystemWindows 函數,目的是讓 View 根據 Window 的縮進進行相應處理,調用後會影響系統狀態欄和導航欄對應用內容的展現,對此的介紹資料網上有不少,就不贅述了。真正完美判斷全面屏的代碼以下:後端

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    WindowInsets windowInsets = decorView.getRootWindowInsets();    
    if (windowInsets != null) {
        DisplayCutout displayCutout = windowInsets.getDisplayCutout();        
        if (displayCutout != null) {            
            List<Rect> rects = displayCutout.getBoundingRects();            
            //經過判斷是否存在rects來肯定是否劉海屏手機
            if (rects != null && rects.size() > 0) {
                isNotchScreen = true;
            }
        }
    }
}複製代碼

2、非 SDK API 適配詳解

2.1 非 SDK API 名單介紹

Android P 版本最大最嚴格的特性變動應該非 SDK 接口限制莫屬了。對於非 SDK API 裏面的部分名單來講,就算在不修改 targetSdkVersion 的前提下,無論是直接、反射仍是經過 JNI 調用都會形成調用失敗、拋出 NoSuchFieldExceptionNoSuchMethodException 等嚴重後果,該行爲影響範圍波及全部調用此接口的應用。api

非 SDK API 名單總共分爲三類:light grey list (淺灰名單)、dark grey list (深灰名單)、dark list(黑名單),詳情:緩存

名單類型 影響 名單說明
淺灰名單 targetSDK>=P 時,警告 目前,針對非 SDK 接口沒有可替代 SDK 接口的這種狀況,谷歌暫時將之放在淺灰名單,並在後續版本考慮增長可替代的接口,而後再將接口轉移到深灰名單。名單經過代碼維護。
深灰名單 targetSDK<P時,警告;>=P 時,不容許調用 目前,針對非 SDK 接口有可替代 SDK 接口的狀況,谷歌將之放在深灰名單中,開發者須要將這些非 SDK 接口進行整改。名單經過代碼維護。
黑名單 全部第三方應用不容許調用 灰名單(深灰+淺灰)以外的其餘全部非 SDK 接口都將被添加到黑名單中,若是應用使用到黑名單接口,需立刻整改或者反饋給谷歌申請加入灰名單。

因此對於咱們應用開發者來講,當前首要任務是適配深灰名單和黑名單。目前 google 官方提供了一個能夠實時查詢三個名單裏面 API 列表的網站:https://android.googlesource.com/platform/frameworks/base/+/master/config/。在以前 DP 版本時開發者若是遇到了不起不使用的黑名單或者深灰名單 API,須要向 google 官方及時提出反饋,申請將其移動到淺灰名單中,可是目前正式版本已經發布,未得知該申請通道是否仍有效。

2.2 非 SDK API 名單掃描

詳細瞭解了非 SDK API 以後,下一步固然是將應用代碼裏面的深灰名單和黑名單 API 調用找出來一一修改。目前官方提供了一個很是實用的掃描工具,該工具能夠把應用裏面三個類型名單的 API 調用都掃描出來(可是可能會有遺漏),使用方法也很簡單:

打包一個應用 APK,建議使用 release 包,排除一些未使用到的單元測試類或者其餘因素的影響,將 APK 放到工具指定目錄下;

執行命令 ./appcompat.sh --dex-file=test.apk,在終端上會輸出三個名單每一個 API 的詳細調用處:

#1: Linking dark greylist Landroid/os/SystemProperties;->get(Ljava/lang/String;)Ljava/lang/String; use(s):
        Ltmsdkobf/gv;->a(Ljava/lang/String;)Ljava/lang/String; 
  
 #2: Linking dark greylist Landroid/os/SystemProperties;->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; use(s):
        Ltmsdkobf/gp;->b(Landroid/content/Context;)Ljava/lang/String;
 
 ....複製代碼


2.3 非 SDK API 適配

通過上一步掃描出應用內非 SDK API 調用以後,接下來就能夠直接開始適配。適配的原則是優先黑名單和深灰名單,淺灰名單在官方未有替代 API 以前能夠暫時不適配,在 Android P 上運行也不會有任何問題。掃描完成以後,不出意外你們應該會有三類須要適配的 API 調用:

應用代碼自己調用到了非 SDK API 接口;

針對應用代碼自己調用到了非 SDK API 接口,用的比較頻繁的例如 SystemProperties.get,就須要去尋找另一個能夠替代的合法 API,若是找不到就只能認爲該 API 調用失敗從而走失敗邏輯,若是實在必需要用到該 API 就儘早去向 google 申請移動到淺灰名單中。

第三方庫調用到了非 SDK API 接口;

針對第三方庫調用到了非 SDK API 接口,解決辦法固然是直接查詢相關資料或者聯繫庫提供方,確認是否有適配 Android P 新版本的 SDK。還有須要提到的一點,就算更換適配完成的第三方 SDK 後,仍然可能會在同一地方掃描出非 SDK API 的調用,這是由於適配工程師只是在調用處加了一個 try-catch 保護邏輯,雖然這樣也勉強叫作適配完成,可是仍是強烈建議你們使用以下的適配方式:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 
 // Android P or above
 } else { 
 // below Android P
 }複製代碼

嚴格按照上面的適配方案,掃描工具就不會再掃描出此處的非 SDK API 調用,咱們也無需每次都去確認全部非 SDK API 調用處都加了保護邏輯。
固然若是第三方庫沒有適配也沒有近期適配的意向,目前有兩種方法:第一種是屏蔽入口;第二種是反編譯 SDK,在關鍵地方加上適配代碼;

  1. Android 官方庫調用到了非 SDK API 接口;

    沒錯!Android 官方庫也會被掃描出非 SDK API 調用,針對這種狀況,須要分狀況討論:



該 API 調用查看 v7 support 包源碼能夠發現已經被 try-catch 住了,測試了相關類也能夠正常運行,並且在適配過程當中升級 rc 版本的 support-v7 包會致使應用編譯不過,因此目前 QQ 音樂暫時認定無需升級到最新版本的 support-v7。除上面介紹的特殊狀況以外仍是建議更換最新版本的官方 SDK。


    3、電源管理改進

    3.1 應用待機羣組

    Android P 上對電源管理又作了一系列的改進措施,無論應用 targetApi 版本是否已經升級到 P,系統都會依據應用最近的使用時間和頻率來給應用進行待機分組,而後根據應用所屬羣組限制應用能夠訪問的資源,目前總共有五類分組:

    1. 活躍:
      通常爲正在使用或者在前臺運行的應用,例如:

      • 應用啓動一個 Activity;

      • 應用正在運行前臺 Service;

      • 應用的同步適配器關聯上了一個前臺應用;

      • 用戶點擊了應用的一個通知;

      系統不會對該類應用有任何的限制;

    2. 工做集:
      應用常常運行,可是當前未屬於活躍狀態就會被歸屬於工做集,該羣組的應用在運行做業和觸發鬧鐘方面會被施加輕度的限制;

    3. 經常使用:
      應用若是被按期使用,但不是天天的話就會被歸到該工做羣組。該羣組的應用在運行做業和觸發鬧鐘方面會被施加較強的限制,FCM 消息數量也會有相關限制;

    4. 極少使用:
      應用若是不常用就會被歸到該工做羣組,系統會對該羣組應用運行做業、觸發鬧鐘和接收高優先級別 FCM 的消息能力方面有嚴格的限制;

    5. 從未使用:
      安裝但從未被使用過的應用會被歸到該工做羣組,該工做羣組的應用會被施加極其嚴格的限制;


    更加詳細的表述能夠參考官網:App Standby Buckets,不一樣羣組的限制的詳細表現見:Power management restrictions。系統會動態的將手機裏面的應用分配到這五類羣組裏面,也會根據須要變化應用羣組,同時藉助了機器學習來將一個應用放到更合適的羣組裏。目前應用能夠經過 UsageStatsManager.getAppStandbyBucket() 函數來獲取當前所屬的應用羣組,藉助這個結果來更好的提高本身的打開頻率,同時能夠藉助此來模擬處於不一樣羣組可否正常工做。另外,位於低電耗模式白名單中的應用不適用基於應用待機羣組的限制。

    3.2 省電模式改進

    Android 9 對省電模式又作了不少改進,開啓省電模式以後會有以下限制:

    1. 系統會更加積極的將應用置於待機模式,無論應用是否空閒;

    2. 後臺執行限制將適用於全部應用,不管他們的 targetApi 是多少;

    3. 屏幕關閉時,位置服務可能被停用;

    4. 後臺應用沒有網絡訪問權限;


    這裏須要重點介紹一下後臺執行限制,該限制於 Android O 版本引入,主要是爲了優化 Android 在多應用多服務運行時,系統負載過大會殺死後臺音樂播放等服務致使用戶體驗降低的問題,它默認只對 targetApi 大於等於 26 的應用生效。目前用戶能夠經過設置頁面對任意應用施加後臺執行限制,後臺執行限制會對應用有兩方面的影響:

    1. 後臺服務限制:
      處於前臺(可見、具備前臺服務或者關聯到前臺應用)或臨時白名單(處理高優先級 FCM、接收短信等廣播或者執行通知的 PendingIntent)時,應用能夠自由建立和運行前臺與後臺服務。 進入後臺時,在一個持續數分鐘的時間窗內,應用仍能夠建立和使用服務,可是超過該時間以後再經過 startService 去啓動一個服務就會拋出 java.lang.IllegalStateException: Not allowed to start service Intent 的錯誤,解決辦法是使用 startForegroundService 或者 JobIntentService

    2. 廣播限制:
      針對 Android O 和之上的應用沒法繼續在其清單中爲隱式廣播註冊廣播接收器。

    4、Apache HTTP client 相關類找不到


    將 compileSdkVersion 升級到 28 以後,若是在項目中用到了 Apache HTTP client 的相關類,就會拋出找不到這些類的錯誤。這是由於官方已經在 Android P 的啓動類加載器中將其移除,若是仍然須要使用 Apache HTTP client,能夠在 Manifest 文件中加入:

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

    或者也能夠直接將 Apache HTTP client 的相關類打包進 APK 中。

    除上面兩種適配方式外,QQ 音樂目前採用了另一種方式。在音樂項目中,咱們已經將使用 Apache HTTP client 的模塊單獨抽離到了一個 module 中,因此暫時只須要保持 module 中的 compileSdkVersion 在 28 如下便可正常編譯運行。

    5、其他適配

    4.1 前臺 Service

    在 Android P 中,若是 targeSdkVersion 升級到 28,使用前臺 Service 必需要申請 FOREGROUND_SERVICE 權限,若是沒有申請該權限,系統會拋出 SecurityException,該權限爲普通權限,申請自動授予應用。

    4.2 隱私安全保護

    1. Build.SERIAL 標識修改:在 Android P 中,對隱私保護又作了更加嚴格的要求。在某些應用中爲了識別手機的惟一性可能會用到 Build.SERIAL 這個標識,但這個標識在 Android P 中已經被設置成了 UNKNOWN,因此會直接致使該功能出現異常。多進程 webview 信息訪問限制:在 Android P 中爲了提高系統的安全性,用戶沒法在多進程的 webview 中共享數據目錄,該目錄下存儲的是一些 cookies、Http 緩存和其餘一些永久、臨時的緩存。當下很多應用會把 webview 放在另外一個進程中打開以免內存泄漏,可是他們 cookies 的設置每每仍是在主進程中,因此開發者須要仔細排查本身的應用是否有這麼使用,webview 相關運行是否正常等。

    4.3 com.android.internal 包下某些類找不到

    升級到 28 以後,應用編譯後拋出 com.android.internal 包下面有些類找不到的異常,通過查找發現這些類已經從 SDK 中移除。針對這種狀況目前有兩種處理辦法:

    1. 移除該類的調用邏輯;

    2. 在應用中新建一個同名類,將被移除類的全部代碼邏輯複製到新建類中(必要時可能須要將被移除類相關類同時拷貝一份到應用中),而後將應用中全部相關 import 引用直接修改爲新建類的包名引用便可;



    Android P 實用新特性



    1、HEIF 圖片格式支持

    HEIF(High Efficiency Image Format),高幀率圖片格式,採用的是 HEVC 編碼格式。蘋果於 iOS11 版本開始支持該圖片格式,而 Android 則是在 Android O MR1 版本開始支持 HEIF 靜態圖的軟解碼,在 P 版本上徹底支持該格式的軟編解碼。HEIF 格式的壓縮率是 JPEG 的 2.39 倍,同等大小質量的圖片可節省 50% 的空間和網絡傳輸流量,並且支持動圖。HEIF 格式比起 GIF 格式來講有着更好的圖片展現效果,因此 HEIF 格式圖片的目標是用來代替 JPEG 成爲主流的圖片壓縮格式。HEIF 格式圖片的擴展名爲 .heif 或者 .heic:



    HEIF WebP JPEG
    最大尺寸 無上限 16383x16383 65535x65535
    編碼 HEVC VP8 JPEG
    是否支持其餘編碼 YES NO NO
    支持音頻/文字 YES NO NO
    支持多圖片 YES YES NO
    支持裁剪 YES NO NO
    支持透明 YES YES NO
    支持縮略圖 YES NO YES
    分塊加載 YES NO NO



    看上去很美好,可是目前還不是全部的 Android P 機型都會支持 HEIF 格式硬編解碼,由於這須要特殊的硬件支持同時還須要繳納必定的專利費,因此在編解碼效率上就會有機型差別,同時 Android P 軟編解碼也只能支持靜態 HEIF 格式圖片。目前開發者能夠經過版原本判斷是否支持 HEIF 編解碼,判斷邏輯以下:

    fun supportHEIF() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P複製代碼

    解碼代碼也很簡單,支持將 HEIF 格式圖片解碼成 Bitmap 和 Drawable:

    @TargetApi(28)
    fun decodeHEIFDrawable(filePath: String): Drawable? {    
        if (!supportHEIF()) {        
            return null
        }    
        var source: ImageDecoder.Source = ImageDecoder.createSource(File(filePath))    
        return ImageDecoder.decodeDrawable(source)
    }
    
    @RequiresApi(28)
    fun decodeHEIFBitmap(filePath: String): Bitmap? {    
        if (!supportHEIF()) {        
           return null
        }    
        var source: ImageDecoder.Source = ImageDecoder.createSource(File(filePath))    
        return ImageDecoder.decodeBitmap(source)
    }複製代碼

    另外掃描本地圖片則繼續使用 ContentResolver 便可,若是設備支持 HEIF 格式,系統會自動掃描上 HEIF 格式的圖片:

    var cursor : Cursor = getContext().getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null)複製代碼

    可是這樣還遠遠沒有適配完成,第三方應用適配 HEIF 格式圖片有一個很困難的地方是本地雖然能夠識別解碼 HEIF 格式的圖片,可是若是某個用戶將其設置爲頭像上傳到後臺,後臺將其下發給其餘不支持 HEIF 圖片格式解碼的手機,這些手機就確定有展現問題。解決這個問題目前有兩種思路:

    1. 終端在上傳以前將其轉碼成 JPEG 格式的圖片,可是這樣就根本沒有充分利用到 HEIF 圖片的高壓縮率的優點;

    2. 在到達後端以後,後端將其轉碼成 JPEG 圖片,同時保存一份 HEIF 和 JEPG,到時候根據用戶是否能夠解碼 HEIF 下發不一樣格式圖片。該方案能夠充分利用 HEIF 的優勢,可是大大增長了後端存儲空間和開發工做量。

    2、ImageDecoder

    上面已經介紹到了 ImageDecoder 在解碼 HEIF 圖片中的應用,可是實際它的功能徹底不只於此,在 Android P 中它能夠徹底替代 BitmapFactoryBitmapFactory.Options 相關類。ImageDocoder 類能夠經過字節數據、文件和 URI 來解碼一張圖片。用法和以前同樣,首先經過 createSource 方法建立一個圖片文件的 ImageDecoder.Source 對象,而後調用 decodeDrawable 或者 decodeBitmap 方法傳入以前的 ImageDecoder.Source 對象就能生成圖片的 Drawable 或者 Bitmap 對象引用。ImageDecoder 支持 PNG、JPEG、WEBP、GIF 和 HEIF 多種格式圖片的解碼,另外解碼 GIF 或者 WEBP 格式圖片獲得的是一個 AnimatedImageDrawable 對象,AnimatedImageDrawable 類的工做原理和 AnimatedVectorDrawable相似,都是使用一個工做線程來解碼,因此解碼線程和顯示線程互不干擾。AnimatedImageDrawable 用法也很簡單:

    var drawable: Drawable = ImageDecoder.decodeDrawable(source);
    if (drawable is AnimatedImageDrawable){
        image.setImageDrawable(drawable)
        drawable.start()
    }複製代碼

    ImageDecoder 除了基礎的解碼功能以外,還有不少很是實用的方法,好比經過設置 OnHeaderDecodedListener 就能夠在解析圖片以前獲取到圖片的寬高等信息,同時還能夠根據須要設置採樣率:

    val listener = object : OnHeaderDecodedListener {    
        fun onHeaderDecoded(decoder: ImageDecoder, info: ImageInfo, source: Source) {
            decoder.setTargetSampleSize(2)
        }
    }
    val drawable = ImageDecoder.decodeDrawable(source, listener)複製代碼

    另外還能夠經過 setPostProcessor 方法來添加一些自定義的效果,好比最經常使用的切圓角:

    var drawable = ImageDecoder.decodeDrawable(source) { decoder, info, src ->
        decoder.setPostProcessor { canvas ->
            val path = Path()        
            path.setFillType(Path.FillType.INVERSE_EVEN_ODD)
            val width = canvas.getWidth()
            val height = canvas.getHeight()        
            path.addRoundRect(0, 0, width, height, 20, 20, Path.Direction.CW)
            val paint = Paint()
            paint.setAntiAlias(true)
            paint.setColor(Color.TRANSPARENT)
            paint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.SRC))
            canvas.drawPath(path, paint)
            PixelFormat.TRANSLUCENT
        }
    }複製代碼

    很是便捷。用法遠不只於此,有了 Canvas 對象,開發者徹底能夠發揮想象去實現本身想要的炫酷效果。另外若是解碼的圖片不完整或者包含錯誤,通常狀況下會拋出 DecodeException,可是若是這個時候經過 setOnPartialImageListener 函數傳遞一個 OnPartialImageListener 對象,而且在 onPartialImage 函數中返回 true,則圖片就會只展現解析成功的一部分而不會拋出 DecodeException

    var drawable = ImageDecoder.decodeDrawable(source) { decoder, info, src ->
        decoder.setOnPartialImageListener { e: ImageDecoder.DecodeException ->
            true
        }
    }複製代碼

    引用

    developer.android.google.cn/about/versi…
    mp.weixin.qq.com/s/03ospQEdY…
    blog.csdn.net/GenlanFeng/…
    developer.android.com/about/versi…
    segmentfault.com/a/119000001…

    最新Android 9.0 系統機型已經上線,有測試需求的同窗可前往WeTest官網免費體驗一把。如今完成我的或企業實名認證,咱們將免費贈送您額外測試福利!



    上圖爲WeTest雲真機選擇界面,能夠自由選擇所需機型及操做系統

    WeTest提供深度兼容測試及遠程調試服務。提供上千臺真實手機,支持市面主流機型,覆蓋真正目標用戶。獨家支持微信/QQ帳號自動登陸,一鍵提測,測試完成微信郵件即時通知。


    點擊:wetest.qq.com/cloud/help/… 搶先領略 Android P測試專區。


    若是使用當中有任何疑問,歡迎聯繫騰訊WeTest企業QQ:2852350015

    相關文章
    相關標籤/搜索