Android Q(10.0 API29)版本新特性和兼容性适配
摘要
1、本文档基于谷歌AndroidQ官方文档和一加Q版本应用兼容性整改指导
2、本文档主要对影响比较大的部分进行简单总结,内容并不全面;
3、版本号对应关系:
Android-Q = Android-10 = Api29
Android-P = Android-9.0 = Api28
一、Android Q 行为变更:版本新特性
权限 | 受影响应用 | 如何启用(影响范围) |
---|---|---|
存储权限 | 访问和共享外部存储设备中的文件的应用 | adb shell sm set-isolated-storage on(下文详述) |
定位权限 | 在后台时请求访问用户位置信息的应用 | 这种权限策略在 Android Q 上始终处于启用状态 |
从后台启动Activity | 不需要用户互动就启动 Activity 的应用 | 关闭允许系统执行后台活动开发者选项即可启用限制 |
设备标识符(deviceId) | 访问设备序列号或 IMEI 的应用 | 在搭载 Android Q 的设备上安装应用 |
无线扫描权限 | 使用 WLAN API 和 Bluetooth API 的应用 | 以 Android Q 为目标平台 |
上面的内容官方文档将这一部分内容独立于Q 行为变更:所有应用来介绍,是因为这一部分内容庞大且重要,其中最大的更新就是用户隐私权限变更. 因为无线扫描权限这种权限的变更影响较少。本文不作详述,如有涉及请查阅官方文档。
二、兼容性适配
以下对各个权限分别作介绍以及解决方法。
1、存储权限
Android Q 在外部存储设备中为每个应用提供了一个“隔离存储沙盒”(例如 /sdcard)。任何其他应用都无法直接访问您应用的沙盒文件。由于文件是您应用的私有文件,因此您不再需要任何权限即可在外部存储设备中访问和保存自己的文件。此变更可让您更轻松地保证用户文件的隐私性,并有助于减少应用所需的权限数量。
沙盒,简单而言就是应用私有专属文件夹,并且访问这个文件夹无需权限。
- 沙箱目录: /sdcard/Android/sandbox/packagename/
- 任何其他应用都无法直接访问您应用的沙盒文件。由于文件是您应用的私有文件,因此您不再需要任何>权限即可在外部存储设备中访问和保存自己的文件
- 当app卸载后,沙箱中的文件删除
谷歌官方推荐应用在沙盒内存储文件的地址为
Context.getExternalFilesDir()下的文件夹。比如要存储一张图片,则应放在Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)中。
以下将按访问的目标文件的地址介绍如何适配。
- 访问自己文件:Q中用更精细的媒体特定权限替换并取消了 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE权限,并且无需特定权限,应用即可访问自己沙盒中的文件。
- 访问系统媒体文件:Q中引入了一个新定义媒体文件的共享集合,如果要访问沙盒外的媒体共享文件,比如照片,音乐,视频等,需要申请新的媒体权限:READ_MEDIA_IMAGES,READ_MEDIA_VIDEO,READ_MEDIA_AUDIO,申请方法同原来的存储权限。
- 访问系统下载文件:对于系统下载文件夹的访问,暂时没做限制,但是,要访问其中其他应用的文件,必须允许用户使用系统的文件选择器应用来选择文件。
- 访问其他应用沙盒文件:如果你的应用需要使用其他应用在沙盒内创建的文件,请点击使用其他应用的文件,本文不做介绍。
所以请判断当应用运行在Q平台上时,取消对READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE两个权限的申请。并替换为新的媒体特定权限。
影响范围
Google 把 Android Q 上会被沙箱化条件设为 Target SDK 至少为 Q (29) 的应用或者运行 Android Q 时全新安装的应用。不符合这个条件的应用将会运行在兼容模式下,在兼容模式中应用行为大致和过去相同,以保证不会出现严重的数据丢失问题。兼容模式在应用重新安装后会被关闭。
注意:即使应用Target SDK < 29也会被沙箱化
影响点
- 沙箱化后,沙箱目录:/sdcard/Android/sandbox/packagename/,应用不能再通过 Java File API 来互相访问内部存储文件数据,应用访问 “/sdcard” 实质上访问的是你的应用的沙箱目录,可以理解为“存储重定向”; getExternalStoragePublicDirectory(),getExternalStorageDirectory(),/storage/emulated/0 都会直接映射到沙箱目录;访问这些文件路径存在自动映射,因此正常直接使用理论上都是没问题的。
- 可能存储数据丢失:当用户手机从Android Q以下版本升级到Android Q版本的时候,应用访问不了之前保存在/sdcard下面的内容。
- 沙箱空间跟过去的内部存储空间内 “Android/data/packagename” 的文件夹一样,会在应用卸载时被永久清除无法恢复,因此比如一些用户主动下载保存的文件固然不能存在沙箱空间,需要存到沙箱外面
情况描述
- 从Android10开始应用将不可直接访问外部存储(/sdcard)文件,否则抛异常。
- 在AndroidQ上运行:
targetSdkVersion<Q,没影响;
targetSdkVersion>=Q,默认启用过滤视图,应用以外的文件需要通过存储访问框架(SAF,StorageAccessFramework)读写。
解决方法
方法一、停用过滤视图,使用旧版存储模式
<manifest ... ><!-- This attribute is "false" by default on apps targeting Android Q. --><application android:requestLegacyExternalStorage="true" ... >...</application></manifest>
方法二、将文件存储到过滤视图中,官方推荐。
// /Android/data/com.example.androidq/files/DocumentsFile dir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS);
优点:不用申请读写权限;
缺点:随应用卸载而删除;
方法三、使用存储访问框架(SAF),由用户指定要读写的文件。
这个功能Android 4.4(API: 19)就有,参考官方文档。
方法四、获取用户指定的某个目录的读写权限
从Android5.0(Api 21)开始就有,官方文档。
步骤
1. 申请目录的访问权限
会打开系统的文件目录,由用户自己选择允许访问的目录,不用申请WRITE/READ_EXTERNAL_STORAGE
权限。
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
startActivityForResult(intent, REQ_CODE);
允许了之后通过onActivityResult()
的intent.getData()
得到该目录的Uri,通过Uri可获取子目录和文件。这种方式的缺点是应用重装后权限失效,即使可以保存了这个Uri也没用。
Uri dirUri = intent.getData();
// 持久化;应用重装后权限失效,即使知道这个uri也没用
SPUtil.setValue(this, SP_DOC_KEY, dirUri.toString());
//重要:少这行代码手机重启后会失去权限
getContentResolver().takePersistableUriPermission(dirUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
2. 通过Uri读写文件
- 创建文件
// 在mUri目录(‘DuoKan’目录)下创建'test.txt'文件
private void createFile() {DocumentFile documentFile = DocumentFile.fromTreeUri(this, mUri);DocumentFile file = documentFile.createFile("text/plain", "test.txt");if (file != null && file.exists()) {LogUtil.log(file.getName() + " created");}
}
主要用到DocumentFile
类,和File
类的方法类似,有isFile、isDirectory、exists、listFiles
等方法
- 删除文件
//删除"test.txt"
private void deleteFile() {DocumentFile documentFile = DocumentFile.fromTreeUri(this, mUri);// listFiles(),列出所有的子文件和文件夹for (DocumentFile file : documentFile.listFiles()) {if (file.isFile() && "test.txt".equals(file.getName())) {boolean delete = file.delete();LogUtil.log("deleteFile: " + delete);break;}}
}
- 写入数据
private void writeFile(Uri uri) {try {ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "w");//这种方法会覆盖原来文件内容OutputStreamWriter output = new OutputStreamWriter(new FileOutputStream(pfd.getFileDescriptor()));// 不能传uri.toString(),否则FileNotFoundException// OutputStreamWriter output = new OutputStreamWriter(new FileOutputStream(uri.toString(), true));output.write("这是一段文件写入测试\n");output.close();LogUtil.log("写入成功。");} catch (IOException e) {LogUtil.log(e);}
}
2、定位权限
为了让用户更好地控制应用对位置信息的访问权限,Android Q 引入了新的位置权限
ACCESS_BACKGROUND_LOCATION
。与现有的ACCESS_FINE_LOCATION
和ACCESS_COARSE_LOCATION
权限不同,新权限仅会影响应用在后台运行时对位置信息的访问权。除非应用的某个 Activity 可见或应用正在运行前台服务,否则应用将被视为在后台运行。与iOS系统一样,Q中也加入了后台位置权限
ACCESS_BACKGROUND_LOCATION
,如果应用需要在后台时也获得用户位置(比如滴滴),就需要动态申请ACCESS_BACKGROUND_LOCATION
权限。当然如果不需要的话,应用就无需任何改动,且谷歌会按照应用的targetSDK作出不同处理:
targetSDK <= P
应用如果请求了ACCESS_FINE_LOCATION
或ACCESS_COARSE_LOCATION
权限,Q设备会自动帮你申请ACCESS_BACKGROUND_LOCATION
权限。
情况描述
- 后台应用要获取位置信息需要动态申请权限,
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
- 在AndroidQ上运行:
- targetSdkVersion<Q,没影响,申请权限时系统默认会加上后台位置权限
- targetSdkVersion>=Q,需申请;
- 应用变为后台应用90s后开始定位失败(Pixel AndroidQ-beta6)
ACCESS_BACKGROUND_LOCATION
不能单独申请,需要和ACCESS_COARSE_LOCATION/ACCESS_FINE_LOCATION
一起申请
解决方法
动态申请即可;
启动前台服务
<!-需要设置foregroundServiceType为“location” ->
<service android:name=".permission.LocationService"android:foregroundServiceType="location"/>
3、禁止后台启动activity
官方文档
情况描述
- AndroidQ上,后台启动Activity会被系统忽略,不管targetSdkVersion多少;
- AndroidQ上,即使应用有前台服务也不行;
- AndroidQ以下版本没影响。
解决方法
发送全屏通知:
//AndroidManifest 声明新权限,不用动态申请
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>Intent intent = new Intent(this, ScopedStorageActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,REQ_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new NotificationCompat.Builder(this, Constants.CHANNEL_ID).setSmallIcon(R.drawable.ic_launcher_foreground).setContentTitle("Incoming call").setContentText("(919) 555-1234").setPriority(NotificationCompat.PRIORITY_HIGH).setCategory(NotificationCompat.CATEGORY_ALARM)//设置全屏通知后,发送通知直接启动Activity.setFullScreenIntent(pendingIntent, true).build();
NotificationManager manager = getSystemService(NotificationManager.class);
manager.notify(445456, notification);
但是:在华为mate20(Api-28)上需要到设置中打开横幅通知;原生AndroidQ(beta6)上有效。
4、设备硬件标识符访问限制
限制应用访问不可重设的设备识别码,如 IMEI、序列号等,系统应用不受影响。
原来的做法
TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
tm.getDeviceId();
Build.getSerial();
- 在低于AndroidQ的系统上没问题
- 在AndroidQ及以上的系统上运行时:
- 如targetSdkVersion<Q,返回null或“unknown”;
- 如targetSdkVersion>=Q,抛异常:
SecurityException: getDeviceId: The user 10196 does not meet the requirements to access device identifiers.
解决方案
方案一:
使用AndroidId代替,缺点是应用签署密钥或用户(如系统恢复出产设置)不同返回的Id不同。与实际测试结果相符。
经实际测试:相同签名密钥的不同应用androidId相同,不同签名的应用androidId不同。恢复出产设置或升级系统没测。
String androidId = Settings.System.getString(context.getContentResolver(),Settings.Secure.ANDROID_ID);
方案二:
通过硬件信息拼接,缺点是还是不能保证唯一。
经测试:似乎与方案一比更稳定,不受密钥影响,但非官方建议,没安全感。
private static String makeDeviceId(Context context) {String deviceInfo = new StringBuilder().append(Build.BOARD).append("#").append(Build.BRAND).append("#")//CPU_ABI,这个值和appp使用的so库是arm64-v8a还是armeabi-v7a有关,舍弃//.append(Build.CPU_ABI).append("#").append(Build.DEVICE).append("#").append(Build.DISPLAY).append("#").append(Build.HOST).append("#").append(Build.ID).append("#").append(Build.MANUFACTURER).append("#").append(Build.MODEL).append("#").append(Build.PRODUCT).append("#").append(Build.TAGS).append("#").append(Build.TYPE).append("#").append(Build.USER).append("#").toString();try {//22a49a46-b39e-36d1-b75f-a0d0b9c72d6creturn UUID.nameUUIDFromBytes(deviceInfo.getBytes("utf8")).toString();} catch (UnsupportedEncodingException e) {e.printStackTrace();}String androidId = Settings.System.getString(context.getContentResolver(),Settings.Secure.ANDROID_ID);return androidId;
}
参考文献
官方文档
Android Q(10.0 API29)版本新特性和兼容性适配相关推荐
- Android Q(10.0)版本新特性和兼容性适配
北京时间2019年3月14日Google正式对外发布Android Q Beta 1及预览版SDK,这意味着安卓开发者们又即将迎来一年一度的新版本适配工作了.Android Q 为开发者们带来了许多新 ...
- Android Q(10.0)版本新特性以及兼容性适配
北京时间2019年3月14日Google正式对外发布Android Q Beta 1及预览版SDK,这意味着安卓开发者们又即将迎来一年一度的新版本适配工作了.Android Q 为开发者们带来了许多新 ...
- Android Q(10.0)的新功能和API
Android Q功能和API 目录: 安全性增强 改进的生物识别认证对话框 直接从APK运行嵌入式DEX代码 TLS 1.3支持 Public Conscrypt API 连接功能 Wi-Fi网络连 ...
- 深入解读 Knative Eventing 0.7 版本新特性
前言 Knative Eventing 0.7 版本已经于 6 月 26 号正式发布.本次发布主要围绕重构 Channel 特性展开.本篇文章重点解读了这些特性,并且以此展望一下 Knative Ev ...
- 全面剖析 Knative Eventing 0.6 版本新特性
前言 Knative Eventing 0.6 版本已经于5月15号正式发布.相比于0.5版本,此次发布包含了一些重要特性及更新.针对这些新特性以及更新,我们如何快速.精准的定位主要技术点.本篇文章针 ...
- Android Q(10.0)黑暗主题
黑暗的主题 Android Q提供了一个新的Dark主题,适用于Android系统UI和设备上运行的应用程序. 黑暗主题有很多好处: 可以大幅减少用电量(取决于设备的屏幕技术). 提高低视力用户和对强 ...
- android q(10.0)app应用卸载白名单
1.概述 在10.0的系统产品开发中,对于app的卸载管理通常是有系统的PM负责管理的.所以在app应用卸载白名单功能的实现也是从PM在卸载方法中,按照白名单进行卸载 2.app应用卸载白名单的核心类 ...
- 华为android o适配名单,华为给出首批升级名单,这8款华为手机率先适配Android Q 10系统...
原标题:华为给出首批升级名单,这8款华为手机率先适配Android Q 10系统 昨天,在谷歌的I/O大会上,谷歌正式向消费者介绍了Android Q 10系统中的新功能,除了自家的Pixel设备率先 ...
- Spark 3.2.0 版本新特性 push-based shuffle 论文详解(一)概要和介绍
前言 本文隶属于专栏<大数据技术体系>,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和参考文献请见大数据技术体系 目录 Spark 3.2.0 ...
最新文章
- RequestResponseServletContext
- 初中文化能学编程吗_儿童早教益智,乐森星际特工智能编程机器人体验
- C++中include 与 include 的区别
- 没有bug队——加贝——Python 练习实例 15,16
- 安装nrm,报错request@2.88.2: request has been deprecated, see https://github.com/request/request/issu
- Zabbix探索:无法获取Windows主机CPU利用率、负载等问题处理
- MySql实现远程连接
- numpy数组与矩阵运算
- Bootstrap 3之美03-独立行,文字环绕,图片自适应,隐藏元素
- Usage of #pragma
- 媒体查询(黑马笔记)-移动端布局rem
- 难道真的是RPWT-LFS日记1
- 你为什么那么努力还是不开心!
- 朴素贝叶斯算法——拼写检查器
- html5 鼠标双击,鼠标双击或触摸双击事件检测jQuery插件
- 《程序员修炼之道》读书笔记(4):注重实效的偏执(防卫策略)
- Unity 手游面数控制
- UNR2 黎明前的巧克力
- yocto recipe构建流程介绍
- 能量原理与变分法笔记03:证明两点之间直线最短