最后
以前一直是自己在网上东平西凑的找,找到的东西也是零零散散,很多时候都是看着看着就没了,时间浪费了,问题却还没得到解决,很让人抓狂。
后面我就自己整理了一套资料,还别说,真香!
资料有条理,有系统,还很全面,我不方便直接放出来,大家可以先看看有没有用得到的地方吧。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
以下我分为两部分讲述,分别是
-
以Adnroid11 为目标版本的应用(targetSdkVersion>=30才有影响)⭐
-
所有应用在Android11设备上适配改动(无论targetSdkVersion是多少,只要在Android11设备上运行的应用都有影响)
为什么先说targetSdkVersion>=30的模块呢?因为一般来说为了Google为了让我们更长时间适应新的内容以及保障线上应用的稳定,都会把改动大的,需要花时间适配的内容放到新的targetSdkVersion对应的应用上,如果你暂时没有适配targetSdkVersion30的需求,也可以看看第二模块,看看是否有涉及你的应用相关内容。GOGOGO!
Tips:此适配文章会不间断更新,根据Android11发布进度调整,欢迎点赞关注。(打⭐的格外注意哦)
2适配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, “KaTeX parse error: Expected '}', got 'EOF' at end of input: …("image uri is uri”) } cursor.close()}
3) 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已经出来很久了,所以资料还是很多的,这里推荐几篇
访问应用专属文件
https://developer.android.google.cn/training/data-storage/app-specific#external
Android 10适配要点,作用域存储
https://blog.csdn.net/guolin_blog/article/details/105419420/
AndroidQ(10)分区存储完美适配
https://www.jianshu.com/p/271bbd13bfcf
说到这里可能又有人问了,那我的应用就是个手机管理器,总不能不让我清其他应用的缓存了吧,有办法!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 这不来了吗。这个权限就是用来获取所有文件的管理权限。🌰:
val intent = Intent()intent.action= Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSIONstartActivity(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中注册好权限,并且添加动态权限申请:
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_PHONE_STATE,Manifest.permission.READ_PHONE_NUMBERS), 100)
搞定,如果你只需要获取手机号码这一个功能,也可以只申请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_CAPTUREstartActivity(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 的设备上,当应用中的某项功能请求在后台访问位置信息时,用户看到的系统对话框不再包含用于启用后台位置信息访问权限的按钮。如需启用后台位置信息访问权限,用户必须在设置页面上针对应用的位置权限设置一律允许选项。
什么意思呢?
在较低版本的Android系统中,当应用获得前台位置信息访问权限时,也会自动获得后台位置信息访问权限。比如我请求一个前台位置访问权限:
requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION), 100)
授权后,就能同时获取前台位置权限和后台位置权限(ACCESS_BACKGROUND_LOCATION)。
但是现在不行了,你必须单独申请后台位置权限,而且,要在获取前台权限之后,顺序还不能乱。
requestPermissions(arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION), 100)
如果在没获取前台权限的时候执行这个获取后台权限的代码会没反应,等获取前台权限(ACCESS_COARSE_LOCATION)之后,申请后台权限就会跳转到一个新的权限页面了,而且必须选择Allow all the time (始终允许)才能获得后台位置权限,看图:
软件包可见性 ⭐
Android 11 更改了应用查询用户已在设备上安装的其他应用以及与之交互的方式。使用新的 元素,应用可以定义一组自身可访问的其他应用。通过告知系统应向您的应用显示哪些其他应用,此元素有助于鼓励最小权限原则。此外,此元素还可帮助 Google Play 等应用商店评估应用为用户提供的隐私权和安全性。
就是说,Android11中,如果你想去获取其他应用的信息,比如包名,名称等等,不能直接获取了,必须在清单文件中添加元素,告知系统你要获取哪些应用信息或者哪一类应用。
比如我这段查询应用信息的代码:
val pm = this.packageManagerval listAppcations: List = pm .getInstalledApplications(PackageManager.GET_META_DATA)for (app in listAppcations) { Log.e(“lz”,app.packageName)}
在Android11版本,只能查询到自己应用和系统应用的信息,查不到其他应用的信息了。怎么呢?添加元素,两种方式:
1)元素中加入具体包名
…
2)元素中加入固定过滤的intent
可能还是有人会疑惑,那我的应用是浏览器或者设备管理器咋办呢?我就要获取所有包名啊?放心,Android11还引入了 QUERY_ALL_PACKAGES 权限,清单文件中加入即可。
但是Google Play可不一定能滥用哦,它为需要QUERY_ALL_PACKAGES 权限的应用会提供相关指南,但是还没出来,具体要看后面的消息了。
至于国内应用市场。。。(希望能有个应用市场一统天下好好管理这混乱的市场吧!)
文档访问限制
为让开发者有时间进行测试,以下与存储访问框架 (SAF) 相关的变更只有在应用以 Android 11 为目标平台时才会生效。
上文存储的时候说过可以通过SAF(存储访问框架–Storage Access Framework)来访问公共目录,但是Android11再次升级,部分目录和文件不能访问了,具体如下:
尾声
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。
当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。
进阶学习视频
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-qHWzi5yh-1715765774516)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!