背景
目前携程旅行线上最新版本已适配到 Android 10(API =29),由于从 API=26 升级到 API=29,跨度较大,我们提前对干系适配进行了调研,希望个中一些履历能对其他开拓者有一定的帮助。
在 Android 10 版本中,官方的改动较大,相应的开拓者适配本钱还是很高的。基于前期调研,我们紧张基于以下几方面进行 Android 10 的适配:
AndroidX 对原始 Android Support 库进行了重大改进,后者现在已不再掩护。AndroidX 软件包完备取代了支持库,不仅供应同等的功能,而且供应了新的库。

Android 系统在刚刚面世的时候,可能连它的设计者也没有想到它会如此成功。随着 Android 系统版本不断地迭代更新,每个版本中都会加入很多新的 API 进去,但是新增的 API 在老版系统中并不存在,因此这就涌现了一个向下兼容的问题。
于是 Android 团队推出了一个鼎鼎大名的 Android Support Library,用于供应向下兼容的功能。比如我们熟知的 support-v4 库,appcompat-v7 库都是属于 Android Support Library 的。4 在这里指的是 Android API 版本号,对应的系统版本是 1.6。support-v4 的意思便是这个库中供应的 API 会向下兼容到 Android 1.6 系统。类似地,appcompat-v7 指的是将库中供应的 API 向下兼容至 API 7,也便是 Android 2.1 系统。
随着韶光的推移,Android1.6、2.1 系统早已被淘汰了,现在 Android 官方支持的最低系统版本已经是 4.0.1,对应的 API 版本号是 15。support-v4、appcompat-v7 库也不再支持那么久远的系统了,但是它们的名字却一贯保留了下来,虽然它们现在的实际浸染已经对不上当初命名的缘故原由了。
Android 团队也意识到这种命名已经非常不得当了,于是对这些 API 的架构进行了一次重新的划分,推出了 AndroidX。因此,AndroidX 实质上实在便是对 Android Support Library 进行的一次升级。
1.2 为什么要升级 AndroidX版本 28.0.0 是 Android Support 库的末了一个版本。官方将不再发布 android.support 库版本。所有新功能都将在 AndroidX 命名空间中开拓。长远来看。AndroidX 重新设计了包构造,旨在鼓励库的小型化,支持库和架构组件包的名字进行了简化。而且这也是减轻 Android 生态系统碎片化的有效办法。与 Android Support 库不同,AndroidX 软件包是单独掩护和更新的。这些 AndroidX 包利用严格的语义版本掌握,从版本 1.0.0 开始,您可以单独更新项目中的 AndroidX 库。1.3 适配步骤1.3.1 环境准备AndroidStudio 3.2.0+gradle:gradle-4.6+其余修正干系 app、library 模块中 build.gradle 的 compileSdkVersion、targetSdkVersion、buildToolsVersion 的配置,都设置为 29,示例如下:
复制代码
android { compileSdkVersion 29 buildToolsVersion 29.0.2 defaultConfig { targetSdkVersion 29 } ...}
1.3.2 修合法前项目的 gradle.properties
复制代码
android.useAndroidX=trueandroid.enableJetifier=true
个中:
android.useAndroidX=true 表示当前项目启用 AndroidX;android.enableJetifier=true 表示将依赖包也迁移到 AndroidX 。如果取值为 false , 表示不迁移依赖包到 AndroidX,但在利用依赖包中的内容时可能会涌现问题,如果你的项目中没有利用任何三方依赖,此项可以设置为 false。1.3.3 修正项目中的 build.gradle 依赖库复制代码
implementation 'com.android.support:appcompat-v7:28.0.0'→ implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'com.android.support:design:28.0.0'→implementation 'com.google.android.material:material:1.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3'→ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
映射关系:
https://developer.android.com/jetpack/androidx/migrate/artifact-mappings
1.3.4 修正支持库类将原来 import 的 android.包删除,重新 import 新的 androidx.包;
import android.support.v7.app.AppCompatActivity; →import androidx.appcompat.app.AppCompatActivity;
1.3.5 迁移官方迁移指南:
https://developer.android.com/jetpack/androidx/migrate#migrate
在 AndroidStudio 3.2 或更高版本(截图中 AndroidStudio 为 3.5 版本)中实行如下操作:菜单 >Refactor > Migrate to AndroidX(如果迁移失落败,就须要重复上面 1,2,3,4 步手动去修正迁移)
把稳:利用 AS 迁移工具并不能完备修正完毕,须要手动修正support 包名涉及到资源修正,牢记检讨资源中的类路径二、分区存储2.1 背景先容
为了更好的保护用户数据并限定设备冗余文件增加,以 Android 10(API 级别 29)及更高版本为目标平台的运用在默认情形下被授予了对外部存储设备的分区访问权限(即分区存储), 对外部存储文件访问办法重新设计,便于用户更好的管理外部存储文件。
运用只能看到本运用专有的目录(通过 Context.getExternalFilesDir() 访问)以及特定类型的媒体。除非您的运用须要访问存放在运用的专有目录以及 MediaStore 之外的文件,否则最好利用分区存储。
要点:
Android Q 文件存储机制修正成了沙盒模式APP 只能访问自己目录下的文件和公共媒体文件Android Q 版本以下机型,还是利用老的文件存储办法Android Q 及以上版本机型,所有运用均须要分区存储, 以是运用须要提前确保支持分区存储须要把稳:在适配 AndroidQ 的时候还要兼容 Q 系统版本以下的,利用 SDK_VERSION 区分
2.2 新特性概览2.2.1 外部存储外部存储被分为运用私有目录以及共享目录两个部分:
运用私有目录:存储运用私有数据,外部存储运用私有目录对应 Android/data/packagename,内部存储运用私有目录对应 data/data/packagename;共享目录:存储其他运用可访问文件, 包含媒体文件、文档文件以及其他文件,对应设备 DCIM、Pictures、Alarms, Music, Notifications,Podcasts, Ringtones、Movies、Download 等目录1)私有目录
运用私有目录文件访问办法与之前 Android 版本同等,可以通过 File path 获取资源。
2)共享目录
共享目录文件须要通过 MediaStore API 或者 Storage Access Framework 办法访问。
MediaStore API 在共享目录指定目录下创建文件或者访问运用自己创建文件,不须要申请存储权限MediaStore API 访问其他运用在共享目录创建的媒体文件 (图片、音频、视频), 须要申请存储权限,未申请存储权限,通过 ContentResolver 查询不到文件 Uri,纵然通过其他办法获取到文件 Uri,读取或创建文件会抛出非常;MediaStore API 不能够访问其他运用创建的非媒体文件 (pdf、office、doc、txt 等), 只能够通过 Storage Access Framework 办法访问;2.3 受影响的变更2.3.1 图片位置信息一些图片会包含位置信息,由于位置对付用户属于敏感信息, Android 10 运用在分区存储模式下图片位置信息默认获取不到,运用通过以下两项设置可以获取图片位置信息:
在 manifest 中申请 ACCESS_MEDIA_LOCATION调用 MediaStore setRequireOriginal(Uri uri) 接口更新图片 Uri2.3.2 访问数据MediaStore.Files 运用分区存储模式下,MediaStore.Files 凑集只能够获取媒体文件信息 (图片、音频、视频), 获取不到非 media(pdf、office、doc、txt 等) 文件。
2.3.3 File Path 路径访问受影响接口开启分区存储新特性, Andrioid 10 不能够通过 File Path 路径直接访问共享目录下资源,以下接口通过 File 路径操作文件资源,功能会受到影响,运用须要利用 MediaStore 或者 SAF 办法访问。
类名称受影响的接口FilecreateNewFile()delete()renameTo(File dest)mkdir()mkdirs()FileInputStreamFileInputStream(File file)FileInputStream(String name)FileOutputStreamFileOutputStream(String name)FileOutputStream(String name, boolean append)FileOutputStream(File file)FileOutputStream(File file, boolean append)BitmapFactorydecodeFile(String pathName)decodeFile(String pathName, Options opts)
2.3.4 存储特性 Android 版本差异概览存储位置路径版本存储权限内部存储data/data/packagename所有否getFilesDir()、getCacheDir()外部存储私有目录Android/data/packagename4.4 以上getExternalFilesDir()、getExternalCacheDir()、SAF共享目录DCIM、Pictures、Alarms, Music, Notifications,Podcasts, Ringtones、Movies、Download<10是Environment.getExternalStorageDirectory()否SAF>=10是访问其他运用 media 文件 -->MediaStore API访问其他运用创建的非 media 文件 --> SAF否访问自己运用创建的文件 -->MediaStore APISAF
2.4 兼容模式运用未完成外部存储适配事情,可以临时以兼容模式运行, 兼容模式下运用申请存储权限,即可拥有外部存储完全目录访问权限,通过 Android10 之前文件访问办法运行,以下两种方法设置运用以兼容模式运行。
2.4.1 AndroidManifest 中申明tagretSDK 大于即是 Android 10(API level 29), 在 manifest 中设置 requestLegacyExternalStorage 属性为 true。
复制代码
<manifest ...>...<application android:requestLegacyExternalStorage="true" ... >...</manifest>
2.4.2、判断兼容模式接口
复制代码
// 返回值//true : 运用以兼容模式运行//false:运用以分区存储特性运行Environment.isExternalStorageLegacy();
备注:运用已完成存储适配事情且已打开分区存储开关,如果当前运用以兼容模式运行,覆盖安装后运用仍旧会以兼容模式运行,卸载重新安装运用才会以分区存储模式运行
2.5 适配方案2.5.1 方案概览分区存储适配包含文件迁移以及文件访问兼容性适配两个部分:
1)文件迁移
文件迁移是将运用共享目录文件迁移到运用私有目录或者 Android10 哀求的 media 凑集目录。
针对只有运用自己访问并且运用卸载后许可删除的文件,须要迁移文件到运用私有目录文件,可以通过 File path 办法访问文件资源,降落适配本钱。许可其他运用访问,并且运用卸载后不许可删除的文件,文件须要存储在共享目录,运用可以选择是否进行目录整改,将文件迁移到 Android10 哀求的 media 凑集目录。2)文件访问兼容性
共享目录文件不能够通过 File path 办法读取,须要利用 MediaStore API 或者 Storage Access Framework 框架进行访问。
2.5.2 适配辅导AndroidQ 中利用 ContentResolver 进行文件的增编削查。
1)获取 (创建) 私有目录下的文件夹
复制代码
// 在自身目录下创建 apk 文件夹File apkFile = context.getExternalFilesDir("apk");
2)创建私有目录文件
天生须要下载的路径,通过输入输出流读取写入
复制代码
String apkFilePath = context.getExternalFilesDir("apk").getAbsolutePath();File newFile = new File(apkFilePath + File.separator + "demo.apk");OutputStream os = null;try { os = new FileOutputStream(newFile); if (os != null) { os.write("file is created".getBytes(StandardCharsets.UTF_8)); os.flush(); }} catch (IOException e) {} finally { try { if (os != null) { os.close(); }catch (IOException e1) { }}
3)创建共享目录文件夹
复制代码
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { ContentResolver resolver = context.getContentResolver(); ContentValues values = new ContentValues(); values.put(MediaStore.Downloads.DISPLAY_NAME, fileName); values.put(MediaStore.Downloads.DESCRIPTION, fileName); // 设置文件类型 values.put(MediaStore.Downloads.MIME_TYPE, "application/vnd.android.package-archive"); // 把稳 MediaStore.Downloads.RELATIVE_PATH 须要 targetVersion=29, // 故该方法只可在 Android10 的手机上实行 values.put(MediaStore.Downloads.RELATIVE_PATH, "Download" + File.separator + "apk"); Uri external = MediaStore.Downloads.EXTERNAL_CONTENT_URI; Uri insertUri = resolver.insert(external, values); return insertUri;}else{ ...}
4)在共享目录指定文件夹下创建文件
紧张是在公共目录下创建文件或文件夹拿到本地路径 uri,不同的 Uri,可以保存到不同的公共目录中。接下来利用输入输出流就可以写入文件。
重点:AndroidQ 中不支持 file:// 类型访问文件,只能通过 uri 办法访问。
复制代码
/ 创建图片地址 uri, 用于保存拍照后的照片 Android 10 往后利用这种方法 /private Uri createImageUri() { String status = Environment.getExternalStorageState(); // 判断是否有 SD 卡, 优先利用 SD 卡存储, 当没有 SD 卡时利用手机存储 if (status.equals(Environment.MEDIA_MOUNTED)) { return getContext().getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new ContentValues()); } else { return getContext().getContentResolver().insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, new ContentValues()); }}
5)通过 MediaStore API 读取公共目录下的文件
复制代码
if (cursor != null && cursor.moveToFirst()) { do { ... int _id = cursor.getInt(cursor.getColumnIndex(MediaStore.Images.Media._ID)); Uri imageUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, _id); ... } while (!cursor.isLast() && cursor.moveToNext());} else {...}
复制代码
// 通过 uri 获取 bitmappublic Bitmap getBitmapFromUri(Context context, Uri uri) { ParcelFileDescriptor parcelFileDescriptor = null; FileDescriptor fileDescriptor = null; Bitmap bitmap = null; try { parcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri, "r"); if (parcelFileDescriptor != null && parcelFileDescriptor.getFileDescriptor() != null) { fileDescriptor = parcelFileDescriptor.getFileDescriptor(); // 转换 uri 为 bitmap 类型 bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor); } } catch (Exception e) { e.printStackTrace(); }finally { try { if (parcelFileDescriptor != null) { parcelFileDescriptor.close(); }catch (IOException e) { } } return bitmap;}
6)利用 MediaStore 删除文件
复制代码
context.getContentResolver().delete(fileUri, null, null);
三、设备 ID
从 Android 10 开始已经无法完备标识一个设备,曾经用 mac 地址、IMEI 等设备信息标识设备的方法,从 Android 10 开始统统失落效。而且无论你的 APP 是否适配过 Android 10。
3.1 IMEI 等设备信息从 Android10 开始普通运用不再许可要求权限 android.permission.READ_PHONE_STATE。而且,无论你的 App 是否适配过 Android Q(既 targetSdkVersion 是否大于即是 29),均无法再获取到设备 IMEI 等设备信息。
受影响的 API:
复制代码
Build.getSerial();TelephonyManager.getImei();TelephonyManager.getMeid()TelephonyManager.getDeviceId();TelephonyManager.getSubscriberId();TelephonyManager.getSimSerialNumber();
targetSdkVersion<29 的运用,其在获取设备 ID 时,会直接返回 nulltargetSdkVersion>=29 的运用,其在获取设备 ID 时,会直接抛出非常 SecurityException
如果您的 App 希望在 Android 10 以下的设备中仍旧获取设备 IMEI 等信息,可按以下办法进行适配:
复制代码
<uses-permission android:name="android.permission.READ_PHONE_STATE" android:maxSdkVersion="28"/>
3.2 Mac 地址随机分配
从 Android10 开始,默认情形下,在搭载 Android 10 或更高版本的设备上,系统会传输随机分配的 MAC 地址。(即从 Android 10 开始,普通运用已经无法获取设备的真正 mac 地址,标识设备已经无法利用 mac 地址)
3.3 如何标识设备唯一性3.3.1 Google 办理方案:如果您的运用有追踪非登任命户的需求,可用 ANDROID_ID 来标识设备。
ANDROID_ID 生成规则:署名 + 设备信息 + 设备用户ANDROID_ID 重置规则:设备规复出厂设置时,ANDROID_ID 将被重置复制代码
String androidId = Settings.Secure.getString(this.getContentResolver(), Settings.Secure.ANDROID_ID);
3.3.2 信通院统一 SDK(OAID)
统一标识依据电信终端家当协会 (TAF)、移动安全同盟 (MSA) 联合推 出的团体标准《移动智能终端补充设备标识规范》开拓,移动智能终端补充设备标识体系统一调用 SDK 集成设备厂商供应的接口,并得到主流设备厂商的授权。
移动安全同盟 (MSA) 组织中国信息通信研究院 (以下简称“中国信通院”) 与终端生产企业、互联网企业共同研究制订了“移动智能终端补充设备标识体系”,定义了移动智能终端补充设备标识体系的体系架构、功能哀求、接口哀求以及安全哀求,使设备生产企业统一开拓接口,为移动运用开拓者供应统一调用办法,方便移动运用接入,降落掩护本钱。
1)SDK 获取
MSA 统一 SDK 下载地址:
移动安全同盟官网, http://www.msa-alliance.cn/
2)接入办法
解压 miit_mdid_sdk_v1.0.13.rar,把 miit_mdid_1.0.13.aar 拷贝到项目中,并设置依赖。将 supplierconfig.json 拷贝到项目 assets 目录下,并修正里边对应 内容,特殊是须要设置 appid 的部分。须要设置 appid 的部分须要去对应的厂 商的运用商店里注书籍身的 app。复制代码
{ "supplier":{ "xiaomi":{ "appid":"" }, "huawei":{ "appid":"" } ... }}
在初始化方法中调用 JLibrary.InitEntry
复制代码
try { JLibrary.InitEntry(FoundationContextHolder.getContext());} catch (Throwable e) {}
实例化 MSA SDK
复制代码
public static void initMSASDK(Context context){ int code = 0; try { code = MdidSdkHelper.InitSdk(context,true,listener); if (code == ErrorCode.INIT_ERROR_MANUFACTURER_NOSUPPORT){//1008611, 不支持的厂商 }else if (code == ErrorCode.INIT_ERROR_DEVICE_NOSUPPORT){//1008612, 不支持的设备 }else if (code == ErrorCode.INIT_ERROR_LOAD_CONFIGFILE){//1008613, 加载配置文件失落败 }else if (code == ErrorCode.INIT_ERROR_RESULT_DELAY){//1008614, 信息将会延迟返回,获取数据可能在异步线程,取决于设备 }else if (code == ErrorCode.INIT_HELPER_CALL_ERROR){//1008615, 反射调用失落败 } //code 可记录非常供剖析 }catch (Throwable throwable){ }} static IIdentifierListener listener = new IIdentifierListener() { @Override public void OnSupport(boolean support, IdSupplier idSupplier) { try{ isSupport = support; if (null != idSupplier && isSupport){ // 是否支持补充设备标识符获取 oaid = idSupplier.getOAID(); aaid = idSupplier.getAAID(); vaid = idSupplier.getVAID(); }else { ... } }catch (Exception e){ } }};
通过以上方法获取到 OAID 等设备标识之后,即可作为唯一标识利用。四、明文 HTTP 限定
当 SDK 版今年夜于 API 28 时,默认限定了 HTTP 要求,并涌现干系日志“ java.net .UnknownServiceException: CLEARTEXT communication to xxx not permitted by network security policy“。
该问题有两种办理方案:
1)在 AndroidManifest.xml 中 Application 节点添加如下代码
复制代码
<application android:usesCleartextTraffic="true">
2)在 res 目录新建 xml 目录,已建的跳过 在 xml 目录新建一个 xml 文件 network_security_config.xml,然后在 AndroidManifest.xml 中 Application 添加如下节点代码。
复制代码
android:networkSecurityConfig="@xml/network_config"
network_config.xml(命名随机)
复制代码
<?xml version="1.0" encoding="utf-8"?><network-security-config> <base-config cleartextTrafficPermitted="true" /></network-security-config>
五、展望
2020 年 2 月 20 号,Google 提前发布了 Android 11 预览版,通过 5G、折叠屏、内置机器学习等新技能,照亮了移动设备的未来。Android 11 依然致力于让用户畅享最新科技,并始终确保将安全和隐私放在首位,帮助用户管理敏感数据和文件的访问权限。此外还对平台的关键区域做出了强化,以保持操作系统的弹性和安全性。
对付像 Android 这样的开放性 OS 来说,霸占的市场份额越大,全体 Android 生态系统的发展会越好。随着 Android 对付碎片化的整理、用户隐私和安全性的重视、5G 和机器学习等新技能的引入,已逐步捉住快速增长的中产阶级用户,未来的市场份额增长量将是不可预估的。
参考文档:1、AndroidX 概览
https://developer.android.google.cn/jetpack/androidx
2、Android 10 先容
https://developer.android.com/about/versions/10
3、Android 11 预览版先容
https://developer.android.com/preview
4、Android Q Adaptation Guide
https://chinesefoodstudio.com/index.php/2019/11/21/android-q-adaptation-guide/
5、Android 10 分区存储先容及百度 APP 适配实践
https://segmentfault.com/a/1190000021760036
作者先容:
曙光,携程资深软件工程师,卖力市场营销干系研发及管理事情。
本文转载自"大众年夜众号携程技能(ID:ctriptech)。
原文链接:
https://mp.weixin.qq.com/s?__biz=MjM5MDI3MjA5MQ==&mid=2697269503&idx=2&sn=f5505724dcee64ebd9904ee16a2bfedb&chksm=8376efcbb40166ddf0f301003b0c05b89f110957fa0872c8ba741cb49b61c404ce849c769978&scene=27#wechat_redirect