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

简介: 没错!Android 11(version 30,Andorid R) 正式发布了!看到这个新闻我知道我不能再拖了,再不好好准备好迎接Android11的到来,到时候迎接我的就是客户的指责,甚至老板的一封休书了 😂

前言


没错!Android 11(version 30,Andorid R) 正式发布了!看到这个新闻我知道我不能再拖了,再不好好准备好迎接Android11的到来,到时候迎接我的就是客户的指责,甚至老板的一封休书了 😂。


今天就和大家一起看看Android11到底改了些什么,以及最重要的,我们需要怎么适配?targetversion不改到30,是不是就不用适配了呢?


以下我分为两部分讲述,分别是


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


为什么先说targetSdkVersion>=30的模块呢?因为一般来说为了Google为了让我们更长时间适应新的内容以及保障线上应用的稳定,都会把改动大的,需要花时间适配的内容放到新的targetSdkVersion对应的应用上,如果你暂时没有适配targetSdkVersion30的需求,也可以看看第二模块,看看是否有涉及你的应用相关内容。GOGOGO!


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


适配targetSdkVersion30


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


分区存储强制执行⭐


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


关于分区存储,在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))


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()


来张截图过过瘾:


0.jpg


电话号码相关权限 ⭐


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小于11的时候,可以前台后台位置权限一起申请,并且对话框提供了文字说明,表示需要随时获取用户位置信息,进入设置选择始终允许即可。但是targetVersion为30的时候,你必须单独申请后台位置权限,而且要在获取前台权限之后,顺序不能乱。并且无任何提示,需要开发者自己设计提示样式。


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


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


requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_BACKGROUND_LOCATION), 100)


执行效果:


1.jpg


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


requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_BACKGROUND_LOCATION), 100)


执行效果:


3.jpg


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)


执行效果:


2.jpg


requestPermissions(arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION), 100)


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


4.jpg


所以,该怎么适配呢?


  • 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>


2)元素中加入固定过滤的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 类型。


举例,如果应用某项前台服务需要访问位置信息、摄像头和麦克风,那么就这样添加:


<manifest>
    <service ...
        android:foregroundServiceType="location|camera|microphone" />
</manifest>


目录
相关文章
|
11月前
|
移动开发 数据库 Android开发
构建高效Android应用:Kotlin协程的实践指南
【5月更文挑战第30天】 在移动开发领域,性能优化和流畅的用户体验是至关重要的因素。对于Android开发者来说,Kotlin协程作为一种异步编程解决方案,提供了强大且轻量级的机制来处理后台任务,而不会对主线程造成阻塞。本文将深入探讨Kotlin协程的概念、优势以及如何在Android应用中实现它们,从而改善应用响应性和用户满意度。通过实例代码和最佳实践的分享,我们将展示如何有效利用协程来处理网络请求、数据库操作和耗时计算,同时确保UI的流畅性。
|
11月前
|
移动开发 调度 Android开发
构建高效Android应用:Kotlin协程的实践指南
【5月更文挑战第30天】在移动开发领域,Android平台的流畅性与效率一直是开发者追求的核心。随着Kotlin语言的普及,其提供的协程特性为编写高效、轻量级的异步代码提供了强大工具。本文将深入探讨如何在Android项目中利用Kotlin协程来优化性能,提升用户体验。我们将从协程的基本概念出发,通过实例演示如何在实际开发中合理运用协程,并讨论协程对资源管理和错误处理的影响。
51 3
|
11月前
|
移动开发 API Android开发
构建高效Android应用:Kotlin协程的实践指南
【5月更文挑战第11天】 在移动开发领域,性能优化和资源管理是至关重要的。特别地,对于Android开发者来说,合理利用Kotlin协程可以极大地改善应用的响应性和稳定性。本文将深入探讨Kotlin协程在Android中的实际应用,包括它们如何简化异步编程模型、提高UI线程的响应性,以及减少内存消耗。我们将通过具体案例分析,了解如何在实际项目中有效地使用协程,从而帮助开发者构建更加高效的Android应用程序。
|
7月前
|
调度 Android开发 UED
Android经典实战之Android 14前台服务适配
本文介绍了在Android 14中适配前台服务的关键步骤与最佳实践,包括指定服务类型、请求权限、优化用户体验及使用WorkManager等。通过遵循这些指南,确保应用在新系统上顺畅运行并提升用户体验。
432 6
|
9月前
|
IDE API Android开发
安卓与iOS开发环境的差异及适配策略
在移动应用开发的广阔舞台上,Android和iOS两大操作系统各据一方,各自拥有独特的开发环境和工具集。本文旨在深入探讨这两个平台在开发环境上的关键差异,并提供有效的适配策略,帮助开发者优化跨平台开发流程。通过比较Android的Java/Kotlin和iOS的Swift/Objective-C语言特性、IDE的选择、以及API和系统服务的访问方式,本文揭示了两个操作系统在开发实践中的主要分歧点,并提出了一套实用的适配方法,以期为移动开发者提供指导和启示。
|
8月前
|
安全 Java Android开发
Android 14适配Google play截止时间临近,适配注意点和经验
本文介绍了Android 14带来的关键更新,包括性能优化、定制化体验、多语言支持、多媒体与图形增强等功能。此外,还强调了适配时的重要事项,如targetSdkVersion升级、前台服务类型声明、蓝牙权限变更等,以及安全性与用户体验方面的改进。开发者需按官方指南更新应用,以充分利用新特性并确保兼容性和安全性。
466 0
|
11月前
|
物联网 区块链 vr&ar
构建高效Android应用:Kotlin协程的实践指南未来交织:新兴技术趋势与跨领域应用探索
【5月更文挑战第28天】随着移动应用开发的不断进步,开发者寻求更高效、更简洁的方式来处理异步任务和提升用户体验。在Android平台上,Kotlin协程作为一种轻量级的线程管理方案,提供了强大的工具来简化并发和异步编程。本文将深入探讨Kotlin协程的核心概念,并通过实例演示如何在Android应用中利用协程优化性能和响应性。通过本文,你将学会如何运用协程来编写更加流畅和高效的代码,同时减少内存消耗和提高应用的稳定性。 【5月更文挑战第28天】 随着科技的迅猛发展,一系列创新技术如区块链、物联网(IoT)、虚拟现实(VR)等正在逐渐从概念验证走向实际应用。这些技术的融合与交叉不仅预示着信息时
|
11月前
|
安全 数据库 Android开发
构建高效Android应用:采用Kotlin与Jetpack的实践指南
【5月更文挑战第22天】 在移动开发领域,Android系统因其开放性和广泛的用户基础而备受开发者青睐。随着技术的不断演进,Kotlin语言以其简洁性和功能性成为Android开发的首选语言。本文将深入探讨如何结合Kotlin和Android Jetpack组件来构建一个高效且易于维护的Android应用。我们将重点讨论如何使用Jetpack的核心组件,如LiveData、ViewModel和Room,以及Kotlin的语言特性来优化代码结构,提高应用性能,并简化数据管理。通过具体案例分析,本文旨在为开发者提供一套实用的技术指导,帮助他们在竞争激烈的市场中脱颖而出。
|
11月前
|
移动开发 Android开发 开发者
构建高效安卓应用:Kotlin 协程的实践指南
【5月更文挑战第18天】 随着移动开发技术的不断进步,安卓平台亟需一种高效的异步编程解决方案来应对日益复杂的应用需求。Kotlin 协程作为一种新兴的轻量级线程管理机制,以其简洁的语法和强大的功能,成为解决这一问题的关键。本文将深入探讨Kotlin协程在安卓开发中的实际应用,从基本概念到高级技巧,为开发者提供一份全面的实践指南,旨在帮助读者构建更加高效、稳定的安卓应用。
|
11月前
|
安全 Android开发 开发者
构建高效Android应用:采用Kotlin与Jetpack的实践指南
【4月更文挑战第30天】 在移动开发领域,随着技术的不断进步,为了提高应用的性能和用户体验,开发者们不断地探索新的工具和框架。对于Android平台而言,Kotlin语言以其简洁性和功能性成为了开发的首选。而Jetpack组件则提供了一套高质量的库、工具和指南,帮助开发者更轻松地构建高质量的应用程序。本文将探讨如何结合Kotlin语言和Jetpack组件来优化Android应用的开发流程,提升应用性能,并保证代码的可维护性和可扩展性。

热门文章

最新文章