摘要:適配的原則是優先黑名單和深灰名單,淺灰名單在官方未有替代之前可以暫時不適配,在上運行也不會有任何問題。除上面兩種適配方式外,音樂目前采用了另外一種方式。隱私安全保護標識修改在中,對隱私保護又做了更加嚴格的要求。
歡迎大家前往騰訊云+社區,獲取更多騰訊海量技術實踐干貨哦~
本文由QQ音樂技術團隊發表于云+社區專欄上篇:Android P 行為變更適配
Android P 這次有很多行為變更,其中不乏一些需要亟需適配的變更。
一、全面屏檢測在 Android 8.0 時代各個手機廠商就開始發布自己的全面屏手機,但是此時 Android 官方并未支持到該功能,所以各個廠商都各自實現了一套全面屏判斷邏輯,對于開發者來說甚是麻煩。終于在 Android P 里官方收歸了該功能的判斷邏輯,Android P 和之后的版本完全可以使用官方 API 來判斷全面屏,當然前提是第三方廠商按照 google 官方接口去實現。Android P 版本判斷全面屏代碼很簡單,但是在適配過程中你可能會在網上發現如下判斷代碼:
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) { Listrects = 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二、非 SDK API 適配詳解 2.1 非 SDK API 名單介紹rects = displayCutout.getBoundingRects(); //通過判斷是否存在rects來確定是否劉海屏手機 if (rects != null && rects.size() > 0) { isNotchScreen = true; } } } }
Android P 版本最大最嚴格的特性變更應該非 SDK 接口限制莫屬了。對于非 SDK API 里面的部分名單來說,就算在不修改 targetSdkVersion 的前提下,不管是直接、反射還是通過 JNI 調用都會造成調用失敗、拋出 NoSuchFieldException或 NoSuchMethodException 等嚴重后果,該行為影響范圍波及所有調用此接口的應用。
非 SDK API 名單總共分為三類:light grey list (淺灰名單)、dark grey list (深灰名單)、dark list(黑名單),詳情:
2.2 非 SDK API 名單掃描
所以對于我們應用開發者來說,當前首要任務是適配深灰名單和黑名單。目前 google 官方提供了一個可以實時查詢三個名單里面 API 列表的網站:https://android.googlesource....。在之前 DP 版本時開發者如果遇到了不得不使用的黑名單或者深灰名單 API,需要向 google 官方及時提出反饋(反饋url:https://issuetracker.google.c...),申請將其移動到淺灰名單中,但是目前正式版本已經發布,未得知該申請通道是否仍有效。
詳細了解了非 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,在關鍵地方加上適配代碼;
Android 官方庫調用到了非 SDK API 接口; 沒錯!Android 官方庫也會被掃描出非 SDK API 調用,針對這種情況,需要分情況討論:
該 API 調用查看 v7 support 包源碼可以發現已經被 try-catch 住了,測試了相關類也可以正常運行,而且在適配過程中升級 rc 版本的 support-v7 包會導致應用編譯不過,所以目前 QQ 音樂暫時認定無需升級到最新版本的 support-v7。除上面介紹的特殊情況之外還是建議更換最新版本的官方 SDK。
三、電源管理改進 3.1 應用待機群組Android P 上對電源管理又做了一系列的改進措施,不管應用 targetApi 版本是否已經升級到 P,系統都會依據應用最近的使用時間和頻率來給應用進行待機分組,然后根據應用所屬群組限制應用可以訪問的資源,目前總共有五類分組:
活躍: 一般為正在使用或者在前臺運行的應用,例如:
應用啟動一個 Activity;
應用正在運行前臺 Service;
應用的同步適配器關聯上了一個前臺應用;
用戶點擊了應用的一個通知; 系統不會對該類應用有任何的限制;
工作集: 應用經常運行,但是當前未屬于活躍狀態就會被歸屬于工作集,該群組的應用在運行作業和觸發鬧鐘方面會被施加輕度的限制;
常用: 應用如果被定期使用,但不是每天的話就會被歸到該工作群組。該群組的應用在運行作業和觸發鬧鐘方面會被施加較強的限制,FCM 消息數量也會有相關限制;
極少使用: 應用如果不經常使用就會被歸到該工作群組,系統會對該群組應用運行作業、觸發鬧鐘和接收高優先級別 FCM 的消息能力方面有嚴格的限制;
從未使用: 安裝但從未被使用過的應用會被歸到該工作群組,該工作群組的應用會被施加極其嚴格的限制;
更加詳細的表述可以參考官網:App Standby Buckets(https://developer.android.com...,不同群組的限制的詳細表現見:Power management restrictions(鏈接:https://developer.android.com...)。系統會動態的將手機里面的應用分配到這五類群組里面,也會根據需要變化應用群組,同時借助了機器學習來將一個應用放到更合適的群組里。目前應用可以通過 UsageStatsManager.getAppStandbyBucket() 函數來獲取當前所屬的應用群組,借助這個結果來更好的提升自己的打開頻率,同時可以借助此來模擬處于不同群組能否正常工作。另外,位于低電耗模式白名單中的應用不適用基于應用待機群組的限制。
3.2 省電模式改進Android 9 對省電模式又做了很多改進,開啟省電模式之后會有如下限制:
系統會更加積極的將應用置于待機模式,不管應用是否空閑;
后臺執行限制將適用于所有應用,無論他們的 targetApi 是多少;
屏幕關閉時,位置服務可能被停用;
后臺應用沒有網絡訪問權限;
這里需要重點介紹一下后臺執行限制,該限制于 Android O 版本引入,主要是為了優化 Android 在多應用多服務運行時,系統負載過大會殺死后臺音樂播放等服務導致用戶體驗下降的問題,它默認只對 targetApi 大于等于 26 的應用生效。目前用戶可以通過設置頁面對任意應用施加后臺執行限制,后臺執行限制會對應用有兩方面的影響:
后臺服務限制: 處于前臺(可見、具有前臺服務或者關聯到前臺應用)或臨時白名單(處理高優先級 FCM、接收短信等廣播或者執行通知的 PendingIntent)時,應用可以自由創建和運行前臺與后臺服務。 進入后臺時,在一個持續數分鐘的時間窗內,應用仍可以創建和使用服務,但是超過該時間之后再通過 startService 去啟動一個服務就會拋出 java.lang.IllegalStateException: Not allowed to start service Intent 的錯誤,解決辦法是使用 startForegroundService 或者 JobIntentService;
廣播限制: 針對 Android O 和之上的應用無法繼續在其清單中為隱式廣播注冊廣播接收器。
四、Apache HTTP client 相關類找不到將 compileSdkVersion 升級到 28 之后,如果在項目中用到了 Apache HTTP client 的相關類,就會拋出找不到這些類的錯誤。這是因為官方已經在 Android P 的啟動類加載器中將其移除,如果仍然需要使用 Apache HTTP client,可以在 Manifest 文件中加入:
或者也可以直接將 Apache HTTP client 的相關類打包進 APK 中。
除上面兩種適配方式外,QQ 音樂目前采用了另外一種方式。在音樂項目中,我們已經將使用 Apache HTTP client 的模塊多帶帶抽離到了一個 module 中,所以暫時只需要保持 module 中的 compileSdkVersion 在 28 以下即可正常編譯運行。
五、其余適配 4.1 前臺 Service在 Android P 中,如果 targeSdkVersion 升級到 28,使用前臺 Service 必須要申請 FOREGROUND_SERVICE 權限,如果沒有申請該權限,系統會拋出 SecurityException,該權限為普通權限,申請自動授予應用。
4.2 隱私安全保護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 中移除。針對這種情況目前有兩種處理辦法:
移除該類的調用邏輯;
在應用中新建一個同名類,將被移除類的所有代碼邏輯復制到新建類中(必要時可能需要將被移除類相關類同時拷貝一份到應用中),然后將應用中所有相關 import 引用直接修改成新建類的包名引用即可;
下篇:Android P 實用新特性Android P 這次當然也有很多豐富的特性,總結了兩個對于第三方應用開發者比較實用的特性
。
一、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 圖片格式解碼的手機,這些手機就肯定有展示問題。解決這個問題目前有兩種思路:
終端在上傳之前將其轉碼成 JPEG 格式的圖片,但是這樣就根本沒有充分利用到 HEIF 圖片的高壓縮率的優勢;
在到達后端之后,后端將其轉碼成 JPEG 圖片,同時保存一份 HEIF 和 JEPG,到時候根據用戶是否可以解碼 HEIF 下發不同格式圖片。該方案可以充分利用 HEIF 的優點,但是大大增加了后端存儲空間和開發工作量。
二、ImageDecoder上面已經介紹到了 ImageDecoder 在解碼 HEIF 圖片中的應用,但是實際它的功能完全不僅于此,在 Android P 中它可以完全替代 BitmapFactory 和 BitmapFactory.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 } }引用
https://developer.android.goo... https://mp.weixin.qq.com/s/03ospQEdY5HLdYqxEiDX1g https://blog.csdn.net/GenlanF... https://developer.android.com/about/versions/pie/power https://segmentfault.com/a/11...問答
Android - 如何修復權限異常?
相關閱讀
Android音頻系統
Android 基本常識
Android全局異常處理
【每日課程推薦】機器學習實戰!快速入門在線廣告業務及CTR相應知識
此文已由作者授權騰訊云+社區發布,更多原文請點擊
搜索關注公眾號「云加社區」,第一時間獲取技術干貨,關注后回復1024 送你一份技術課程大禮包!
海量技術實踐經驗,盡在云加社區!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/98222.html
摘要:不努力不奮斗,可能就會在基層一輩子止步不前。不過,只一句,如果你還在做這一行,還是一名程序猿媛,想走上坡路的你,也許我這到手的十幾家一線互聯網公司性能優化項目實戰可能會對你有所幫助。 ...
摘要:學會提出正確的問題數據分析的目標并不是數據本身,而是解決問題。當好的數據分析師能夠同時以旁觀者和業務方的角度來提問時,會更容易定位真正的問題所在。這些都是一個數據分析師最有效率的成長方式。 經常有小伙伴在各種渠道問我,數據分析師怎么入門?應該讀什么書?如何能成為被大公司認可的數據分析師? Facebook 數據分析師鄒昕曾分享過這樣一張數據分析核心技能地圖: showImg(https...
閱讀 3451·2023-04-25 19:39
閱讀 3799·2021-11-18 13:12
閱讀 3634·2021-09-22 15:45
閱讀 2433·2021-09-22 15:32
閱讀 716·2021-09-04 16:40
閱讀 3727·2019-08-30 14:11
閱讀 1883·2019-08-30 13:46
閱讀 1563·2019-08-29 15:43