Android Q(10) 重要更新与兼容适配

说明:本文将从三个角度讲解Android 10的重要更新与兼容问题

  • 隐私功能

推出新的保护措施,可保护您将需要在应用中支持的用户隐私

  • 行为变更

可能会影响您在 Android 10 上运行的应用的系统更改

  • 新功能和 API

用于可折叠设备、深色主题、手势导航、连接、媒体、NNAPI 和生物识别等方面的 API

1. 隐私权变更

Android 10 引入了大量变更(如改进了系统界面、让权限授予更加严格以及对应用能够使用哪些数据实施了限制),目的是保护隐私权并赋予用户控制权。以下是重大变更:

隐私权变更 受影响的应用 缓解策略
分区存储 访问和共享外部存储中的文件的应用 使用特定于应用的目录和媒体集合目录
增强了用户对位置权限的控制力 在后台时请求访问用户位置信息的应用 确保在没有后台位置信息更新的情况下优雅降级使用 Android 10 中引入的权限在后台获取位置信息
针对从后台启动 Activity 的限制 不需要用户互动就启动 Activity 的应用 使用通知触发的 Activity
设备唯一标识符的变更 访问设备序列号或 IMEI 的应用 使用用户可以重置的标识符

分区存储

Android Q 在外部存储设备中为每个应用提供了一个“隔离存储沙盒”(例如 /sdcard)。任何其他应用都无法直接访问您应用的沙盒文件。由于文件是您应用的私有文件,因此您不再需要任何权限即可在外部存储设备中访问和保存自己的文件。对于以 Android 10 及更高版本为目标平台的应用,其访问权限范围限定为外部存储,即分区存储。
此类应用可以查看外部存储设备内以下类型的文件,无需请求任何与存储相关的用户权限:

  • 特定于应用的目录中的文件(使用 getExternalFilesDir() 访问,这是应用专属文件)

     public File getPrivateAlbumStorageDir(Context context, String albumName) {
          // Get the directory for the app's private pictures directory.
          File file = new File(context.getExternalFilesDir(
                  Environment.DIRECTORY_PICTURES), albumName);
          if (!file.mkdirs()) {
              Log.e(LOG_TAG, "Directory not created");
          }
          return file;
      }
    
    
  • 应用创建的照片、视频和音频片段(通过媒体库访问,这是可共享的媒体文件)

MediaStore 包括所有属于用户的、其他应用可见的媒体文件,所有应用可以在没任何权限下为MediaStore提供内容,但是要访问其他应用提供的内容则必须获取READ_EXTERNAL_STORAGE,以下是相关权限:


MediaStore API 提供访问以下类型的媒体文件的接口:

  照片:存储在 MediaStore.Images 中
  视频:存储在 MediaStore.Video 中
  音频文件:存储在 MediaStore.Audio 中
  MediaStore 还包含一个名为 MediaStore.Files 的集合,该集合提供访问所有类型的媒体文件的接口。

如果你要访问系统媒体文件:

   Android Q中引入了一个新定义媒体文件的共享集合,如果要访问沙盒外的媒体共享文件,比如照片,音乐,视频等,需要申请新的媒体权限:
   READ_MEDIA_IMAGES,READ_MEDIA_VIDEO,READ_MEDIA_AUDIO,申请方法同原来的存储权限。

这里举一个保存图片的例子:

在Android 10之前我们是这样保存图片的

public static void saveImg (File originalFile,String fileName) {
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
      return null;
  }
  //创建项目图片公共缓存目录
  File file = new File(Environment.getExternalStorageDirectory()+
                  File.separator +
                  Environment.DIRECTORY_PICTURES +
                  File.separator +
                  "yourAppName" +
                  File.separator +
                  "images";);
  if (! file.exists()) {
      file.mkdirs();
  }
  //创建对应图片的缓存路径
  File newFile = new File(file + File.separator + fileName);
 FileOutputStream outStream = new FileOutputStream(newFile,true)
//伪代码
// 将originalFile文件写入到输出流完成图片保存
}

在Android 10或者更高的版本我们不能通过File API去操作了,不然会报无权限访问异常。

private static void saveImg (File originalFile,String imageName, String imageType) {
  if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
      return null;
  }
  if (TextUtils.isEmpty(relativePath)) {
      return null;
  }
  Uri insertUri = null;
  ContentResolver resolver = context.getContentResolver();
  //设置文件参数到ContentValues中
  ContentValues values = new ContentValues();
  //设置文件名
  values.put(MediaStore.Images.Media.DISPLAY_NAME, imageName);
  //设置文件描述,这里以文件名代替
  values.put(MediaStore.Images.Media.DESCRIPTION, imageName);
  //设置文件类型为image/*
  values.put(MediaStore.Images.Media.MIME_TYPE, "image/" + imageType);
  //注意:MediaStore.Images.Media.RELATIVE_PATH需要targetSdkVersion=29,
  //故该方法只可在Android10的手机上执行
  values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_DCIM);
  //insertUri表示文件保存的uri路径
  insertUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);      
  //获取输出流
  ContentResolver resolver = context.getContentResolver();    
  OutputStream os = resolver.openOutputStream(insertUri)                                                  
  //伪代码
  //将originalFile文件写入到输出流完成图片保存
}

** 在您的应用与分区存储完全兼容之前,您可以使用以下暂时停用分区存储 :**

  • 以 Android 9(API 级别 28)或更低版本为目标平台。
  • 如果您以 Android 10(API 级别 29)或更高版本为目标平台,请在应用的清单文件中将 requestLegacyExternalStorage 的值设置为 true:
<manifest ... >
  <!-- This attribute is "false" by default on apps targeting
       Android 10 or higher. -->
    <application android:requestLegacyExternalStorage="true" ... >
      ...
    </application>
</manifest>

在后台运行时访问设备位置信息需要权限

为了让用户更好地控制应用对位置信息的访问权限,Android 10 引入了 ACCESS_BACKGROUND_LOCATION 权限,与 ACCESS_FINE_LOCATION 和 ACCESS_COARSE_LOCATION 权限不同,ACCESS_BACKGROUND_LOCATION 权限仅会影响应用在后台运行时对位置信息的访问权限。除非符合以下条件之一,否则应用将被视为在后台访问位置信息:

  • 属于该应用的 Activity 可见
  • 该应用运行的某个前台设备已声明前台服务类型为 location,要声明您的应用中的某个服务的前台服务类型,请将应用的 targetSdkVersion 或 compileSdkVersion 设置为 29 或更高版本

如果你的应用在Anroid 10或者更高的版本上运行,但你的目标版本低于Android 10,如果您的应用添加了 ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION 权限,则系统会在安装期间自动为应用添加 ACCESS_BACKGROUND_LOCATION 权限,如果应用请求ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION 权限,系统会自动将 ACCESS_BACKGROUND_LOCATION 权限添加到请求中。

针对从后台启动 Activity 的限制

从 Android 10 开始,系统会增加针对从后台启动 Activity 的限制。此项行为变更有助于最大限度地减少对用户造成的中断,并且可以让用户更好地控制其屏幕上显示的内容。只要您的应用启动 Activity 是因用户互动直接引发的,该应用就极有可能不会受到这些限制的影响。在特定情况下,如果您的应用需要立即引起用户的注意,可以使用以下方案:

  • 创建高优先级通知

创建通知时,请务必添加描述性标题和消息。您还可以选择使用全屏 intent(必须在应用的清单文件中请求 USE_FULL_SCREEN_INTENT 权限。这是普通权限)

  • 向用户显示通知

将通知与前台服务相关联, startForeground(notificationId, notification);

设备唯一标识符的变更

从 Android 10 开始,应用必须具有 READ_PRIVILEGED_PHONE_STATE 特许权限才能访问设备的不可重置标识符(包含 IMEI 和序列号)

** 注意:从 Google Play 商店安装的第三方应用无法声明特许权限**

受影响的方法包括:

 Build
 getSerial()

 TelephonyManager
 getImei()
 getDeviceId()
 getMeid()
 getSimSerialNumber()
 getSubscriberId()

如果您的应用没有该权限,但您仍尝试查询不可重置标识符的相关信息,则平台的响应会因目标 SDK 版本而异:

如果应用以 Android 10 或更高版本为目标平台,则会发生 SecurityException。
如果应用以 Android 9(API 级别 28)或更低版本为目标平台,则相应方法会返回 null 或占位符数据(如果应用具有 READ_PHONE_STATE 权限)。否则,会发生 SecurityException。

2. 行为变更

限制非 SDK 接口

非SDK接口限制就是某些SDK中的私用方法,如private方法,你通过Java反射等方法获取并调用了。那么这些调用将在target>=P或target>=Q的设备上被限制使用,当你使用了这些方法后,会报错。如果你想查看你的应用是非使用了非SDK接口,可以使用以下方法:

  • 您可以通过在搭载 Android 9(API 级别 28)或更高版本的设备或模拟器上构建和运行可调试应用来测试该应用是否使用非 SDK 接口。请确保您使用的设备或模拟器与应用的目标 API 级别相匹配。在您的应用上运行测试时,如果该应用访问了某些非 SDK 接口,系统就会输出一条日志消息。您可以检查应用的日志消息,查找以下详细信息:

    • 声明的类、名称和类型(采用 Android 运行时所使用的格式)。
    • 访问方式:链接、反射或 JNI
    • 所访问的非 SDK 接口属于哪个名单。

    您可以使用 adb logcat 来查看这些日志消息,这些消息显示在所运行应用的 PID 下。举例而言,日志中可能包含如下条目:

    Accessing hidden field Landroid/os/Message;->flags:I (light greylist, JNI)
    
  • 使用 veridex 工具进行测试

    您可以在 APK 上运行静态分析工具 veridex。下载解压后打开命令行切换到veridex目录下运行命令:

    ./appcompat.sh --dex-file=your-app.apk
    

    veridex 工具会扫描 APK 的整个代码库(包括所有第三方库), 扫描结果分为两部分,一部分为被调用的非SDK接口的位置,另一部分为非SDK接口数量统计。

    • 开发者自己编写的反射

    解决方案:此时我们需要讲使用非SDK接口迁移到SDK接口

    • 第三方SDK中使用了非SDK接口

    解决方案:提交工单或者换第三方SDK

针对全屏 Intent 的权限变更

如果应用以 Android 10 或更高版本为目标平台并使用涉及全屏 intent 的通知,则必须在应用的清单文件中请求 USE_FULL_SCREEN_INTENT 权限。这是普通权限,因此,系统会自动为请求权限的应用授予此权限,如果以 Android 10 或更高版本为目标平台的应用尝试创建使用全屏 intent 的通知而未请求必要权限,则系统会忽略此全屏 intent

3. 新功能和 API

深色主题

应用的深色主题背景将与系统控制的夜间模式标记相关联

有以下两种方式实现深色主题:

  • 设置主题方式

    将主题设置为继承 DayNight 主题背景

    <style name="AppTheme" parent="Theme.AppCompat.DayNight">
    

    您还可以使用 MaterialComponent 的深色主题背景:
    <style name="AppTheme" parent="Theme.MaterialComponents.DayNight">
    如要切换主题背景,调用 AppCompatDelegate.setDefaultNightMode(),在 AppCompat v1.1.0 后会自动重新创建所有已启动的 Activity

  • Force Dark方式

    Force Dark 可让开发者快速实现深色主题背景,而无需明确设置 DayNight 主题背景,如果你想启用Force Dark,在主题背景设置:

    android:forceDarkAllowed="true"
    

    如果你想在单独的视图上停用Force Dark,在布局属性上添加 android:forceDarkAllowed或者代码调用 setForceDarkAllowed()

    ** 注意如果你使用了深色主题或者使用了DayNight主题背景,系统将不会应用Force Dark **

前台服务类型

Android 10 引入了 foregroundServiceType XML 清单属性,您可以将其包含在多项特定服务的定义中。虽然很少适用,但您可以为一项特定服务分配多个前台服务类型。

下表显示了不同的前台服务类型,以及适合在其中声明特定类型的服务:

前台服务类型 应声明相应类型的服务的示例使用情形
connectedDevice 监控穿戴式设备健身跟踪器
dataSync 从网络下载文件
location 延续用户发起的操作
mediaPlayback 播放有声读物、播客或音乐
mediaProjection 简短地录屏
phoneCall 处理正在进行的通话
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342