拖不得了,Android11真的要来了,最全适配实践指南奉上

前言

最近看到一条新闻,Android 11(version 30,Andorid R)最终Beta版 如期发布,看到这个新闻我知道我不能再拖了,再很差好准备好迎接Android11的到来,到时候迎接个人就是客户的指责,甚至老板的一封休书了 😂。java

今天就和你们一块儿看看Android11到底改了些什么,以及最重要的,咱们须要怎么适配?targetversion不改到30,是否是就不用适配了呢?android

如下我分为两部分讲述,分别是面试

  • Android11 为目标版本的应用(targetSdkVersion>=30才有影响)⭐
  • 全部应用在Android11设备上适配改动(不管targetSdkVersion是多少,只要在Android11设备上运行的应用都有影响)

为何先说targetSdkVersion>=30的模块呢?由于通常来讲为了Google为了让咱们更长时间适应新的内容以及保障线上应用的稳定,都会把改动大的,须要花时间适配的内容放到新的targetSdkVersion对应的应用上,若是你暂时没有适配targetSdkVersion30的需求,也能够看看第二模块,看看是否有涉及你的应用相关内容。GOGOGO!数据库

Tips:此适配文章会不间断更新,根据Android11发布进度调整,欢迎点赞关注。(打⭐的格外注意哦)c#

适配targetSdkVersion30

此模块的修改内容只针对targetSdkVersion 30或者以上才生效。api

分区存储强制执行⭐

对外部存储目录的访问仅限于应用专属目录,以及应用已建立的特定类型的媒体。浏览器

关于分区存储,在Android10就已经推行了,简单的说,就是应用对于文件的读写只能在沙盒环境,也就是属于本身应用的目录里面读写。其余媒体文件能够经过MediaStore进行访问。缓存

可是在android10的时候,Google仍是为开发者考虑,留了一手。在targetSdkVersion = 29应用中,设置android:requestLegacyExternalStorage="true",就能够不启动分区存储,让之前的文件读取正常使用。可是targetSdkVersion = 30中不行了,强制开启分区存储。
固然,做为人性化的android,仍是为开发者留了一小手,若是是覆盖安装呢,能够增长android:preserveLegacyExternalStorage="true",暂时关闭分区存储,好让开发者完成数据迁移的工做。为何是暂时呢?由于只要卸载重装,就会失效了。如下是关于分区存储会遇到的全部状况,给你们罗列出来了,先上代码:安全

fun saveFile() {
        if (checkPermission()) {
            //getExternalStoragePublicDirectory被弃用,分区存储开启后就不容许访问了
            val filePath = Environment.getExternalStoragePublicDirectory("").toString() + "/test3.txt"
            val fw = FileWriter(filePath)
            fw.write("hello world")
            fw.close()
            showToast("文件写入成功")
        }
    }
复制代码

分状况运行:
1) targetSdkVersion = 28,运行后正常读写。
2) targetSdkVersion = 29,不删除应用,targetSdkVersion 由28修改到29,覆盖安装,运行后正常读写。
3) targetSdkVersion = 29,删除应用,从新运行,读写报错,程序崩溃(open failed: EACCES (Permission denied))
4) targetSdkVersion = 29,添加android:requestLegacyExternalStorage="true"(不启用分区存储),读写正常不报错
5) targetSdkVersion = 30,不删除应用,targetSdkVersion 由29修改到30,读写报错,程序崩溃(open failed: EACCES (Permission denied))
6) targetSdkVersion = 30,不删除应用,targetSdkVersion 由29修改到30,增长android:preserveLegacyExternalStorage="true",读写正常不报错
7) targetSdkVersion = 30,删除应用,从新运行,读写报错,程序崩溃(open failed: EACCES (Permission denied))markdown

ok,那到底应该怎么改呢?三种方法访问文件:

1)应用专属目录

//分区存储空间
val file = File(context.filesDir, filename)

//应用专属外部存储空间
val appSpecificExternalDir = File(context.getExternalFilesDir(), filename)

复制代码

2)访问公共媒体目录文件

val cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, "${MediaStore.MediaColumns.DATE_ADDED} desc")
if (cursor != null) {
    while (cursor.moveToNext()) {
        val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
        val uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
        println("image uri is $uri")
    }
    cursor.close()
}

复制代码
  1. SAF(存储访问框架--Storage Access Framework)
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
    intent.addCategory(Intent.CATEGORY_OPENABLE)
    intent.type = "image/*"
    startActivityForResult(intent, 100)

    @RequiresApi(Build.VERSION_CODES.KITKAT)
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (data == null || resultCode != Activity.RESULT_OK) return
        if (requestCode == 100) {
            val uri = data.data
            println("image uri is $uri")
        }
    }

复制代码

具体还有不少操做能够看看网上关于分区存储的资料,由于Android10已经出来好久了,因此资料仍是不少的,这里推荐几篇
访问应用专属文件
Android 10适配要点,做用域存储
AndroidQ(10)分区存储完美适配

说到这里可能又有人问了,那个人应用就是个手机管理器,总不能不让我清其余应用的缓存了吧,有办法!Android提供了两个intent入口:

  • 调用ACTION_MANAGE_STORAGE intent 操做检查可用空间。
  • 调用ACTION_CLEAR_APP_CACHE intent 操做清除全部缓存。

说来讲去,反正应用数据私有化是大势所趋,仍是早点适配分区存储,别等之后手机只有沙盒机制的时候,就来不及了

媒体文件访问权限 ⭐

为了在保证用户隐私的同时能够更轻松地访问媒体,Android 11 增长了如下功能。执行批量操做和使用直接文件路径和原生库访问文件。

1)执行批量操做

这里的批量操做指的是Android 11 向 MediaStore API 中添加了多种方法,用于简化特定媒体文件更改流程(例如在原位置编辑照片),分别是:

  • createWriteRequest() 用户向应用授予对指定媒体文件组的写入访问权限的请求。
  • createFavoriteRequest()用户将设备上指定的媒体文件标记为“收藏”的请求。对该文件具备读取访问权限的任何应用均可以看到用户已将该文件标记为“收藏”。
  • createTrashRequest() 用户将指定的媒体文件放入设备垃圾箱的请求。垃圾箱中的内容会在系统定义的时间段后被永久删除。
  • createDeleteRequest() 用户当即永久删除指定的媒体文件(而不是先将其放入垃圾箱)的请求。

直接看个例子:

val urisToModify = listOf(uri,uri,...)
val editPendingIntent = MediaStore.createWriteRequest(contentResolver,
        urisToModify)

// Launch a system prompt requesting user permission for the operation.
startIntentSenderForResult(editPendingIntent.intentSender, EDIT_REQUEST_CODE,
    null, 0, 0, 0)


override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    when (requestCode) {
        EDIT_REQUEST_CODE ->
            if (resultCode == Activity.RESULT_OK) {
                /* Edit request granted; proceed. */
            } else {
                /* Edit request not granted; explain to the user. */
            }
    }
}    

复制代码

传入uri的集合,获取用户的赞成后,就能够进行操做了。

2)直接文件路径和原生库访问文件

没错!Android11又恢复了使用直接文件路径访问访问媒体文件!哈哈,这样就方便多了。也就是除了 MediaStore API以外还有两种方式能够访问媒体文件:

  • File API。
  • 原生库,例如 fopen()。

Android10咋办呢??要不就用MediaStore,要不就直接把分区存储关了吧(requestLegacyExternalStorage=true)

全部文件访问权限 ⭐

虽说了这么多,可是还有些应用就要访问全部文件,好比杀毒软件,文件管理器。放心,有办法!MANAGE_EXTERNAL_STORAGE 这不来了吗。 这个权限就是用来获取全部文件的管理权限。🌰:

<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权限:
    val isHasStoragePermission= Environment.isExternalStorageManager()
复制代码

来张截图过过瘾:

申请全部文件访问权限

电话号码相关权限 ⭐

Android 11 更改了您的应用在读取电话号码时使用的与电话相关的权限。

具体改了什么呢?其实就是两个API:

  • TelecomManager 类中的 getLine1Number() 方法
  • TelecomManager 类中的 getMsisdn() 方法

也就是当用到这两个API的时候,原来的READ_PHONE_STATE权限无论用了,须要READ_PHONE_NUMBERS权限才行。

下面具体说说,targetSdkVersion修改到30,而后运行一个获取电话号码的程序:

ActivityCompat.requestPermissions(this,
        arrayOf(Manifest.permission.READ_PHONE_STATE), 100)

        btn2.setOnClickListener {
            val tm = this.applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
            val phoneNumber = tm.line1Number
            showToast(phoneNumber)
        }

复制代码

崩溃了:

java.lang.SecurityException: getLine1NumberForDisplay: Neither user 10151 nor current process has android.permission.READ_PHONE_STATE, android.permission.READ_SMS, or android.permission.READ_PHONE_NUMBERS

复制代码

预想之中哈,Andmanifest.xml中注册好权限,而且添加动态权限申请:

<uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />


    ActivityCompat.requestPermissions(this,
        arrayOf(Manifest.permission.READ_PHONE_STATE,Manifest.permission.READ_PHONE_NUMBERS), 100)

复制代码

搞定,若是你只须要获取手机号码这一个功能,也能够只申请READ_PHONE_NUMBERS这一个权限:

<uses-permission android:name="android.permission.READ_PHONE_STATE"  android:maxSdkVersion="29" />
    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
复制代码

自定义消息框视图被屏蔽 ⭐

从 Android 11 开始,已弃用自定义消息框视图。若是您的应用以 Android 11 为目标平台,包含自定义视图的消息框在从后台发布时会被屏蔽

可能有人会奇怪了,什么是自定义消息框视图啊?我说英文你就知道了,英文是custom toast views,也就是自定义toast。简单写个代码:

Toast toast = new Toast(context);
    toast.setDuration(show_length);
    toast.setView(view);
    toast.show();
复制代码

糟了糟了,自定义toast被弃用了?咱们项目就是用的这个啊!不用担忧,只是不容许自定义toast从后台显示了。 好比我写一个3秒后再显示toast,而后应用一打开就进入后台,看看会发生什么:

Handler().postDelayed({
          IToast.show("你好,我是自定义toast")
     }, 3000)


     W/NotificationService: Blocking custom toast from package com.example.studynote due to package not in the foreground
复制代码

啥也没显示,只是发出来一个警告。 因此不用太过担忧,若是实在须要后台显示,就用普通的toast吧!

如今须要 APK 签名方案 v2 ⭐

对于以 Android 11(API 级别 30)为目标平台,且目前仅使用 APK 签名方案 v1 签名的应用,如今还必须使用 APK 签名方案 v2 或更高版本进行签名。用户没法在搭载 Android 11 的设备上安装或更新仅经过 APK 签名方案 v1 签名的应用。

这个介绍已经很明显了吧,若是你的targetSdkVersion修改到30,那么你就必需要加上v2签名才行。不然没法安装和更新。

媒体intent操做须要系统默认相机 ⭐

从 Android 11 开始,只有预装的系统相机应用能够响应如下 intent 操做:

android.media.action.VIDEO_CAPTURE
android.media.action.IMAGE_CAPTURE
android.media.action.IMAGE_CAPTURE_SECURE

也就是说,若是我调用intent唤起照相机,使用VIDEO_CAPTURE的action,只有系统的相机可以响应,而第三方的相机应用不会响应了。

val intent=Intent()
    intent.action=android.provider.MediaStore.ACTION_IMAGE_CAPTURE
    startActivity(intent)

    //没法唤起第三方相机了,只能唤起系统相机
复制代码

这点对普通的相机应用仍是有点打击的,官方给的建议是若是要使用特定的第三方相机应用来表明其捕获图片或视频,能够经过为intent设置软件包名称或组件来使这些intent变得明确。

5G ⭐

Android 11 添加了在您的应用中支持 5G 的功能

新的Android11也是支持了5G相关的一些功能,包括:

  • 检测是否链接到了5G网络
  • 检查按流量计费性

首先是检测5G网络,经过TelephonyManager的监听方法:

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("高级专业版 LTE (5Ge)")
                    TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA -> showToast("NR (5G) - 5G Sub-6 网络")
                    TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE -> showToast("5G+/5G UW - 5G mmWave 网络")
                    else -> showToast("other")
                }
            }

        }, PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED)
    }
复制代码

若是是5g网络,就免不了要去判断是否是按流量计费的,不然5G的流量可不是开玩笑的。

检测流量计费方法也很简单,监听网络,在回调中判断:

val manager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
     manager.registerDefaultNetworkCallback(object : ConnectivityManager.NetworkCallback() {
        override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
          super.onCapabilitiesChanged(network, networkCapabilities)

            //true 表明链接不按流量计费
            val isNotFlowPay=networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) ||
                            networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)
          }
    })
复制代码

判断该值,若是为 true,则将链接视为不按流量计费。

后台位置信息访问权限 ⭐

在搭载 Android 11 的设备上,当应用中的某项功能请求在后台访问位置信息时,用户看到的系统对话框再也不包含用于启用后台位置信息访问权限的按钮。如需启用后台位置信息访问权限,用户必须在设置页面上针对应用的位置权限设置一概容许选项。

什么意思呢?主要涉及到两点:

  • 从Android10系统的设备开始,就须要请求后台位置权限(ACCESS_BACKGROUND_LOCATION),并选择Allow all the time (始终容许)才能得到后台位置权限。Android11设备上再次增强对后台权限的管理,主要表如今系统对话框上,对话框再也不提示始终容许字样,而是提供了位置权限的设置入口,须要在设置页面选择始终容许才能得到后台位置权限。
  • 在搭载Android11系统的设备上,targetVersion小于30的时候,能够前台后台位置权限一块儿申请,而且对话框提供了文字说明,表示须要随时获取用户位置信息,进入设置选择始终容许便可。可是targetVersion为30的时候,你必须单独申请后台位置权限,并且要在获取前台权限以后,顺序不能乱。而且无任何提示,须要开发者本身设计提示样式。

可能有点绕,操做几个例子说明:

1)Android10设备,申请前台和后台位置权限(任意targetSdkVersion):

requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_BACKGROUND_LOCATION), 100)
复制代码

执行效果: Android10设备申请位置权限

2)Android11设备,targetSdkVersion<=29(Android 10),申请前台和后台位置权限:

requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_BACKGROUND_LOCATION), 100)
复制代码

执行效果: Android11设备targetSdkVersion29申请位置权限

3)Android11设备,targetSdkVersion=30(Android 11),申请前台和后台位置权限:

requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_BACKGROUND_LOCATION), 100)
复制代码

执行无反应

4)Android11设备,targetSdkVersion=30(Android 11),先申请前台位置权限,后申请后台位置权限:

requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION), 100)
复制代码

执行效果: Android11设备targetSdkVersion30申请位置权限

requestPermissions(arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION), 100)
复制代码

执行效果(直接跳转到设置页面,无任何说明): 权限图

因此,该怎么适配呢?

  • targetSdkVersion<30状况下,若是你以前就有判断过前台和后台位置权限,那就无需担忧,没有什么须要适配。
  • targetSdkVersion>30状况下,须要分开申请先后台位置权限,而且对后台位置权限申请作好说明和引导,固然也是为了更好的服务用户。

权限申请的demo代码:

val permissionAccessCoarseLocationApproved = ActivityCompat
        .checkSelfPermission(this, permission.ACCESS_COARSE_LOCATION) ==
        PackageManager.PERMISSION_GRANTED

    if (permissionAccessCoarseLocationApproved) {
       val backgroundLocationPermissionApproved = ActivityCompat
           .checkSelfPermission(this, permission.ACCESS_BACKGROUND_LOCATION) ==
           PackageManager.PERMISSION_GRANTED

       if (backgroundLocationPermissionApproved) {
            //先后台位置权限都有
       } else {
            //申请后台权限
            if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.R){
                ActivityCompat.requestPermissions(this,
                        arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
                        200)
            }else{
                AlertDialog.Builder(this).setMessage("须要提供后台位置权限,请在设置页面选择始终容许")
                        .setPositiveButton("肯定", DialogInterface.OnClickListener { dialog, which ->
                            ActivityCompat.requestPermissions(this,
                                    arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
                                    200)
                        }).create().show()
            }

       }
    } else {
        if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.R){
            //申请前台和后台位置权限
            ActivityCompat.requestPermissions(this,
                    arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_BACKGROUND_LOCATION),
                    100)
        }else{
            //申请前台位置权限
            ActivityCompat.requestPermissions(this,
                    arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION),
                    100)
        }
    }
    
复制代码

软件包可见性 ⭐

Android 11 更改了应用查询用户已在设备上安装的其余应用以及与之交互的方式。使用新的 元素,应用能够定义一组自身可访问的其余应用。经过告知系统应向您的应用显示哪些其余应用,此元素有助于鼓励最小权限原则。此外,此元素还可帮助 Google Play 等应用商店评估应用为用户提供的隐私权和安全性。

也就是说,Android11中,若是你想去获取其余应用的信息,好比包名,名称等等,不能直接获取了,必须在清单文件中添加<queries>元素,告知系统你要获取哪些应用信息或者哪一类应用。

好比我这段查询应用信息的代码:

val pm = this.packageManager
    val listAppcations: List<ApplicationInfo> = pm
            .getInstalledApplications(PackageManager.GET_META_DATA)
    for (app in listAppcations) {
        Log.e("lz",app.packageName)
    }
复制代码

Android11版本,只能查询到本身应用和系统应用的信息,查不到其余应用的信息了。怎么呢?添加<queries>元素,两种方式:

1)元素中加入具体包名

<manifest package="com.example.game">
    <queries>
        <package android:name="com.example.store" />
        <package android:name="com.example.services" />
    </queries>
    ...
</manifest>
复制代码

1)元素中加入固定过滤的intent

<manifest package="com.example.game">
    <queries>
        <intent>
            <action android:name="android.intent.action.SEND" />
            <data android:mimeType="image/jpeg" />
        </intent>
    </queries>
</manifest>
复制代码

可能仍是有人会疑惑,那个人应用是浏览器或者设备管理器咋办呢?我就要获取全部包名啊? 放心,Android11还引入了 QUERY_ALL_PACKAGES 权限,清单文件中加入便可。可是Google Play可不必定能滥用哦,它为须要QUERY_ALL_PACKAGES 权限的应用会提供相关指南,可是还没出来,具体要看后面的消息了。

至于国内市场。。。(但愿能有个应用市场一统天下好好管理这混乱的市场吧!)

文档访问限制

为让开发者有时间进行测试,如下与存储访问框架 (SAF) 相关的变动只有在应用以 Android 11 为目标平台时才会生效。

上文存储的时候说过能够经过SAF(存储访问框架--Storage Access Framework)来访问公共目录,可是Android11再次升级,部分目录和文件不能访问了,具体以下:

没法再使用 ACTION_OPEN_DOCUMENT_TREE intent 操做请求访问如下目录:

  • 内部存储卷的根目录。
  • 设备制造商认为可靠的各个 SD 卡卷的根目录,不管该卡是模拟卡仍是可移除的卡。可靠的卷是指应用在大多数状况下能够成功访问的卷。
  • Download 目录。

没法再使用 ACTION_OPEN_DOCUMENT_TREEACTION_OPEN_DOCUMENT intent 操做请求用户从如下目录中选择单独的文件:

  • Android/data/ 目录及其全部子目录。
  • Android/obb/ 目录及其全部子目录。

限制对 APN 数据库的读取访问

以 Android 11 为目标平台的应用如今必须具有 Manifest.permission.WRITE_APN_SETTINGS 特权,才能读取或访问电话提供程序 APN 数据库。若是在不具有此权限的状况下尝试访问 APN 数据库,会生成安全异常。

问题来了,APN是啥?

  • 指一种网络接入技术,是经过手机上网时必须配置的一个参数,APN配置参数包括名字,运营商编号,APN接入点等等。

就是说若是没有Manifest.permission.WRITE_APN_SETTINGS权限就不能读取APN数据库了,可是!这个权限很早以前就被限定只有系统程序才能申请这个权限了,如今这个特权没理解到是什么意思,难道系统程序都不能随便申请了?有大神能够评论区留言告知。

在元数据文件中声明“无障碍”按钮使用状况

从 Android 11 开始,您的无障碍服务没法在运行时声明与系统的“无障碍”按钮的关联。若是您将 AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON 附加到 AccessibilityServiceInfo 对象的 flags 属性,框架就不会将“无障碍”按钮回调事件传递给您的服务。

作过无障碍辅助功能的应该都知道AccessibilityServiceInfo要设置flag为FLAG_REQUEST_ACCESSIBILITY_BUTTON,getAccessibilityButtonController方法获取辅助功能按钮控制器,而且可用于查询辅助功能按钮的状态并注册监听器以进行交互和辅助功能按钮的状态更改。

可是,Android 11开始,这样写不能获取辅助按钮回调事件了,得换成另一种写法。在元数据文件(一般为 res/raw/accessibilityservice.xml)中使用 flagRequestAccessibilityButton 标记声明您的无障碍服务与“无障碍”按钮的关联。

Firebase JobDispatcher 和 GCMNetworkManager

若是您的应用以 API 级别 30 或更高级别为目标平台,在搭载 Android 6.0(API 级别 23)或更高版本的设备上会停用 Firebase JobDispatcher 和 GcmNetworkManager API 调用。

这两个api国内都用不了,主要用于后台任务。官方给出的替代意见是WorkManager,这个国内是能够用的,属于jetpack组件,主要用于调度和执行可延期的后台工做。

设备到设备文件传输

若是您的应用以 Android 11 为目标平台,您将没法再使用 allowBackup 属性停用应用文件的设备到设备迁移。系统会自动启用此功能。不过,即便您的应用以 Android 11 为目标平台,您也能够经过将 allowBackup 属性设置为 false 来停用应用文件的云端备份和恢复。

android:allowBackup属性

  • 表明是否容许应用参与备份和恢复基础架构。若是将此属性设为 false,则永远不会为该应用执行备份或恢复,即便是采用全系统备份方法也不例外(这种备份方法一般会经过 adb 保存全部应用数据)。此属性的默认值为 true。

因此这里是不能停用文件的设备到设备迁移,可是能够停用云端备份和恢复

自动重置权限

若是应用以 Android 11 为目标平台而且数月未使用,系统会经过自动重置用户已授予应用的运行时敏感权限来保护用户数据。此操做与用户在系统设置中查看权限并将应用的访问权限级别更改成拒绝的作法效果同样。若是应用已遵循有关在运行时请求权限的最佳作法,那么您没必要对应用进行任何更改。这是由于,当用户与应用中的功能互动时,您应该会验证相关功能是否具备所需权限。

官方说明说的很清楚了,并且只要应用遵循有关在运行时请求权限的最佳作法,也就是每次须要调用权限的时候都会去判断,那么就不会有什么问题。

若是须要关闭这个功能怎么办呢?只有引导用户去设置页面关闭了,能够调用包含Settings.ACTION_APPLICATION_DETAILS_SETTINGS action的 Intent将用户定向到系统设置中应用的页面。

怎么检查应用是否停用自动重置功能呢?调用 PackageManager的isAutoRevokeWhitelisted()方法。若是此方法返回 true,表明系统不会自动重置应用的权限。

前台服务类型

从 Android 9 开始,应用仅限于在前台访问摄像头和麦克风。为了进一步保护用户,Android 11 更改了前台服务访问摄像头和麦克风相关数据的方式。若是您的应用以 Android 11 为目标平台而且在某项前台服务中访问这些类型的数据,您须要在该前台服务的声明的 foregroundServiceType 属性中添加新的 camera 和 microphone 类型。

在Android10的时候,对于前台定位服务就必须加上android:foregroundServiceType="location",如今Android11上又增长了两个权限限制,一个是摄像头一个是麦克风。

因此总结下来就是,应用某项前台服务须要访问位置信息、摄像头和麦克风,那么就要在清单文件中这样添加:

<manifest>
    <service ...
        android:foregroundServiceType="location|camera|microphone" />
</manifest>
复制代码

有的朋友可能测试发现,不加foregroundServiceType的前提下,让Activity启动了一个前台服务,并在服务里去获取定位,居然能够获取到定位信息,难道官方说错了?

其实这是由于你并无让前台服务单独运行,你能够试着在Activity启动Service后,进入Home界面,而后过几秒再请求位置,就请求不到了。可是不会崩溃,由于这个被系统设置的权限类别为MODE_IGNORED,也就是静默失败模式。

因此为了保险起见,只要前台服务涉及到了这三个功能,就在清单文件加上android:foregroundServiceType

适配Android11手机

此模块的修改内容针对全部项目在Android11手机上存在的改动,与targetSdkVersion无关。

数据访问审核 ⭐

为了让应用及其依赖项访问用户私密数据的过程更加透明,Android 11 引入了数据访问审核功能。借助此流程得出的看法,您能够更好地识别和纠正可能出现的意外数据访问。

哪些范畴属于用户私密数据呢?其实就是危险权限的调用,因此这个功能就是提供了能够监听危险权限调用的监听。主要涉及到的方法是AppOpsManager.OnOpNotedCallback。不管是应用自己,仍是依赖库或者SDK中的代码,只要访问到私密数据(危险权限),都会回调给咱们。

对于工程庞大或者使用较多SDK的工程比较适合用上这个功能,让本身应用的私有数据管理更加透明规范,不然对于私有数据的使用和管理并不全面和方便。并且还能够对权限使用添加归因,也就是一个tag,标志权限用到了什么地方。方便回调的时候知晓哪里使用了私有数据

🌰来:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test1)

        //建立归因(attribute) 
        attributionContext = createAttributionContext("shareLocation")

        //监听事件
        val appOpsCallback = object : AppOpsManager.OnOpNotedCallback() {
            private fun logPrivateDataAccess( opCode: String, attributionTag: String, trace: String) {
                Log.i(TAG, "Private data accessed. " +
                        "Operation: $opCode\n " +
                        "Attribution Tag:$attributionTag\nStack Trace:\n$trace")
            }

            override fun onNoted(syncNotedAppOp: SyncNotedAppOp) {
                syncNotedAppOp.attributionTag?.let {
                    logPrivateDataAccess(syncNotedAppOp.op,
                            it,
                            Throwable().stackTrace.toString())
                }
            }

            override fun onSelfNoted(syncNotedAppOp: SyncNotedAppOp) {
                syncNotedAppOp.attributionTag?.let {
                    logPrivateDataAccess(syncNotedAppOp.op,
                            it,
                            Throwable().stackTrace.toString())
                }
            }

            override fun onAsyncNoted(asyncNotedAppOp: AsyncNotedAppOp) {
                asyncNotedAppOp.attributionTag?.let {
                    logPrivateDataAccess(asyncNotedAppOp.op,
                            it,
                            asyncNotedAppOp.message)
                }
            }
        }

        //开启私密数据监听
        val appOpsManager =
                getSystemService(AppOpsManager::class.java) as AppOpsManager
        appOpsManager.setOnOpNotedCallback(mainExecutor, appOpsCallback)

        btn1.setOnClickListener {
            getLocation()
        }
    }

    fun getLocation() {
        val locationManager = attributionContext.getSystemService(
                LocationManager::class.java) as LocationManager
        if (!checkPermission()) {
            return
        }
        val location: Location? = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
        if (location != null) {
            showToast("${location.latitude}")
        }
    }

复制代码

该例子主要展现了一个获取位置信息的功能,若是调用到getLocation方法,就会触发onNoted回调,回调信息包括危险权限code以及归因。

其中OnOpNotedCallback 一共三个回调方法:

  • onNoted 正常状况下都会回调到该方法
  • onAsyncNoted 若是数据访问并不是发生在应用调用API期间,就会调用onAsyncNoted(),好比一些监听器的回调。
  • onSelfNoted 在极少数状况下,若是应用将自身的UID传递到 noteOp(),须要调用 onSelfNoted()。

最后点击按钮,看下回调的结果日志:

Private data accessed. Operation: android:coarse_location
     Attribution Tag:shareLocation
    Stack Trace:
    [Ljava.lang.StackTraceElement;@14f5a16
复制代码

能够看到权限代码:android:coarse_location 以及归因 shareLocation

单次受权

在 Android 11 中,每当应用请求与位置信息、麦克风或摄像头相关的权限时,面向用户的权限对话框会包含仅限这一次选项。若是用户在对话框中选择此选项,系统会向应用授予临时的单次受权。

简单的说,就是在申请与位置信息、麦克风或摄像头相关的权限时,系统会自动提供一个单次受权的选项,只供这一次权限获取。而后用户下次打开app的时候,系统会再次提示用户授予权限。这个影响应该不大,只要咱们每次使用的时候都去判断权限,没有就去申请便可。放一张新版本权限获取样式:

新权限弹窗

权限对话框的可见性

Android 11 建议不要请求用户已选择拒绝的权限。在应用安装到设备上后,若是用户在使用过程当中多次针对某项特定的权限点按拒绝,此操做表示其但愿“再也不询问”。

这个都算不上改动,只是官方的一个良好建议。建议在用户屡次拒绝以后,不要再展现权限申请。

Scudo Hardened Allocator

Android 11 在内部使用 Scudo Hardened Allocator 为堆分配提供服务。Scudo 可以检测并减轻某些类型的内存安全违规行为。若是您在原生代码崩溃报告中发现与 Scudo 相关的崩溃(例如 Scudo ERROR:),请参阅 Scudo 问题排查文档。

Scudo是一种动态的用户模式内存分配器,旨在抵御与堆相关的漏洞,同时保持良好的性能。它是一个开源的项目。 Android 11中,将采用这个新的heap分配器,性能更好,更安全。

文件描述符排错程序

Android 10 引入了 fdsan(文件描述符排错程序)。fdsan 检测错误处理文件描述符全部权的错误,例如 use-after-close 和 double-close。在 Android 11 中,fdsan 的默认模式发生了变化。如今,fdsan 会在检测到错误时停止,而之前的行为则是记录警告并继续。

问题来了,fdsan是啥?先要了解fd是啥

文件描述符(FileDescriptor) 是Unix/Linux系统文件操做的相关概念,它在形式上是一个非负整数。当程序打开一个现有文件或者建立一个新文件时,内核向进程返回一个文件描述符。 系统的进程也就是使用了这个fd来标示打开的文件,有了它就能对文件作各类操做,得到文件的各类相关信息了。

因此fdsan也就是检测文件处理中发生的一些错误。

应用使用状况统计信息

为了更好地保护用户,Android 11 将每一个用户的应用使用状况统计信息存储在凭据加密存储空间中。

这就涉及到了UsageStatsManagerUsageStatsManager是Android提供统计应用使用状况的服务。经过这个服务能够获取指定时间区间内应用使用统计数据、组件状态变化事件统计数据以及硬件配置信息统计数据。

好比queryAndAggregateUsageStats方法,能够获取指定时间区间内使用统计数据,以应用包名为键值进行数据合并。

可是在Android 11 设备中,很差意思,不能随意使用这些信息了。只有当isUserUnlocked()方法返回true的时候,才能正常访问这些数据。也就是如下两种状况:

  • 用户在系统启动后首次解锁其设备
  • 用户在设备上切换到本身的账号

JobScheduler API 调用限制调试

JobScheduler任务调度器,能够在设备空闲时作一些任务处理。Android11中若是你设置为debug模式(debuggable 清单属性设置为 true),超出速率限制的JobScheduler API调用将返回 RESULT_FAILURE。这个有什么用呢?应该能够帮助咱们发现一些性能问题,感兴趣的能够本身试试。

顺便提下,Jetpack组件WorkManager也是用到了JobScheduler,不熟悉的同窗能够去了解下,JobScheduler是由SystemServer进程启动的一个系统服务,因此才能够有这么大的权限。

无障碍操做

在之前的 Android 版本中,框架会向未正确处理基于点击的无障碍操做的微件分派触摸事件。一般,这些视图会直接处理触摸事件,而不是注册点击监听器。

为了在正肯定义无障碍操做的应用中建立更一致的行为,Android 11 毫不会分派触摸事件。相反,系统会彻底依赖于基于点击的无障碍操做:ACTION_CLICK 和 ACTION_LONG_CLICK。此更改会影响屏幕阅读器的行为。

Android手机上有个预安装的屏幕阅读服务,叫作TalkBack,为视力障碍人士或者视力状态不佳的老年人提供。那咱们应用为了让这个阅读器可以读懂你的自定义view操做,必须给与自定义控件定义处理程序,包括点击,长按等操做。原来版本可能对于OnTouchListener也支持无障碍触摸事件,而在Android11中,必须专门制定点击或者长按事件才行了。给个🌰:

class TriSwitch(context: Context) : Switch(context) {
    // 0, 1, or 2.
    var currentState: Int = 0
        private set

    init {
        updateAccessibilityActions()
    }

    private fun updateAccessibilityActions() {
        ViewCompat.replaceAccessibilityAction(this, ACTION_CLICK,
            action-label) {
            view, args -> moveToNextState()
        })
    }

    private fun moveToNextState() {
        currentState = (currentState + 1) % 3
    }
}
复制代码

一个自定义控件TriSwitch,继承自Switch,因为和Switch的点击效果不同,因此必须经过替换 ViewCompat.replaceAccessibilityAction() 来从新定义相应的无障碍操做。

非SDK接口限制

Android 11 包含更新后的受限制非 SDK 接口列表(基于与 Android 开发者之间的协做以及最新的内部测试)。在限制使用非 SDK 接口以前,咱们会尽量确保提供公开替代方案。

老样子,Android11也会限制一些接口,包括灰名单和白名单,具体看非SDK接口列表

总结

一路分析下来也能够看到,若是是重要的改动,特别是涉及到崩溃的改动仍是放到了targetSdkVersion=30的内容中,这也是每次Android发版的一个潜规则吧,为了最大程度不影响已上线的app所做出的举动。
可是,这并不意味咱们就能够不改。由于应用可拖不起,用户可拖不起,毕竟升级才能给到用户最好的体验。并且各大应用市场也都会建议或者强制应用升级targetSdkVersion,以便适配最新的手机。

因此,行动吧。

附件

官网改动介绍


个人公众号:码上积木,天天三问面试题,详细剖析,助你成为offer收割机。

谢谢你的阅读,若是你以为写的还行,就点个赞支持下吧!感谢!
你的一个👍,就是我分享的动力❤️。

相关文章
相关标签/搜索