Android 11 变更及适配攻略
終于開始了Android 11的適配工作。記錄一下,供需要的人參考。
1. 準(zhǔn)備工作
老規(guī)矩,首先將我們項目中的 targetSdkVersion 改為 30。或者使用兼容性調(diào)試工具,后面我會說到。
2. 存儲機制更新
Scoped Storage(分區(qū)存儲)
具體適配方法和去年的Android 10 適配攻略中的沒有太大區(qū)別。
不過需要注意的是,應(yīng)用targetSdkVersion >= 30,強制執(zhí)行分區(qū)存儲機制。之前在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 有更多優(yōu)勢,因此官方強烈建議直接使用 MediaStore API。
MANAGE_EXTERNAL_STORAGE
當(dāng)然還有一種簡單粗暴的適配方法,獲取外部存儲管理權(quán)限。如果你的應(yīng)用是手機管家、文件管理器這類需要訪問大量文件的app,可以申請MANAGE_EXTERNAL_STORAGE權(quán)限,將用戶引導(dǎo)至系統(tǒng)設(shè)置頁面開啟。代碼如下:
<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權(quán)限,也無法訪問Android/data/目錄下的文件。
對于MANAGE_EXTERNAL_STORAGE權(quán)限,國內(nèi)使用應(yīng)該沒有什么影響。但是在Google Play上需要說明為什么已有的SAF或MediaStore不滿足你的應(yīng)用需求,審核通過才允許上架使用。所以一般情況下,我個人不推薦你為了適配簡單,直接申請使用MANAGE_EXTERNAL_STORAGE權(quán)限。
其他細(xì)節(jié)變更見文檔:Android 11 中的存儲機制更新。
相關(guān)api變更及使用推薦郭霖大神的這篇:Android 11新特性,Scoped Storage又有了新花樣。
存儲訪問框架 (SAF)變更
Android 11對SAF添加以下限制:
- 使用
ACTION_OPEN_DOCUMENT_TREE或ACTION_OPEN_DOCUMENT,無法瀏覽到Android/data/和Android/obb/目錄及其所有子目錄。 - 使用
ACTION_OPEN_DOCUMENT_TREE無法授權(quán)訪問存儲根目錄、Download文件夾。
REQUEST_INSTALL_PACKAGES
在8.0的適配中,我們安裝apk包之前需要申請“安裝未知來源應(yīng)用”的權(quán)限。一般來說首次是跳轉(zhuǎn)到授權(quán)頁面讓用戶手動開啟,然后返回app進行安裝。
在Android 11中當(dāng)用戶開啟“安裝未知來源應(yīng)用”的權(quán)限,app就會被殺死。該行為與強制分區(qū)存儲有關(guān),因為持有 REQUEST_INSTALL_PACKAGES 權(quán)限的應(yīng)用可以訪問其他應(yīng)用的Android/obb 目錄。
好在用戶授予權(quán)限之后,雖然app會被殺死,但是安裝頁面依然會彈出。
目前對于這一變更我沒有發(fā)現(xiàn)可以適配處理的方式,詳細(xì)介紹見:Android 11特性調(diào)整:安裝外部來源應(yīng)用需要重啟APP
這里補充一下,因為其他應(yīng)用無法訪問應(yīng)用的Android/data/ 和 Android/obb/目錄及其所有子目錄。所以需要注意保存在這里面的文件是否會被其他程序訪問。
比如我在用系統(tǒng)的裁切功能時,因為設(shè)置的MediaStore.EXTRA_OUTPUT文件是私有目錄下的,導(dǎo)致裁剪后的圖片無法正確生成。所以需要針對android 11進行適配:
String fileName = System.currentTimeMillis() + ".jpg";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {// 裁剪無法訪問App的私有目錄,所以可以保存至公有目錄ContentValues values = new ContentValues();values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/Crop");Uri uri = this.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
} else {...
}
或者保存至Android/media共享文件目錄,這樣不用適配版本。
String fileName = System.currentTimeMillis() + ".jpg";
File file = new File(this.getExternalMediaDirs()[0].getAbsolutePath() + File.separator + fileName);
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
當(dāng)然如果你是自己實現(xiàn)的裁剪功能,那么不受影響。
3.權(quán)限變化
單次權(quán)限授權(quán)
從 Android 11 開始,每當(dāng)應(yīng)用請求與位置信息、麥克風(fēng)或攝像頭相關(guān)的權(quán)限時,面向用戶的權(quán)限對話框會包含僅限這一次選項。如果用戶在對話框中選擇此選項,系統(tǒng)會向應(yīng)用授予臨時的單次授權(quán)。
單次權(quán)限授權(quán)的應(yīng)用可以在一段時間內(nèi)訪問相關(guān)數(shù)據(jù),具體時間取決于應(yīng)用的行為和用戶的操作:
- 當(dāng)應(yīng)用的 Activity 可見時,應(yīng)用可以訪問相關(guān)數(shù)據(jù)。
- 如果用戶將應(yīng)用轉(zhuǎn)為后臺運行,應(yīng)用可以在短時間內(nèi)繼續(xù)訪問相關(guān)數(shù)據(jù)。
- 如果您在 Activity 可見時啟動了一項前臺服務(wù),并且用戶隨后將您的應(yīng)用轉(zhuǎn)到后臺,那么您的應(yīng)用可以繼續(xù)訪問相關(guān)數(shù)據(jù),直到該前臺服務(wù)停止。
- 如果用戶撤消單次授權(quán)(例如在系統(tǒng)設(shè)置中撤消),無論您是否啟動了前臺服務(wù),應(yīng)用都無法訪問相關(guān)數(shù)據(jù)。與任何權(quán)限一樣,如果用戶撤消了應(yīng)用的單次授權(quán),應(yīng)用進程就會終止。
當(dāng)用戶下次打開應(yīng)用并且應(yīng)用中的某項功能請求訪問位置信息、麥克風(fēng)或攝像頭時,系統(tǒng)會再次提示用戶授予權(quán)限。
如果你之前就是使用權(quán)限時才請求相關(guān)權(quán)限,那么這一變更對于你的應(yīng)用沒有影響。
請求位置權(quán)限
這部分在Android 10的適配有過調(diào)整,當(dāng)時規(guī)則如下:
請求
ACCESS_FINE_LOCATION或ACCESS_COARSE_LOCATION權(quán)限表示在前臺時擁有訪問設(shè)備位置信息的權(quán)限。在請求彈框中,選擇“始終允許”表示前后臺都可以獲取位置信息,選擇“僅在應(yīng)用使用過程中允許”只表示擁有前臺的權(quán)限。
在Android 11中,請求彈框中取消了“始終允許”這一選項。也就是說默認(rèn)不會授予你后臺訪問設(shè)備位置信息的權(quán)限。如果嘗試請求ACCESS_BACKGROUND_LOCATION權(quán)限的同時請求任何其他權(quán)限,系統(tǒng)會拋出異常,不會向應(yīng)用授予其中的任一權(quán)限。
官方給出的適配建議及原因如下:
建議應(yīng)用對位置權(quán)限執(zhí)行遞增請求,先請求前臺位置信息訪問權(quán)限,再請求后臺位置信息訪問權(quán)限。執(zhí)行遞增請求可以為用戶提供更大的控制權(quán)和透明度,因為他們可以更好地了解應(yīng)用中的哪些功能需要后臺位置信息訪問權(quán)限。
總結(jié)一下得出兩點:
- 先請求前臺位置信息訪問權(quán)限,再請求后臺位置信息訪問權(quán)限。
- 單獨請求后臺位置信息訪問權(quán)限,不要與其他權(quán)限一同請求。
這里還需要注意不同目標(biāo)平臺應(yīng)用在Android 11上的表現(xiàn):
- Android 10 為目標(biāo)平臺的應(yīng)用
允許同時訪問前后臺的位置信息權(quán)限,但同樣不會有“始終允許”這一選項。
- 沒有前后臺的位置信息權(quán)限時:
- 有前臺的位置信息權(quán)限時:
- Android 11 為目標(biāo)平臺的應(yīng)用
- 沒有前后臺的位置信息權(quán)限時,只能先請求前臺的位置信息權(quán)限:
- 有前臺的位置信息權(quán)限,請求后臺的位置信息時系統(tǒng)會跳轉(zhuǎn)到下面的設(shè)置頁面。
選擇“始終允許”表示具有前后臺位置信息訪問權(quán)限,如果用戶拒絕兩次應(yīng)用定位訪問請求(直接返回等),后面請求相同權(quán)限都會被直接提示請求失敗。(這里就需要我們給用戶以引導(dǎo)了)
這里解釋一下“拒絕兩次”,這是Android 11 上添加的權(quán)限對話框的可見性,以前我們點擊了“不再詢問”表示拒絕授權(quán)。現(xiàn)在還包含類似上面這種轉(zhuǎn)到系統(tǒng)設(shè)置,然后點返回按鈕,也算是拒絕授權(quán)。當(dāng)然,用戶按返回按鈕關(guān)閉權(quán)限對話框,此操作不算。
總結(jié)一下,與Android 10的區(qū)別就是將后臺權(quán)限的申請分離了出來,增加了用戶“拒絕”的條件,避免了應(yīng)用重復(fù)請求用戶已拒絕的權(quán)限。
軟件包可見性
軟件包可見性是Android 11上提升系統(tǒng)隱私安全性的一個新特性。它的作用是限制app隨意獲取其他app的信息和安裝狀態(tài)。避免病毒軟件、間諜軟件利用,引發(fā)網(wǎng)絡(luò)釣魚、用戶安裝信息泄露等安全事件。
獲取自動可見應(yīng)用的列表,可以執(zhí)行命令adb shell dumpsys package queries,找到 forceQueryable 部分。下面是在vivo iqoo手機的執(zhí)行結(jié)果。
Queries:system apps queryable: falseforceQueryable:[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.nfccom.android.secom.android.networkstack.permissionconfigcom.android.shellcom.android.providers.media.modulecom.android.wifi.resources.overlay.commoncom.android.theme.icon_pack.filled.themepickercom.android.theme.icon_pack.circular.themepickercom.android.server.telecom.overlay.common
......
可以看到都是系統(tǒng)應(yīng)用包名,所以我們的三方應(yīng)用默認(rèn)是不可見的。此項變更影響比較多的是分享支付一類需要與其他應(yīng)用交互的功能。下面舉一個簡單的例子:
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中,這些三方默認(rèn)都是不可見的。所以都會返回false。類似方法getInstalledPackages、getPackageInfo也受到相應(yīng)的限制。
解決方法很簡單,在AndroidManifest.xml 中添加queries元素,里面添加需要可見的應(yīng)用包名。
<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>
具體的規(guī)則參見:管理軟件包可見性
當(dāng)然,還有一種簡單粗暴的方式,可以直接申請權(quán)限QUERY_ALL_PACKAGES。如果你的應(yīng)用需要上架Google Play,那么可能要注意相關(guān)政策。為了尊重用戶隱私,建議我們的應(yīng)用按正常工作所需的最小軟件包可見性來適配。
有一點需要說明一下,我們?nèi)粘J褂玫?code>startActivity 方法不受系統(tǒng)軟件包可見性行為的影響,即使hasActivity為false,一樣可以跳轉(zhuǎn)。如果我們在做跳轉(zhuǎn)前,進行類似hasActivity的判斷,那么會受影響。
最后需要注意的是,使用queries元素需要Android Gradle 插件版本是 4.1及以上,因為舊版本的插件并不兼容此元素,出現(xiàn)合并 manifest 的錯誤。
前臺服務(wù)類型
Android 10中,在前臺服務(wù)訪問位置信息,需要在對應(yīng)的service中添加 location 服務(wù)類型。
同樣的,Android 11中,在前臺服務(wù)訪問攝像頭或麥克風(fēng),需要在對應(yīng)的service中添加camera或microphone 服務(wù)類型。
<manifest>...<service android:name="MyService"android:foregroundServiceType="microphone|camera" />
</manifest>
這一限制的變更,使得程序無法在后臺啟動服務(wù)訪問攝像頭和麥克風(fēng)。如需使用,只能是前臺開啟前臺服務(wù)。除非有如下情況:
- 服務(wù)由系統(tǒng)組件啟動。
- 服務(wù)是通過應(yīng)用小部件啟動。
- 服務(wù)是通過與通知交互啟動的。
- 服務(wù)是
PendingIntent啟動的,它是從另一個可見的應(yīng)用程序發(fā)送過來的。 - 服務(wù)由一個應(yīng)用程序啟動,該應(yīng)用是一個DPC,且在設(shè)備所有者模式下運行。
- 服務(wù)由一個提供
VoiceInteractionService的應(yīng)用啟動。 - 服務(wù)由一個具有
START_ACTIVITIES_FROM_BACKGROUND權(quán)限的應(yīng)用啟動。
權(quán)限自動重置
如果應(yīng)用以 Android 11 或更高版本為目標(biāo)平臺并且數(shù)月未使用,系統(tǒng)會通過自動重置用戶已授予應(yīng)用的運行時敏感權(quán)限來保護用戶數(shù)據(jù)。如下圖所示:
注意上圖中有一個啟動自動重置的開關(guān)。如果我們的應(yīng)用有特殊需要,可以引導(dǎo)用戶關(guān)閉它。示例代碼如下:
public void checkAutoRevokePermission(Context context) {// 判斷是否開啟if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&!context.getPackageManager().isAutoRevokeWhitelisted()) {// 跳轉(zhuǎn)設(shè)置頁 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權(quán)限
這部分我在適配中沒有用到,直接照搬文檔:
在 Android 11 中,系統(tǒng)會根據(jù)請求自動向某些類型的應(yīng)用授予 SYSTEM_ALERT_WINDOW 權(quán)限:
-
系統(tǒng)會自動向具有
ROLE_CALL_SCREENING且請求SYSTEM_ALERT_WINDOW的所有應(yīng)用授予該權(quán)限。如果應(yīng)用失去ROLE_CALL_SCREENING,就會失去該權(quán)限。 -
系統(tǒng)會自動向通過
MediaProjection截取屏幕且請求SYSTEM_ALERT_WINDOW的所有應(yīng)用授予該權(quán)限,除非用戶已明確拒絕向應(yīng)用授予該權(quán)限。當(dāng)應(yīng)用停止截取屏幕時,就會失去該權(quán)限。此用例主要用于游戲直播應(yīng)用。
這些應(yīng)用無需發(fā)送 ACTION_MANAGE_OVERLAY_PERMISSION 以獲取 SYSTEM_ALERT_WINDOW 權(quán)限,它們只需直接請求 SYSTEM_ALERT_WINDOW 即可。
MANAGE_OVERLAY_PERMISSION intent 始終會將用戶轉(zhuǎn)至系統(tǒng)權(quán)限屏幕
從 Android 11 開始,ACTION_MANAGE_OVERLAY_PERMISSION intent 始終會將用戶轉(zhuǎn)至頂級設(shè)置屏幕,用戶可在其中授予或撤消應(yīng)用的 SYSTEM_ALERT_WINDOW 權(quán)限。intent 中的任何 package: 數(shù)據(jù)都會被忽略。
在更低版本的 Android 中,ACTION_MANAGE_OVERLAY_PERMISSION intent 可以指定一個軟件包,它會將用戶轉(zhuǎn)至應(yīng)用專用屏幕以管理權(quán)限。從 Android 11 開始將不再支持此功能,而是必須由用戶先選擇要授予或撤消哪些應(yīng)用的權(quán)限。此變更可以讓權(quán)限的授予更有目的性,從而達(dá)到保護用戶的目的。
讀取手機號
如果你是通過TelecomManager的getLine1Number方法,或TelephonyManager的getMsisdn方法獲取電話號碼。那么在Android 11中需要增加READ_PHONE_NUMBERS權(quán)限。使用其他方法不受限。
<manifest><!-- 如果應(yīng)用僅在 Android 10及更低版本中使用該權(quán)限,可以添加 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 為目標(biāo)平臺的應(yīng)用,從后臺發(fā)送自定義view的Toast消息系統(tǒng)會進行屏蔽。前臺使用不受影響。Toast相應(yīng)的setView 和 getView也已經(jīng)廢棄不建議使用。
如果要在后臺使用,推薦使用默認(rèn)的toast或Snackbar替代。
APK簽名
Android 11 為目標(biāo)平臺的應(yīng)用,僅通過v1 簽名的應(yīng)用無法在Android 11的設(shè)備上安裝或更新。必須使用v2或更高版本進行簽名。
簽名版本驗證:apksigner verify -v --print-certs (apk path)
同時Android 11 添加了對 APK 簽名方案 v4 的支持。
AsyncTask
AsyncTask在Android 11已經(jīng)不建議使用,建議遷移至kotlin的協(xié)程。
此外Handler未指定Looper的構(gòu)造方法也已不建議使用。
建議明確指定Looper:
private Handler handler = new Handler(Looper.myLooper());
// 或
private Handler handler = new Handler(Looper.getMainLooper());
狀態(tài)欄高度
發(fā)現(xiàn)Android 11上targetSdkVersion 是30時獲取狀態(tài)欄高度為0,低于30獲取值正常。。。因此需要使用WindowMetrics適配一下:
public static int getStatusBarHeight(Context context) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();WindowInsets windowInsets = windowMetrics.getWindowInsets();Insets insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.statusBars() | WindowInsets.Type.displayCutout());return insets.top;} ....
}
WindowMetrics是Android 11新增的類,用于獲取窗口邊界,同樣可以用來獲取導(dǎo)航欄高度。
指針標(biāo)記
適配中發(fā)現(xiàn)一進入視頻播放頁面就會崩潰,播放在線視頻時崩潰,而本地視頻播放正常。崩潰日志如下:
A/libc: Fatal signal 7 (SIGBUS), code 1 (BUS_ADRALN), fault addr 0x696f72646e612e in tid 17179 (ff_read), pid 14940
Softversion: PD2055_A_1.9.5
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'vivo/PD2055/PD2055:11/RP1A.200720.012/compiler1008171113:user/release-keys'
Revision: '0'
ABI: 'arm64'
pid: 14940, tid: 17179, name: ff_read >>> com.xxx.xx <<<
uid: 10427
signal 7 (SIGBUS), code 1 (BUS_ADRALN), fault addr 0x696f72646e612e
x0 b400007a6e298000 x1 0000000000000002 x2 0000007b20246868 x3 0000000000001018
x4 64696f72646e612e x5 8080808080808080 x6 fefefefefefefeff x7 7f7f7f7f7f7f7f7f
x8 0101010101010101 x9 772d50ac8daccd9d x10 0000000000000003 x11 0000000000002938
x12 0000000010624dd3 x13 0000000000000018 x14 000006b6301c88bc x15 00ffffffffffffff
x16 0000007a92360788 x17 0000007c2892c4b0 x18 0000007a7e55e000 x19 0000007b20246868
x20 b400007a6e298000 x21 b400007a6e051f98 x22 b400007a6e051f40 x23 0000000000000000
x24 00000000abb6a7bb x25 0000007b20247ad8 x26 0000007a92279000 x27 0000000000000001
x28 0000007b2014f000 x29 0000007b20247c70
lr 0000007a920f334c sp 0000007b20246810 pc 00696f72646e612e pst 0000000000001000
backtrace:
#00 pc 00696f72646e612e <unknown>
#01 pc 00000000002a7348 /data/app/~~gUlAHF_d4JakYrxOxXV1jw==/com.dailyyoga.inc-tn9wgDr_j8tvxYSidl_7IQ==/lib/arm64/libpldroidplayer.so (av_application_on_http_event+24)
大致一看問題出到了libpldroidplayer.so上面,這個是App中用到七牛播放器的so文件。
從 Android 11開始,對于64位進程,所有堆分配都具有一個由實現(xiàn)定義的標(biāo)記,該標(biāo)記在具有對ARM Top-byte Ignore (TBI) 的內(nèi)核支持的設(shè)備上的指針頂部字節(jié)中設(shè)置。在回收期間檢查該標(biāo)記時,任何修改此標(biāo)記的應(yīng)用都會被終止。對于未來支持 ARM 內(nèi)存標(biāo)記擴展 (MTE) 的硬件來說,這是必需的。
所以需要播放器so將指針的頂部字節(jié)錯誤使用部分修改:
- 指向特定類型的指針將特定于應(yīng)用的元數(shù)據(jù)存儲在前16個地址位中。
- 指針的類型轉(zhuǎn)換為雙精度,然后又恢復(fù)為原來的類型,因此丟失了較低的地址位。
- 代碼計算不同堆棧幀的局部變量地址之間的差異,作為測量遞歸深度的方法。
因為使用播放器版本過老,升級新版本成本過大,所以使用過渡方案,關(guān)閉指針標(biāo)記功能,在 AndroidManifest.xml 文件中添加:
<application android:allowNativeHeapPointerTagging="false">...
</application>
文檔中介紹這個方法在未來的Android版本中將取消,所以后面有時間需要升級一下播放器sdk了。
詳細(xì)信息可以參見:已加標(biāo)記的指針
5.新增工具
兼容性調(diào)試工具
以往我們做適配的時候,需要先將我們項目中的 targetSdkVersion 修改為對應(yīng)版本。這就導(dǎo)致你適配過程中有可能受到其他變更的影響,而這個新增的兼容性調(diào)試工具可以讓你在不升級targetSdkVersion的情況下,針對每項變更逐個開啟適配。
使用方法:
- 開發(fā)者選項中找到應(yīng)用兼容性變更選項。
- 點擊進入找到你需要調(diào)試的應(yīng)用
- 在變更列表中,找到想要開啟或關(guān)閉的變更,然后點擊相應(yīng)的開關(guān)。
上面第一行DEFAULT_SCOPED_STORAGE就是啟用分區(qū)儲存,這些常量詳細(xì)的含義見:Android 11 變更列表。
對于兼容性調(diào)試工具詳細(xì)的使用方法見:兼容性框架工具,這里限于篇幅就不展開說了。
無線調(diào)試
Android 11的開發(fā)者選項中添加了一個無線調(diào)試的功能。類似于連接藍(lán)牙耳機功能,可以無需USB連接線進行日常開發(fā)調(diào)試工作。(區(qū)別于以前的Android WIFI ADB,這個是真無線,哈哈)
使用方法:
- 開發(fā)者選項中找到無線調(diào)試并打開。
- 首次配對需點擊“使用配對碼配對設(shè)備”
- 運行
adb pair ipaddr:port后輸入配對碼進行連接。
注意事項:
- 保持電腦和手機在一個網(wǎng)絡(luò)。
- Platform Tools 版本需大于30.0。可使用
adb --version查看。
不過我自己體驗下來,感覺連接不是很穩(wěn)定,不知是AS的問題還是手機問題。同時鎖屏后也會斷開連接,體驗不是很好。。。期待后續(xù)的優(yōu)化吧。
本篇內(nèi)容有點多。總結(jié)一下,Android 11在權(quán)限上的變更比較多,但如果你一直遵守申請權(quán)限相關(guān)的最佳做法,那么基本上不需要額外的適配工作。
最后強調(diào)一下,對于單次授權(quán),權(quán)限對話框的可見性,SYSTEM_ALERT_WINDOW 權(quán)限,安裝apk這些變更只要在Android 11上就會生效,不論你是否適配Android 11。對于其他變更和API(相機、5G、瀑布屏、鍵盤等),因為我暫時沒有遇到,也就沒有列出,有需要的可以點擊文末的官方文檔鏈接查看。
截止發(fā)這篇博客時,我手機上只發(fā)現(xiàn)嗶哩嗶哩已經(jīng)適配了Android 11。大多數(shù)停留在28、29,更有甚者還在26(Android 8.0 國內(nèi)上架的最低適配標(biāo)準(zhǔn))。
所以我順便附上之前寫的Android 9、10的適配攻略:
- Android 9.0 適配指南
- Android 10 適配攻略
可能本篇你暫時也用不上,你可以不用,但是不能沒有。點贊收藏一波不過分吧~~
參考
- Android 11官方文檔
- Android 11 中的存儲機制更新
- 微信開發(fā)平臺 - Android 11 系統(tǒng)策略更新
- OPPO - Android 11 應(yīng)用兼容性適配指導(dǎo)
總結(jié)
以上是生活随笔為你收集整理的Android 11 变更及适配攻略的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 哈罗app怎么买火车票? 哈罗订购火车票
- 下一篇: 1亿像素60倍变焦:vivo X50 P