AndroidQ和R版本新特性全面总结.docx
- 文档编号:8202806
- 上传时间:2023-01-29
- 格式:DOCX
- 页数:39
- 大小:8.23MB
AndroidQ和R版本新特性全面总结.docx
《AndroidQ和R版本新特性全面总结.docx》由会员分享,可在线阅读,更多相关《AndroidQ和R版本新特性全面总结.docx(39页珍藏版)》请在冰豆网上搜索。
AndroidQ和R版本新特性全面总结
AndroidQ、R变更文档总结
文档说明
1、本文档主要对影响比较大的部分以及项目中用到的部分进行简单总结,内容并不全面;
2、本文档基于谷歌Android官方文档
3、版本号对应关系:
Android-R=Android-11=Api30
Android-Q=Android-10=Api29
Android-P=Android-9.0=Api28
Android10用户隐私权限变更
AndroidQ引入了大量更改和限制以增强对用户隐私的保护。
具体变更的权限如下:
一、设备硬件标识符访问限制
为了更好保护用户隐私,谷歌对安卓Q系统中所有获取设备识别码的接口都增加了新的权限控制:
READ_PRIVILEGED_PHONE_STATE,该权限需要系统签名的应用才能申请。
限制应用访问不可重设的设备识别码,如IMEI、序列号等,系统应用不受影响。
1.1原来的做法
在低于AndroidQ的系统上运行没问题
在AndroidQ及以上的系统上运行时:
如targetSdkVersion 如targetSdkVersion>=Q,抛异常: SecurityException: getDeviceId: Theuser10196doesnotmeettherequirementstoaccessdeviceidentifiers. 受影响的方法 ∙Build ∙getSerial() ∙TelephonyManager ∙getImei() ∙getDeviceId() getMeid()(备注: IMEI是联通移动手机的标识,MEID是电信手机的标识) ∙getSimSerialNumber() ∙getSubscriberId() 1.2替代方案 方案一: 使用AndroidId代替 缺点: 应用签署密钥或用户(如系统恢复出产设置)不同返回的Id不同。 与实际测试结果相符。 同时root手机用户,androidid可以改变 方案二: 通过硬件信息拼接 缺点是还是不能保证唯一。 经测试: 似乎与方案一比更稳定,不受密钥影响,但非官方建议,没安全感。 方案三: Google推荐使用GoogleAdvertisingID 通过GoogleService可以获取GoogleAdvertisingID(如果没有GoogleService就会取不到GoogleAdvertisingID,此时可以结合方案二用硬件信息拼出来一个ID)。 注: 目前此方法项目在用 方案四: 接入设备厂商提供的ID 此方法比较麻烦 以下是某手机厂商开放平台的通知,可以参见官方SDK文档 1.3了解与扩展 4.0到8.0如何正确获取IMEI及MEID? 在手机4.0以下,我们可以使用 那么在5.0以上难道getDeviceId方法就不行了么? 不是不行,是返回的结果或许不是我们想象的一样。 因为5.0系统以上,安卓的碎片化更加严重,有单卡槽的,有双卡槽的,有全网通的,有卡槽一个是uim一个是sim的,有双sim的。 5.0的系统如果想获取MEID/IMEI1/IMEI2----framework层提供了两个属性值“ril.cdma.meid”和“ril.gsm.imei”获取 而在6.0系统以后,安卓官方已经支持双卡的api了。 获取更加简单,此时如果IMEI不存在直接返回null。 所以直接去拿MEID。 工具方法一般这样封装: 1.4总结 从AndroidQ开始,应用必须具有READ_PRIVILEGED_PHONE_STATE签名权限才能访问设备的不可重置标识符(包含IMEI和序列号) 原来的READ_PHONE_STATE权限已经不能获得IMEI和序列号,如果想在Q设备上通过 ((TelephonyManager)getActivity().getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId() 获得设备ID,会返回空值(targetSDK<=P)或者报错(targetSDK==Q)。 且官方所说的READ_PRIVILEGED_PHONE_STATE权限只提供给系统app,所以这个方法算是废了。 Deviceid/IMEI的获取用谷歌id和设备拼出来的id结合使用来代替 其他相关的sim卡信息都是通过SubscriptionManager来获取,此类之前就有,没什么变化。 目前在我们项目中: imsi通过iccid来代替,而iccid的获取也是通过SubscriptionManager类先取到SubscriptionInfo来得到。 下面是获取sim卡信息的步骤: 步骤一: SubscriptionManagersubscriptionManager=(SubscriptionManager)context .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); 步骤二: finalList subscriptionManager.getActiveSubscriptionInfoList(); 看下面源码此方法需要申请READ_PHONE_STATE权限 拿到SubscriptionInfo对象里面就包含所有sim卡信息,下面是源码中定义 SubscriptionManager还提供了一些其他方法,例如: 获取默认数据的subid subId=SubscriptionManager.getDefaultDataSubscriptionId(); 注: 项目中具体方法如何获取参考SimInfoUtil类 项目中获取iccid遇到的问题: 前提条件: targetSdkVersion29 1、运行在androidQ的设备上不是系统默认应用也可以获取到iccid 2、运行在androidR的设备上是系统默认应用才可以获取到iccid 采用方案: 适配一下androidR 二、分区存储 1、概念 分区存储新特性对外部存储进行了重新设计,外部存储被分为应用私有目录以及共享目录两个部分 (1)应用私有目录: 存储应用私有数据,外部存储应用私有目录对应/storage/emulated/0/Android/data/ ,同样拥有files文件夹和cache文件夹,使用如下代码可以获取相应路径: files目录 getExternalFilesDir()是需要一个字符串参数的, 如果我们传入空字符串,则获取到: /storage/emulated/0/Android/data/ 如果我们传入“test”则获取到: /storage/emulated/0/Android/data/ 官方建议我们不要随意命名,应该使用Environment下定义好的名字: 内部存储应用私有目录对应data/data/packagename; cache目录 (2)共享目录: 存储其他应用可访问文件,包含媒体文件、文档文件以及其他文件,对应设备DCIM、Pictures、Alarms,Music,Notifications,Podcasts,Ringtones、Movies、Download等目录 1.1私有目录文件访问 应用私有目录文件访问方式与之前Android版本一致,可以通过filepath获取资源 1.2共享目录文件访问 共享目录文件需要通过MediaStore API或者StorageAccessFramework方式访问 (1)MediaStore API在共享目录指定目录下创建文件或者访问应用自己创建文件,不需要申请存储权限 (2)MediaStoreAPI访问其他应用在共享目录创建的媒体文件(图片、音频、视频),需要申请存储权限,未申请存储权限,通过ContentResolver查询不到文件Uri,即使通过其他方式获取到文件Uri,读取或创建文件会抛出异常; (3)MediaStoreAPI不能够访问其他应用创建的非媒体文件(pdf、office、doc、txt等),只能够通过StorageAccessFramework方式访问; 1.3其他受影响变更 (1) 图片位置信息一些图片会包含位置信息,因为位置对于用户属于敏感信息,Android10应用在分区存储模式下图片位置信息默认获取不到,应用通过以下两项设置可以获取图片位置信息: 在manifest中申请ACCESS_MEDIA_LOCATION 调用MediaStore.setRequireOriginal(Uriuri)接口更新图片Uri (2) MediaStore.Files应用分区存储模式下,MediaStore.Files 集合只能够获取媒体文件信息(图片、音频、视频), 获取不到非media(pdf、office、doc、txt等)文件 (3)FilePath路径访问受影响接口 开启分区存储新特性,Andrioid10不能够通过FilePath路径直接访问共享目录下资源,以下接口通过File路径操作文件资源,功能会受到影响,应用需要使用MediaStore或者SAF方式访问 2、存储特性版本差异 3、兼容模式 3.1兼容模式设置 应用未完成外部存储适配工作,可以临时以兼容模式运行,兼容模式下应用申请存储权限,即可拥有外部存储完整目录访问权限,通过Android10之前文件访问方式运行,以下两种方法设置应用以兼容模式运行 (1)Target小于等于Android9(APIlevel28) (2)Tagret大于等于Android10(APIlevel29),在manifest中设置requestLegacyExternalStorage属性为true 3.2判断兼容模式接口 Environment.isExternalStorageLegacy() 返回值 true: 应用以兼容模式运行 false: 应用以分区存储特性运行 备注: 应用已完成存储适配工作且已打开分区存储开关,如果当前应用以兼容模式运行,覆盖安装后应用仍然会以兼容模式运行,卸载重新安装应用才会以分区存储模式运行 4、分区适配方案 分区存储适配包含文件迁移以及文件访问兼容性适配两个部分; 4.1文件迁移 文件迁移是将应用共享目录文件迁移到应用私有目录或者Android10要求的media集合目录 (1)针对只有应用自己访问并且应用卸载后允许删除的文件,需要迁移文件到应用私有目录文件,可以通过Filepath方式访问文件资源,降低适配成本 (2)允许其他应用访问,并且应用卸载后不允许删除的文件,文件需要存储在共享目录,应用可以选择是否进行目录整改,将文件迁移到Android10要求的media集合目录 4.2文件访问兼容性适配 共享目录文件不能够通过Filepath方式读取,需要使用MediaStoreAPI或者StorageAccessFramework框架进行访问 5、API相关 5.1MediaStoreAPI简述 系统会自动扫描外部存储,添加文件到系统已定义的Images、Videos、Audiofiles、Downloadedfiles集合中,Android10通过MediaStore.Images、MediaStore.Video、MediaStore.Audio、MediaStore.Downloads访问共享目录文件资源 访问文件方式 若要加载媒体文件,请从 ContentResolver 调用以下方法之一: ∙对于单个媒体文件,请使用 openFileDescriptor()。 ∙对于单个媒体文件的缩略图,请使用 loadThumbnail(),并传入要加载的缩略图的大小。 ∙对于媒体文件的集合,请使用 query()。 以下代码段展示了如何访问媒体文件: 5.2MediaStoreAPI创建文件 Android10版本MeidaStoreAPI只允许在共享目录指定目录创建文件,非指定目录创建文件会抛出IllegalArgumentException,创建文件目录汇总如下: 备注: MediaStore.Downloads.EXTERNAL_CONTENT_URI是Android10版本新增API,用于创建、访问非媒体文件; 5.3不同存储权限MediaStoreAPI可访问文件区域 5.4MediaStoreAPI文件访问 5.5StorageAccessFramework Android4.4引入了StorageAccessFramework框架,应用通过系统选择器访问DocumentsProvider提供文件(包含外部存储以及云端存储,外部存储包含应用私有目录以及共享目录),SAF机制不需要申请任何存储权限,包含Documentprovider、Clientapp、Picker三部分 (1)Documentprovider: 文档提供者是DocumentsProvider子类,数据模型是基于文件层级进行设计的,文档提供者通过存储服务(例如GoogleDrive)管理文件 (2)Clientapp: 通过调用ACTION_CREATE_DOCUMENT,ACTION_OPEN_DOCUMENT,andACTION_OPEN_DOCUMENT_TREEIntent获取Documentprovider提供的文件,应用可以设置MIMEtype或者EXTRA_INITIAL_URI选择需要获取的文件,onActivityResult接口会返回选择文件Uri (3)Picker: 系统UI,应用通过调起系统选择器获取Documentprovider提供的文件信息 5.6分享场景适配 (1)APP主动分享文件给其他应用,可以使用FileProvider方式赋予其他应用文件读取权限,FileProvider应用基于XML配置生成文件Uri,其他应用不需要申请存储权限就可以通过接收Uri获取文件资源; (2)Android10应用开启分区存储,通过File协议Uri或者MediaStoreUri分享文件给其他应用,功能会受到影响,具体如下表格: 5.7业务适配方案参考 排查外部存储共享目录通过File方式访问的资源,针对历史遗留文件,业务方根据具体场景选择是否进行文件迁移; 针对只有应用自己访问并且应用卸载后允许删除的文件,通过文件迁移到外部存储私有目录方式进行适配; 针对允许其他应用访问,并且应用卸载后不允许删除的文件,通过使用MediaStoreAPI或者SAF方式进行适配 5.8MediaStore实际操作案例 1、使用MediaStore将图片保存到Pictures目录 在Environment中我们能找到很多公有目录文件夹的名字,其中Pictures这个文件夹就适合用来保存图片数据: 在以前,我们经常会根据目录或文件的绝对路径得到File对象,再将File对象传给FileOutputStream得到输出流,然后就可以愉快地写入数据了。 Android10以后,我们要向这些公有目录写入数据,必须要用MediaStore了。 下面,我们通过代码学习如何将Bitmap保存到Pictures文件夹下: 我们创建了ContentValues对象,并往里面添加了三种信息: 1、DISPLAY_NAME: 图片的名字,需要包含后缀名。 在这里,我们使用的是当前的时间戳命名 2、MIME_TYPE: 文件的mime类型。 在这里,我们使用的是image/jpeg 3、RELATIVE_PATH、DATA: 文件的存储路径。 在Android10中,新增了RELATIVE_PATH,它表示文件存储的相对路径,可选值其实就是Environment里面那堆,比如Pictures、Music等。 但是注意看我们的getAppPicturePath()中的代码: "${Environment.DIRECTORY_PICTURES}/$APP_FOLDER_NAME/",后面还跟了一个$APP_FOLDER_NAME,表示在Pictures这个目录下面,还要创建一个名叫: ExternalScopeTestApp的文件夹,这是因为如果所有应用都将图片保存到Pictures的根目录,势必会非常混乱,因此我们针对自己的应用建立了二级文件夹,将图片都保存到自己的二级文件夹中。 DATA这个字段是Android10以前使用的字段,在Android10中已经废弃,但为了兼容老版本系统,我们还是要用。 这个字段需要文件的绝对路径。 ContentValues里面的值都设置完成后,我们就可以使用ContentResolver的insert()方法插入数据了,插入完成后会得到插入图片的Uri,接下来我们要根据这个Uri得到OutputStream对象,通过: contentResolver.openOutputStream(uri)就可得到,剩下的就是将Bitmap写入了。 我们发现,以前我们可以通过真实路径得到输出流,而现在只能通过Uri得到了。 如果我们不是将Bitmap保存到公有目录,而是网络上的图片呢? 其实原理都是一样的,网络上的图片我们肯定是可以得到输入流的,输出流还是通过Uri获取,然后读取输入流写入输出流不就行了吗? 到此,保存图片就学习完了,保存音频、视频都类似。 注意,这些操作都是不需要权限的。 2、使用MediaStore获取媒体库中的图片 我们向Pictures中添加了图片文件,怎么才能获取到呢? 也必须通过MediaStore。 如果我们没有获得存储空间权限,那么我们只能通过MediaStore获取到自己应用创建的图片;如果我们获取了存储空间权限,那么我们就可以获取到其它应用创建的图片了。 我们通过代码来学习: 代码很简单,最终通过Uri与图片文件id的组合,得到了图片文件的Uri。 得到了这个图片的Uri后,怎么显示出来呢? 可以使用Glide,因为Glide原生支持Uri: Glide.with(this).load(uri).into(ivPicture) 如果没有使用Glide呢? 可以这样来做,得到一个Bitmap: 3、使用MediaStore删除媒体库中的图片 同样的,删除自己创建的图片不需要任何权限,但是删除或者修改其它应用创建的图片就需要权限了,而且即使我们拥有了存储权限,也不能修改或删除其它APP的资源,需要由MediaProvider弹出弹框给用户选择是否允许APP修改或删除图片、视频、音频文件。 用户操作的结果,将通过onActivityResult回调返回到APP。 如果用户允许,APP将获得该uri的修改权限,直到设备下一次重启。 我们先来学习删除自己应用的图片: 很简单,图片从ContentResolver中查询出来的时候,我们可以获取到id,图片的uri就是通过MediaStore.Images.Media.EXTERNAL_CONTENT_URI与图片的id组合而来。 现在只需要通过uri进行删除即可。 我们来看下在没有存储权限时,删除应用创建图片的效果,当然如果有存储权限,也是一样的 但是这段代码是不够严谨的,因为当我们删除的是其它应用的资源,程序会闪退,并抛出: RecoverableSecurityException异常。 因此我们需要捕获这个异常,提示用户给予此uri修改或删除的权限: 简单解释下,当修改或删除非本应用创建的文件uri时,在Android10+的系统中,会抛出RecoverableSecurityException,我们捕获到这个异常后,从异常中获得了IntentSender,并使用它来向用户索取该uri的修改、删除权限。 代码都很简单,不再赘述,效果如下: 4、我想读取Download文件夹下的某个非媒体文件怎么办? 拿PDF举例,显然,PDF不属于音频、视频、图片,因此我们不能使用MediaStore来获取。 对于这种其它类型的文件,我们一般使用SAF(存储访问框架)让用户选择: 注意,ACTION_OPEN_DOCUMENT用于打开文件。 5、想创建任意类型文件怎么办? 也是使用SAF(存储访问框架)让用户去创建。 其IntentAction为: ACTION_CREATE_DOCUMENT,我们可以使用Intent的putExtra()来指定文件的名字: intent.putExtra(Intent.EXTRA_TITLE,"Android.pdf"),有点类似于"另存为"功能。 其它的用法差不多,就不多说了。 6、想将文件下载到Download目录怎么办? 拿下载app为例,在Android10以前,只要获取到了File对象,就能得到输入流,而由于我们适配了Android10中的分区存储,因此不能这样做了。 MediaStore中提供了一种Downloads集合,专门用于执行文件下载操作。 它的使用和添加图片是几乎一样的: 由于我们获取到的这个uri本来就是content: //开头,所以不需要使用FileProvider。 对于应用私有目录的文件,我们可以使用FileProvider进行分享。 6、总结 6.1情况描述 AndroidQ文件存储机制修改成了沙盒模式 APP只能访问自己目录下的文件和公共媒体文件 对于AndroidQ以下,还是使用老的文件存储方式 在AndroidQ上运行: targetSdkVersion targetSdkVersion>=Q,默认启用分区存储,应用以外的文件需要通过存储访问框架(SAF,StorageAccessFramework)读写。 6.2替代方案 方法一、停用分区存储,使用旧版存储模式 方法二、将文件存储到分区存储中,官方推荐 优点: 不用申请读写权限; 缺点: 随应用卸载而删除; 方法三、使用存储访问框架(SAF),由用户指定要读写的文件。 这个功能Android4.4(API: 19)就有。 方法四、获取用户指定的某个目录的读写权限 从Android5.0(Api21)开始就有。 步骤 1、申请目录的访问权限 会打开系统的文件目录,由用户自己选择允许访问的目录,不用申请WRITE/READ_EXTERNAL_STORAGE权限。 弹出界面点击“允许访问”之后通过onActivityResult()的intent.getData()得到该目录的Uri,通过Uri可获取子目录和文件。 这种方式的缺点是应用重装后权限失效,即使可以保存了这个Uri也没用。 2、通过Uri读写文件 创建文件 主要用到DocumentFile类,和File类的方法类似,有isFile、isDirectory、exists、listFiles等方法 删除文件 写入文件 6.3重新总结 简而言之,在Android10中, 对于私有目录的读写没有变化,仍然可以使
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- AndroidQ 版本 特性 全面 总结
![提示](https://static.bdocx.com/images/bang_tan.gif)