拖不得了,Android11真的來了,最全適配實踐指南奉上

前言

沒錯!Android 11(version 30,Andorid R) 正式發佈了!看到這個新聞我知道我不能再拖了,再很差好準備好迎接Android11的到來,到時候迎接個人就是客戶的指責,甚至老闆的一封休書了 😂。java

今天就和你們一塊兒看看Android11到底改了些什麼,以及最重要的,咱們須要怎麼適配?targetversion不改到30,是否是就不用適配了呢?android

如下我分爲兩部分講述,分別是web

  • Android11 爲目標版本的應用( targetSdkVersion>=30纔有影響)⭐
  • 全部應用在 Android11設備上適配改動(不管targetSdkVersion是多少,只要在 Android11設備上運行的應用都有影響)

爲何先說targetSdkVersion>=30的模塊呢?由於通常來講爲了Google爲了讓咱們更長時間適應新的內容以及保障線上應用的穩定,都會把改動大的,須要花時間適配的內容放到新的targetSdkVersion對應的應用上,若是你暫時沒有適配targetSdkVersion30的需求,也能夠看看第二模塊,看看是否有涉及你的應用相關內容。GOGOGO!面試

Tips:此適配文章會不間斷更新,根據Android11發佈進度調整,歡迎點贊關注。(打⭐的格外注意哦)數據庫

適配targetSdkVersion30

此模塊的修改內容只針對targetSdkVersion 30或者以上才生效。api

分區存儲強制執行⭐

對外部存儲目錄的訪問僅限於應用專屬目錄,以及應用已建立的特定類型的媒體。瀏覽器

關於分區存儲,在Android10就已經推行了,簡單的說,就是應用對於文件的讀寫只能在沙盒環境,也就是屬於本身應用的目錄裏面讀寫。其餘媒體文件能夠經過MediaStore進行訪問。緩存

可是在android10的時候,Google仍是爲開發者考慮,留了一手。在targetSdkVersion = 29應用中,設置android:requestLegacyExternalStorage="true",就能夠不啓動分區存儲,讓之前的文件讀取正常使用。可是targetSdkVersion = 30中不行了,強制開啓分區存儲。
固然,做爲人性化的android,仍是爲開發者留了一小手,若是是覆蓋安裝呢,能夠增長android:preserveLegacyExternalStorage="true",暫時關閉分區存儲,好讓開發者完成數據遷移的工做。爲何是暫時呢?由於只要卸載重裝,就會失效了。如下是關於分區存儲會遇到的全部狀況,給你們羅列出來了,先上代碼:安全

    fun saveFile() {
        if (checkPermission()) {
            //getExternalStoragePublicDirectory被棄用,分區存儲開啓後就不容許訪問了
            val filePath = Environment.getExternalStoragePublicDirectory("").toString() + "/test3.txt"
            val fw = FileWriter(filePath)
            fw.write("hello world")
            fw.close()
            showToast("文件寫入成功")
        }
    }

分狀況運行:
1) targetSdkVersion = 28,運行後正常讀寫。
2) targetSdkVersion = 29,不刪除應用,targetSdkVersion 由28修改到29,覆蓋安裝,運行後正常讀寫。
3) targetSdkVersion = 29,刪除應用,從新運行,讀寫報錯,程序崩潰(open failed: EACCES (Permission denied))
4) targetSdkVersion = 29,添加android:requestLegacyExternalStorage="true"(不啓用分區存儲),讀寫正常不報錯
5) targetSdkVersion = 30,不刪除應用,targetSdkVersion 由29修改到30,讀寫報錯,程序崩潰(open failed: EACCES (Permission denied))
6) targetSdkVersion = 30,不刪除應用,targetSdkVersion 由29修改到30,增長android:preserveLegacyExternalStorage="true",讀寫正常不報錯
7) targetSdkVersion = 30,刪除應用,從新運行,讀寫報錯,程序崩潰(open failed: EACCES (Permission denied))微信

ok,那到底應該怎麼改呢?三種方法訪問文件:

1)應用專屬目錄

//分區存儲空間
val file = File(context.filesDir, filename)

//應用專屬外部存儲空間
val appSpecificExternalDir = File(context.getExternalFilesDir(), filename)

2)訪問公共媒體目錄文件

val cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, nullnullnull"${MediaStore.MediaColumns.DATE_ADDED} desc")
if (cursor != null) {
    while (cursor.moveToNext()) {
        val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
        val uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
        println("image uri is $uri")
    }
    cursor.close()
}

  1. SAF(存儲訪問框架--Storage Access Framework)
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
    intent.addCategory(Intent.CATEGORY_OPENABLE)
    intent.type = "image/*"
    startActivityForResult(intent, 100)

    @RequiresApi(Build.VERSION_CODES.KITKAT)
    override fun onActivityResult(requestCode: Int, resultCode: IntdataIntent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (data == null || resultCode != Activity.RESULT_OK) return
        if (requestCode == 100) {
            val uri = data.data
            println("image uri is $uri")
        }
    }

具體還有不少操做能夠看看網上關於分區存儲的資料,由於Android10已經出來好久了,因此資料仍是不少的,這裏推薦幾篇
訪問應用專屬文件
Android 10適配要點,做用域存儲
AndroidQ(10)分區存儲完美適配

說到這裏可能又有人問了,那個人應用就是個手機管理器,總不能不讓我清其餘應用的緩存了吧,有辦法!Android提供了兩個intent入口:

  • 調用 ACTION_MANAGE_STORAGE intent 操做檢查可用空間。
  • 調用 ACTION_CLEAR_APP_CACHE intent 操做清除全部緩存。

說來講去,反正應用數據私有化是大勢所趨,仍是早點適配分區存儲,別等之後手機只有沙盒機制的時候,就來不及了

媒體文件訪問權限 ⭐

爲了在保證用戶隱私的同時能夠更輕鬆地訪問媒體,Android 11 增長了如下功能。執行批量操做和使用直接文件路徑和原生庫訪問文件。

1)執行批量操做

這裏的批量操做指的是Android 11 向 MediaStore API 中添加了多種方法,用於簡化特定媒體文件更改流程(例如在原位置編輯照片),分別是:

  • createWriteRequest() 用戶嚮應用授予對指定媒體文件組的寫入訪問權限的請求。
  • createFavoriteRequest()用戶將設備上指定的媒體文件標記爲「收藏」的請求。對該文件具備讀取訪問權限的任何應用均可以看到用戶已將該文件標記爲「收藏」。
  • createTrashRequest() 用戶將指定的媒體文件放入設備垃圾箱的請求。垃圾箱中的內容會在系統定義的時間段後被永久刪除。
  • createDeleteRequest() 用戶當即永久刪除指定的媒體文件(而不是先將其放入垃圾箱)的請求。

直接看個例子:

val urisToModify = listOf(uri,uri,...)
val editPendingIntent = MediaStore.createWriteRequest(contentResolver,
        urisToModify)

// Launch a system prompt requesting user permission for the operation.
startIntentSenderForResult(editPendingIntent.intentSender, EDIT_REQUEST_CODE,
    null000)


override fun onActivityResult(requestCode: Int, resultCode: Int,
                 dataIntent?)
 {
    when (requestCode) {
        EDIT_REQUEST_CODE ->
            if (resultCode == Activity.RESULT_OK) {
                /* Edit request granted; proceed. */
            } else {
                /* Edit request not granted; explain to the user. */
            }
    }
}    

傳入uri的集合,獲取用戶的贊成後,就能夠進行操做了。

2)直接文件路徑和原生庫訪問文件

沒錯!Android11又恢復了使用直接文件路徑訪問訪問媒體文件!哈哈,這樣就方便多了。也就是除了 MediaStore API以外還有兩種方式能夠訪問媒體文件:

  • File API。
  • 原生庫,例如 fopen()。

Android10咋辦呢??要不就用MediaStore,要不就直接把分區存儲關了吧(requestLegacyExternalStorage=true)

全部文件訪問權限 ⭐

雖說了這麼多,可是還有些應用就要訪問全部文件,好比殺毒軟件,文件管理器。放心,有辦法!MANAGE_EXTERNAL_STORAGE 這不來了嗎。這個權限就是用來獲取全部文件的管理權限。🌰:

    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

    val intent = Intent()
    intent.action= Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION
    startActivity(intent)

    //判斷是否獲取MANAGE_EXTERNAL_STORAGE權限:
    val isHasStoragePermission= Environment.isExternalStorageManager()

來張截圖過過癮:

申請全部文件訪問權限

電話號碼相關權限 ⭐

Android 11 更改了您的應用在讀取電話號碼時使用的與電話相關的權限。

具體改了什麼呢?其實就是兩個API:

  • TelecomManager 類中的 getLine1Number() 方法
  • TelecomManager 類中的 getMsisdn() 方法

也就是當用到這兩個API的時候,原來的READ_PHONE_STATE權限無論用了,須要READ_PHONE_NUMBERS權限才行。

下面具體說說,targetSdkVersion修改到30,而後運行一個獲取電話號碼的程序:


    ActivityCompat.requestPermissions(this,
        arrayOf(Manifest.permission.READ_PHONE_STATE), 100)

        btn2.setOnClickListener {
            val tm = this.applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
            val phoneNumber = tm.line1Number
            showToast(phoneNumber)
        }

崩潰了:


java.lang.SecurityException: getLine1NumberForDisplay: Neither user 10151 nor current process has android.permission.READ_PHONE_STATE, android.permission.READ_SMS, or android.permission.READ_PHONE_NUMBERS

預想之中哈,Andmanifest.xml中註冊好權限,而且添加動態權限申請:


    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />


    ActivityCompat.requestPermissions(this,
        arrayOf(Manifest.permission.READ_PHONE_STATE,Manifest.permission.READ_PHONE_NUMBERS), 100)

搞定,若是你只須要獲取手機號碼這一個功能,也能夠只申請READ_PHONE_NUMBERS這一個權限:

    <uses-permission android:name="android.permission.READ_PHONE_STATE"  android:maxSdkVersion="29" />
    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />

自定義消息框視圖被屏蔽 ⭐

從 Android 11 開始,已棄用自定義消息框視圖。若是您的應用以 Android 11 爲目標平臺,包含自定義視圖的消息框在從後臺發佈時會被屏蔽

可能有人會奇怪了,什麼是自定義消息框視圖啊?我說英文你就知道了,英文是custom toast views,也就是自定義toast。簡單寫個代碼:

    Toast toast = new Toast(context);
    toast.setDuration(show_length);
    toast.setView(view);
    toast.show();

糟了糟了,自定義toast被棄用了?咱們項目就是用的這個啊!不用擔憂,只是不容許自定義toast從後臺顯示了。好比我寫一個3秒後再顯示toast,而後應用一打開就進入後臺,看看會發生什麼:

    Handler().postDelayed({
          IToast.show("你好,我是自定義toast")
     }, 3000)


     W/NotificationService: Blocking custom toast from package com.example.studynote due to package not in the foreground

啥也沒顯示,只是發出來一個警告。因此不用太過擔憂,若是實在須要後臺顯示,就用普通的toast吧!

如今須要 APK 簽名方案 v2 ⭐

對於以 Android 11(API 級別 30)爲目標平臺,且目前僅使用 APK 簽名方案 v1 簽名的應用,如今還必須使用 APK 簽名方案 v2 或更高版本進行簽名。用戶沒法在搭載 Android 11 的設備上安裝或更新僅經過 APK 簽名方案 v1 簽名的應用。

這個介紹已經很明顯了吧,若是你的targetSdkVersion修改到30,那麼你就必需要加上v2簽名才行。不然沒法安裝和更新。

媒體intent操做須要系統默認相機 ⭐

從 Android 11 開始,只有預裝的系統相機應用能夠響應如下 intent 操做:

android.media.action.VIDEO_CAPTURE
android.media.action.IMAGE_CAPTURE
android.media.action.IMAGE_CAPTURE_SECURE

也就是說,若是我調用intent喚起照相機,使用VIDEO_CAPTURE的action,只有系統的相機可以響應,而第三方的相機應用不會響應了。

    val intent=Intent()
    intent.action=android.provider.MediaStore.ACTION_IMAGE_CAPTURE
    startActivity(intent)

    //沒法喚起第三方相機了,只能喚起系統相機

這點對普通的相機應用仍是有點打擊的,官方給的建議是若是要使用特定的第三方相機應用來表明其捕獲圖片或視頻,能夠經過爲intent設置軟件包名稱或組件來使這些intent變得明確。

5G ⭐

Android 11 添加了在您的應用中支持 5G 的功能

新的Android11也是支持了5G相關的一些功能,包括:

  • 檢測是否鏈接到了5G網絡
  • 檢查按流量計費性

首先是檢測5G網絡,經過TelephonyManager的監聽方法:

    private fun getNetworkType(){
        val tManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        tManager.listen(object : PhoneStateListener() {

            @RequiresApi(Build.VERSION_CODES.R)
            override fun onDisplayInfoChanged(telephonyDisplayInfo: TelephonyDisplayInfo) {
                if (ActivityCompat.checkSelfPermission(this@Android11Test2Activity, android.Manifest.permission.READ_PHONE_STATE) != android.content.pm.PackageManager.PERMISSION_GRANTED) {
                    return
                }
                super.onDisplayInfoChanged(telephonyDisplayInfo)

                when(telephonyDisplayInfo.networkType) {
                    TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO -> showToast("高級專業版 LTE (5Ge)")
                    TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA -> showToast("NR (5G) - 5G Sub-6 網絡")
                    TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE -> showToast("5G+/5G UW - 5G mmWave 網絡")
                    else -> showToast("other")
                }
            }

        }, PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED)
    }

若是是5g網絡,就免不了要去判斷是否是按流量計費的,不然5G的流量可不是開玩笑的。

檢測流量計費方法也很簡單,監聽網絡,在回調中判斷:

    val manager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
     manager.registerDefaultNetworkCallback(object : ConnectivityManager.NetworkCallback() {
        override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
          super.onCapabilitiesChanged(network, networkCapabilities)

            //true 表明鏈接不按流量計費
            val isNotFlowPay=networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) ||
                            networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)
          }
    })

判斷該值,若是爲 true,則將鏈接視爲不按流量計費。

後臺位置信息訪問權限 ⭐

在搭載 Android 11 的設備上,當應用中的某項功能請求在後臺訪問位置信息時,用戶看到的系統對話框再也不包含用於啓用後臺位置信息訪問權限的按鈕。如需啓用後臺位置信息訪問權限,用戶必須在設置頁面上針對應用的位置權限設置一概容許選項。

什麼意思呢?主要涉及到兩點:

  • 從Android10系統的設備開始,就須要請求後臺位置權限 (ACCESS_BACKGROUND_LOCATION),並選擇 Allow all the time (始終容許)才能得到後臺位置權限。Android11設備上再次 增強對後臺權限的管理,主要表如今 系統對話框上,對話框再也不提示始終容許字樣,而是提供了位置權限的設置入口,須要在設置頁面選擇 始終容許才能得到後臺位置權限。
  • 在搭載 Android11系統的設備上,targetVersion小於11的時候,能夠前臺後臺位置權限一塊兒申請,而且對話框提供了文字說明,表示須要隨時獲取用戶位置信息,進入設置選擇 始終容許便可。可是targetVersion爲30的時候,你必須 單獨申請後臺位置權限,並且要在獲取前臺權限以後, 順序不能亂。而且無任何提示,須要開發者本身設計提示樣式。

可能有點繞,操做幾個例子說明:

1)Android10設備,申請前臺和後臺位置權限(任意targetSdkVersion):

requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_BACKGROUND_LOCATION), 100)

執行效果:

2)Android11設備,targetSdkVersion<=29(Android 10),申請前臺和後臺位置權限:

requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_BACKGROUND_LOCATION), 100)

執行效果:

3)Android11設備,targetSdkVersion=30(Android 11),申請前臺和後臺位置權限:

requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_BACKGROUND_LOCATION), 100)

執行無反應

4)Android11設備,targetSdkVersion=30(Android 11),先申請前臺位置權限,後申請後臺位置權限:

requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION), 100)

執行效果:

requestPermissions(arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION), 100)

執行效果(直接跳轉到設置頁面,無任何說明):

因此,該怎麼適配呢?

  • targetSdkVersion<30狀況下,若是你以前就有判斷過前臺和後臺位置權限,那就無需擔憂,沒有什麼須要適配。
  • targetSdkVersion>30狀況下,須要分開申請先後臺位置權限,而且對後臺位置權限申請作好說明和引導,固然也是爲了更好的服務用戶。

權限申請的demo代碼:

    val permissionAccessCoarseLocationApproved = ActivityCompat
        .checkSelfPermission(this, permission.ACCESS_COARSE_LOCATION) ==
        PackageManager.PERMISSION_GRANTED

    if (permissionAccessCoarseLocationApproved) {
       val backgroundLocationPermissionApproved = ActivityCompat
           .checkSelfPermission(this, permission.ACCESS_BACKGROUND_LOCATION) ==
           PackageManager.PERMISSION_GRANTED

       if (backgroundLocationPermissionApproved) {
            //先後臺位置權限都有
       } else {
            //申請後臺權限
            if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.R){
                ActivityCompat.requestPermissions(this,
                        arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
                        200)
            }else{
                AlertDialog.Builder(this).setMessage("須要提供後臺位置權限,請在設置頁面選擇始終容許")
                        .setPositiveButton("肯定", DialogInterface.OnClickListener { dialog, which ->
                            ActivityCompat.requestPermissions(this,
                                    arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
                                    200)
                        }).create().show()
            }

       }
    } else {
        if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.R){
            //申請前臺和後臺位置權限
            ActivityCompat.requestPermissions(this,
                    arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_BACKGROUND_LOCATION),
                    100)
        }else{
            //申請前臺位置權限
            ActivityCompat.requestPermissions(this,
                    arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION),
                    100)
        }
    }
    

軟件包可見性 ⭐

Android 11 更改了應用查詢用戶已在設備上安裝的其餘應用以及與之交互的方式。使用新的 元素,應用能夠定義一組自身可訪問的其餘應用。經過告知系統應向您的應用顯示哪些其餘應用,此元素有助於鼓勵最小權限原則。此外,此元素還可幫助 Google Play 等應用商店評估應用爲用戶提供的隱私權和安全性。

也就是說,Android11中,若是你想去獲取其餘應用的信息,好比包名,名稱等等,不能直接獲取了,必須在清單文件中添加<queries>元素,告知系統你要獲取哪些應用信息或者哪一類應用。

好比我這段查詢應用信息的代碼:

    val pm = this.packageManager
    val listAppcations: List<ApplicationInfo> = pm
            .getInstalledApplications(PackageManager.GET_META_DATA)
    for (app in listAppcations) {
        Log.e("lz",app.packageName)
    }

Android11版本,只能查詢到本身應用和系統應用的信息,查不到其餘應用的信息了。怎麼呢?添加<queries>元素,兩種方式:

1) 元素中加入具體包名

<manifest package="com.example.game">
    <queries>
        <package android:name="com.example.store" />
        <package android:name="com.example.services" />
    </queries>
    ...
</manifest>

1) 元素中加入固定過濾的 intent

<manifest package="com.example.game">
    <queries>
        <intent>
            <action android:name="android.intent.action.SEND" />
            <data android:mimeType="image/jpeg" />
        </intent>
    </queries>
</manifest>

可能仍是有人會疑惑,那個人應用是瀏覽器或者設備管理器咋辦呢?我就要獲取全部包名啊?放心,Android11還引入了 QUERY_ALL_PACKAGES 權限,清單文件中加入便可。可是Google Play可不必定能濫用哦,它爲須要QUERY_ALL_PACKAGES 權限的應用會提供相關指南,可是還沒出來,具體要看後面的消息了。

至於國內市場。。。(但願能有個應用市場一統天下好好管理這混亂的市場吧!)

文檔訪問限制

爲讓開發者有時間進行測試,如下與存儲訪問框架 (SAF) 相關的變動只有在應用以 Android 11 爲目標平臺時纔會生效。

上文存儲的時候說過能夠經過SAF(存儲訪問框架--Storage Access Framework)來訪問公共目錄,可是Android11再次升級,部分目錄和文件不能訪問了,具體以下:

沒法再使用 ACTION_OPEN_DOCUMENT_TREE intent 操做請求訪問如下目錄:

  • 內部存儲卷的根目錄。
  • 設備製造商認爲可靠的各個 SD 卡卷的根目錄,不管該卡是模擬卡仍是可移除的卡。可靠的卷是指應用在大多數狀況下能夠成功訪問的卷。
  • Download 目錄。

沒法再使用 ACTION_OPEN_DOCUMENT_TREEACTION_OPEN_DOCUMENT intent 操做請求用戶從如下目錄中選擇單獨的文件:

  • Android/data/ 目錄及其全部子目錄。
  • Android/obb/ 目錄及其全部子目錄。

限制對 APN 數據庫的讀取訪問

以 Android 11 爲目標平臺的應用如今必須具有 Manifest.permission.WRITE_APN_SETTINGS 特權,才能讀取或訪問電話提供程序 APN 數據庫。若是在不具有此權限的狀況下嘗試訪問 APN 數據庫,會生成安全異常。

問題來了,APN是啥?

  • 指一種網絡接入技術,是經過手機上網時必須配置的一個參數,APN配置參數包括名字,運營商編號,APN接入點等等。

就是說若是沒有Manifest.permission.WRITE_APN_SETTINGS權限就不能讀取APN數據庫了,可是!這個權限很早以前就被限定只有系統程序才能申請這個權限了,如今這個特權沒理解到是什麼意思,難道系統程序都不能隨便申請了?有大神能夠評論區留言告知。

在元數據文件中聲明「無障礙」按鈕使用狀況

從 Android 11 開始,您的無障礙服務沒法在運行時聲明與系統的「無障礙」按鈕的關聯。若是您將 AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON 附加到 AccessibilityServiceInfo 對象的 flags 屬性,框架就不會將「無障礙」按鈕回調事件傳遞給您的服務。

作過無障礙輔助功能的應該都知道AccessibilityServiceInfo要設置flag爲FLAG_REQUEST_ACCESSIBILITY_BUTTON,getAccessibilityButtonController方法獲取輔助功能按鈕控制器,而且可用於查詢輔助功能按鈕的狀態並註冊監聽器以進行交互和輔助功能按鈕的狀態更改。

可是,Android 11開始,這樣寫不能獲取輔助按鈕回調事件了,得換成另一種寫法。在元數據文件(一般爲 res/raw/accessibilityservice.xml)中使用 flagRequestAccessibilityButton 標記聲明您的無障礙服務與「無障礙」按鈕的關聯。

Firebase JobDispatcher 和 GCMNetworkManager

若是您的應用以 API 級別 30 或更高級別爲目標平臺,在搭載 Android 6.0(API 級別 23)或更高版本的設備上會停用 Firebase JobDispatcher 和 GcmNetworkManager API 調用。

這兩個api國內都用不了,主要用於後臺任務。官方給出的替代意見是WorkManager,這個國內是能夠用的,屬於jetpack組件,主要用於調度和執行可延期的後臺工做。

設備到設備文件傳輸

若是您的應用以 Android 11 爲目標平臺,您將沒法再使用 allowBackup 屬性停用應用文件的設備到設備遷移。系統會自動啓用此功能。不過,即便您的應用以 Android 11 爲目標平臺,您也能夠經過將 allowBackup 屬性設置爲 false 來停用應用文件的雲端備份和恢復。

android:allowBackup屬性

  • 表明是否容許應用參與備份和恢復基礎架構。若是將此屬性設爲 false,則永遠不會爲該應用執行 備份或恢復,即便是採用全系統備份方法也不例外(這種備份方法一般會經過 adb 保存全部應用數據)。此屬性的默認值爲 true。

因此這裏是不能停用文件的設備到設備遷移,可是能夠停用雲端備份和恢復

自動重置權限

若是應用以 Android 11 爲目標平臺而且數月未使用,系統會經過自動重置用戶已授予應用的運行時敏感權限來保護用戶數據。此操做與用戶在系統設置中查看權限並將應用的訪問權限級別更改成拒絕的作法效果同樣。若是應用已遵循有關在運行時請求權限的最佳作法,那麼您沒必要對應用進行任何更改。這是由於,當用戶與應用中的功能互動時,您應該會驗證相關功能是否具備所需權限。

官方說明說的很清楚了,並且只要應用遵循有關在運行時請求權限的最佳作法,也就是每次須要調用權限的時候都會去判斷,那麼就不會有什麼問題。

若是須要關閉這個功能怎麼辦呢?只有引導用戶去設置頁面關閉了,能夠調用包含Settings.ACTION_APPLICATION_DETAILS_SETTINGS action的 Intent將用戶定向到系統設置中應用的頁面。

怎麼檢查應用是否停用自動重置功能呢?調用 PackageManager的isAutoRevokeWhitelisted()方法。若是此方法返回 true,表明系統不會自動重置應用的權限。

前臺服務類型

從 Android 9 開始,應用僅限於在前臺訪問攝像頭和麥克風。爲了進一步保護用戶,Android 11 更改了前臺服務訪問攝像頭和麥克風相關數據的方式。若是您的應用以 Android 11 爲目標平臺而且在某項前臺服務中訪問這些類型的數據,您須要在該前臺服務的聲明的 foregroundServiceType 屬性中添加新的 camera 和 microphone 類型。

舉例,若是應用某項前臺服務須要訪問位置信息、攝像頭和麥克風,那麼就這樣添加:

<manifest>
    <service ...
        android:foregroundServiceType="location|camera|microphone" />
</manifest>

適配Android11手機

此模塊的修改內容針對全部項目在Android11手機上存在的改動,與targetSdkVersion無關。

數據訪問審覈 ⭐

爲了讓應用及其依賴項訪問用戶私密數據的過程更加透明,Android 11 引入了數據訪問審覈功能。藉助此流程得出的看法,您能夠更好地識別和糾正可能出現的意外數據訪問。

哪些範疇屬於用戶私密數據呢?其實就是危險權限的調用,因此這個功能就是提供了能夠監聽危險權限調用的監聽。主要涉及到的方法是AppOpsManager.OnOpNotedCallback。不管是應用自己,仍是依賴庫或者SDK中的代碼,只要訪問到私密數據(危險權限),都會回調給咱們。

對於工程龐大或者使用較多SDK的工程比較適合用上這個功能,讓本身應用的私有數據管理更加透明規範,不然對於私有數據的使用和管理並不全面和方便。並且還能夠對權限使用添加歸因,也就是一個tag,標誌權限用到了什麼地方。方便回調的時候知曉哪裏使用了私有數據

🌰來:


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test1)

        //建立歸因(attribute)  
        attributionContext = createAttributionContext("shareLocation")

        //監聽事件
        val appOpsCallback = object : AppOpsManager.OnOpNotedCallback() {
            private fun logPrivateDataAccess(
                    opCode: String, attributionTag: String, trace: String)
 {
                Log.i(TAG, "Private data accessed. " +
                        "Operation: $opCode\n " +
                        "Attribution Tag:$attributionTag\nStack Trace:\n$trace")
            }

            override fun onNoted(syncNotedAppOp: SyncNotedAppOp) {
                syncNotedAppOp.attributionTag?.let {
                    logPrivateDataAccess(syncNotedAppOp.op,
                            it,
                            Throwable().stackTrace.toString())
                }
            }

            override fun onSelfNoted(syncNotedAppOp: SyncNotedAppOp) {
                syncNotedAppOp.attributionTag?.let {
                    logPrivateDataAccess(syncNotedAppOp.op,
                            it,
                            Throwable().stackTrace.toString())
                }
            }

            override fun onAsyncNoted(asyncNotedAppOp: AsyncNotedAppOp) {
                asyncNotedAppOp.attributionTag?.let {
                    logPrivateDataAccess(asyncNotedAppOp.op,
                            it,
                            asyncNotedAppOp.message)
                }
            }
        }

        //開啓私密數據監聽
        val appOpsManager =
                getSystemService(AppOpsManager::class.javaas AppOpsManager
        appOpsManager.setOnOpNotedCallback(mainExecutor, appOpsCallback)

        btn1.setOnClickListener {
            getLocation()
        }
    }

    fun getLocation() {
        val locationManager = attributionContext.getSystemService(
                LocationManager::class.javaas LocationManager
        if (!checkPermission()) {
            return
        }
        val location: Location? = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
        if (location != null) {
            showToast("${location.latitude}")
        }
    }

該例子主要展現了一個獲取位置信息的功能,若是調用到getLocation方法,就會觸發onNoted回調,回調信息包括危險權限code以及歸因。

其中OnOpNotedCallback 一共三個回調方法:

  • onNoted 正常狀況下都會回調到該方法
  • onAsyncNoted 若是數據訪問並不是發生在應用調用API期間,就會調用onAsyncNoted(),好比一些監聽器的回調。
  • onSelfNoted 在極少數狀況下,若是應用將自身的UID傳遞到 noteOp(),須要調用 onSelfNoted()。

最後點擊按鈕,看下回調的結果日誌:

Private data accessed. Operation: android:coarse_location
     Attribution Tag:shareLocation
    Stack Trace:
    [Ljava.lang.StackTraceElement;@14f5a16

能夠看到權限代碼:android:coarse_location 以及歸因 shareLocation

單次受權

在 Android 11 中,每當應用請求與位置信息、麥克風或攝像頭相關的權限時,面向用戶的權限對話框會包含僅限這一次選項。若是用戶在對話框中選擇此選項,系統會嚮應用授予臨時的單次受權。

簡單的說,就是在申請與位置信息、麥克風或攝像頭相關的權限時,系統會自動提供一個單次受權的選項,只供這一次權限獲取。而後用戶下次打開app的時候,系統會再次提示用戶授予權限。這個影響應該不大,只要咱們每次使用的時候都去判斷權限,沒有就去申請便可。放一張新版本權限獲取樣式:

新權限彈窗

權限對話框的可見性

Android 11 建議不要請求用戶已選擇拒絕的權限。在應用安裝到設備上後,若是用戶在使用過程當中多次針對某項特定的權限點按拒絕,此操做表示其但願「再也不詢問」。

這個都算不上改動,只是官方的一個良好建議。建議在用戶屢次拒絕以後,不要再展現權限申請。

Scudo Hardened Allocator

Android 11 在內部使用 Scudo Hardened Allocator 爲堆分配提供服務。Scudo 可以檢測並減輕某些類型的內存安全違規行爲。若是您在原生代碼崩潰報告中發現與 Scudo 相關的崩潰(例如 Scudo ERROR:),請參閱 Scudo 問題排查文檔。

Scudo是一種動態的用戶模式內存分配器,旨在抵禦與堆相關的漏洞,同時保持良好的性能。它是一個開源的項目。Android 11中,將採用這個新的heap分配器,性能更好,更安全。

文件描述符排錯程序

Android 10 引入了 fdsan(文件描述符排錯程序)。fdsan 檢測錯誤處理文件描述符全部權的錯誤,例如 use-after-close 和 double-close。在 Android 11 中,fdsan 的默認模式發生了變化。如今,fdsan 會在檢測到錯誤時停止,而之前的行爲則是記錄警告並繼續。

問題來了,fdsan是啥?先要了解fd是啥

文件描述符(FileDescriptor) 是Unix/Linux系統文件操做的相關概念,它在形式上是一個非負整數。當程序打開一個現有文件或者建立一個新文件時,內核向進程返回一個文件描述符。系統的進程也就是使用了這個fd來標示打開的文件,有了它就能對文件作各類操做,得到文件的各類相關信息了。

因此fdsan也就是檢測文件處理中發生的一些錯誤。

應用使用狀況統計信息

爲了更好地保護用戶,Android 11 將每一個用戶的應用使用狀況統計信息存儲在憑據加密存儲空間中。

這就涉及到了UsageStatsManagerUsageStatsManager是Android提供統計應用使用狀況的服務。經過這個服務能夠獲取指定時間區間內應用使用統計數據、組件狀態變化事件統計數據以及硬件配置信息統計數據。

好比queryAndAggregateUsageStats方法,能夠獲取指定時間區間內使用統計數據,以應用包名爲鍵值進行數據合併。

可是在Android 11 設備中,很差意思,不能隨意使用這些信息了。只有當isUserUnlocked()方法返回true的時候,才能正常訪問這些數據。也就是如下兩種狀況:

  • 用戶在系統啓動後首次解鎖其設備
  • 用戶在設備上切換到本身的賬號

JobScheduler API 調用限制調試

JobScheduler任務調度器,能夠在設備空閒時作一些任務處理。Android11中若是你設置爲debug模式(debuggable 清單屬性設置爲 true),超出速率限制的JobScheduler API調用將返回 RESULT_FAILURE。這個有什麼用呢?應該能夠幫助咱們發現一些性能問題,感興趣的能夠本身試試。

順便提下,Jetpack組件WorkManager也是用到了JobScheduler,不熟悉的同窗能夠去了解下,JobScheduler是由SystemServer進程啓動的一個系統服務,因此才能夠有這麼大的權限。

無障礙操做

在之前的 Android 版本中,框架會向未正確處理基於點擊的無障礙操做的微件分派觸摸事件。一般,這些視圖會直接處理觸摸事件,而不是註冊點擊監聽器。爲了在正肯定義無障礙操做的應用中建立更一致的行爲,Android 11 毫不會分派觸摸事件。相反,系統會徹底依賴於基於點擊的無障礙操做:ACTION_CLICK 和 ACTION_LONG_CLICK。此更改會影響屏幕閱讀器的行爲。

Android手機上有個預安裝的屏幕閱讀服務,叫作TalkBack,爲視力障礙人士或者視力狀態不佳的老年人提供。那咱們應用爲了讓這個閱讀器可以讀懂你的自定義view操做,必須給與自定義控件定義處理程序,包括點擊,長按等操做。原來版本可能對於OnTouchListener也支持無障礙觸摸事件,而在Android11中,必須專門制定點擊或者長按事件才行了。給個🌰:

class TriSwitch(context: Context) : Switch(context) {
    // 0, 1, or 2.
    var currentState: Int = 0
        private set

    init {
        updateAccessibilityActions()
    }

    private fun updateAccessibilityActions() {
        ViewCompat.replaceAccessibilityAction(this, ACTION_CLICK,
            action-label) {
            view, args -> moveToNextState()
        })
    }

    private fun moveToNextState() {
        currentState = (currentState + 1) % 3
    }
}

一個自定義控件TriSwitch,繼承自Switch,因爲和Switch的點擊效果不同,因此必須經過替換 ViewCompat.replaceAccessibilityAction() 來從新定義相應的無障礙操做。

非SDK接口限制

Android 11 包含更新後的受限制非 SDK 接口列表(基於與 Android 開發者之間的協做以及最新的內部測試)。在限制使用非 SDK 接口以前,咱們會盡量確保提供公開替代方案。

老樣子,Android11也會限制一些接口,包括灰名單和白名單,具體看非SDK接口列表

總結

一路分析下來也能夠看到,若是是重要的改動,特別是涉及到崩潰的改動仍是放到了targetSdkVersion=30的內容中,這也是每次Android發版的一個潛規則吧,爲了最大程度不影響已上線的app所做出的舉動。
可是,這並不意味咱們就能夠不改。由於應用可拖不起,用戶可拖不起,畢竟升級才能給到用戶最好的體驗。並且各大應用市場也都會建議或者強制應用升級targetSdkVersion,以便適配最新的手機。

因此,行動吧。

附件

官網改動介紹


個人公衆號:碼上積木,天天三問面試題,詳細剖析,助你成爲offer收割機。

謝謝你的閱讀,若是你以爲寫的還行,就點個贊支持下吧!感謝!
你的一個👍,就是我分享的動力❤️。





本文分享自微信公衆號 - 碼上積木(Lzjimu)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索