
終于開始了Android 11的適配作業,記錄一下,供需要的人參考,
1. 準備作業
老規矩,首先將我們專案中的 targetSdkVersion 改為 30,或者使用兼容性除錯工具,后面我會說到,
2. 存盤機制更新
Scoped Storage(磁區存盤)
具體適配方法和去年的Android 10 適配攻略中的沒有太大區別,
不過需要注意的是,應用targetSdkVersion >= 30,強制執行磁區存盤機制,之前在AndroidManifest.xml中添加 android:requestLegacyExternalStorage="true"的適配方式已不起作用,
還有一個變化:Android 11 允許使用除 MediaStore API 之外的 API 通過檔案路徑直接訪問共享存盤空間中的媒體檔案,其中包括:
FileAPI,- 原生庫,例如
fopen(),
如果你之前沒有適配Android 10,這一點對你來說是個好訊息,Android 10在AndroidManifest.xml中添加 android:requestLegacyExternalStorage="true"來適配,Android 11上直接使用File API訪問媒體檔案,不得不說,等等黨的勝利?
不過,使用原始檔案路徑直接訪問共享存盤空間中的媒體檔案會重定向到 MediaStore API,這次重定向會造成性能影響(隨機讀寫慢一倍左右),而且直接使用原始檔案路徑,并不會比使用 MediaStore API 有更多優勢,因此官方強烈建議直接使用 MediaStore API,
MANAGE_EXTERNAL_STORAGE
當然還有一種簡單粗暴的適配方法,獲取外部存盤管理權限,如果你的應用是手機管家、檔案管理器這類需要訪問大量檔案的app,可以申請MANAGE_EXTERNAL_STORAGE權限,將用戶引導至系統設定頁面開啟,代碼如下:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
public static void checkStorageManagerPermission(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
!Environment.isExternalStorageManager()) {
Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}

需要注意的是即使你有了MANAGE_EXTERNAL_STORAGE權限,也無法訪問Android/data/目錄下的檔案,
對于MANAGE_EXTERNAL_STORAGE權限,國內使用應該沒有什么影響,但是在Google Play上需要說明為什么已有的SAF或MediaStore不滿足你的應用需求,審核通過才允許上架使用,所以一般情況下,我個人不推薦你為了適配簡單,直接申請使用MANAGE_EXTERNAL_STORAGE權限,
其他細節變更見檔案:Android 11 中的存盤機制更新,
相關api變更及使用推薦郭霖大神的這篇:Android 11新特性,Scoped Storage又有了新花樣,
存盤訪問框架 (SAF)變更
Android 11對SAF添加以下限制:
- 使用
ACTION_OPEN_DOCUMENT_TREE或ACTION_OPEN_DOCUMENT,無法瀏覽到Android/data/和Android/obb/目錄及其所有子目錄, - 使用
ACTION_OPEN_DOCUMENT_TREE無法授權訪問存盤根目錄、Download檔案夾,
REQUEST_INSTALL_PACKAGES
在8.0的適配中,我們安裝apk包之前需要申請“安裝未知來源應用”的權限,一般來說首次是跳轉到授權頁面讓用戶手動開啟,然后回傳app進行安裝,
在Android 11中當用戶開啟“安裝未知來源應用”的權限,app就會被殺死,該行為與強制磁區存盤有關,因為持有 REQUEST_INSTALL_PACKAGES 權限的應用可以訪問其他應用的Android/obb 目錄,
好在用戶授予權限之后,雖然app會被殺死,但是安裝頁面依然會彈出,
目前對于這一變更我沒有發現可以適配處理的方式,詳細介紹見:Android 11特性調整:安裝外部來源應用需要重啟APP
3.權限變化
單次權限授權
從 Android 11 開始,每當應用請求與位置資訊、麥克風或攝像頭相關的權限時,面向用戶的權限對話框會包含僅限這一次選項,如果用戶在對話框中選擇此選項,系統會向應用授予臨時的單次授權,

單次權限授權的應用可以在一段時間內訪問相關資料,具體時間取決于應用的行為和用戶的操作:
- 當應用的 Activity 可見時,應用可以訪問相關資料,
- 如果用戶將應用轉為后臺運行,應用可以在短時間內繼續訪問相關資料,
- 如果您在 Activity 可見時啟動了一項前臺服務,并且用戶隨后將您的應用轉到后臺,那么您的應用可以繼續訪問相關資料,直到該前臺服務停止,
- 如果用戶撤消單次授權(例如在系統設定中撤消),無論您是否啟動了前臺服務,應用都無法訪問相關資料,與任何權限一樣,如果用戶撤消了應用的單次授權,應用行程就會終止,
當用戶下次打開應用并且應用中的某項功能請求訪問位置資訊、麥克風或攝像頭時,系統會再次提示用戶授予權限,
如果你之前就是使用權限時才請求相關權限,那么這一變更對于你的應用沒有影響,
請求位置權限
這部分在Android 10的適配有過調整,當時規則如下:
請求
ACCESS_FINE_LOCATION或ACCESS_COARSE_LOCATION權限表示在前臺時擁有訪問設備位置資訊的權限,在請求彈框中,選擇“始終允許”表示前后臺都可以獲取位置資訊,選擇“僅在應用使用程序中允許”只表示擁有前臺的權限,
在Android 11中,請求彈框中取消了“始終允許”這一選項,也就是說默認不會授予你后臺訪問設備位置資訊的權限,如果嘗試請求ACCESS_BACKGROUND_LOCATION權限的同時請求任何其他權限,系統會拋出例外,不會向應用授予其中的任一權限,
官方給出的適配建議及原因如下:
建議應用對位置權限執行遞增請求,先請求前臺位置資訊訪問權限,再請求后臺位置資訊訪問權限,執行遞增請求可以為用戶提供更大的控制權和透明度,因為他們可以更好地了解應用中的哪些功能需要后臺位置資訊訪問權限,
總結一下得出兩點:
- 先請求前臺位置資訊訪問權限,再請求后臺位置資訊訪問權限,
- 單獨請求后臺位置資訊訪問權限,不要與其他權限一同請求,
這里還需要注意不同目標平臺應用在Android 11上的表現:
- Android 10 為目標平臺的應用
允許同時訪問前后臺的位置資訊權限,但同樣不會有“始終允許”這一選項,
- 沒有前后臺的位置資訊權限時:

- 有前臺的位置資訊權限時:

- Android 11 為目標平臺的應用
- 沒有前后臺的位置資訊權限時,只能先請求前臺的位置資訊權限:

- 有前臺的位置資訊權限,請求后臺的位置資訊時系統會跳轉到下面的設定頁面,

選擇“始終允許”表示具有前后臺位置資訊訪問權限,如果用戶拒絕兩次應用定位訪問請求(直接回傳等),后面請求相同權限都會被直接提示請求失敗,(這里就需要我們給用戶以引導了)
這里解釋一下“拒絕兩次”,這是Android 11 上添加的權限對話框的可見性,以前我們點擊了“不再詢問”表示拒絕授權,現在還包含類似上面這種轉到系統設定,然后點回傳按鈕,也算是拒絕授權,當然,用戶按回傳按鈕關閉權限對話框,此操作不算,
總結一下,與Android 10的區別就是將后臺權限的申請分離了出來,增加了用戶“拒絕”的條件,避免了應用重復請求用戶已拒絕的權限,
軟體包可見性
軟體包可見性是Android 11上提升系統隱私安全性的一個新特性,它的作用是限制app隨意獲取其他app的資訊和安裝狀態,避免病毒軟體、間諜軟體利用,引發網路釣魚、用戶安裝資訊泄露等安全事件,
獲取自動可見應用的串列,可以執行命令adb shell dumpsys package queries,找到 forceQueryable 部分,下面是在vivo iqoo手機的執行結果,
Queries:
system apps queryable: false
forceQueryable:
[com.android.BBKCrontab,com.vivo.fingerprint,com.vivo.epm,com.vivo.abe,com.vivo.fingerprintengineer,com.vivo.contentcatcher,com.vivo.floatingball,com.vivo.agent,com.vivo.nightpearl,android,com.wapi.wapicertmanage,com.vivo.vms,co
m.android.providers.settings,com.vivo.upslide,com.vivo.assistant,com.vivo.vivokaraoke,com.vivo.fingerprintui,com.android.wallpaperbackup,com.bbk.facewake,com.vivo.faceunlock,com.vivo.doubleinstance,com.vivo.audiofx,com.iqoo.powersav
ing,com.bbk.SuperPowerSave,com.vivo.vibrator4d,com.vivo.smartunlock,com.vivo.globalanimation,com.vivo.appfilter,com.vivo.voicewakeup,com.vivo.minscreen,com.android.bbklog,com.mobile.cos.iroaming,com.vivo.networkstate,com.vivo.daemon
Service,com.vivo.smartshot,com.vivo.vtouch,com.android.networkstack.tethering.inprocess,com.android.localtransport,com.vivo.pem,com.vivo.wifiengineermode,com.android.server.telecom,com.vivo.gamecube,com.vivo.aiengine,com.vivo.multin
lp,com.vivo.smartmultiwindow,com.vivo.permissionmanager,com.qti.diagservices,com.vivo.bsptest,com.qti.snapdragon.qdcm_ff,com.vivo.dr,com.vivo.sps,com.android.dynsystem,com.vivo.setupwizard,com.vivo.gamewatch,com.android.keychain,com
.vivo.faceui,com.android.networkstack.inprocess,com.android.location.fused,com.android.inputdevices,com.android.settings,com.iqoo.engineermode,com.vivo.fuelsummary]
[com.qualcomm.uimremoteserver,com.vivo.devicereg,com.qti.qualcomm.deviceinfo,com.volte.config,com.android.mms.service,com.android.ons,com.qualcomm.qcrilmsgtunnel,com.vivo.sim.contacts,com.qualcomm.qti.uimGbaApp,com.qualcomm.qti.
modemtestmode,com.android.stk,com.android.vendors.bridge.softsim,com.qualcomm.uimremoteclient,com.qti.qualcomm.datastatusnotification,com.qualcomm.qti.uim,com.android.phone,com.qualcomm.qti.dynamicddsservice,com.qualcomm.qti.telepho
nyservice,com.android.cellbroadcastservice,com.android.providers.telephony,com.qti.dpmserviceapp,com.android.incallui]
[com.android.vivo.tws.vivotws,com.android.bluetooth]
com.android.nfc
com.android.se
com.android.networkstack.permissionconfig
com.android.shell
com.android.providers.media.module
com.android.wifi.resources.overlay.common
com.android.theme.icon_pack.filled.themepicker
com.android.theme.icon_pack.circular.themepicker
com.android.server.telecom.overlay.common
......
可以看到都是系統應用包名,所以我們的三方應用默認是不可見的,此項變更影響比較多的是分享支付一類需要與其他應用互動的功能,下面舉一個簡單的例子:
private static boolean hasActivity(Context context, Intent intent) {
PackageManager packageManager = context.getPackageManager();
return packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
}
public void test() {
Intent intent = new Intent();
intent.setClassName("com.tencent.mm", "com.tencent.mm.ui.tools.ShareImgUI");
Log.d("hasActivity:", hasActivity(this, intent) + "");
}
hasActivity方法中通過queryIntentActivities來判斷此頁面是否存在,但是在targetSdkVersion >= 30中,這些三方默認都是不可見的,所以都會回傳false,類似方法getInstalledPackages、getPackageInfo也受到相應的限制,
解決方法很簡單,在AndroidManifest.xml 中添加queries元素,里面添加需要可見的應用包名,
<manifest package="com.example.app">
<queries>
<package android:name="com.tencent.mm" /> <- 指定微信包名
</queries>
...
</manifest>
我在適配中用到的還有下面的包名,我們可以按需添加:
<queries>
<!-- 微博 -->
<package android:name="com.sina.weibo" />
<!-- QQ -->
<package android:name="com.tencent.mobileqq" />
<!-- 支付寶 -->
<package android:name="com.eg.android.AlipayGphone" />
<!-- AlipayHK -->
<package android:name="hk.alipay.wallet" />
</queries>
除了直接添加包名的方式外,我們可以按intent和provider來添加:
<manifest package="com.example.app">
<queries>
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="image/jpeg" />
</intent>
<provider android:authorities="com.example.settings.files" />
</queries>
...
</manifest>
具體的規則參見:管理軟體包可見性
當然,還有一種簡單粗暴的方式,可以直接申請權限QUERY_ALL_PACKAGES,如果你的應用需要上架Google Play,那么可能要注意相關政策,為了尊重用戶隱私,建議我們的應用按正常作業所需的最小軟體包可見性來適配,
有一點需要說明一下,我們日常使用的startActivity 方法不受系統軟體包可見性行為的影響,即使hasActivity為false,一樣可以跳轉,如果我們在做跳轉前,進行類似hasActivity的判斷,那么會受影響,
最后需要注意的是,使用queries元素需要Android Gradle 插件版本是 4.1及以上,因為舊版本的插件并不兼容此元素,出現合并 manifest 的錯誤,
前臺服務型別
Android 10中,在前臺服務訪問位置資訊,需要在對應的service中添加 location 服務型別,
同樣的,Android 11中,在前臺服務訪問攝像頭或麥克風,需要在對應的service中添加camera或microphone 服務型別,
<manifest>
...
<service
android:name="MyService"
android:foregroundServiceType="microphone|camera" />
</manifest>
這一限制的變更,使得程式無法在后臺啟動服務訪問攝像頭和麥克風,如需使用,只能是前臺開啟前臺服務,除非有如下情況:
- 服務由系統組件啟動,
- 服務是通過應用小部件啟動,
- 服務是通過與通知互動啟動的,
- 服務是
PendingIntent啟動的,它是從另一個可見的應用程式發送過來的, - 服務由一個應用程式啟動,該應用是一個DPC,且在設備所有者模式下運行,
- 服務由一個提供
VoiceInteractionService的應用啟動, - 服務由一個具有
START_ACTIVITIES_FROM_BACKGROUND權限的應用啟動,
權限自動重置
如果應用以 Android 11 或更高版本為目標平臺并且數月未使用,系統會通過自動重置用戶已授予應用的運行時敏感權限來保護用戶資料,如下圖所示:

注意上圖中有一個啟動自動重置的開關,如果我們的應用有特殊需要,可以引導用戶關閉它,示例代碼如下:
public void checkAutoRevokePermission(Context context) {
// 判斷是否開啟
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
!context.getPackageManager().isAutoRevokeWhitelisted()) {
// 跳轉設定頁
Intent intent = new Intent(Intent.ACTION_AUTO_REVOKE_PERMISSIONS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setData(Uri.fromParts("package", context.getPackageName(), null));
context.startActivity(intent);
}
}
SYSTEM_ALERT_WINDOW權限
這部分我在適配中沒有用到,直接照搬檔案:
在 Android 11 中,系統會根據請求自動向某些型別的應用授予 SYSTEM_ALERT_WINDOW 權限:
-
系統會自動向具有
ROLE_CALL_SCREENING且請求SYSTEM_ALERT_WINDOW的所有應用授予該權限,如果應用失去ROLE_CALL_SCREENING,就會失去該權限, -
系統會自動向通過
MediaProjection截取螢屏且請求SYSTEM_ALERT_WINDOW的所有應用授予該權限,除非用戶已明確拒絕向應用授予該權限,當應用停止截取螢屏時,就會失去該權限,此用例主要用于游戲直播應用,
這些應用無需發送 ACTION_MANAGE_OVERLAY_PERMISSION 以獲取 SYSTEM_ALERT_WINDOW 權限,它們只需直接請求 SYSTEM_ALERT_WINDOW 即可,
MANAGE_OVERLAY_PERMISSION intent 始侄訓將用戶轉至系統權限螢屏
從 Android 11 開始,ACTION_MANAGE_OVERLAY_PERMISSION intent 始侄訓將用戶轉至頂級設定螢屏,用戶可在其中授予或撤消應用的 SYSTEM_ALERT_WINDOW 權限,intent 中的任何 package: 資料都會被忽略,
在更低版本的 Android 中,ACTION_MANAGE_OVERLAY_PERMISSION intent 可以指定一個軟體包,它會將用戶轉至應用專用螢屏以管理權限,從 Android 11 開始將不再支持此功能,而是必須由用戶先選擇要授予或撤消哪些應用的權限,此變更可以讓權限的授予更有目的性,從而達到保護用戶的目的,
讀取手機號
如果你是通過TelecomManager的getLine1Number方法,或TelephonyManager的getMsisdn方法獲取電話號碼,那么在Android 11中需要增加READ_PHONE_NUMBERS權限,使用其他方法不受限,
<manifest>
<!-- 如果應用僅在 Android 10及更低版本中使用該權限,可以添加 maxSdkVersion="29" -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"
android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
</manifest>
4.其他行為變更
自定義view的Toast
Android 11 為目標平臺的應用,從后臺發送自定義view的Toast訊息系統會進行屏蔽,前臺使用不受影響,Toast相應的setView 和 getView也已經廢棄不建議使用,
如果要在后臺使用,推薦使用默認的toast或Snackbar替代,
APK簽名
Android 11 為目標平臺的應用,僅通過v1 簽名的應用無法在Android 11的設備上安裝或更新,必須使用v2或更高版本進行簽名,
同時Android 11 添加了對 APK 簽名方案 v4 的支持,
AsyncTask
AsyncTask在Android 11已經不建議使用,建議遷移至kotlin的協程,
此外Handler未指定Looper的構造方法也已不建議使用,

建議明確指定Looper:
private Handler handler = new Handler(Looper.myLooper());
// 或
private Handler handler = new Handler(Looper.getMainLooper());
5.新增工具
兼容性除錯工具
以往我們做適配的時候,需要先將我們專案中的 targetSdkVersion 修改為對應版本,這就導致你適配程序中有可能受到其他變更的影響,而這個新增的兼容性除錯工具可以讓你在不升級targetSdkVersion的情況下,針對每項變更逐個開啟適配,
使用方法:
- 開發者選項中找到應用兼容性變更選項,
- 點擊進入找到你需要除錯的應用
- 在變更串列中,找到想要開啟或關閉的變更,然后點擊相應的開關,

上面第一行DEFAULT_SCOPED_STORAGE就是啟用磁區儲存,這些常量詳細的含義見:Android 11 變更串列,
對于兼容性除錯工具詳細的使用方法見:兼容性框架工具,這里限于篇幅就不展開說了,
無線除錯
Android 11的開發者選項中添加了一個無線除錯的功能,類似于連接藍牙耳機功能,可以無需USB連接線進行日常開發除錯作業,(區別于以前的Android WIFI ADB,這個是真無線,哈哈)

使用方法:
- 開發者選項中找到無線除錯并打開,
- 首次配對需點擊“使用配對碼配對設備”
- 運行
adb pair ipaddr:port后輸入配對碼進行連接,
注意事項:
- 保持電腦和手機在一個網路,
- Platform Tools 版本需大于30.0,可使用
adb --version查看,

不過我自己體驗下來,感覺連接不是很穩定,不知是AS的問題還是手機問題,同時鎖屏后也會斷開連接,體驗不是很好,,,期待后續的優化吧,
本篇內容有點多,總結一下,Android 11在權限上的變更比較多,但如果你一直遵守申請權限相關的最佳做法,那么基本上不需要額外的適配作業,
最后強調一下,對于單次授權,權限對話框的可見性,SYSTEM_ALERT_WINDOW 權限,安裝apk這些變更只要在Android 11上就會生效,不論你是否適配Android 11,對于其他變更和API(相機、5G、瀑布屏、鍵盤等),因為我暫時沒有遇到,也就沒有列出,有需要的可以點擊文末的官方檔案鏈接查看,
截止發這篇博客時,我手機上只發現嗶哩嗶哩已經適配了Android 11,大多數停留在28、29,更有甚者還在26(Android 8.0 國內上架的最低適配標準),
所以我順便附上之前寫的Android 9、10的適配攻略:
- Android 9.0 適配指南
- Android 10 適配攻略

可能本篇你暫時也用不上,你可以不用,但是不能沒有,點贊收藏一波不過分吧~~
參考
- Android 11官方檔案
- Android 11 中的存盤機制更新
- 微信開發平臺 - Android 11 系統策略更新
- OPPO - Android 11 應用兼容性適配指導
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/273653.html
標籤:AI
