Android11新特性及部分適配

Android11新特性及部分適配

以下我分為兩部分講述唉堪,分別是

以Android11為目標(biāo)版本的應(yīng)用(targetSdkVersion>=30才有影響)

所有應(yīng)用在Android11設(shè)備上適配改動(dòng)(無(wú)論targetSdkVersion是多少前硫,只要在Android11設(shè)備上運(yùn)行的應(yīng)用都有影響)

一般來(lái)說(shuō)為了Google為了讓我們更長(zhǎng)時(shí)間適應(yīng)新的內(nèi)容以及保障線(xiàn)上應(yīng)用的穩(wěn)定,都會(huì)把改動(dòng)大的,需要花時(shí)間適配的內(nèi)容放到新的targetSdkVersion對(duì)應(yīng)的應(yīng)用上。

一、適配targetSdkVersion=30

此模塊的修改內(nèi)容只針對(duì)targetSdkVersion 30或者以上才生效愤钾。

1.1分區(qū)存儲(chǔ)強(qiáng)制執(zhí)行

●? 公共目錄:Downloads、Documents候醒、Pictures 能颁、DCIM、Movies倒淫、Music伙菊、Ringtones等

? ? ■ 公共目錄的文件在App卸載后,不會(huì)刪除

? ? ■ 可以通過(guò)SAF(Storage Access Framework)、MediaStore接口訪問(wèn)

? ? ■ 擁有權(quán)限镜硕,也能通過(guò)路徑直接訪問(wèn)

●??應(yīng)用專(zhuān)屬目錄

? ? ■ 應(yīng)用專(zhuān)屬目錄只能自己直接訪問(wèn)

? ? ■ App卸載运翼,數(shù)據(jù)會(huì)清除。

關(guān)于分區(qū)存儲(chǔ)兴枯,在Android10就已經(jīng)推行了南蹂,簡(jiǎn)單的說(shuō),就是應(yīng)用對(duì)于文件的讀寫(xiě)只能在沙盒環(huán)境念恍,也就是屬于自己應(yīng)用的目錄里面讀寫(xiě)。其他媒體文件可以通過(guò)MediaStore進(jìn)行訪問(wèn)晚顷。

但是在android10的時(shí)候峰伙,Google還是為開(kāi)發(fā)者考慮,留了一手该默。在targetSdkVersion = 29應(yīng)用中瞳氓,設(shè)置android:requestLegacyExternalStorage="true",就可以不啟動(dòng)分區(qū)存儲(chǔ)栓袖,讓以前的文件讀取正常使用匣摘。但是targetSdkVersion = 30中不行了,強(qiáng)制開(kāi)啟分區(qū)存儲(chǔ)裹刮。 當(dāng)然音榜,作為人性化的android,還是為開(kāi)發(fā)者留了一小手捧弃,如果是覆蓋安裝呢赠叼,可以增加android:preserveLegacyExternalStorage="true",暫時(shí)關(guān)閉分區(qū)存儲(chǔ)违霞,好讓開(kāi)發(fā)者完成數(shù)據(jù)遷移的工作嘴办。為什么是暫時(shí)呢?因?yàn)橹灰遁d重裝买鸽,就會(huì)失效了涧郊。以下是關(guān)于分區(qū)存儲(chǔ)會(huì)遇到的所有情況,給大家羅列出來(lái)了眼五。

private void saverFile() {

? ? try {

? ? ? ? //APP私有目錄:/storage/emulated/0/Android/data/包名/files

? ? ? ? //File externalFilesDir = getExternalFilesDir(null);

? ? ? ? //分區(qū)存儲(chǔ)開(kāi)啟后就不允許訪問(wèn)了,被棄用

? ? ? ? //公有目錄/storage/emulated/0/text1.txt

? ? ? ? String filePath = Environment.getExternalStoragePublicDirectory("").toString()+"/text1.txt";

? ? ? ? FileWriter fw = new FileWriter(filePath);

? ? ? ? fw.write("hello world");

? ? ? ? fw.close();

? ? ? ? Log.e(TAG, "saverFile: 文件寫(xiě)入成功" );

? ? } catch (IOException e) {

? ? ? ? e.printStackTrace();

? ? }

}

分情況運(yùn)行:

1) targetSdkVersion = 28妆艘,運(yùn)行后正常讀寫(xiě)。

2) targetSdkVersion = 29弹砚,不刪除應(yīng)用双仍,targetSdkVersion 由28修改到29,覆蓋安裝桌吃,運(yùn)行后正常讀寫(xiě)朱沃。

3) targetSdkVersion = 29,刪除應(yīng)用,重新運(yùn)行逗物,讀寫(xiě)報(bào)錯(cuò)搬卒,程序崩潰(open failed: EACCES (Permission denied))

4) targetSdkVersion = 29,添加android:requestLegacyExternalStorage="true"(不啟用分區(qū)存儲(chǔ))翎卓,讀寫(xiě)正常不報(bào)錯(cuò)

5) targetSdkVersion = 30契邀,不刪除應(yīng)用,targetSdkVersion 由29修改到30失暴,讀寫(xiě)報(bào)錯(cuò)坯门,程序崩潰(open failed: EACCES (Permission denied))

6) targetSdkVersion = 30,不刪除應(yīng)用逗扒,targetSdkVersion 由29修改到30古戴,增加android:preserveLegacyExternalStorage="true",讀寫(xiě)正常不報(bào)錯(cuò)

7) targetSdkVersion = 30矩肩,刪除應(yīng)用现恼,重新運(yùn)行,讀寫(xiě)報(bào)錯(cuò)黍檩,程序崩潰(open failed: EACCES (Permission denied))

1.2媒體文件訪問(wèn)權(quán)限

為了在保證用戶(hù)隱私的同時(shí)可以更輕松地訪問(wèn)媒體叉袍,Android 11 增加了以下功能。執(zhí)行批量操作和使用直接文件路徑和原生庫(kù)訪問(wèn)文件刽酱。

1)執(zhí)行批量操作

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

createWriteRequest() 用戶(hù)向應(yīng)用授予對(duì)指定媒體文件組的寫(xiě)入訪問(wèn)權(quán)限的請(qǐng)求棵里。

createFavoriteRequest()用戶(hù)將設(shè)備上指定的媒體文件標(biāo)記為“收藏”的請(qǐng)求艺配。對(duì)該文件具有讀取訪問(wèn)權(quán)限的任何應(yīng)用都可以看到用戶(hù)已將該文件標(biāo)記為“收藏”。

createTrashRequest() 用戶(hù)將指定的媒體文件放入設(shè)備垃圾箱的請(qǐng)求衍慎。垃圾箱中的內(nèi)容會(huì)在系統(tǒng)定義的時(shí)間段后被永久刪除转唉。

createDeleteRequest() 用戶(hù)立即永久刪除指定的媒體文件(而不是先將其放入垃圾箱)的請(qǐng)求。

例如稳捆,以下是構(gòu)建?createWriteRequest()?調(diào)用的方法赠法,傳入uri的集合,獲取用戶(hù)的同意后乔夯,就可以進(jìn)行操作了砖织。

List<Uri> urisToModify = /* A collection of content URIs to modify. */

PendingIntent editPendingIntent = MediaStore.createWriteRequest(contentResolver,

? ? ? ? ? ? ? ? ? urisToModify);

// Launch a system prompt requesting user permission for the operation.

startIntentSenderForResult(editPendingIntent.getIntentSender(),

? ? EDIT_REQUEST_CODE, null, 0, 0, 0);

@Override

protected void onActivityResult(int requestCode, int resultCode,

? ? ? ? ? ? ? ? ? ?@Nullable Intent data) {

? ? ...

? ? if (requestCode == EDIT_REQUEST_CODE) {

? ? ? ? if (resultCode == Activity.RESULT_OK) {

? ? ? ? ? ? /* Edit request granted; proceed. */

? ? ? ? } else {

? ? ? ? ? ? /* Edit request not granted; explain to the user. */

? ? ? ? }

? ? }

}

2)直接文件路徑和原生庫(kù)訪問(wèn)文件

Android11又恢復(fù)了使用直接文件路徑訪問(wèn)訪問(wèn)媒體文件!也就是除了 MediaStore API之外還有兩種方式可以訪問(wèn)媒體文件:

File API末荐。

原生庫(kù)侧纯,例如 fopen()。

性能:

當(dāng)您使用直接文件路徑依序讀取媒體文件時(shí)甲脏,其性能與?MediaStore?API 相當(dāng)眶熬。

但是妹笆,當(dāng)您使用直接文件路徑隨機(jī)讀取和寫(xiě)入媒體文件時(shí),進(jìn)程的速度可能最多會(huì)慢一倍娜氏。在此類(lèi)情況下拳缠,我們建議您改為使用?MediaStore?API。

1.3 軟件包可見(jiàn)性?

Android11中贸弥,如果你想去獲取其他應(yīng)用的信息窟坐,比如包名,名稱(chēng)等等绵疲,不能直接獲取了哲鸳,必須在清單文件中添加<queries>元素,告知系統(tǒng)你要獲取哪些應(yīng)用信息或者哪一類(lèi)應(yīng)用盔憨。

PackageManager packageManager = this.getPackageManager();

List<ApplicationInfo> listAppcations = packageManager

? ? ? ? .getInstalledApplications(PackageManager.GET_META_DATA);

for (ApplicationInfo ap:listAppcations){

? ? Log.d(TAG, "包名:? "+ap.packageName);

}

在Android11版本帕胆,只能查詢(xún)到自己應(yīng)用和系統(tǒng)應(yīng)用的信息,查不到其他應(yīng)用的信息般渡。

1.3.1查詢(xún)特定軟件包及與之交互

如果您知道要查詢(xún)或與之交互的一組特定應(yīng)用(例如,與您的應(yīng)用集成的應(yīng)用或您使用其服務(wù)的應(yīng)用)芙盘,請(qǐng)將其軟件包名稱(chēng)添加到?<queries>?元素內(nèi)的一組?<package>?元素中:

<manifest package="com.example.game">

? ? <queries>

? ? ? ? <package android:name="com.example.store" />

? ? ? ? <package android:name="com.example.services" />

? ? </queries>

? ? ...

</manifest>

1.3.2 在給定 intent 過(guò)濾器的情況下查詢(xún)應(yīng)用及與之交互

您的應(yīng)用可能需要查詢(xún)一組具有特定用途的應(yīng)用或與之交互驯用,但您可能不知道要添加的具體軟件包名稱(chēng)。在這種情況下儒老,您可以在?<queries>?元素中列出 intent 過(guò)濾器簽名蝴乔。然后,您的應(yīng)用就可以發(fā)現(xiàn)具有匹配的?<intent-filter>?元素的應(yīng)用驮樊。

<manifest package="com.example.game">

? ? <queries>

? ? ? ? <intent>

? ? ? ? ? ? <action android:name="android.intent.action.SEND" />

? ? ? ? ? ? <data android:mimeType="image/jpeg" />

? ? ? ? </intent>

? ? </queries>

? ? ...

</manifest>

1.3.3 查詢(xún)所有應(yīng)用及與之交互

在極少數(shù)情況下薇正,您的應(yīng)用可能需要查詢(xún)?cè)O(shè)備上的所有已安裝應(yīng)用或與之交互,不管這些應(yīng)用包含哪些組件囚衔。為了允許您的應(yīng)用看到其他所有已安裝應(yīng)用挖腰,Android?11 引入了QUERY_ALL_PACKAGES權(quán)限。

下面列出了適合添加?QUERY_ALL_PACKAGES?權(quán)限的用例的一些示例:

啟動(dòng)器應(yīng)用

無(wú)障礙應(yīng)用

瀏覽器

點(diǎn)對(duì)點(diǎn) (P2P) 共享應(yīng)用

設(shè)備管理應(yīng)用

安全應(yīng)用

不過(guò)练湿,在絕大多數(shù)情況下猴仑,可以通過(guò)聲明?<queries>?元素實(shí)現(xiàn)應(yīng)用的用例。為了尊重用戶(hù)隱私肥哎,您的應(yīng)用應(yīng)請(qǐng)求應(yīng)用正常工作所需的最小軟件包可見(jiàn)性辽俗。

1.4 所有文件訪問(wèn)權(quán)限

絕大多數(shù)需要共享存儲(chǔ)空間訪問(wèn)權(quán)限的應(yīng)用都可以遵循分區(qū)存儲(chǔ)最佳做法,例如存儲(chǔ)訪問(wèn)框架或 MediaStore API篡诽。但是崖飘,某些應(yīng)用的核心用例需要廣泛訪問(wèn)設(shè)備上的文件,但無(wú)法采用注重隱私保護(hù)的存儲(chǔ)最佳做法高效地完成這些操作杈女。

例如朱浴,防病毒應(yīng)用的主要用例可能需要定期掃描不同目錄中的許多文件吊圾。如果此掃描需要反復(fù)的用戶(hù)交互,讓其使用系統(tǒng)文件選擇器選擇目錄赊琳,可能就會(huì)帶來(lái)糟糕的用戶(hù)體驗(yàn)街夭。其他用例(如文件管理器應(yīng)用、備份和恢復(fù)應(yīng)用以及文檔管理應(yīng)用)可能也需要考慮類(lèi)似情況躏筏。

應(yīng)用可通過(guò)執(zhí)行以下操作板丽,向用戶(hù)請(qǐng)求名為“所有文件訪問(wèn)權(quán)限”的特殊應(yīng)用訪問(wèn)權(quán)限:

在清單中聲明MANAGE_EXTERNAL_STORAGE權(quán)限。

使用ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSIONintent 操作將用戶(hù)引導(dǎo)至一個(gè)系統(tǒng)設(shè)置頁(yè)面趁尼,在該頁(yè)面上埃碱,用戶(hù)可以為您的應(yīng)用啟用以下選項(xiàng):授予所有文件的管理權(quán)限。

如需確定您的應(yīng)用是否已獲得?MANAGE_EXTERNAL_STORAGE?權(quán)限酥泞,請(qǐng)調(diào)用Environment.isExternalStorageManager()砚殿。

? ? <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權(quán)限:

? ? val isHasStoragePermission= Environment.isExternalStorageManager()

1.5 電話(huà)號(hào)碼相關(guān)權(quán)限

Android 11 更改了您的應(yīng)用在讀取電話(huà)號(hào)碼時(shí)使用的與電話(huà)相關(guān)的權(quán)限。

其實(shí)就是兩個(gè)API:

TelecomManager 類(lèi)中的 getLine1Number() 方法

TelecomManager 類(lèi)中的 getMsisdn() 方法

也就是當(dāng)用到這兩個(gè)API的時(shí)候芝囤,原來(lái)的READ_PHONE_STATE權(quán)限不管用了似炎,需要READ_PHONE_NUMBERS權(quán)限才行。

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

從 Android 11 開(kāi)始悯姊,已棄用自定義消息框視圖羡藐。如果您的應(yīng)用以 Android 11 為目標(biāo)平臺(tái),包含自定義視圖的消息框在從后臺(tái)發(fā)布時(shí)會(huì)被屏蔽悯许。

? ? Toast toast = new Toast(context);

? ? toast.setDuration(show_length);

? ? toast.setView(view);//棄用

? ? /**

? ? *如果您希望在消息框(文本消息框或自定義消息框)出現(xiàn)或消失時(shí)收到通知仆嗦,請(qǐng)使用新的?addCallback()?方法

? ? */

? ? toast.addCallback(new Toast.Callback() {

? ? @Override

? ? public void onToastShown() {

? ? ? ? super.onToastShown();

? ? }

? ? @Override

? ? public void onToastHidden() {

? ? ? ? super.onToastHidden();

? ? }

});

? ? toast.show();

如果您的應(yīng)用仍嘗試從后臺(tái)發(fā)布包含自定義視圖的消息框,系統(tǒng)不會(huì)向用戶(hù)顯示相應(yīng)的消息先壕,而是會(huì)在 logcat 中記錄以下消息:

W/NotificationService: Blocking custom toast from package \

? <package> due to package not in the foreground

1.7 媒體intent操作需要系統(tǒng)默認(rèn)相機(jī)

從 Android?11 開(kāi)始瘩扼,只有預(yù)裝的系統(tǒng)相機(jī)應(yīng)用可以響應(yīng)以下 intent 操作:

android.media.action.VIDEO_CAPTURE

android.media.action.IMAGE_CAPTURE

android.media.action.IMAGE_CAPTURE_SECURE

如果有多個(gè)預(yù)裝的系統(tǒng)相機(jī)應(yīng)用可用,系統(tǒng)會(huì)顯示一個(gè)對(duì)話(huà)框垃僚,供用戶(hù)選擇應(yīng)用集绰。如果您希望自己的應(yīng)用使用特定的第三方相機(jī)應(yīng)用來(lái)代表其捕獲圖片或視頻,可以通過(guò)為 intent 設(shè)置軟件包名稱(chēng)或組件來(lái)使這些 intent 變得明確谆棺。

1.8 5G

Android 11 添加了在您的應(yīng)用中支持 5G 的功能

檢測(cè)是否連接到了5G網(wǎng)絡(luò)

檢查按流量計(jì)費(fèi)性

首先是檢測(cè)5G網(wǎng)絡(luò)倒慧,通過(guò)TelephonyManager的監(jiān)聽(tīng)方法:

? ? 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("高級(jí)專(zhuān)業(yè)版 LTE (5Ge)")

? ? ? ? ? ? ? ? ? ? TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA -> showToast("NR (5G) - 5G Sub-6 網(wǎng)絡(luò)")

? ? ? ? ? ? ? ? ? ? TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE -> showToast("5G+/5G UW - 5G mmWave 網(wǎng)絡(luò)")

? ? ? ? ? ? ? ? ? ? else -> showToast("other")

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }, PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED)

? ? }

檢測(cè)流量計(jì)費(fèi)方法也很簡(jiǎn)單,監(jiān)聽(tīng)網(wǎng)絡(luò)包券,在回調(diào)中判斷:

? ? val manager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager

? ? manager.registerDefaultNetworkCallback(object : ConnectivityManager.NetworkCallback() {

? ? ? ? override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {

? ? ? ? ? super.onCapabilitiesChanged(network, networkCapabilities)

? ? ? ? ? ? //true 代表連接不按流量計(jì)費(fèi)

? ? ? ? ? ? val isNotFlowPay=networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) ||

? ? ? ? ? ? ? ? ? ? ? ? ? ? networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)

? ? ? ? ? }

? ? })

判斷該值纫谅,如果為 true,則將連接視為不按流量計(jì)費(fèi)溅固。

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

對(duì)于以 Android 11(API 級(jí)別 30)為目標(biāo)平臺(tái)付秕,且目前僅使用 APK 簽名方案 v1 簽名的應(yīng)用,現(xiàn)在還必須使用 APK 簽名方案 v2 或更高版本進(jìn)行簽名侍郭。用戶(hù)無(wú)法在搭載 Android 11 的設(shè)備上安裝或更新僅通過(guò) APK 簽名方案 v1 簽名的應(yīng)用询吴。

如果你的targetSdkVersion修改到30掠河,那么你就必須要加上v2簽名才行。否則無(wú)法安裝和更新猛计。

2.0 后臺(tái)位置信息訪問(wèn)權(quán)限

在搭載 Android 11 的設(shè)備上唠摹,當(dāng)應(yīng)用中的某項(xiàng)功能請(qǐng)求在后臺(tái)訪問(wèn)位置信息時(shí),用戶(hù)看到的系統(tǒng)對(duì)話(huà)框不再包含用于啟用后臺(tái)位置信息訪問(wèn)權(quán)限的按鈕奉瘤。如需啟用后臺(tái)位置信息訪問(wèn)權(quán)限勾拉,用戶(hù)必須在設(shè)置頁(yè)面上針對(duì)應(yīng)用的位置權(quán)限設(shè)置一律允許選項(xiàng)。

在較低版本的Android系統(tǒng)中盗温,當(dāng)應(yīng)用獲得前臺(tái)位置信息訪問(wèn)權(quán)限時(shí)藕赞,也會(huì)自動(dòng)獲得后臺(tái)位置信息訪問(wèn)權(quán)限。比如我請(qǐng)求一個(gè)前臺(tái)位置訪問(wèn)權(quán)限:

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

授權(quán)后卖局,就能同時(shí)獲取前臺(tái)位置權(quán)限和后臺(tái)位置權(quán)限(ACCESS_BACKGROUND_LOCATION)斧蜕。

但是現(xiàn)在不行了,你必須單獨(dú)申請(qǐng)后臺(tái)位置權(quán)限砚偶,而且批销,要在獲取前臺(tái)權(quán)限之后,順序還不能亂染坯。

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

如果在沒(méi)獲取前臺(tái)權(quán)限的時(shí)候執(zhí)行這個(gè)獲取后臺(tái)權(quán)限的代碼會(huì)沒(méi)反應(yīng)均芽,等獲取前臺(tái)權(quán)限(ACCESS_COARSE_LOCATION)之后,申請(qǐng)后臺(tái)權(quán)限就會(huì)跳轉(zhuǎn)到一個(gè)新的權(quán)限頁(yè)面了酒请,而且必須選擇Allow all the time (始終允許)才能獲得后臺(tái)位置權(quán)限,看圖:

二鸣个、適配Android11手機(jī)

此模塊的修改內(nèi)容針對(duì)所有項(xiàng)目在Android11手機(jī)上存在的改動(dòng)羞反,與targetSdkVersion無(wú)關(guān)。

2.1數(shù)據(jù)訪問(wèn)審核

為了讓?xiě)?yīng)用及其依賴(lài)項(xiàng)訪問(wèn)用戶(hù)私密數(shù)據(jù)的過(guò)程更加透明囤萤,Android 11 引入了數(shù)據(jù)訪問(wèn)審核功能昼窗。

其實(shí)就是危險(xiǎn)權(quán)限的調(diào)用,所以這個(gè)功能就是提供了可以監(jiān)聽(tīng)危險(xiǎn)權(quán)限調(diào)用的監(jiān)聽(tīng)涛舍。主要涉及到的方法是AppOpsManager.OnOpNotedCallback澄惊。無(wú)論是應(yīng)用本身,還是依賴(lài)庫(kù)或者SDK中的代碼富雅,只要訪問(wèn)到私密數(shù)據(jù)(危險(xiǎn)權(quán)限)掸驱,都會(huì)回調(diào)給我們。

該例子主要展示了一個(gè)獲取位置信息的功能没佑,如果調(diào)用到getLocation方法毕贼,就會(huì)觸發(fā)onNoted回調(diào),回調(diào)信息包括危險(xiǎn)權(quán)限code以及歸因蛤奢。

其中OnOpNotedCallback 一共三個(gè)回調(diào)方法:

1鬼癣、onNoted 正常情況下都會(huì)回調(diào)到該方法

2陶贼、onAsyncNoted 如果數(shù)據(jù)訪問(wèn)并非發(fā)生在應(yīng)用調(diào)用API期間,就會(huì)調(diào)用onAsyncNoted()待秃,比如一些監(jiān)聽(tīng)器的回調(diào)拜秧。

3、onSelfNoted 在極少數(shù)情況下章郁,如果應(yīng)用將自身的UID傳遞到 noteOp()枉氮,需要調(diào)用 onSelfNoted()。

//創(chuàng)建歸因(attribute)?

shareLocation = createAttributionContext("shareLocation");

//監(jiān)聽(tīng)事件

AppOpsManager.OnOpNotedCallback myAppTag = new AppOpsManager.OnOpNotedCallback() {

? ? private void logPrivateDataAccess(String opCode,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? String attributionTag, String trace) {

? ? ? ? Log.i("MY_APP_TAG", "Private data accessed. \n" +

? ? ? ? ? ? ? ? "Operation: " + opCode + "\n " +

? ? ? ? ? ? ? ? "Attribution Tag:" + attributionTag + "\nStack Trace:\n" + trace);

? ? }

? ? @Override

? ? public void onNoted(@NonNull SyncNotedAppOp syncNotedAppOp) {

? ? ? ? logPrivateDataAccess(syncNotedAppOp.getOp(),

? ? ? ? ? ? ? ? syncNotedAppOp.getAttributionTag(),

? ? ? ? ? ? ? ? Arrays.toString(new Throwable().getStackTrace()));

? ? }

? ? @Override

? ? public void onSelfNoted(@NonNull SyncNotedAppOp syncNotedAppOp) {

? ? ? ? logPrivateDataAccess(syncNotedAppOp.getOp(),

? ? ? ? ? ? ? ? syncNotedAppOp.getAttributionTag(),

? ? ? ? ? ? ? ? Arrays.toString(new Throwable().getStackTrace()));

? ? }

? ? @Override

? ? public void onAsyncNoted(@NonNull AsyncNotedAppOp asyncNotedAppOp) {

? ? ? ? logPrivateDataAccess(asyncNotedAppOp.getOp(),

? ? ? ? ? ? ? ? asyncNotedAppOp.getAttributionTag(),

? ? ? ? ? ? ? ? asyncNotedAppOp.getMessage());

? ? }

};

//打開(kāi)私密數(shù)據(jù)監(jiān)聽(tīng)

AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);

if (appOpsManager != null) {

? ? appOpsManager.setOnOpNotedCallback(getMainExecutor(), myAppTag);

}

private void getLocation() {

? ? LocationManager locationManager = shareLocation.getSystemService(LocationManager.class);

? ? if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {

? ? ? ? return;

? ? }

? ? Location lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);

}

看下回調(diào)的結(jié)果日志:

com.example.myandroid11 I/MY_APP_TAG: Private data accessed.

? ? ? ? Operation: android:fine_location

? ? Attribution Tag:shareLocation

? ? [com.example.myandroid11.MainActivity$1.onNoted(MainActivity.java:62), android.app.AppOpsManager.readAndLogNotedAppops(AppOpsManager.java:8204), android.os.Parcel.readExceptionCode(Parcel.java:2304), android.os.Parcel.readException(Parcel.java:2279), android.location.ILocationManager$Stub$Proxy.getLastLocation(ILocationManager.java:1225), android.location.LocationManager.getLastKnownLocation(LocationManager.java:648), com.example.myandroid11.MainActivity.getLocation(MainActivity.java:149), com.example.myandroid11.MainActivity.access$100(MainActivity.java:38), com.example.myandroid11.MainActivity$4.onClick(MainActivity.java:109), android.view.View.performClick(View.java:7448), android.view.View.performClickInternal(View.java:7425), android.view.View.access$3600(View.java:810), android.view.View$PerformClick.run(View.java:28305), android.os.Handler.handleCallback(Handler.java:938), android.os.Handler.dispatchMessage(Handler.java:99), android.os.Looper.loop(Looper.java:223), android.app.ActivityThread.main(ActivityThread.java:7656), java.lang.reflect.Method.invoke(Native Method), com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592), com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)]

2.2 單次授權(quán)

就是在申請(qǐng)與位置信息驱犹、麥克風(fēng)或攝像頭相關(guān)的權(quán)限時(shí)嘲恍,系統(tǒng)會(huì)自動(dòng)提供一個(gè)單次授權(quán)的選項(xiàng),只供這一次權(quán)限獲取雄驹。然后用戶(hù)下次打開(kāi)app的時(shí)候佃牛,系統(tǒng)會(huì)再次提示用戶(hù)授予權(quán)限。這個(gè)影響應(yīng)該不大医舆,只要我們每次使用的時(shí)候都去判斷權(quán)限俘侠,沒(méi)有就去申請(qǐng)即可。放一張新版本權(quán)限獲取樣式:

2.3 權(quán)限對(duì)話(huà)框的可見(jiàn)性

Android 11 建議不要請(qǐng)求用戶(hù)已選擇拒絕的權(quán)限蔬将。在應(yīng)用安裝到設(shè)備上后爷速,如果用戶(hù)在使用過(guò)程中屢次針對(duì)某項(xiàng)特定的權(quán)限點(diǎn)按拒絕,此操作表示其希望“不再詢(xún)問(wèn)”霞怀。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惫东,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子毙石,更是在濱河造成了極大的恐慌廉沮,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件徐矩,死亡現(xiàn)場(chǎng)離奇詭異滞时,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)滤灯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)坪稽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人鳞骤,你說(shuō)我怎么就攤上這事窒百。” “怎么了豫尽?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵贝咙,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我拂募,道長(zhǎng)庭猩,這世上最難降的妖魔是什么窟她? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮蔼水,結(jié)果婚禮上震糖,老公的妹妹穿的比我還像新娘。我一直安慰自己趴腋,他們只是感情好吊说,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著优炬,像睡著了一般颁井。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蠢护,一...
    開(kāi)封第一講書(shū)人閱讀 52,457評(píng)論 1 311
  • 那天雅宾,我揣著相機(jī)與錄音,去河邊找鬼葵硕。 笑死眉抬,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的懈凹。 我是一名探鬼主播蜀变,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼介评!你這毒婦竟也來(lái)了库北?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤们陆,失蹤者是張志新(化名)和其女友劉穎寒瓦,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體棒掠,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡孵构,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年屁商,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了烟很。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蜡镶,死狀恐怖雾袱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情官还,我是刑警寧澤芹橡,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站望伦,受9級(jí)特大地震影響林说,放射性物質(zhì)發(fā)生泄漏煎殷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一腿箩、第九天 我趴在偏房一處隱蔽的房頂上張望豪直。 院中可真熱鬧,春花似錦珠移、人聲如沸弓乙。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)暇韧。三九已至,卻和暖如春浓瞪,著一層夾襖步出監(jiān)牢的瞬間懈玻,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工追逮, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酪刀,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓钮孵,卻偏偏與公主長(zhǎng)得像骂倘,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子巴席,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360