適配Android Q指南

原文鏈接:https://developer.android.google.cn/preview/behavior-changes-all

一 、行爲變更:所有應用

Android Q 平臺包含一些行爲變更,這些變更可能會影響您的應用。以下行爲變更將影響在 Android Q 上運行的所有應用,無論其採用哪種 targetSdkVersion 都不例外。您應該測試您的應用,然後根據需要進行更改以適當地支持這些變更(如果適用)。

此外,請務必查看僅影響以 Android Q 爲目標平臺的應用的行爲變更列表。

注意:除了此頁面所列的變更以外,Android Q 還引入了大量變更和限制以增強用戶隱私保護。有關詳情,請參閱隱私權頁面。

非 SDK 接口限制

爲了幫助確保應用穩定性和兼容性,Android 平臺開始限制您的應用可在 Android 9(API 級別 28)中使用哪些非 SDK 接口。Android Q 包含更新後的受限非 SDK 接口列表(基於與 Android 開發者之間的協作以及最新的內部測試)。我們的目標是在限制使用非 SDK 接口之前確保有可用的公開替代方案。

如果您不打算以 Android Q 爲目標平臺,那麼其中一些變更可能不會立即對您產生影響。雖然您目前可以使用灰名單中的一些非 SDK 接口(取決於您應用的目標 API 級別),但如果您使用任何非 SDK 方法或字段,則應用無法運行的風險終歸較高。

如果您不確定自己的應用是否使用了非 SDK 接口,則可以測試該應用進行確認。如果您的應用依賴於非 SDK 接口,則應該開始計劃遷移到 SDK 替代方案。不過,我們知道某些應用具有使用非 SDK 接口的有效用例。如果您無法爲應用中的某項功能找到使用非 SDK 接口的替代方案,則應該請求新的公共 API

要了解詳情,請參閱非 SDK 接口在 Android Q 中的受限情況出現變化以及針對非 SDK 接口的限制

訪問受限的非 SDK 接口時可能會出現的預期行爲

下表說明了當您的應用嘗試訪問黑名單中的非 SDK 接口時可能會出現的預期行爲。

訪問方式 結果
Dalvik 指令引用某個字段 拋出 NoSuchFieldError
Dalvik 指令引用某個方法 拋出 NoSuchMethodError
通過 Class.getDeclaredField()Class.getField() 進行反射 拋出 NoSuchFieldException
通過 Class.getDeclaredMethod()Class.getMethod() 進行反射 拋出 NoSuchMethodException
通過 Class.getDeclaredFields()Class.getFields() 進行反射 結果中未獲取到非 SDK 成員
通過 Class.getDeclaredMethods()Class.getMethods() 進行反射 結果中未獲取到非 SDK 成員
通過 env->GetFieldID() 進行 JNI 調用 返回 NULL,拋出 NoSuchFieldError
通過 env->GetMethodID() 進行 JNI 調用 返回 NULL,拋出 NoSuchMethodError

 

手勢導航

從 Android Q 開始,用戶可以在設備中啓用手勢導航。如果用戶啓用手勢導航,則會影響設備上的所有應用,無論應用是否以 Android Q 爲目標平臺,都是如此。例如,如果用戶從屏幕邊緣向內滑動,系統會將該手勢解讀爲“返回”導航,除非應用針對屏幕的相應部分明確替換該手勢

爲了確保您的應用與手勢導航兼容,您需要將應用內容擴展到屏幕邊緣,並適當地處理存在衝突的手勢。有關信息,請參閱手勢導航文檔。

NDK

Android Q 包含以下 NDK 方面的變更。

共享對象不得包含文本重定位

Android 6.0(API 級別 23)已禁止在共享對象中使用文本重定位。代碼必須按原樣加載,且不得修改。此變更可以縮短應用的加載時間並提高安全性。

在 Android Q 測試版 1 和 2 中,SELinux 對以 Android 8.0(API 級別 26)及更高版本爲目標平臺的應用強制執行此限制。從 Android Q 測試版 3 開始,將對以 Android Q(API 級別 29)及更高版本爲目標平臺的應用強制執行此限制。如果這些應用繼續使用包含文本重定位的共享對象,則出現故障的風險較高。

Bionic 庫和動態鏈接器路徑變更

從 Android Q 開始,多個路徑不再採用常規文件形式,而是採用符號鏈接形式。如果應用一直以來依賴的都是採用常規文件形式的路徑,則可能會出現故障:

  • /system/lib/libc.so -> /apex/com.android.runtime/lib/bionic/libc.so
  • /system/lib/libm.so -> /apex/com.android.runtime/lib/bionic/libm.so
  • /system/lib/libdl.so -> /apex/com.android.runtime/lib/bionic/libdl.so
  • /system/bin/linker -> /apex/com.android.runtime/bin/linker

這些變更也會影響文件的 64 位版本,對於這些版本,會將 lib/ 替換爲 lib64/

爲了確保兼容性,新符號鏈接會基於舊路徑提供,例如 /system/lib/libc.so 現在是指向 /apex/com.android.runtime/lib/bionic/libc.so 的符號鏈接,等等。因此,dlopen(“/system/lib/libc.so”) 會繼續工作,但當應用嘗試通過讀取 /proc/self/maps 或類似項來檢測已加載的庫時,將會發現不同之處。這並不常見,但我們發現一些應用會將這種做法作爲對抗黑客攻擊的一項舉措。如果是這樣,則應該將新的 /apex/… 路徑添加爲 Bionic 文件的有效路徑。

系統二進制文件/庫會映射到只執行內存

從 Android Q 開始,系統二進制文件和庫會映射到只執行(不可讀取)內存,作爲應對代碼重用攻擊的安全強化技術。有意或意外讀入已標記爲只執行的內存段會拋出 SIGSEGV,無論此讀入行爲是來自錯誤、漏洞還是有意的內存自省都不例外。

您可以通過檢查 /data/tombstones/ 中的相關 tombstone 文件來確定崩潰是否由變更改所導致。與只執行相關的崩潰包含以下中止消息:

    Cause: execute-only (no-read) memory access error; likely due to data in .text.
    

 

要解決此問題,開發者可以通過調用 mprotect() 將只執行內存段標記爲“讀取+執行”,例如用於執行內存檢查。不過,我們強烈建議您事後將其重新設爲只執行,因爲這樣可以更好地保護您的應用和用戶。

ptrace 的調用不會受到影響,因此 ptrace 調試也不會受到影響。

安全性

Android Q 包含以下安全性方面的變更。

移除了應用主目錄的執行權限

以 Android Q 爲目標平臺的不受信任的應用無法再針對應用主目錄中的文件調用 exec()。這種從可寫應用的主目錄執行文件的行爲違反了 W^X。應用應該僅加載嵌入到應用的 APK 文件中的二進制代碼。

此外,以 Android Q 爲目標平臺的應用無法針對已執行 dlopen() 的文件中的可執行代碼進行內存中修改。這包括含有文本重定位的所有共享對象 (.so) 文件。

WLAN 直連廣播

在 Android Q 中,以下與 WLAN 直連相關的廣播不再具有粘性。

如果您的應用依賴於在註冊時接收這些廣播(因爲其之前一直具有粘性),請在初始化時使用適當的 get() 方法獲取信息。

WLAN 感知功能

Android Q 擴大了支持範圍,現在可以使用 WLAN 感知數據路徑輕鬆創建 TCP/UDP 套接字。要創建連接到 ServerSocket 的 TCP/UDP 套接字,客戶端設備需要知道服務器的 IPv6 地址和端口。這在之前需要通過頻外方式進行通信(例如使用 BT 或 WLAN 感知第 2 層消息傳遞),或者使用其他協議(例如 mDNS)通過頻內方式發現。而藉助 Android Q,可以將此類消息作爲網絡設置的一部分進行傳遞。

服務器可以執行以下任一操作:

  • 初始化 ServerSocket 並設置或獲取要使用的端口。
  • 將端口信息指定爲 WLAN 感知網絡請求的一部分。

以下代碼示例顯示瞭如何將端口信息指定爲網絡請求的一部分:

KotlinJava更多

    val ss = ServerSocket()
    val ns = WifiAwareNetworkSpecifier.Builder(discoverySession, peerHandle)
      .setPskPassphrase("some-password")
      .setPort(ss.localPort)
      .build()

    val myNetworkRequest = NetworkRequest.Builder()
      .addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
      .setNetworkSpecifier(ns)
      .build()
    

 

    ServerSocket ss = new ServerSocket();
    WifiAwareNetworkSpecifier ns = new WifiAwareNetworkSpecifier
      .Builder(discoverySession, peerHandle)
      .setPskPassphrase(“some-password”)
      .setPort(ss.getLocalPort())
      .build();

    NetworkRequest myNetworkRequest = new NetworkRequest.Builder()
      .addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
      .setNetworkSpecifier(ns)
      .build();
    

 

然後,客戶端會執行 WLAN 感知網絡請求來獲取服務器提供的 IPv6 和端口:

KotlinJava更多

    val callback = object : ConnectivityManager.NetworkCallback() {
      override fun onAvailable(network: Network) {
        ...
      }

      override fun onLinkPropertiesChanged(network: Network,
          linkProperties: LinkProperties) {
        ...
      }

      override fun onCapabilitiesChanged(network: Network,
          networkCapabilities: NetworkCapabilities) {
        ...
        val ti = networkCapabilities.transportInfo
        if (ti is WifiAwareNetworkInfo) {
           val peerAddress = ti.peerIpv6Addr
           val peerPort = ti.port
        }
      }
      override fun onLost(network: Network) {
        ...
      }
    };

    connMgr.requestNetwork(networkRequest, callback)
    

 

    callback = new ConnectivityManager.NetworkCallback() {
      @Override
      public void onAvailable(Network network) {
        ...
      }
      @Override
      public void onLinkPropertiesChanged(Network network,
          LinkProperties linkProperties) {
        ...
      }
      @Override
      Public void onCapabilitiesChanged(Network network,
          NetworkCapabilities networkCapabilities) {
        ...
        TransportInfo ti = networkCapabilities.getTransportInfo();
        if (ti instanceof WifiAwareNetworkInfo) {
           WifiAwareNetworkInfo info = (WifiAwareNetworkInfo) ti;
           Inet6Address peerAddress = info.getPeerIpv6Addr();
           int peerPort = info.getPort();
        }
      }
      @Override
      public void onLost(Network network) {
        ...
      }
    };

    connMgr.requestNetwork(networkRequest, callback);
    

 

Go 設備上的 SYSTEM_ALERT_WINDOW

在 Android Q(Go 版本)設備上運行的應用無法獲得 SYSTEM_ALERT_WINDOW 權限。這是因爲繪製疊加層窗口會使用過多的內存,這對低內存 Android 設備的性能十分有害。

如果在搭載 Android 9 或更低版本的 Go 版設備上運行的應用獲得了 SYSTEM_ALERT_WINDOW 權限,則即使設備升級到 Android Q 也會保留此權限。不過,尚不具有此權限的應用在設備升級後便無法獲得此權限了。

如果 Go 設備上的應用發送具有 ACTION_MANAGE_OVERLAY_PERMISSION 操作的 intent,則系統會自動拒絕此請求,並將用戶轉到設置屏幕,上面會顯示不允許授予此權限,原因是它會減慢設備的運行速度。如果 Go 設備上的應用調用 Settings.canDrawOverlays(),則此方法始終返回 false。同樣,這些限制不適用於在設備升級到 Android Q 之前便已收到 SYSTEM_ALERT_WINDOW 權限的應用。

關於以舊版 Android 系統爲目標平臺的應用的警告

在 Android Q 中,當用戶首次運行以 Android 6.0(API 級別 23)以下的版本爲目標平臺的任何應用時,Android 平臺會向用戶發出警告。如果此應用要求用戶授予權限,則系統會先向用戶提供調整應用權限的機會,然後纔會允許此應用首次運行。

由於 Google Play 的目標 API 方面的要求,用戶只有在運行最近未更新的應用時纔會看到這些警告。對於通過其他商店分發的應用,我們也將於 2019 年引入類似的目標 API 方面的要求。要詳細瞭解這些要求,請參閱在 2019 年擴展目標 API 級別方面的要求

移除了 SHA-2 CBC 加密套件

以下 SHA-2 CBC 加密套件已從平臺中移除:

  • TLS_RSA_WITH_AES_128_CBC_SHA256
  • TLS_RSA_WITH_AES_256_CBC_SHA256
  • TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
  • TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
  • TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
  • TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384

這些加密套件不如使用 GCM 的類似加密套件安全,並且大多數服務器要麼同時支持這些加密套件的 GCM 變體和 CBC 變體,要麼二者均不支持。

注意:應用和庫應該讓其所需的加密套件集與 getSupportedCipherSuites() 中返回的值相交,以便提前防範加密套件日後遭到移除。

應用使用情況

Android Q 引入了與應用使用情況相關的以下行爲變更:

  • UsageStats 應用使用情況的改進 -- 當在分屏或畫中畫模式下使用應用時,Android Q 現在能夠使用 UsageStats 準確地跟蹤應用使用情況。此外,Android Q 現在可以跟蹤免安裝應用的使用情況。

  • 按應用開啓灰度模式 -- Android Q 現在可以將應用設爲灰度顯示模式。

  • 按應用開啓干擾模式 -- Android Q 現在可以選擇性地將應用設爲“干擾模式”,此時系統會禁止顯示其通知,並且不會將其顯示爲推薦的應用。

  • 暫停和播放 -- 在 Android Q 中,暫停的應用無法再播放音頻。

HTTPS 連接變更

如果運行 Android Q 的應用將 null 傳遞給 setSSLSocketFactory(),現在會出現 IllegalArgumentException。在以前的版本中,將 null 傳遞給 setSSLSocketFactory() 與傳入當前的默認 SSL 套接字工廠效果相同。

android.preference 庫現已棄用

android.preference 庫現已棄用。開發者應該改爲使用 AndroidX preference 庫,這是Android Jetpack 的一部分。如需獲取其他有助於遷移和開發的資源,請查看經過更新的設置指南以及我們的公開示例應用參考文檔

ZIP 文件實用程序庫變更

Android Q 對 java.util.zip 軟件包(用於處理 ZIP 文件)中的類做出了以下變更。這些變更會讓庫的行爲在 Android 和使用 java.util.zip 的其他平臺之間更加一致。

Inflater

在以前的版本中,如果在調用 end() 之後調用 Inflater 類中的某些方法,這些方法會拋出 IllegalStateException。在 Android Q 中,這些方法會拋出 NullPointerException

ZipFile

如果所提供的 ZIP 文件不包含任何文件,則 ZipFile 的構造函數(採用的參數類型爲 FileintCharset)不再拋出 ZipException

ZipOutputStream

如果 ZipOutputStream 中的 finish() 方法嘗試爲不包含任何文件的 ZIP 文件寫入輸出流,此方法不再拋出 ZipException

攝像頭變更

很多使用攝像頭的應用都會假定如果設備採用縱向配置,則物理設備也會處於縱向,正如攝像頭方向中所述。在過去可以做出這樣的假定,但在推出新型設備(例如可摺疊設備)後,這發生了變化。針對這些設備做出這樣的假定可能導致相機取景器的顯示產生錯誤的旋轉和/或縮放。

以 API 級別 24 或更高級別爲目標平臺的應用應該明確設置 android:resizeableActivity,並提供必要的功能來處理多窗口操作。

電池用量跟蹤

從 Android Q 開始,只有在發生重大充電事件之後拔下設備電源插頭,SystemHealthManager 纔會重置其電池用量統計信息。一般來說,重大充電事件指的是設備電池已充滿,或者設備電量從幾乎耗盡變爲即將充滿。

在 Android Q 之前,無論何時拔下設備電源插頭,無論電池電量有多微小的變化,電池用量統計信息都會重置。

二、Android Q 中的隱私權:重大隱私權變更

1、分區儲存

與舊版 Android 相比,從 Android Q 測試版 5 開始,以 Android 9(API 級別 28)或更低版本爲目標平臺的應用在存儲工作方式方面默認沒有任何變化。當您更新現有應用以使用分區存儲時,即使您的應用以 Android 9 或更低版本爲目標平臺,您也可以使用新的 requestLegacyExternalStorage 清單屬性爲 Android Q 設備上的應用啓用這種新行爲。

爲了讓用戶更好地控制自己的文件,減少文件混亂情況,Android Q 更改了應用對設備外部存儲設備中的文件(例如存儲在路徑 /sdcard 下的文件)的訪問方式。Android Q 會繼續使用 READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE 權限,這些權限與面向用戶的存儲運行時權限相對應。不過,默認情況下,以 Android Q 爲目標平臺的應用(以及選擇接受這些變更的應用)在訪問外部存儲設備中的文件時會進入過濾視圖。此類應用只能查看特定於應用的目錄和特定類型的媒體,因此應用無需請求任何其他用戶權限。

注意:在早期測試版(READ_MEDIA_IMAGESREAD_MEDIA_AUDIOREAD_MEDIA_VIDEO)中引入的特定於媒體集合的權限現已過時。

本指南介紹了過濾視圖中包含的文件,以及如何更新應用,使其可以繼續共享、訪問和修改保存在外部存儲設備中的文件。本指南還介紹了與照片中的位置信息、從原生代碼訪問媒體文件以及在內容查詢中使用列名稱相關的幾個注意事項。

要詳細瞭解 Android Q 中針對外部存儲的變更,請參閱關於在外部存儲中創建文件的相關改進的部分。

注意:如果您在使用此功能時遇到問題,請使用此常用表向我們發送錯誤報告

訪問外部存儲設備中的文件時進入過濾視圖

注意:訪問外部存儲設備中的文件時進入的這一視圖在早期測試版本中稱爲“沙盒視圖”。

默認情況下,如果應用以 Android Q 爲目標平臺,則在訪問外部存儲設備中的文件時會進入過濾視圖。應用可以使用 Context.getExternalFilesDir() 將專用於自己的文件存儲在特定於自己的目錄中。

具有過濾視圖的應用對其創建的文件始終擁有讀/寫權限,無論文件位於特定於此應用的目錄以內還是以外。您的應用無需聲明任何存儲權限即可訪問這些文件。

只有在滿足以下兩個條件時,您的應用才能訪問其他應用創建的文件:

  1. 您的應用已獲得 READ_EXTERNAL_STORAGE 權限。
  2. 這些文件位於以下其中一個明確定義的媒體集合中:

爲了訪問另一應用創建的任何其他文件(包括“downloads”目錄下的文件),您的應用必須使用存儲訪問框架,用戶可以通過該框架選擇特定文件。

注意:訪問外部存儲設備中的文件時會進入過濾視圖的應用不具有對 /sdcard/DCIM/IMG1024.JPG 等路徑的直接內核訪問權限。要訪問此類文件,應用必須使用 MediaStore,並調用 openFile() 等方法。

過濾視圖還施加了以下媒體相關數據限制:

  • 除非您的應用已獲得 ACCESS_MEDIA_LOCATION 權限,否則圖片文件中的 Exif 元數據會被遮蓋。如需瞭解詳情,請參閱關於如何訪問照片中的位置信息的部分。
  • 媒體存儲器中每個文件的 DATA 列都會被遮蓋。
  • MediaStore.Files 表格會自行過濾,僅顯示照片、視頻和音頻文件。例如,該表格不會再顯示 PDF 文件。

要在原生代碼中訪問媒體文件,請在基於 Java 或基於 Kotlin 的代碼中使用 MediaStore 檢索相應文件,然後將對應的文件描述符傳遞到原生代碼。如需瞭解詳情,請參閱關於如何從原生代碼訪問媒體文件的部分。

卸載後保留應用的文件

如果應用在訪問外部存儲設備中的文件時會進入過濾視圖,那麼在卸載該應用後,系統會清除特定於該應用的目錄中的所有文件。要在卸載後保留這些文件,請將其保存到 MediaStore 中的某個目錄下。

選擇停用過濾視圖

警告:明年,所有應用的主要平臺版本都需要分區存儲,無論其採用哪種目標 SDK 級別。因此,您應該提前確保您的應用支持分區存儲。爲此,請確保在運行您應用的 Android Q 設備上啓用該行爲。

大多數已遵循存儲最佳做法的應用只需做出很小的改動即可支持分區存儲。在您的應用完全兼容或經過測試之前,您可以根據應用的目標 SDK 級別或名爲 requestLegacyExternalStorage 的新清單屬性,臨時選擇停用分區存儲行爲:

  • 以 Android 9(API 級別 28)或更低版本爲目標平臺。

  • 如果您以 Android Q 爲目標平臺,請在應用的清單文件中將 requestLegacyExternalStorage 的值設爲 true

        <manifest ... >
          <!-- This attribute is "false" by default on apps targeting Android Q. -->
          <application android:requestLegacyExternalStorage="true" ... >
            ...
          </application>
        </manifest>
        

     

如果某個應用在安裝時啓用了舊版外部存儲,則該應用仍會保持此模式,直到將其卸載爲止。無論設備之後是否升級爲運行 Android Q,或者應用之後更新爲以 Android Q 爲目標平臺,此兼容性行爲均仍適用。

注意:要測試以 Android 9 或更低版本爲目標平臺的應用是否支持分區存儲,您可以通過將 requestLegacyExternalStorage 的值設爲 false,選擇啓用相應行爲。

設置虛擬外部存儲設備

在沒有可移動外部存儲設備的設備上,請使用以下命令啓用虛擬磁盤以供測試:

    adb shell sm set-virtual-disk true
    

 

過濾視圖文件訪問權限摘要

下表總結了在訪問外部存儲設備中的文件時會進入過濾視圖的應用訪問文件的方式:

文件位置 所需權限 訪問方法 (*) 卸載應用時是否移除文件?
特定於應用的目錄 getExternalFilesDir()
媒體集合
(照片、視頻、音頻)
READ_EXTERNAL_STORAGE
(僅當
訪問其他應用的文件時)
MediaStore
下載內容
(文檔和
電子書籍)
存儲訪問框架
(加載系統的文件選擇器)

*您可以使用存儲訪問框架訪問上表中顯示的每個位置,而無需請求任何權限。

調整特定類型的使用模式以適應變更

本部分針對幾種特定類型的基於媒體的應用提供了建議,幫助這些應用適應以 Android Q 爲目標平臺的應用中發生的存儲行爲變更。

除非您的應用需要訪問不在特定於該應用的目錄或 MediaStore 中的文件,否則最好使用過濾視圖。

分享媒體文件

某些應用允許用戶彼此分享媒體文件。例如,用戶可以通過社交媒體應用與朋友分享照片和視頻。

要訪問用戶希望共享的媒體文件,請使用 MediaStore API。利用 Android Q 中引入的改進,您可以使用此 API 存儲用戶通過應用收到的任何文件。

如果您提供一組配套應用(例如短信應用和個人資料應用),請使用 content:// URI 設置文件共享。我們已經建議將此工作流作爲一項安全最佳做法

使用文檔

某些應用將文檔用作存儲單元,用戶可以在其中輸入可能要與同伴分享或要導入其他文檔的數據。例如,用戶打開企業辦公文檔或打開另存爲 EPUB 文件的圖書。

在這些情況下,通過調用 ACTION_OPEN_DOCUMENT intent 使用戶能選擇要打開的文件,此 intent 會打開系統的文件選擇器應用。要僅顯示您的應用所支持類型的文件,請在您的 intent 中包含 Intent.EXTRA_MIME_TYPES extra。

GitHub 上的 ActionOpenDocument 示例說明了在徵得用戶同意後如何使用 ACTION_OPEN_DOCUMENT 打開文件。

管理文件組

文件管理和媒體創建應用通常會管理目錄層次結構中的文件組。這些應用可以調用 ACTION_OPEN_DOCUMENT_TREE intent,以允許用戶授予對整個目錄樹的訪問權限。此類應用可以修改所選目錄及其任何子目錄中的任何文件。

使用此界面,用戶可以從任何已安裝的 DocumentsProvider 實例訪問文件,而任何受本地支持或基於雲的解決方案都支持這些實例。

GitHub 上的 ActionOpenDocumentTree 示例說明了在徵得用戶同意後如何使用 ACTION_OPEN_DOCUMENT_TREE 打開目錄樹。

注意:在使用 ACTION_OPEN_DOCUMENT_TREE 時,您的應用只能訪問用戶選擇的目錄中的文件。您無權訪問用戶選擇的目錄之外的其他應用的文件。藉助這種由用戶控制的訪問權限,用戶可以準確地選擇自己願意與您的應用共享哪些內容。

訪問和修改媒體內容

本部分提供了有關在外部存儲設備中加載和存儲媒體文件的最佳做法,以便讓您的應用能夠在 Android Q 中繼續提供良好的用戶體驗。

注意:如果應用在訪問外部存儲設備中的文件時需要進入過濾視圖,那麼在請求存儲運行時權限時,僅當給定文件位於特定於此應用的目錄中或以下其中一個媒體集合中時,此應用才能訪問相應文件:

即使具有存儲權限,此類訪問外部存儲設備的原始文件系統視圖的應用也只能訪問此應用的特定於軟件包的原始路徑。如果應用試圖使用原始文件系統視圖打開其特定於軟件包的路徑之外的文件,則會發生錯誤:

訪問文件

不要使用已棄用的 DATA 列加載媒體文件。請改爲從 ContentResolver 調用以下其中一種方法:

  • 對於單個媒體文件的縮略圖,請使用 loadThumbnail(),並傳遞要加載的縮略圖的大小。
  • 對於單個媒體文件,請使用 openFileDescriptor()
  • 對於媒體文件的集合,請使用 query()

注意:您可以通過調用 MediaStore.setIncludePending() 查看待處理媒體文件集。

以下代碼段展示瞭如何訪問媒體文件:

    // Load thumbnail of a specific media item.
    val mediaThumbnail = resolver.loadThumbnail(item, Size(640, 480), null)

    // Open a specific media item.
    resolver.openFileDescriptor(item, mode).use { pfd ->
        // ...
    }

    // Find all videos on a given storage device, including pending files.
    val collection = MediaStore.Video.Media.getContentUri(volumeName)
    val collectionWithPending = MediaStore.setIncludePending(collection)
    resolver.query(collectionWithPending, null, null, null).use { c ->
        // ...
    }
    

 

從原生代碼訪問

您可能會遇到您的應用需要在原生代碼中使用特定媒體文件的情況,例如其他應用與您的應用共享的文件,或用戶的媒體合集中的媒體文件。在這些情況下,請在基於 Java 或基於 Kotlin 的代碼中找到相應媒體文件,然後將與此文件相關的文件描述符傳遞到原生代碼。

以下代碼段演示瞭如何將媒體對象的文件描述符傳遞到應用的原生代碼:

KotlinJava更多

    val contentUri: Uri =
            ContentUris.withAppendedId(
            android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
            cursor.getLong(BaseColumns._ID))
    val fileOpenMode = "r"
    val parcelFd = resolver.openFileDescriptor(uri, fileOpenMode)
    val fd = parcelFd?.detachFd()
    // Pass the integer value "fd" into your native code. Remember to call
    // close(2) on the file descriptor when you're done using it.
    

 

    Uri contentUri = ContentUris.withAppendedId(
            android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
            cursor.getLong(Integer.parseInt(BaseColumns._ID)));
    String fileOpenMode = "r";
    ParcelFileDescriptor parcelFd = resolver.openFileDescriptor(uri, fileOpenMode);
    if (parcelFd != null) {
        int fd = parcelFd.detachFd();
        // Pass the integer value "fd" into your native code. Remember to call
        // close(2) on the file descriptor when you're done using it.
    }
    

 

要詳細瞭解如何在原生代碼中訪問文件,請觀看 2018 年 Android 開發者峯會的 Files for Miles 演講(從15:20 處開始觀看)。

更新其他應用的媒體文件

注意:預計以下行爲將在未來的 Android Q 測試版中生效。

要修改另一個應用最初保存到外部存儲設備的給定媒體文件,請捕獲平臺拋出的 RecoverableSecurityException。然後,您可以請求用戶授予您的應用對此特定內容的寫入權限,如以下代碼段所示:

KotlinJava更多

    try {
        // ...
    } catch (rse: RecoverableSecurityException) {
        val requestAccessIntentSender = rse.userAction.actionIntent.intentSender

        // In your code, handle IntentSender.SendIntentException.
        startIntentSenderForResult(requestAccessIntentSender, your-request-code,
                null, 0, 0, 0, null)
    }
    

 

    try {
        // ...
    } catch (RecoverableSecurityException rse) {
        IntentSender requestAccessIntentSender = rse.getUserAction()
                .getActionIntent().getIntentSender();

        // In your code, handle IntentSender.SendIntentException.
        startIntentSenderForResult(requestAccessIntentSender, your-request-code,
                null, 0, 0, 0, null);
    }
    

 

照片中的位置信息

一些照片在其 Exif 元數據中包含位置信息,以便用戶查看照片的拍攝地點。由於此類位置信息很敏感,因此如果您的應用在訪問外部存儲設備中的文件時會進入過濾視圖,Android Q 會默認對您的應用隱藏此類信息。這種位置信息限制與適用於相機功能的限制不同。

如果您的應用需要訪問照片的位置信息,請完成以下步驟:

  1. 將新的 ACCESS_MEDIA_LOCATION 權限添加到應用清單中。
  2. MediaStore 對象中調用 setRequireOriginal(),在調用時傳入照片的 URI。

以下代碼段是此流程的一個示例:

KotlinJava更多

    // Get location data from the ExifInterface class.
    val photoUri = MediaStore.setRequireOriginal(photoUri)
    contentResolver.openInputStream(photoUri).use { stream ->
        ExifInterface(stream).run {
            // If lat/long is null, fall back to the coordinates (0, 0).
            val latLong = ?: doubleArrayOf(0.0, 0.0)
        }
    }
    

 

    Uri photoUri = Uri.withAppendedPath(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            cursor.getString(idColumnIndex));

    final double[] latLong;

    // Get location data from the ExifInterface class.
    photoUri = MediaStore.setRequireOriginal(photoUri);
    InputStream stream = getContentResolver().openInputStream(photoUri);
    if (stream != null) {
        ExifInterface exifInterface = new ExifInterface(stream);
        double[] returnedLatLong = exifInterface.getLatLong();

        // If lat/long is null, fall back to the coordinates (0, 0).
        latLong = returnedLatLong != null ? returnedLatLong : new double[2];

        // Don't reuse the stream associated with the instance of "ExifInterface".
        stream.close();
    } else {
        // Failed to load the stream, so return the coordinates (0, 0).
        latLong = new double[2];
    }
   

 

內容查詢中的列名稱

如果您應用的代碼使用列名稱投影(例如 mime_type AS MimeType),請注意,Android Q 需要使用 MediaStore API 中定義的列名稱。

如果您的代碼依賴於需要 Android API 中未定義的列名稱的庫(例如 MimeType),請使用 CursorWrapper 在應用的進程中動態轉換列名稱。

2、用戶可控制應用對設備位置信息的訪問權限

從 Android Q 測試版 5 開始,此項變更具有以下特性:

  • 如果您的應用在後臺時請求訪問設備位置信息,則會影響您的應用
  • 嚮應用授予在後臺訪問設備位置信息的權限之後,用戶可能會收到提醒
  • 緩解方式包括:使用新權限在後臺訪問位置信息,並在沒有後臺位置信息更新的情況下確保優雅降級
  • 這種行爲在 Android Q 上始終處於啓用狀態

歡迎您提供相關反饋! 在 Android Q 測試版計劃期間,歡迎報告您在使用此功能時發現的問題

 

圖 1. 用戶嘗試在設備未連接到網絡時打開網頁。Chrome 彈出互聯網連接設置面板…

 

 

 

圖 2. 用戶可以開啓 WLAN 並選擇網絡,而無需離開 Chrome 應用。

例如,假設用戶打開了網絡瀏覽器,而其設備已開啓飛行模式。在 Android Q 之前的版本中,此應用只能顯示一條通用消息,要求用戶打開設置以恢復連接。而藉助 Android Q,瀏覽器應用便可以顯示一個內嵌面板,其中會顯示各種主要連接設置,例如飛行模式、WLAN(包括附近的網絡)和移動數據。藉助此面板,用戶無需離開應用即可恢復連接。

要顯示設置面板,請發出具有某個新 Settings.Panel 操作的 intent:

KotlinJava更多

    val panelIntent = Intent(Settings.Panel.settings_panel_type)
    startActivityForResult(panelIntent)
    

 

    Intent panelIntent = new Intent(Settings.Panel.settings_panel_type);
    startActivityForResult(panelIntent);
    

 

settings_panel_type 可以是下列項之一:

ACTION_INTERNET_CONNECTIVITY

顯示與互聯網連接相關的設置,例如飛行模式、WLAN 和移動數據。

ACTION_WIFI

顯示 WLAN 設置,但不顯示其他連接設置。這對於需要 WLAN 連接以執行大容量上傳或下載的應用非常有用。

ACTION_NFC

顯示與近距離無線通信 (NFC) 相關的所有設置。

ACTION_VOLUME

顯示所有音頻流的音量設置。

我們計劃針對此功能引入一個 AndroidX 封裝容器。在搭載 Android 9(API 級別 28)或更低級別的設備上調用時,此封裝容器會在設置應用中打開最合適的頁面。

共享功能方面的改進

Android Q 爲共享功能提供了多項改進。要了解所有詳情,請參閱 Android Q 中共享功能方面的改進

深色主題背景

Android Q 提供全新的深色主題背景,既會應用於 Android 系統界面,也會應用於設備上運行的應用。要了解所有詳情,請參閱深色主題背景

前臺服務類型

Android Q 引入了一個新的 XML 清單屬性 foregroundServiceType,您可以將其包含在多項特定服務的定義中。雖然很少適用,但您可以爲一項特定服務分配多個前臺服務類型。

下表顯示了不同的前臺服務類型,以及適合在其中聲明特定類型的服務:

前臺服務類型 應聲明相應類型的服務的示例使用情形
connectedDevice 監控穿戴式設備健身跟蹤器
dataSync 從網絡下載文件
location 延續用戶發起的操作
mediaPlayback 播放有聲讀物、播客或音樂
mediaProjection 簡短地錄屏
phoneCall 處理正在進行的通話

Kotlin

Android Q 對 Kotlin 開發進行了以下更新。

libcore API 的可空性註釋

Android Q 改進了 SDK 中針對 libcore API 的可空性註釋的覆蓋範圍。藉助這些註釋,在 Android Studio 中使用 Kotlin 或 Java 可空性分析的應用開發者可以在與這些 API 互動時獲取非 Null 信息。

通常,Kotlin 中的爲空性合同違規行爲會導致編譯錯誤。爲確保與現有代碼兼容,所有新註釋都僅限於 @RecentlyNullable@RecentlyNonNull。這意味着爲空性違規行爲會引發警告,而不是錯誤。

此外,Android 9 中添加的所有 @RecentlyNullable@RecentlyNonNull 註釋都會分別更改爲 @Nullable@NonNull。這意味着爲空性違規行爲現在會引發錯誤,而不是警告。

要詳細瞭解註釋方面的變更,請參閱 Android 開發者博客中的 Android Pie SDK 現已更適用於 Kotlin一文。

NDK

Android Q 包含以下 NDK 方面的變更。

改進了文件描述符所有權的調試

Android Q 增加了 fdsan,它可以幫助您更輕鬆地查找和修復文件描述符所有權方面的問題。

與錯誤處理文件描述符所有權相關的錯誤(通常表現爲“use-after-close”和“double-close”)類似於內存分配“use-after-free”和“double-free”錯誤,但通常更難以診斷和修復。“fdsan”會嘗試通過強制執行文件描述符所有權來檢測和/或防止文件描述符誤管理。

要詳細瞭解與這些問題相關的崩潰,請參閱 fdsan 檢測到的錯誤。要詳細瞭解 fdsan,請參閱關於 fdsan 的 Googlesource 頁面

ELF TLS

使用 API 級別 29 及更高版本的 NDK 編譯的應用無需再使用 emutls,但可以改爲使用 ELF TLS。我們增加了對動態和靜態鏈接器的支持,以支持處理線程局部變量的新方法。

對於針對 API 級別 28 及更低版本編譯的應用,我們實現了針對 libgcc/compiler-rt 的改進,以便解決一些 emutls 問題。

有關詳情,請參閱面向 NDK 開發者的 Android 變更

運行時

Android Q 包含以下運行時方面的變更。

觸發基於 Mallinfo 的垃圾回收

當小型平臺 Java 對象引用 C++ 堆中的大型對象時,通常只有在系統已回收並(舉例而言)最終確定 Java 對象後,才能回收 C++ 對象。在之前的版本中,平臺會估算與 Java 對象相關聯的許多 C++ 對象的大小。這種估算並不總是準確,並且偶爾會導致內存使用量大大增加,因爲平臺無法在應該進行垃圾回收時完成回收。

在 Android Q 中,垃圾回收器 (GC) 會跟蹤系統 malloc() 分配的堆的總大小,以確保 malloc() 分配的大型堆始終包含在可觸發 GC 的計算中。因此,與 Java 執行交錯大量 C++ 分配的應用可能會出現垃圾回收頻率提高的現象。其他應用的頻率則可能會略有下降。

測試和調試

Android Q 包含以下測試和調試方面的改進。

改進了設備上系統跟蹤功能

現在,您在執行設備上系統跟蹤時可以指定跟蹤的記錄大小和持續時間限制。在您指定任一值後,系統便會執行長期跟蹤,並在記錄跟蹤時定期將跟蹤緩衝區複製到目標文件。在達到您指定的記錄大小或持續時間限制後,跟蹤便會完成。

請使用這些附加參數來測試除了您使用標準跟蹤進行測試的用例之外的其他用例。例如,您可能正在診斷某個性能錯誤,而此錯誤僅在您的應用長時間運行後纔會發生。在這種情況下,您可以記錄爲期一整天的長期跟蹤,然後分析 CPU 調度程序、磁盤活動、應用線程以及報告中的其他數據,以幫助您確定造成此錯誤的原因。

TextClassifier 改進

Android Q 在 TextClassifier 接口中提供了其他文本分類功能。

語言檢測

TextClassifier 現在具有 detectLanguage() 方法。此方法的工作方式與現有分類方法類似,即接收 TextLanguage.Request 對象並返回 TextLanguage 對象。

新的 TextLanguage 對象包含一系列有序對。每個有序對都包含所請求文本示例的語言區域和相應的置信度得分。

建議採取的對話操作

TextClassifier 現在具有 suggestConversationActions() 方法。此方法的工作方式與現有分類方法類似,即接收 ConversationActions.Request 對象並返回 ConversationActions 對象。

新的 ConversationActions 對象包含一系列 ConversationAction 對象。每個 ConversationAction 對象都包含建議採取的可行操作及其置信度得分。

通知中的智能回覆/操作

Android 9 引入了在通知中顯示建議回覆的功能。從 Android Q 開始,通知中還可以包含基於 intent 的建議操作。此外,現在系統可以自動生成這些建議。應用仍然可以提供它們自己的建議,或選擇停用系統生成的建議。

用於生成這些回覆的 API 是 TextClassifier 的一部分,且已在 Android Q 中直接提供給開發者。如需瞭解詳情,請參閱關於 TextClassifier 改進的部分

如果您的應用提供自己的建議,則平臺不會生成任何自動建議。如果您不希望應用的通知顯示任何建議回覆或操作,可以通過使用 setAllowGeneratedReplies()setAllowSystemGeneratedContextualActions() 選擇停用系統生成的回覆和操作。

 

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