我的公众号程序员徐公,四年中大厂工作教训,回复黑马,支付 Android 学习视频一份,回复徐公666,能够取得我精心整顿的简历模板,带你走近大厂。

作者:唯鹿
起源:https://juejin.cn/post/694821...


终于开始了Android 11的适配工作。记录一下,供须要的人参考。

1. 筹备工作

老规矩,首先将咱们我的项目中的 targetSdkVersion 改为 30。或者应用兼容性调试工具,前面我会说到。

2. 存储机制更新

Scoped Storage(分区存储)

具体适配办法和去年的[Android 10
适配攻略](https://weilu.blog.csdn.net/a...

不过须要留神的是,利用targetSdkVersion >= 30,强制执行分区存储机制。之前在AndroidManifest.xml中增加
android:requestLegacyExternalStorage="true"的适配形式已不起作用。

还有一个变动:Android 11 容许应用除 MediaStore API 之外的 API 通过文件门路间接访问共享存储空间中的媒体文件。其中包含:

  • File API。
  • 原生库,例如 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上须要阐明为什么已有的SAFMediaStore不满足你的利用需要,审核通过才容许上架应用。所以个别状况下,我集体不举荐你为了适配简略,间接申请应用MANAGE_EXTERNAL_STORAGE权限。

其余细节变更见文档:[Android 11
中的存储机制更新](https://developer.android.goo...

相干api变更及应用举荐郭霖大神的这篇:[Android 11新个性,Scoped
Storage又有了新花样](https://guolin.blog.csdn.net/...

存储拜访框架 (SAF)变更

Android 11对SAF增加以下限度:

  • 应用 ACTION_OPEN_DOCUMENT_TREEACTION_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](https://news.51cto.com/art/20...


这里补充一下,因为其余利用无法访问利用的Android/data/
Android/obb/目录及其所有子目录。所以须要留神保留在这外面的文件是否会被其余程序拜访。

比方我在用零碎的裁切性能时,因为设置的MediaStore.EXTRA_OUTPUT文件是公有目录下的,导致裁剪后的图片无奈正确生成。所以须要针对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));

当然如果你是本人实现的裁剪性能,那么不受影响。

3.权限变动

单次权限受权

从 Android 11
开始,每当利用申请与地位信息、麦克风或摄像头相干的权限时,面向用户的权限对话框会蕴含仅限这一次选项。如果用户在对话框中抉择此选项,零碎会向利用授予长期的单次受权。

单次权限受权的利用能够在一段时间内拜访相干数据,具体工夫取决于利用的行为和用户的操作:

  • 当利用的 Activity 可见时,利用能够拜访相干数据。
  • 如果用户将利用转为后盾运行,利用能够在短时间内持续拜访相干数据。
  • 如果您在 Activity 可见时启动了一项前台服务,并且用户随后将您的利用转到后盾,那么您的利用能够持续拜访相干数据,直到该前台服务进行。
  • 如果用户吊销单次受权(例如在零碎设置中吊销),无论您是否启动了前台服务,利用都无法访问相干数据。与任何权限一样,如果用户吊销了利用的单次受权,利用过程就会终止。

当用户下次关上利用并且利用中的某项性能申请拜访地位信息、麦克风或摄像头时,零碎会再次提醒用户授予权限。

如果你之前就是应用权限时才申请相干权限,那么这一变更对于你的利用没有影响。

申请地位权限

这部分在Android 10的适配有过调整,过后规定如下:

申请ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION权限示意在前台时领有拜访设施地位信息的权限。在申请弹框中,抉择“始终容许”示意前后台都能够获取地位信息,抉择“仅在利用应用过程中容许”只示意领有前台的权限。

在Android 11中,申请弹框中勾销了“始终容许”这一选项。也就是说 默认不会授予你后盾拜访设施地位信息的权限
。如果尝试申请ACCESS_BACKGROUND_LOCATION权限的同时申请任何其余权限,零碎会抛出异样,不会向利用授予其中的任一权限。

官网给出的适配倡议及起因如下:

倡议利用对地位权限执行递增申请,先申请前台地位信息拜访权限,再申请后盾地位信息拜访权限。执行递增申请能够为用户提供更大的控制权和透明度,因为他们能够更好地理解利用中的哪些性能须要后盾地位信息拜访权限。

总结一下得出两点:

  • 先申请前台地位信息拜访权限,再申请后盾地位信息拜访权限。
  • 独自申请后盾地位信息拜访权限,不要与其余权限一起申请。

这里还须要留神不同指标平台利用在Android 11上的体现:

  • Android 10 为指标平台的利用 容许同时拜访前后台的地位信息权限,但同样不会有“始终容许”这一选项。
  1. 没有前后台的地位信息权限时:

  1. 有前台的地位信息权限时:

  • Android 11 为指标平台的利用
  1. 没有前后台的地位信息权限时,只能先申请前台的地位信息权限:

  1. 有前台的地位信息权限,申请后盾的地位信息时零碎会跳转到上面的设置页面。


抉择“始终容许”示意具备前后台地位信息拜访权限,如果用户回绝两次利用定位拜访申请(间接返回等),前面申请雷同权限都会被间接提醒申请失败。(这里就须要咱们给用户以疏导了)

这里解释一下“回绝两次”,这是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,com.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.powersaving,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.daemonService,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.multinlp,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.telephonyservice,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。相似办法getInstalledPackagesgetPackageInfo`也受到相应的限度。

解决办法很简略,在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中增加cameramicrophone 服务类型。

<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
开始将不再反对此性能,而是必须由用户先抉择要授予或吊销哪些利用的权限。此变更能够让权限的授予更有目的性,从而达到爱护用户的目标。

读取手机号

如果你是通过TelecomManagergetLine1Number办法,或TelephonyManagergetMsisdn办法获取电话号码。那么在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相应的setViewgetView也曾经废除不倡议应用。

如果要在后盾应用,举荐应用默认的toast或Snackbar代替。

APK签名

Android 11 为指标平台的利用,仅通过v1 签名的利用无奈在Android 11的设施上装置或更新。必须应用v2或更高版本进行签名。

同时Android 11 增加了对 APK [签名计划
v4](https://developer.android.goo... 的反对。

AsyncTask

AsyncTask在Android 11曾经不倡议应用,倡议迁徙至kotlin的协程。

此外Handler未指定Looper的构造方法也已不倡议应用。

倡议明确指定Looper

private Handler handler = new Handler(Looper.myLooper());// 或private Handler handler = new Handler(Looper.getMainLooper());

状态栏高度

发现零碎为Android 11的手机上targetSdkVersion
是30时获取状态栏高度为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.navigationBars() | WindowInsets.Type.displayCutout());        return insets.top;    }               ....}

WindowMetrics是Android
11新增的类,用于获取窗口边界,同样能够用来获取导航栏高度。

5.新增工具

兼容性调试工具

以往咱们做适配的时候,须要先将咱们我的项目中的 targetSdkVersion
批改为对应版本。这就导致你适配过程中有可能受到其余变更的影响,而这个新增的兼容性调试工具能够让你在不降级targetSdkVersion的状况下,针对每项变更一一开启适配。

应用办法:

  • 开发者选项中找到利用兼容性变更选项。
  • 点击进入找到你须要调试的利用
  • 在变更列表中,找到想要开启或敞开的变更,而后点击相应的开关。


下面第一行DEFAULT_SCOPED_STORAGE就是启用分区贮存,这些常量具体的含意见:[Android 11
变更列表](https://developer.android.goo...

对于兼容性调试工具具体的应用办法见:兼容性框架工具,这里限于篇幅就不开展说了。

无线调试

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 利用兼容性适配领导

如果感觉对你有所帮忙的话,能够关注我的微信公众号程序员徐公

  1. 公众号程序员徐公回复黑马,获取 Android 学习视频
  2. 公众号程序员徐公回复徐公666,获取简历模板,教你如何优化简历,走进大厂
  3. 公众号程序员徐公回复面试,能够取得面试常见算法,剑指 offer 题解
  4. 公众号程序员徐公回复马士兵,能够取得马士兵学习视频一份