Android 9.0適配及部分新特性介紹

1、Android 9.0應用遷移

1.1 概述

在最開始針對速貸進行Android9.0版本的適配時,我使用的是真機是vivo X21A,將targetSdkVersion升到28,運行發現網絡請求全報400 Bad Request,查閱了網上針對android9.0網絡請求問題的解決方案以及其他的一些遷移到Android9.0需注意的點(下文會講到),做了些適配和調整,發現仍有問題,通過抓包也並沒有發現問題的原因所在。隨後試了下模擬器以及借來的google pixel(9.0)真機運行了下,能正常使用,並沒有發現什麼問題,於是猜想是機子本身系統的問題。

  • 11.01更新:莫名又正常運行了。
1.2 Android9的遷移和適配

在模擬器上的適配我這邊做的不多,官網有比較詳細的介紹: 將應用遷移到 Android 9

1.2.1 針對Android 9設備上運行的所有應用都有影響的關鍵變化有(列舉了常用的,具體看官方文檔):
  • * 對非 SDK 接口的限制:現已禁止訪問特定的非 SDK 接口,無論是直接訪問,還是通過 JNI 或反射進行間接訪問。嘗試訪問受限制的接口時,會生成 NoSuchFieldException 和 NoSuchMethodException 之類的錯誤。
  • 移除加密提供程序:從 Android 9 開始,Crypto JCA 提供程序已被移除。調用 SecureRandom.getInstance("SHA1PRNG", "Crypto") 將會引發 NoSuchProviderException。
  • 更嚴格的 UTF-8 解碼器:在 Android 9 中,針對 Java 語言的 UTF-8 解碼器比以往更嚴格,並且遵循 Unicode 標準。
  • 強制執行 FLAG_ACTIVITY_NEW_TASK :在 Android 9 中,您不能從非 Activity 環境中啓動 Activity,除非您傳遞 Intent 標誌 FLAG_ACTIVITY_NEW_TASK。 如果您嘗試在不傳遞此標誌的情況下啓動 Activity,則該 Activity 不會啓動,系統會在日誌中輸出一則消息。
1.2.2 targetSdkVersion 設置爲 28 時影響應用的關鍵變化(列舉了常用的,具體看官方文檔):
  • * 默認情況下啓用網絡傳輸層安全協議 (TLS):如果應用以 Android 9 或更高版本爲目標平臺,則默認情況下 isCleartextTrafficPermitted() 函數返回 false。 如果您的應用需要爲特定域名啓用明文,您必須在應用的網絡安全性配置中針對這些域名將 cleartextTrafficPermitted 顯式設置爲 true。
  • * 前臺服務:針對 Android 9 或更高版本並使用前臺服務的應用必須請求 FOREGROUND_SERVICE 權限。 這是普通權限,因此,系統會自動爲請求權限的應用授予此權限。
  • 按進程分設基於網絡的數據目錄:爲改善 Android 9 中的應用穩定性和數據完整性,應用無法再讓多個進程共用同一 WebView 數據目錄。 此類數據目錄一般存儲 Cookie、HTTP 緩存以及其他與網絡瀏覽有關的持久性和臨時性存儲。
  • 構建序列號棄用:在 Android 9 中,Build.SERIAL 始終設置爲 "UNKNOWN" 以保護用戶的隱私。 如果您的應用需要訪問設備的硬件序列號,您應改爲請求 READ_PHONE_STATE 權限,然後調用 getSerial()。

其中星號*標註的是我在項目中添加的修改部分。

1.3、適配詳解(重要部分)
1.3.1、non-sdk 接口限制

non-sdk即非 SDK 接口,它們是不屬於官方 Android SDK 的 Java 字段和函數,它們屬於實現詳情,不提倡被調用或者被禁止調用的,需要通過反射等其他手段來實現;而SDK接口是官方提供的,公開的標準接口,可以被我們調用。

我們可以通過查看日誌消息來得知調用的非SDK接口屬於灰名單還是黑名單:

也可以使用命令掃描整個app裏面存在的非 SDK 接口:

1appcompat.sh --dex-file=apk路徑

如:

/Users/Clem/常用工具/runtime-master-appcompat/veridex-mac/appcompat.sh --dex-file=/Users/Clem/常用工具/runtime-master-appcompat/app-jianrongsudai-debug.apk 結果如圖:

  • 白名單:即SDK
  • 淺灰名單(72個):仍可以訪問的非 SDK 函數/字段
  • 深灰名單(7個):對於目標 SDK 低於 API 級別 28 的應用,允許使用深灰名單接口; 對於目標 SDK 爲 API 28 或更高級別的應用:行爲與黑名單相同
  • 黑名單(0個):受限,無論目標 SDK 如何,平臺將表現爲似乎接口並不存在

列入淺灰名單的非 SDK 接口包含可以在 Android 9 中繼續工作的函數和字段,但不能保證在未來版本的平臺中能夠繼續訪問,主要需要關注深灰名單和黑名單,需要找到可以替代的SDK接口進行適配。網上有人發現了繞過API檢查的方法,也有專門的庫允許在Android P上使用反射而沒有任何限制,如FreeReflection:

1//允許在Android P上使用反射而不受任何限制
2implementation 'me.weishu:free_reflection:1.2.0'
3
4//在App.java中加入即可:
5Reflection.unseal(this);
1.3.2、http網絡請求的問題

在我們項目中,使用OKHttp請求會出現如下異常:

1java.net.UnknownServiceException: CLEARTEXT communication ** not permitted by network security policy

也就是說,當API級別爲28及以上時,應用使用的如果是非加密的明文流量的http網絡請求,則會導致該應用無法進行網絡請求,https則不會受影響,網上提供了三種方案:

  • APP改用https請求
  • targetSdkVersion 降到27及以下
  • 在 res 下新增一個 xml 目錄,然後創建一個名爲:network_security_config.xml 文件(名字可自定),大概意思就是允許開啓http請求,內容如下:
1<?xml version="1.0" encoding="utf-8"?>
2<network-security-config>
3    <base-config cleartextTrafficPermitted="true" />
4</network-security-config>

然後在APP的AndroidManifest.xml文件下的application標籤增加以下屬性即可完成:

1<application
2...
3 android:networkSecurityConfig="@xml/network_security_config"
4...
5/>
1.4、後續問題

在完成上述的適配修改後,交給測試測了以後發現瞭如下問題:

  • 1、綁定公積金,選擇城市時,頁面會崩潰
  • 2、環境切換功能,點擊測試入口時,頁面會崩潰
  • 3、需支持支付寶h5支付
  • 4、偶現人臉識別不成功,反覆進入活體認證頁面

針對問題一和二:我測試了下Android9.0的模擬器以及google pixel(9.0)真機,發現並沒有這些問題,而在vivo X21A真機上面,當頁面崩潰時也沒有任何明確的錯誤日誌,只看到如圖的信息:

通過打斷點調試發現在vivo X21A真機上,無法使用Spinner和RadioButton控件(目前看到的就這兩個),一旦使用就會造成崩潰,這個比較頭疼,猜測是系統問題。

  • 11.07更新:使用新機子華爲 Mate 20發現並沒有這個問題。

針對問題三:測試了一下只有targetSdkVersion 設置爲 28時纔會出現該問題,與設備的系統版本無關,上螞蟻金服平臺查了下最新文檔發現,App支付功能近期有了更新和升級,具體來說就是:打包方式更換爲 AAR,替代之前的 JAR 打包,SDK 支付接口部分不變(親測有效)。下載官方demo可以發現附帶的更新日誌文檔中也有記錄這些,如圖:

針對問題四,由於是偶現,且也沒有任何明確的報錯日誌,需要多個真機進行多次測試。

2、新特性介紹(功能及API)

2.1、顯示屏缺口支持:layoutInDisplayCutoutMode

Android P的真機設備或模擬器上都可以模擬屏幕缺口,提供了三種樣式。

  • 11.07更新:使用新機子華爲 Mate 20發現開發者選項中沒有提供模擬屏幕缺口,且該機也自帶有凹口位置,狀態欄也一直處於劉海區域。下面提到的模式針對該機效果都不變。

API 28也提供了新的類: DisplayCutout 類,該類主要用於獲取凹口位置和安全區域的位置等。 主要接口如下:

方法

接口說明

getBoundingRects()

返回Rects的列表,每個Rects都是顯示屏上非功能區域的邊界矩形

getSafeInsetLeft ()

返回安全區域距離屏幕左邊的距離,單位是px。

getSafeInsetRight ()

返回安全區域距離屏幕右邊的距離,單位是px。

getSafeInsetTop ()

返回安全區域距離屏幕頂部的距離,單位是px。

getSafeInsetBottom()

返回安全區域距離屏幕底部的距離,單位是px。

此外,API 28中還提供了新的佈局參數屬性 layoutInDisplayCutoutMode ,包含了三種不同模式:

模式

模式說明

LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT

只有當DisplayCutout完全包含在系統欄中時,才允許窗口延伸到DisplayCutout區域。 否則,窗口布局不與DisplayCutout區域重疊。

LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER

該窗口決不允許與DisplayCutout區域重疊。

LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES

該窗口始終允許延伸到屏幕短邊上的DisplayCutout區域。

注:在Android P之前,各大手機廠商針對劉海適配方案都不太一樣。

2.2、適用於可繪製對象和位圖: ImageDecoder

可以將PNG, JPEG, WEBP, GIF, or HEIF 格式的圖片的轉換成Drawable 或者Bitmap 對象的類,可不再使用BitmapFactoryBitmapFactory.Options API。

 1ImageDecoder.OnHeaderDecodedListener listener = new ImageDecoder.OnHeaderDecodedListener() {
 2                @Override
 3                public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info, ImageDecoder.Source source) {
 4                //將解碼的圖像縮放到精確尺寸
 5                    decoder.setTargetSampleSize(2);
 6                }
 7            };
 8            ImageDecoder.Source source = ImageDecoder.createSource(getResources(), R.mipmap.ic_launcher);
 9            //轉換成Drawable對象
10            Drawable drawable = ImageDecoder.decodeDrawable(source, listener);
11            //轉換成Bitmap對象
12            //Bitmap bitmap = ImageDecoder.decodeBitmap(source);
2.3、動畫:AnimatedImageDrawable

Android 9 引入了 AnimatedImageDrawable 類,用於繪製和顯示 GIF 和 WebP 動畫圖像。 AnimatedImageDrawable 的工作方式與 AnimatedVectorDrawable 的相似之處在於,都是渲染線程驅動 AnimatedImageDrawable 的動畫。 渲染線程還使用工作線程進行解碼,因此,解碼不會干擾渲染線程的其他操作。 這種實現機制允許您的應用在顯示動畫圖像時,無需管理其更新,也不會干擾應用界面線程上的其他事件。

1private void decodeImage() throws IOException {
2    Drawable decodedAnimation = ImageDecoder.decodeDrawable(
3        ImageDecoder.createSource(getResources(), R.drawable.my_drawable));
4
5    if (decodedAnimation instanceof AnimatedImageDrawable) {
6        // 如果屬於動畫,則優先開啓動畫,展示第一幀
7        ((AnimatedImageDrawable) decodedAnimation).start();
8    }
9}
2.4、Magnifier(放大鏡)

Android P引入了Magnifier來提升用戶選擇文字的體驗,它通過放大鏡將文字放大從而來幫助用戶準確定位想要選擇的文字:

 1@RequiresApi(api = 28)
 2        @Override
 3        public boolean onTouch(View v, MotionEvent event) {
 4            Magnifier magnifier = new Magnifier(v);
 5            switch (event.getActionMasked()) {
 6                case MotionEvent.ACTION_DOWN:
 7                    magnifier.show(event.getX(), event.getY());
 8                    break;
 9                case MotionEvent.ACTION_MOVE:
10                    magnifier.show(event.getX(), event.getY());
11                    break;
12                case MotionEvent.ACTION_UP:
13                    magnifier.dismiss();
14                    break;
15                default:
16                    break;
17            }
18            return true;
19        }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章