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)”霞怀。