Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说携程 Android 10 适配踩坑指南,希望能够帮助你!!!。
2019 年 9 月 3 日,Google 发布了 Android 10 正式版。Android 10 聚焦移动创新、安全隐私和数字健康三大主题,全面打造最佳用户体验。
目前携程旅行线上最新版本已适配到 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 进行的一次升级。
另外修改相关 app、library 模块中 build.gradle 的 compileSdkVersion、targetSdkVersion、buildToolsVersion 的配置,都设置为 29,示例如下:
复制代码
android { compileSdkVersion 29 buildToolsVersion 29.0.2 defaultConfig { targetSdkVersion 29 } ...}
复制代码
android.useAndroidX=trueandroid.enableJetifier=true
其中:
复制代码
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
将原来 import 的 android.包删除,重新 import 新的 androidx.包;
import android.support.v7.app.AppCompatActivity; →import androidx.appcompat.app.AppCompatActivity;
官方迁移指南:
https://developer.android.com/jetpack/androidx/migrate#migrate
在 AndroidStudio 3.2 或更高版本(截图中 AndroidStudio 为 3.5 版本)中执行如下操作:菜单 >Refactor > Migrate to AndroidX(如果迁移失败,就需要重复上面 1,2,3,4 步手动去修改迁移)
为了更好的保护用户数据并限制设备冗余文件增加,以 Android 10(API 级别 29)及更高版本为目标平台的应用在默认情况下被赋予了对外部存储设备的分区访问权限(即分区存储), 对外部存储文件访问方式重新设计,便于用户更好的管理外部存储文件。
应用只能看到本应用专有的目录(通过 Context.getExternalFilesDir() 访问)以及特定类型的媒体。除非您的应用需要访问存放在应用的专有目录以及 MediaStore 之外的文件,否则最好使用分区存储。
要点:
需要注意:在适配 AndroidQ 的时候还要兼容 Q 系统版本以下的,使用 SDK_VERSION 区分
外部存储被分为应用私有目录以及共享目录两个部分:
1)私有目录
应用私有目录文件访问方式与之前 Android 版本一致,可以通过 File path 获取资源。
2)共享目录
共享目录文件需要通过 MediaStore API 或者 Storage Access Framework 方式访问。
一些图片会包含位置信息,因为位置对于用户属于敏感信息, Android 10 应用在分区存储模式下图片位置信息默认获取不到,应用通过以下两项设置可以获取图片位置信息:
MediaStore.Files 应用分区存储模式下,MediaStore.Files 集合只能够获取媒体文件信息 (图片、音频、视频), 获取不到非 media(pdf、office、doc、txt 等) 文件。
开启分区存储新特性, 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)
存储位置路径版本存储权限内部存储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
应用未完成外部存储适配工作,可以临时以兼容模式运行, 兼容模式下应用申请存储权限,即可拥有外部存储完整目录访问权限,通过 Android10 之前文件访问方式运行,以下两种方法设置应用以兼容模式运行。
tagretSDK 大于等于 Android 10(API level 29), 在 manifest 中设置 requestLegacyExternalStorage 属性为 true。
复制代码
<manifest ...>...<application android:requestLegacyExternalStorage="true" ... >...</manifest>
复制代码
// 返回值//true : 应用以兼容模式运行//false:应用以分区存储特性运行Environment.isExternalStorageLegacy();
备注:应用已完成存储适配工作且已打开分区存储开关,如果当前应用以兼容模式运行,覆盖安装后应用仍然会以兼容模式运行,卸载重新安装应用才会以分区存储模式运行
分区存储适配包含文件迁移以及文件访问兼容性适配两个部分:
1)文件迁移
文件迁移是将应用共享目录文件迁移到应用私有目录或者 Android10 要求的 media 集合目录。
2)文件访问兼容性
共享目录文件不能够通过 File path 方式读取,需要使用 MediaStore API 或者 Storage Access Framework 框架进行访问。
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);
从 Android 10 开始已经无法完全标识一个设备,曾经用 mac 地址、IMEI 等设备信息标识设备的方法,从 Android 10 开始统统失效。而且无论你的 APP 是否适配过 Android 10。
从 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();
如果您的 App 希望在 Android 10 以下的设备中仍然获取设备 IMEI 等信息,可按以下方式进行适配:
复制代码
<uses-permission android:name="android.permission.READ_PHONE_STATE" android:maxSdkVersion="28"/>
从 Android10 开始,默认情况下,在搭载 Android 10 或更高版本的设备上,系统会传输随机分配的 MAC 地址。(即从 Android 10 开始,普通应用已经无法获取设备的真正 mac 地址,标识设备已经无法使用 mac 地址)
如果您的应用有追踪非登录用户的需求,可用 ANDROID_ID 来标识设备。
复制代码
String androidId = Settings.Secure.getString(this.getContentResolver(), Settings.Secure.ANDROID_ID);
统一标识依据电信终端产业协会 (TAF)、移动安全联盟 (MSA) 联合推 出的团体标准《移动智能终端补充设备标识规范》开发,移动智能终端补充设备标识体系统一调用 SDK 集成设备厂商提供的接口,并获得主流设备厂商的授权。
移动安全联盟 (MSA) 组织中国信息通信研究院 (以下简称“中国信通院”) 与终端生产企业、互联网企业共同研究制定了“移动智能终端补充设备标识体系”,定义了移动智能终端补充设备标识体系的体系架构、功能要求、接口要求以及安全要求,使设备生产企业统一开发接口,为移动应用开发者提供统一调用方式,方便移动应用接入,降低维护成本。
1)SDK 获取
MSA 统一 SDK 下载地址:
移动安全联盟官网, http://www.msa-alliance.cn/
2)接入方式
复制代码
{ "supplier":{ "xiaomi":{ "appid":"***" }, "huawei":{ "appid":"***" } ... }}
复制代码
try { JLibrary.InitEntry(FoundationContextHolder.getContext());} catch (Throwable e) {}
复制代码
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){ } }};
当 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
今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。