Android修煉之Pie 適配的搬運工

自嘲時刻

Android P正式版(如下稱爲Pie)已經正式上線了,各大廠商已經開始了系統升級工做,咱作上層開發的也得跟上節奏。固然了,新版本全部的行爲更改內容均可以在官網上找到,對於其中如何繞開非SDK接口限制的問題,也有各路大神給出瞭解決方案。因此,我只能當搬運工了(偷笑ing)。下面我會結合現有的開發經驗,聊聊Pie中對咱們開發影響較大的一些更新,重頭戲仍是適配齊劉海非SDK接口限制java

行爲變動

這裏只會列出部分行爲變動(僅表明我的看法),感興趣的同窗直接去官網查看。linux

Pie官方文檔android

通知渠道設置更新

更新內容
git

  • 屏蔽渠道組:如今,用戶能夠針對某個應用在通知設置中屏蔽整個渠道組。 您可使用 isBlocked() 函數肯定什麼時候屏蔽一個渠道組,從而不會向該組中的渠道發送任何通知。
  • 全新的廣播 Intent 類型:如今,當通知渠道和渠道組的屏蔽狀態發生變動時,Android 系統將發送廣播 Intent。 擁有已屏蔽的渠道或渠道組的應用能夠偵聽這些 Intent 並作出相應的迴應。

影響:APP的推送功能可能須要適配,Pie中能夠知道渠道組的狀態了。github

ImageDecoder & AnimatedImageDrawable

更新內容:
api

  • ImageDecoder 類,可提供現代化的圖像解碼方法。 使用該類取代 BitmapFactory 和 BitmapFactory.Options API。ImageDecoder 讓您可經過字節緩衝區、文件或 URI 來建立 Drawable 或 Bitmap。
  • AnimatedImageDrawable 類,用於繪製和顯示 GIF 和 WebP 動畫圖像。 AnimatedImageDrawable 的工做方式與 AnimatedVectorDrawable 的類似之處在於,都是渲染線程驅動 AnimatedImageDrawable 的動畫。

影響:對APP級別的影響應該較小,畢竟大部分都是使用第三方庫來實現圖片資源的加載。可能對SDK影響較大,SDK裏基本都是本身寫Image壓縮解碼邏輯,因此能夠考慮使用。緩存

統一輩子物識別身份驗證對話框

更新內容:
在 Android 9 中,系統表明您的應用提供生物識別身份驗證對話框。 該功能可建立標準化的對話框外觀、風格和位置,讓用戶更加確信,他們在使用可信的生物識別憑據檢查程序進行身份驗證。安全

影響:接入了生物識別功能的(如:指紋)APP就有統一的提示對話框了,挺好的。就是不知道標準的風格是否適合各類應用。微信

屏幕旋轉

更新內容:
網絡

  • 爲避免無心的旋轉,咱們新增了一種模式,哪怕設備位置發生變化,也會固定在當前屏幕方向上。 必要時用戶能夠經過按系統欄上的一個按鈕手動觸發旋轉。
  • 從 Android 9 開始,對縱向旋轉模式作出了重大變動。縱向模式已重命名爲旋轉鎖定,它會在自動屏幕旋轉關閉時啓用。當設備處於旋轉鎖定模式時,用戶可將其屏幕鎖定到頂層可見 Activity 所支持的任何旋轉。 Activity 不該假定它將始終以縱向呈現。

影響:用戶能夠在關閉自動旋轉的狀況下,手動旋轉屏幕;可否旋轉取決於頂層可見Activity所支持的旋轉設置。

文本

更新內容:

  • 文本預先計算:PrecomputedText 類使您能提早計算和緩存所需信息,改善了文本渲染性能。
  • 放大器:Magnifier 類是一種可提供放大器API的微件,可在全部應用中實現一致的放大器功能體驗。
  • Smart Linkify:Android 9加強了TextClassifier類,該類可利用機器學習在選定文本中識別一些實體並建議採起相應的操做。
  • 文本佈局:藉助幾種便捷函數和屬性,能夠更輕鬆地實現界面設計。

影響:這個無疑強化了TextView的能力,應該比如今設置TextVie的樣式方便。

電源管理

更新內容:

  • 新增了一個應用待機羣組的概念,系統將根據用戶的使用模式限制應用對 CPU 或電池等設備資源的訪問。
    五個羣組以下:
  1. 活躍
    若是用戶當前正在使用應用,應用將被歸到「活躍」羣組中。
    若是應用處於「活躍」羣組,系統不會對應用的做業、報警或 FCM 消息施加任何限制。
  2. 工做集
    若是應用常常運行,但當前未處於活躍狀態,它將被歸到「工做集」羣組中。
    若是應用處於「工做集」羣組,系統會對它運行做業和觸發報警的能力施加輕度限制。
  3. 經常使用
    若是應用會按期使用,但不是天天都必須使用,它將被歸到「經常使用」羣組中。
    若是應用處於「經常使用」羣組,系統將對它運行做業和觸發報警的能力施加較強的限制,也會對高優先級 FCM 消息的數量設定限制。
  4. 極少使用
    若是應用不常用,那麼它屬於「極少使用」羣組。
    若是應用處於「極少使用」羣組,系統將對它運行做業、觸發警報和接收高優先級FCM消息的能力施加嚴格限制。系統還會限制應用鏈接到網絡的能力。
  5. 從未使用
    安裝可是從未運行過的應用會被歸到「從未使用」羣組中。 系統會對這些應用施加極強的限制。

影響:無疑,Google在幫Android機省電、省資源,算是一個比較好的更新。可是,我相信廠商會對分組策略稍做修改(或者白名單啥的),以方便自身應用能夠隨時喚起。總的來講,應該對應用的Push類功能影響較大。

權限變動

更新內容:

  • 構建序列號棄用:在 Android 9 中,Build.SERIAL 始終設置爲 "UNKNOWN" 以保護用戶的隱私。 若是您的應用須要訪問設備的硬件序列號,您應改成請求 READ_PHONE_STATE 權限,而後調用 getSerial()。
  • 前臺服務:針對 Android 9 或更高版本並使用前臺服務的應用必須請求 FOREGROUND_SERVICE 權限。
  • 後臺對傳感器的訪問受限:Android 9限制後臺應用訪問用戶輸入和傳感器數據的能力。
  • 限制訪問通話記錄:Android 9 引入 CALL_LOG 權限組並將 READ_CALL_LOG、WRITE_CALL_LOG 和 PROCESS_OUTGOING_CALLS 權限移入該組。
  • 限制訪問電話號碼:在未首先得到 READ_CALL_LOG 權限的狀況下,除了應用的用例須要的其餘權限以外,運行於 Android 9 上的應用沒法讀取電話號碼或手機狀態。
  • 限制訪問 Wi-Fi 位置和鏈接信息:在 Android 9 中,應用進行 Wi-Fi 掃描的權限要求比以前的版本更嚴格。具體限制見官網。

影響:權限變動對收集手機應用信息和監聽應用WiFi狀態(主要是位置信息)都有很大影響,特別是對這些信息有依賴的業務。

Apache Http被棄用

更新內容:
從 Android 9 開始,默認狀況下Apache網絡庫已從 bootclasspath 中移除且不可用於應用。

影響:終於要完全拋棄Apache庫了,但業務硬要用的話,仍是能夠本身導入jar包或者在AndroidManifest.xml中使用uses-library標籤。

適配齊劉海

有Pixel的同窗能夠把系統升級到Pie查看效果,也能夠下載相應鏡像用模擬器測試(最好使用AS3.1以上)。Pie提供了三種劉海樣式:

  1. 邊角劉海(我相信絕對沒廠商用...)

邊角劉海

  1. 正常劉海(被iPhone X帶出來的風格...)

正常劉海

  1. 劉海+下巴(爲啥要有下巴...)

劉海

下巴

在Pie中,官方提供了DisplayCutout類來獲取劉海塊相關數據;經過getDisplayCutout()函數能夠知道劉海是否存在;經過窗口布局屬性layoutInDisplayCutoutMode可讓應用爲設備屏幕缺口周圍的內容進行佈局。直接上代碼吧:

@TargetApi(28)
    public void showDisplayCutout(View view) {
        // 注意:getRootWindowInsets()只有在視圖加載完成後,纔會返回,不然返回null
        DisplayCutout cutout = getWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
        if (cutout == null) { // 沒有劉海的時候拿到的DisplayCutout爲null
            Log.d(TAG, "showDisplayCutout: 普通屏,沒有劉海");
            return;
        }
        List<Rect> rects = cutout.getBoundingRects(); // 獲取可能的凹凸塊
        if (rects.size() == 1) {
            Log.d(TAG, "showDisplayCutout: 有劉海");
            Rect rect = rects.get(0);
            Log.d(TAG, "showDisplayCutout: 劉海區域:" + rect);
        } else {
            Log.d(TAG, "showDisplayCutout: 有劉海和下巴");
            for (Rect rect : rects) {
                Log.d(TAG, "showDisplayCutout: 區域:" + rect);
            }
        }

        // 獲取安全區域的數據,單位px
        int safeInsetLeft = cutout.getSafeInsetLeft();
        int safeInsetTop = cutout.getSafeInsetTop();
        int safeInsetRight = cutout.getSafeInsetRight();
        int safeInsetBottom = cutout.getSafeInsetBottom();
        Log.d(TAG, "showDisplayCutout: 安全區域距離屏幕左邊:" + safeInsetLeft);
        Log.d(TAG, "showDisplayCutout: 安全區域距離屏幕頂部:" + safeInsetTop);
        Log.d(TAG, "showDisplayCutout: 安全區域距離屏幕右邊:" + safeInsetRight);
        Log.d(TAG, "showDisplayCutout: 安全區域距離屏幕底部:" + safeInsetBottom);

        int statusBarHeight = getStatusBarHeight();
        Log.d(TAG, "showDisplayCutout: 狀態欄高度:" + statusBarHeight);
    }

    private int getStatusBarHeight() {
        int result = 0;
        int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            result = getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }
複製代碼

注意

  • getRootWindowInsets()只有在View加載完後纔會返回值,不然返回null。因此,若是你想在生命週期(onCreate等)裏直接調用的話,可能會出現空指針異常,可使用post runnable的方式處理。
  • 劉海的高度不必定和狀態欄的高度同樣,應該是小於登陸狀態欄高度。

設置窗口屬性:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        // 設置全屏
        getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);

        //沉浸式狀態欄
// getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);

        WindowManager.LayoutParams lp = getWindow().getAttributes();
        // 只有當DisplayCutout徹底包含在系統欄中時,才容許窗口延伸到DisplayCutout區域。 不然,窗口布局不與DisplayCutout區域重疊。
        lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
        // 該窗口決不容許與DisplayCutout區域重疊。
// lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
        // 該窗口始終容許延伸到屏幕短邊上的DisplayCutout區域。
// lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
        getWindow().setAttributes(lp);
    }
複製代碼

部分效果圖:

  1. 普通頁面帶狀態欄

    頁面帶狀態欄

  2. 全屏頁面默認狀況

    全屏頁面默認狀況

  3. 全屏頁面強制使用劉海區域

    全屏強制使用劉海區域

  4. 沉浸式

    沉浸式頁面

適配方案

  • 對於有狀態欄的頁面,不會受到劉海的影響,內容會展現在狀態欄下方。
  • 對於全屏頁面,默認狀況下系統會把展現內容下移到劉海之下,會產生一個黑條,此時須要注意頁面底部的內容,要避免由於下移致使的顯示不全問題;若是修改了窗口配置爲LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES,則會強制使用劉海區域,這時頁面要規避劉海塊的那一點區域。
  • 對於沉浸式頁面,是受到影響最大的,默認狀況下就會使用劉海區域,因此頁面內容要進行區域規避。

非SDK接口限制

Android 9(API 級別 28)引入了針對非 SDK 接口的使用限制,不管是直接使用仍是經過反射或 JNI 間接使用。不管應用是引用非 SDK 接口仍是嘗試使用反射或 JNI 獲取其句柄,均適用這些限制。
若是咱們使用非SDK接口,會出現幾種狀況:

  1. logcat日誌
    相似 Accessing hidden field Landroid/os/Message;->flags:I (light greylist, JNI) 格式;
  2. 彈警告Toast
  3. 引起系統錯誤

非SDK接口都記錄在了不一樣的名單下:

  1. 白名單:SDK
  2. 淺灰名單:仍能夠訪問的非 SDK 函數/字段。
  3. 深灰名單: 對於目標 SDK 低於 API 級別 28 的應用,容許使用深灰名單接口。 對於目標 SDK 爲 API 28 或更高級別的應用:行爲與黑名單相同。
  4. 黑名單:受限,不管目標 SDK 如何,平臺將表現爲彷佛接口並不存在。

全部的名單文件都在:android.googlesource.com/platform/pr… ,其中還有一個veridex檢測工具(目前只支持linux和mac)。

限制原理分析

直接借用大佬們的文章: 360奇卓社-Android P 調用隱藏API限制原理
大體過程:

  1. 編譯過程當中有一個hiddenapi過程,會根據名單文件,對dex中全部函數和字段進行從新標記;
  2. runtime的時候,Art虛擬機會根據函數和字段的標記,並結合調用者的身份返回特定值;這些值就決定了調用者是否能成功調用接口。

階段一

階段二

如何繞過

仍是大佬們的文章:

  1. 360奇卓社-突破Android P(Preview 1)對調用隱藏API限制的方法
  2. 一種繞過Android P對非SDK接口限制的簡單方法
  3. 突破Android P非SDK API限制的幾種代碼實現

其中替換classloader的方式目前是最可靠的。

總結

  1. 文中適配齊劉海的演示代碼能夠在:github.com/zjxstar/And… 中查看。
  2. 對於非SDK接口,能不用就不用吧(不用纔怪...)。
  3. 對於其餘行爲變動,主要關注隱私權限的變動,畢竟用戶對隱私權限愈來愈敏感了。

參考資料:

  1. Google官網;(強調一下)
  2. 其餘資料已經在文中列出,再也不重複;

關注微信公衆號,最新技術乾貨實時推送

相關文章
相關標籤/搜索