Android11最全適配實踐指南

  • Android11 為目標版本的應用(targetSdkVersion>=30才有影響)
  • 所有應用在Android11設備上適配改動(無論targetSdkVersion是多少,只要在Android11設備上運行的應用都有影響)

為什么先說targetSdkVersion>=30的模塊呢?因為一般來說為了Google為了讓我們更長時間適應新的內容以及保障線上應用的穩(wěn)定,都會把改動大的替废,需要花時間適配的內容放到新的targetSdkVersion對應的應用上,如果你暫時沒有適配targetSdkVersion30的需求台舱,也可以看看第二模塊挣磨,看看是否有涉及你的應用相關內容莽鸭。

適配targetSdkVersion30

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

分區(qū)存儲強制執(zhí)行

對外部存儲目錄的訪問僅限于應用專屬目錄刷后,以及應用已創(chuàng)建的特定類型的媒體。

關于分區(qū)存儲渊抄,在Android10就已經(jīng)推行了尝胆,簡單的說,就是應用對于文件的讀寫只能在沙盒環(huán)境护桦,也就是屬于自己應用的目錄里面讀寫含衔。其他媒體文件可以通過MediaStore進行訪問。

但是在android10的時候,Google還是為開發(fā)者考慮抱慌,留了一手逊桦。在targetSdkVersion = 29應用中眨猎,設置android:requestLegacyExternalStorage="true"抑进,就可以不啟動分區(qū)存儲,讓以前的文件讀取正常使用睡陪。但是targetSdkVersion = 30中不行了寺渗,強制開啟分區(qū)存儲。

當然兰迫,作為人性化的android信殊,還是為開發(fā)者留了一小手,如果是覆蓋安裝呢汁果,可以增加android:preserveLegacyExternalStorage="true"涡拘,暫時關閉分區(qū)存儲,好讓開發(fā)者完成數(shù)據(jù)遷移的工作据德。為什么是暫時呢鳄乏?因為只要卸載重裝,就會失效了棘利。以下是關于分區(qū)存儲會遇到的所有情況橱野,給大家羅列出來了,先上代碼:

    fun saveFile() {
        if (checkPermission()) {
            //getExternalStoragePublicDirectory被棄用善玫,分區(qū)存儲開啟后就不允許訪問了
            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"(不啟用分區(qū)存儲)循帐,讀寫正常不報錯
  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))

那到底應該怎么改呢漾岳?三種方法訪問文件:

  1. 應用專屬目錄
//分區(qū)存儲空間
val file = File(context.filesDir, filename)

//應用專屬外部存儲空間
val appSpecificExternalDir = File(context.getExternalFilesDir(), filename)
  1. 訪問公共媒體目錄文件
val cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, "${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: Int, data: Intent?) {
    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")
    }
}

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

  • 調用ACTION_MANAGE_STORAGE intent 操作檢查可用空間唧垦。
  • 調用ACTION_CLEAR_APP_CACHE intent 操作清除所有緩存捅儒。

說來說去,反正應用數(shù)據(jù)私有化是大勢所趨振亮,還是早點適配分區(qū)存儲巧还,別等以后手機只有沙盒機制的時候,就來不及了双炕。

媒體文件訪問權限

為了在保證用戶隱私的同時可以更輕松地訪問媒體狞悲,Android 11 增加了以下功能。執(zhí)行批量操作和使用直接文件路徑和原生庫訪問文件妇斤。

  1. 執(zhí)行批量操作
    這里的批量操作指的是Android 11 向 MediaStore API 中添加了多種方法摇锋,用于簡化特定媒體文件更改流程(例如在原位置編輯照片),分別是:
  • createWriteRequest() 用戶向應用授予對指定媒體文件組的寫入訪問權限的請求站超。
  • createFavoriteRequest()用戶將設備上指定的媒體文件標記為“收藏”的請求荸恕。對該文件具有讀取訪問權限的任何應用都可以看到用戶已將該文件標記為“收藏”。
  • createTrashRequest() 用戶將指定的媒體文件放入設備垃圾箱的請求死相。垃圾箱中的內容會在系統(tǒng)定義的時間段后被永久刪除融求。
  • 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,
    null, 0, 0, 0)


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

傳入uri的集合算撮,獲取用戶的同意后生宛,就可以進行操作了。

  1. 直接文件路徑和原生庫訪問文件

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

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

Android10咋辦呢仰剿?创淡?要不就用MediaStore,要不就直接把分區(qū)存儲關了吧(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中注冊好權限,并且添加動態(tài)權限申請:

<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 為目標平臺践樱,包含自定義視圖的消息框在從后臺發(fā)布時會被屏蔽

可能有人會奇怪了,什么是自定義消息框視圖巴雇琛拷邢?我說英文你就知道了,英文是custom toast views屎慢,也就是自定義toast瞭稼。簡單寫個代碼:

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

糟了糟了,自定義toast被棄用了腻惠?我們項目就是用的這個盎分狻!不用擔心妖枚,只是不允許自定義toast從后臺顯示了廷臼。
比如我寫一個3秒后再顯示toast,然后應用一打開就進入后臺,看看會發(fā)生什么:

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

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

啥也沒顯示寂恬,只是發(fā)出來一個警告。
所以不用太過擔心莱没,如果實在需要后臺顯示初肉,就用普通的toast吧!

現(xiàn)在需要 APK 簽名方案 v2

對于以 Android 11(API 級別 30)為目標平臺饰躲,且目前僅使用 APK 簽名方案 v1 簽名的應用牙咏,現(xiàn)在還必須使用 APK 簽名方案 v2 或更高版本進行簽名。用戶無法在搭載 Android 11 的設備上安裝或更新僅通過 APK 簽名方案 v1 簽名的應用嘹裂。

這個介紹已經(jīng)很明顯了吧妄壶,如果你的targetSdkVersion修改到30,那么你就必須要加上v2簽名才行寄狼。否則無法安裝和更新丁寄。

媒體intent操作需要系統(tǒng)默認相機

從 Android 11 開始,只有預裝的系統(tǒng)相機應用可以響應以下 intent 操作:

  • android.media.action.VIDEO_CAPTURE
  • android.media.action.IMAGE_CAPTURE
  • android.media.action.IMAGE_CAPTURE_SECURE
    也就是說泊愧,如果我調用intent喚起照相機伊磺,使用VIDEO_CAPTURE的action,只有系統(tǒng)的相機能夠響應删咱,而第三方的相機應用不會響應了屑埋。
    val intent=Intent()
    intent.action=android.provider.MediaStore.ACTION_IMAGE_CAPTURE
    startActivity(intent)

    //無法喚起第三方相機了,只能喚起系統(tǒng)相機

這點對普通的相機應用還是有點打擊的痰滋,官方給的建議是如果要使用特定的第三方相機應用來代表其捕獲圖片或視頻摘能,可以通過為intent設置軟件包名稱或組件來使這些intent變得明確。

5G

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

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

  • 檢測是否連接到了5G網(wǎng)絡
  • 檢查按流量計費性

首先是檢測5G網(wǎng)絡徊哑,通過TelephonyManager的監(jiān)聽方法:

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("高級專業(yè)版 LTE (5Ge)")
                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA -> showToast("NR (5G) - 5G Sub-6 網(wǎng)絡")
                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE -> showToast("5G+/5G UW - 5G mmWave 網(wǎng)絡")
                else -> showToast("other")
            }
        }

    }, PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED)
}

如果是5g網(wǎng)絡,就免不了要去判斷是不是按流量計費的聪富,否則5G的流量可不是開玩笑的莺丑。

檢測流量計費方法也很簡單,監(jiān)聽網(wǎng)絡墩蔓,在回調中判斷:

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 的設備上昏名,當應用中的某項功能請求在后臺訪問位置信息時,用戶看到的系統(tǒng)對話框不再包含用于啟用后臺位置信息訪問權限的按鈕阵面。如需啟用后臺位置信息訪問權限轻局,用戶必須在設置頁面上針對應用的位置權限設置一律允許選項洪鸭。

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

  • 從Android10系統(tǒng)的設備開始仑扑,就需要請求后臺位置權限(ACCESS_BACKGROUND_LOCATION)览爵,并選擇Allow all the time (始終允許)才能獲得后臺位置權限。Android11設備上再次加強對后臺權限的管理镇饮,主要表現(xiàn)在系統(tǒng)對話框上蜓竹,對話框不再提示始終允許字樣,而是提供了位置權限的設置入口储藐,需要在設置頁面選擇始終允許才能獲得后臺位置權限俱济。
  • 在搭載Android11系統(tǒng)的設備上,targetVersion小于30的時候钙勃,可以前臺后臺位置權限一起申請蛛碌,并且對話框提供了文字說明,表示需要隨時獲取用戶位置信息肺缕,進入設置選擇始終允許即可左医。但是targetVersion為30的時候,你必須單獨申請后臺位置權限同木,而且要在獲取前臺權限之后,順序不能亂跛十。并且無任何提示彤路,需要開發(fā)者自己設計提示樣式。

可能有點繞芥映,操作幾個例子說明:

  1. Android10設備洲尊,申請前臺和后臺位置權限(任意targetSdkVersion):
requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_BACKGROUND_LOCATION), 100)

執(zhí)行效果:


  1. Android11設備,targetSdkVersion<=29(Android 10),申請前臺和后臺位置權限:
requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_BACKGROUND_LOCATION), 100)

執(zhí)行效果:


  1. Android11設備奈偏,targetSdkVersion=30(Android 11),申請前臺和后臺位置權限:
requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_BACKGROUND_LOCATION), 100)

執(zhí)行無反應

  1. Android11設備坞嘀,targetSdkVersion=30(Android 11),先申請前臺位置權限,后申請后臺位置權限:
requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION), 100)

執(zhí)行效果:


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

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


所以丽涩,該怎么適配呢?

  • 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 更改了應用查詢用戶已在設備上安裝的其他應用以及與之交互的方式崔泵。使用新的 元素,應用可以定義一組自身可訪問的其他應用猪瞬。通過告知系統(tǒng)應向您的應用顯示哪些其他應用管削,此元素有助于鼓勵最小權限原則。此外撑螺,此元素還可幫助 Google Play 等應用商店評估應用為用戶提供的隱私權和安全性含思。

也就是說,Android11中甘晤,如果你想去獲取其他應用的信息含潘,比如包名,名稱等等线婚,不能直接獲取了遏弱,必須在清單文件中添加<queries>元素,告知系統(tǒng)你要獲取哪些應用信息或者哪一類應用塞弊。
比如我這段查詢應用信息的代碼:

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

Android11版本漱逸,只能查詢到自己應用和系統(tǒng)應用的信息,查不到其他應用的信息了游沿。怎么呢饰抒?添加<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 權限的應用會提供相關指南,但是還沒出來郁轻,具體要看后面的消息了翅娶。
至于國內市場。范咨。故觅。(希望能有個應用市場一統(tǒng)天下好好管理這混亂的市場吧!)

文檔訪問限制

為讓開發(fā)者有時間進行測試渠啊,以下與存儲訪問框架 (SAF) 相關的變更只有在應用以 Android 11 為目標平臺時才會生效输吏。

上文存儲的時候說過可以通過SAF(存儲訪問框架--Storage Access Framework)來訪問公共目錄,但是Android11再次升級替蛉,部分目錄和文件不能訪問了贯溅,具體如下:
無法再使用 ACTION_OPEN_DOCUMENT_TREE intent 操作請求訪問以下目錄:

  • 內部存儲卷的根目錄拄氯。
  • 設備制造商認為可靠的各個 SD 卡卷的根目錄,無論該卡是模擬卡還是可移除的卡它浅∫氚兀可靠的卷是指應用在大多數(shù)情況下可以成功訪問的卷。
  • Download 目錄姐霍。

無法再使用 ACTION_OPEN_DOCUMENT_TREEACTION_OPEN_DOCUMENT intent 操作請求用戶從以下目錄中選擇單獨的文件:

  • Android/data/ 目錄及其所有子目錄鄙麦。
  • Android/obb/ 目錄及其所有子目錄。

限制對 APN 數(shù)據(jù)庫的讀取訪問

以 Android 11 為目標平臺的應用現(xiàn)在必須具備 Manifest.permission.WRITE_APN_SETTINGS 特權镊折,才能讀取或訪問電話提供程序 APN 數(shù)據(jù)庫胯府。如果在不具備此權限的情況下嘗試訪問 APN 數(shù)據(jù)庫,會生成安全異常恨胚。

問題來了骂因,APN是啥?

  • 指一種網(wǎng)絡接入技術,是通過手機上網(wǎng)時必須配置的一個參數(shù),APN配置參數(shù)包括名字均函,運營商編號,APN接入點等等俄烁。

就是說如果沒有Manifest.permission.WRITE_APN_SETTINGS權限就不能讀取APN數(shù)據(jù)庫了,但是僚碎!這個權限很早之前就被限定只有系統(tǒng)程序才能申請這個權限了猴娩,現(xiàn)在這個特權沒理解到是什么意思,難道系統(tǒng)程序都不能隨便申請了勺阐?

在元數(shù)據(jù)文件中聲明“無障礙”按鈕使用情況

從 Android 11 開始,您的無障礙服務無法在運行時聲明與系統(tǒng)的“無障礙”按鈕的關聯(lián)矛双。如果您將 AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON 附加到 AccessibilityServiceInfo 對象的 flags 屬性渊抽,框架就不會將“無障礙”按鈕回調事件傳遞給您的服務。

做過無障礙輔助功能的應該都知道AccessibilityServiceInfo要設置flag為FLAG_REQUEST_ACCESSIBILITY_BUTTON议忽,getAccessibilityButtonController方法獲取輔助功能按鈕控制器懒闷,并且可用于查詢輔助功能按鈕的狀態(tài)并注冊監(jiān)聽器以進行交互和輔助功能按鈕的狀態(tài)更改。

但是栈幸,Android 11開始愤估,這樣寫不能獲取輔助按鈕回調事件了,得換成另外一種寫法速址。在元數(shù)據(jù)文件(通常為 res/raw/accessibilityservice.xml)中使用 flagRequestAccessibilityButton 標記聲明您的無障礙服務與“無障礙”按鈕的關聯(lián)玩焰。

Firebase JobDispatcher 和 GCMNetworkManager

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

這兩個api國內都用不了昔园,主要用于后臺任務蔓榄。官方給出的替代意見是WorkManager,這個國內是可以用的默刚,屬于jetpack組件甥郑,主要用于調度和執(zhí)行可延期的后臺工作。

設備到設備文件傳輸

如果您的應用以 Android 11 為目標平臺荤西,您將無法再使用 allowBackup 屬性停用應用文件的設備到設備遷移澜搅。系統(tǒng)會自動啟用此功能。不過邪锌,即使您的應用以 Android 11 為目標平臺勉躺,您也可以通過將 allowBackup 屬性設置為 false 來停用應用文件的云端備份和恢復。

android:allowBackup屬性

  • 代表是否允許應用參與備份和恢復基礎架構秃流。如果將此屬性設為 false赂蕴,則永遠不會為該應用執(zhí)行備份或恢復,即使是采用全系統(tǒng)備份方法也不例外(這種備份方法通常會通過 adb 保存所有應用數(shù)據(jù))舶胀。此屬性的默認值為 true概说。

所以這里是不能停用文件的設備到設備遷移,但是可以停用云端備份和恢復

自動重置權限

如果應用以 Android 11 為目標平臺并且數(shù)月未使用嚣伐,系統(tǒng)會通過自動重置用戶已授予應用的運行時敏感權限來保護用戶數(shù)據(jù)糖赔。此操作與用戶在系統(tǒng)設置中查看權限并將應用的訪問權限級別更改為拒絕的做法效果一樣。如果應用已遵循有關在運行時請求權限的最佳做法轩端,那么您不必對應用進行任何更改放典。這是因為,當用戶與應用中的功能互動時基茵,您應該會驗證相關功能是否具有所需權限奋构。

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

如果需要關閉這個功能怎么辦呢根灯?只有引導用戶去設置頁面關閉了径缅,可以調用包含Settings.ACTION_APPLICATION_DETAILS_SETTINGS action的 Intent將用戶定向到系統(tǒng)設置中應用的頁面。

怎么檢查應用是否停用自動重置功能呢烙肺?調用 PackageManager的isAutoRevokeWhitelisted()方法纳猪。如果此方法返回 true,代表系統(tǒng)不會自動重置應用的權限桃笙。

前臺服務類型

從 Android 9 開始氏堤,應用僅限于在前臺訪問攝像頭和麥克風。為了進一步保護用戶怎栽,Android 11 更改了前臺服務訪問攝像頭和麥克風相關數(shù)據(jù)的方式丽猬。如果您的應用以 Android 11 為目標平臺并且在某項前臺服務中訪問這些類型的數(shù)據(jù)宿饱,您需要在該前臺服務的聲明的 foregroundServiceType 屬性中添加新的 camera 和 microphone 類型。

在Android10的時候脚祟,對于前臺定位服務就必須加上android:foregroundServiceType="location"谬以,現(xiàn)在Android11上又增加了兩個權限限制,一個是攝像頭一個是麥克風由桌。

所以總結下來就是为黎,應用某項前臺服務需要訪問位置信息、攝像頭和麥克風行您,那么就要在清單文件中這樣添加:

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

有的朋友可能測試發(fā)現(xiàn)铭乾,不加foregroundServiceType的前提下,讓Activity啟動了一個前臺服務娃循,并在服務里去獲取定位炕檩,竟然可以獲取到定位信息,難道官方說錯了捌斧?

其實這是因為你并沒有讓前臺服務單獨運行笛质,你可以試著在Activity啟動Service后,進入Home界面捞蚂,然后過幾秒再請求位置妇押,就請求不到了。但是不會崩潰姓迅,因為這個被系統(tǒng)設置的權限類別為MODE_IGNORED敲霍,也就是靜默失敗模式。

所以為了保險起見丁存,只要前臺服務涉及到了這三個功能肩杈,就在清單文件加上android:foregroundServiceType

適配Android11手機

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

數(shù)據(jù)訪問審核

為了讓應用及其依賴項訪問用戶私密數(shù)據(jù)的過程更加透明锋恬,Android 11 引入了數(shù)據(jù)訪問審核功能。借助此流程得出的見解编丘,您可以更好地識別和糾正可能出現(xiàn)的意外數(shù)據(jù)訪問。

哪些范疇屬于用戶私密數(shù)據(jù)呢彤悔?其實就是危險權限的調用嘉抓,所以這個功能就是提供了可以監(jiān)聽危險權限調用的監(jiān)聽。主要涉及到的方法是AppOpsManager.OnOpNotedCallback晕窑。無論是應用本身抑片,還是依賴庫或者SDK中的代碼,只要訪問到私密數(shù)據(jù)(危險權限)杨赤,都會回調給我們敞斋。
對于工程龐大或者使用較多SDK的工程比較適合用上這個功能截汪,讓自己應用的私有數(shù)據(jù)管理更加透明規(guī)范,否則對于私有數(shù)據(jù)的使用和管理并不全面和方便植捎。而且還可以對權限使用添加歸因衙解,也就是一個tag,標志權限用到了什么地方焰枢。方便回調的時候知曉哪里使用了私有數(shù)據(jù)蚓峦。

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

    //創(chuàng)建歸因(attribute)  
    attributionContext = createAttributionContext("shareLocation")

    //監(jiān)聽事件
    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)
            }
        }
    }

    //開啟私密數(shù)據(jù)監(jiān)聽
    val appOpsManager =
            getSystemService(AppOpsManager::class.java) as AppOpsManager
    appOpsManager.setOnOpNotedCallback(mainExecutor, appOpsCallback)

    btn1.setOnClickListener {
        getLocation()
    }
}

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

該例子主要展示了一個獲取位置信息的功能,如果調用到getLocation方法济锄,就會觸發(fā)onNoted回調暑椰,回調信息包括危險權限code以及歸因。
其中OnOpNotedCallback 一共三個回調方法:

  • onNoted 正常情況下都會回調到該方法
  • onAsyncNoted 如果數(shù)據(jù)訪問并非發(fā)生在應用調用API期間荐绝,就會調用onAsyncNoted()一汽,比如一些監(jiān)聽器的回調。
  • onSelfNoted 在極少數(shù)情況下低滩,如果應用將自身的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 中戳鹅,每當應用請求與位置信息、麥克風或攝像頭相關的權限時昏兆,面向用戶的權限對話框會包含僅限這一次選項枫虏。如果用戶在對話框中選擇此選項,系統(tǒng)會向應用授予臨時的單次授權爬虱。

簡單的說隶债,就是在申請與位置信息、麥克風或攝像頭相關的權限時跑筝,系統(tǒng)會自動提供一個單次授權的選項死讹,只供這一次權限獲取。然后用戶下次打開app的時候曲梗,系統(tǒng)會再次提示用戶授予權限赞警。這個影響應該不大,只要我們每次使用的時候都去判斷權限虏两,沒有就去申請即可愧旦。放一張新版本權限獲取樣式:

權限對話框的可見性

Android 11 建議不要請求用戶已選擇拒絕的權限。在應用安裝到設備上后定罢,如果用戶在使用過程中屢次針對某項特定的權限點按拒絕笤虫,此操作表示其希望“不再詢問”。

這個都算不上改動,只是官方的一個良好建議琼蚯。建議在用戶多次拒絕之后酬凳,不要再展示權限申請。

Scudo Hardened Allocator

Android 11 在內部使用 Scudo Hardened Allocator 為堆分配提供服務遭庶。Scudo 能夠檢測并減輕某些類型的內存安全違規(guī)行為宁仔。如果您在原生代碼崩潰報告中發(fā)現(xiàn)與 Scudo 相關的崩潰(例如 Scudo ERROR:),請參閱 Scudo 問題排查文檔罚拟。

Scudo是一種動態(tài)的用戶模式內存分配器台诗,旨在抵御與堆相關的漏洞,同時保持良好的性能赐俗。它是一個開源的項目拉队。
Android 11中,將采用這個新的heap分配器阻逮,性能更好粱快,更安全。

文件描述符排錯程序

Android 10 引入了 fdsan(文件描述符排錯程序)叔扼。fdsan 檢測錯誤處理文件描述符所有權的錯誤事哭,例如 use-after-close 和 double-close。在 Android 11 中瓜富,fdsan 的默認模式發(fā)生了變化△⒃郏現(xiàn)在,fdsan 會在檢測到錯誤時中止与柑,而以前的行為則是記錄警告并繼續(xù)谤辜。

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

文件描述符(FileDescriptor) 是Unix/Linux系統(tǒng)文件操作的相關概念丑念,它在形式上是一個非負整數(shù)。當程序打開一個現(xiàn)有文件或者創(chuàng)建一個新文件時结蟋,內核向進程返回一個文件描述符脯倚。系統(tǒng)的進程也就是使用了這個fd來標示打開的文件,有了它就能對文件做各種操作嵌屎,獲得文件的各種相關信息了推正。

所以fdsan也就是檢測文件處理中發(fā)生的一些錯誤。

應用使用情況統(tǒng)計信息

為了更好地保護用戶宝惰,Android 11 將每個用戶的應用使用情況統(tǒng)計信息存儲在憑據(jù)加密存儲空間中舔稀。

這就涉及到了UsageStatsManager,UsageStatsManager是Android提供統(tǒng)計應用使用情況的服務掌测。通過這個服務可以獲取指定時間區(qū)間內應用使用統(tǒng)計數(shù)據(jù)、組件狀態(tài)變化事件統(tǒng)計數(shù)據(jù)以及硬件配置信息統(tǒng)計數(shù)據(jù)。

比如queryAndAggregateUsageStats方法汞斧,可以獲取指定時間區(qū)間內使用統(tǒng)計數(shù)據(jù)夜郁,以應用包名為鍵值進行數(shù)據(jù)合并。

但是在Android 11 設備中粘勒,不好意思竞端,不能隨意使用這些信息了。只有當isUserUnlocked()方法返回true的時候庙睡,才能正常訪問這些數(shù)據(jù)事富。也就是以下兩種情況:

  • 用戶在系統(tǒng)啟動后首次解鎖其設備
  • 用戶在設備上切換到自己的帳號

JobScheduler API 調用限制調試

JobScheduler任務調度器,可以在設備空閑時做一些任務處理乘陪。Android11中如果你設置為debug模式(debuggable 清單屬性設置為 true)统台,超出速率限制的JobScheduler API調用將返回 RESULT_FAILURE。這個有什么用呢啡邑?應該可以幫助我們發(fā)現(xiàn)一些性能問題贱勃,感興趣的可以自己試試。

順便提下谤逼,Jetpack組件WorkManager也是用到了JobScheduler贵扰,不熟悉的同學可以去了解下,JobScheduler是由SystemServer進程啟動的一個系統(tǒng)服務流部,所以才可以有這么大的權限戚绕。

無障礙操作

在以前的 Android 版本中,框架會向未正確處理基于點擊的無障礙操作的微件分派觸摸事件枝冀。通常舞丛,這些視圖會直接處理觸摸事件,而不是注冊點擊監(jiān)聽器宾茂。為了在正確定義無障礙操作的應用中創(chuàng)建更一致的行為瓷马,Android 11 絕不會分派觸摸事件。相反跨晴,系統(tǒng)會完全依賴于基于點擊的無障礙操作:ACTION_CLICK 和 ACTION_LONG_CLICK欧聘。此更改會影響屏幕閱讀器的行為。

在Android手機上有個預安裝的屏幕閱讀服務端盆,叫做TalkBack怀骤,為視力障礙人士或者視力狀態(tài)不佳的老年人提供。那我們應用為了讓這個閱讀器能夠讀懂你的自定義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 開發(fā)者之間的協(xié)作以及最新的內部測試)渊啰。在限制使用非 SDK 接口之前,我們會盡可能確保提供公開替代方案申屹。

總結

一路分析下來也可以看到绘证,如果是重要的改動,特別是涉及到崩潰的改動還是放到了targetSdkVersion=30的內容中哗讥,這也是每次Android發(fā)版的一個潛規(guī)則吧嚷那,為了最大程度不影響已上線的app所作出的舉動。

附件

官網(wǎng)改動介紹

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末忌栅,一起剝皮案震驚了整個濱河市车酣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌索绪,老刑警劉巖湖员,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瑞驱,居然都是意外死亡娘摔,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門唤反,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凳寺,“玉大人,你說我怎么就攤上這事彤侍〕τВ” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵盏阶,是天一觀的道長晒奕。 經(jīng)常有香客問我,道長名斟,這世上最難降的妖魔是什么脑慧? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮砰盐,結果婚禮上闷袒,老公的妹妹穿的比我還像新娘。我一直安慰自己岩梳,他們只是感情好囊骤,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布晃择。 她就那樣靜靜地躺著,像睡著了一般淘捡。 火紅的嫁衣襯著肌膚如雪藕各。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天焦除,我揣著相機與錄音,去河邊找鬼作彤。 笑死膘魄,一個胖子當著我的面吹牛,可吹牛的內容都是我干的竭讳。 我是一名探鬼主播创葡,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼绢慢!你這毒婦竟也來了灿渴?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤胰舆,失蹤者是張志新(化名)和其女友劉穎骚露,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缚窿,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡棘幸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了倦零。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片误续。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖扫茅,靈堂內的尸體忽然破棺而出蹋嵌,到底是詐尸還是另有隱情,我是刑警寧澤葫隙,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布栽烂,位于F島的核電站,受9級特大地震影響停蕉,放射性物質發(fā)生泄漏愕鼓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一慧起、第九天 我趴在偏房一處隱蔽的房頂上張望菇晃。 院中可真熱鬧,春花似錦蚓挤、人聲如沸磺送。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽估灿。三九已至崇呵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間馅袁,已是汗流浹背域慷。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留汗销,地道東北人犹褒。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像弛针,于是被迫代替她去往敵國和親叠骑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內容