您的位置:首页 > 移动开发 > Android开发

Android R 新特性分析及适配指南

2021-03-12 15:48 2396 查看

Android R(Android 11 API 30)于2020年9月9日正式发布,随国内各终端厂商在售Android设备的版本更新升级,应用软件对Android R 版本的兼容适配已迫在眉睫。

对于Android R的新特性,这里按照以下几个方面进行了归纳:

分区存储、权限、隐私、性能、安全

官方文档描述:https://developer.android.google.cn/about/versions/11

一、分区存储

从Android 10(API 29)开始,Android

默认开启分区存储
功能,不过Android 10 可通过增加
android:requestLegacyExternalStorage="true"
配置
停用分区存储
; 从Android 11(API 30)开始,
强制执行分区存储
,对于Android 11及以上设备,
android:requestLegacyExternalStorage="true"
配置将不再有效。

Android 11 分区存储官方描述: https://developer.android.google.cn/training/data-storage#scoped-storage Android 10 默认开启分区存储: https://xiaxl.blog.csdn.net/article/details/103125117

1.1、访问目录

开启分区存储后,应用默认情况下只能访问

应用专属目录(内部存储、外部存储应用专属目录)
,以及
本应用所创建的特定类型的媒体文件

  • 应用专属目录 包括

    内部存储
    外部存储专属目录
    (若应用包名com.xiaxl.demo):
    /data/data/com.xiaxl.demo/files,
    /sdcard/Android/data/com.xiaxl.demo/files
    分别采用以下API进行访问:
    File appFile = new File(context.getFilesDir(), filename);
    File appExternalFile = new File(context.getExternalFilesDir(), filename);

  • 共享存储目录 包括媒体、文档和其他文件。例如DCIM、Pictures、Movies、Download等目录; 注:

    Android 10(Android Q)中共享存储目录使用MediaStore API访问;
    Android 11(Android R)中共享存储目录支持MediaStore API与File API访问。
    为保证应用在Android 10、Android 11设备中,使用
    File API对共享存储目录具有相同的文件访问权限
    。建议在应用 AndroidManifest配置文件中,增加
    requestLegacyExternalStorage="true"
    标识,以
    关闭Android 10设备上的分区存储功能
    ,使
    分区存储只对Android 11以上设备生效

1.2、访问所需权限

  • 应用专属目录 应用专属目录(
    内部存储
    外部存储专属目录
    )的读写,Android 4.4以上设备不需要任何权限;
  • 共享存储目录 共享存储路径的读写,需要
    READ_EXTERNAL_STORAGE
    WRITE_EXTERNAL_STORAGE
    权限;

Android 11以上设备中,如果您的应用再次请求

READ_EXTERNAL_STORAGE
权限时,动态权限申请弹窗将变化为
“您的应用正在请求访问照片和媒体”

文件媒体访问 官方描述: https://developer.android.google.cn/training/data-storage#scoped-storage

1.3、共享文件

如果需要与其他应用共享单个文件或应用数据,可以使用API:

  • FileProvider
    (分享自己的一个或多个文件) 如果应用需要将自己的一个或多个文件提供给其他应用,安全的做法是向接收方应用发送文件的内容 URI,并授予对该 URI 的临时访问权限。 Android
    FileProvider
    组件提供了
    getUriForFile()
    方法,用于生成文件的内容
    URI
  • ContentProvider
    (获取替他应用提供的数据) 如果您需要向其他应用提供数据,可以使用
    ContentProvider
    ContentProvider
    是一种标准接口,可将一个进程中的数据与另一个进程中运行的代码进行连。

Android 11 共享文件官方描述: https://developer.android.google.cn/training/data-storage#scoped-storage

1.4、所有文件的访问权限

有一些应用需要获取所有文件的访问权限,例如:文件管理器软件。 获取所有文件的访问权限,可申请

MANAGE_EXTERNAL_STORAGE
权限。

// 权限配置
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

// 是否拥有MANAGE_EXTERNAL_STORAGE权限判断
Environment.isExternalStorageManager();

// 跳转到设置页,请求用户授权
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
startActivity(intent);

MANAGE_EXTERNAL_STORAGE
相关官方描述: https://developer.android.google.cn/training/data-storage/manage-all-files

二、权限

Android 11 中对权限进行了如下更改:

  • 新增
    READ_PHONE_NUMBERS
    权限,获取手机号码;
  • 后台访问位置
    权限调整;
  • 用户
    多次针对某项特定的权限请求
    拒绝
    ,表示用户希望
    不再询问
  • 应用
    长时间未使用
    ,系统会
    自动重置用户已授予敏感权限
  • 针对
    位置、麦克风、摄像头
    授权弹窗新增
    仅限这一次
    授权按钮;
  • SYSTEM_ALERT_WINDOW
    权限授权方式改变为系统自动授权;

参考 Android 11 权限更新官方文档: https://developer.android.google.cn/about/versions/11/privacy/permissions#one-time

2.1、新增 READ_PHONE_NUMBERS 权限

当应用的

targetSdkVersion>=30
时,使用以下API
获取手机号码
时,需要申请
READ_PHONE_NUMBERS
权限,而不再是
READ_PHONE_STATE
权限。

  • TelephonyManager
    类和
    TelecomManager
    类中的
    getLine1Number()
    方法。
  • TelephonyManager
    类中不受支持的
    getMsisdn()
    方法。

在Android 10及之前的设备,可以继续使用

READ_PHONE_STATE
获取手机号; 对Android11及以上设备,需获取
READ_PHONE_NUMBERS
权限,才能获取手机号;

<manifest>
<!-- 仅在Android 10及以下设备获取READ_PHONE_STATE权限,以获取终端手机号码-->
<uses-permission android:name="READ_PHONE_STATE"
android:maxSdkVersion="29" />
<!-- Android 11及以上设备获取READ_PHONE_NUMBERS权限,以获取终端手机号码-->
<uses-permission android:name="READ_PHONE_NUMBERS" />
</manifest>

对于

READ_PHONE_STATE
权限

READ_PHONE_NUMBERS
权限官方API描述: https://developer.android.google.cn/reference/android/Manifest.permission#READ_PHONE_NUMBERS

2.2、后台访问位置权限调整

  • 在Android10设备上,同时
    申请前台、后台位置权限
    时,并在用户选择
    始终允许
    后,才能获得后台位置权限。
  • 在Android11设备上,对于
    targetSdkVersion<=29(Android 10)
    的应用,同时
    申请前台、后台位置权限
    时,对话框不再提示始终允许字样,而是提供了位置权限的设置入口,需要
    用户在设置页面选择始终允许
    才能获得后台位置权限。
  • 在Android11设备上,对于
    targetSdkVersion=30(Android 11)
    的应用,同时
    申请前台、后台位置权限
    时,系统会忽略该请求,无任何响应(
    需首先获取前台位置权限,再次申请后台位置权限
    )。
  • 在Android11设备上,对于
    targetSdkVersion=30(Android 11)
    的应用,
    先申请前台位置权限,后申请后台位置权限

后台访问位置权限 官方描述: https://developer.android.google.cn/training/location/background

a、Android10设备

在Android10设备上,同时

申请前台、后台位置权限
时,并在用户选择
始终允许
后,才能获得后台位置权限。

// 在Android10设备上,同时 申请前台、后台位置权限
ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 101);

b、Android11设备 targetSdkVersion<=29

在Android11设备上,对于

targetSdkVersion<=29(Android 10)
的应用,同时
申请前台、后台位置权限
时,对话框不再提示始终允许字样,而是提供了位置权限的设置入口,需要
用户在设置页面选择始终允许
才能获得后台位置权限。

// 在Android11设备上,targetSdkVersion<=29的应用,同时 申请前台、后台位置权限
ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 101);

c、Android11设备 targetSdkVersion=30 同时申请前台、后台位置权限

  • 在Android11设备上,对于
    targetSdkVersion=30(Android 11)
    的应用,同时
    申请前台、后台位置权限
    时,系统会忽略该请求,无任何响应(
    需首先获取前台位置权限,再次申请后台位置权限
    )。
// 在Android11设备上,targetSdkVersion=30的应用,同时 申请前台、后台位置权限
// 请求无反应,此为错误写法
ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 101);

d、Android11设备 targetSdkVersion=30 依次申请前台、后台位置权限

在Android11设备上,对于

targetSdkVersion=30(Android 11)
的应用,
先申请前台位置权限,后申请后台位置权限

// 在Android11设备上,targetSdkVersion=30的应用,申请前台位置权限
ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.ACCESS_COARSE_LOCATION}, 101);

Android11设备上,targetSdkVersion=30的应用,申请后台位置权限,直接跳转到设置页面。

// 在Android11设备上,targetSdkVersion=30的应用,申请后台位置权限
ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 101);

2.3、用户
多次针对某项特定的权限请求
拒绝

在 Android 11 中,用户

多次针对某项特定的权限请求
点击了
拒绝
,那么应用再次请求该项权限时,用户将不会看到系统权限弹窗,该操作表示用户希望
不再询问

2.4、长时间未使用,自动重置已授予敏感权限

在 Android 11 中,当targetSdkVersion>=30时,

应用在一段时间内未使用
,系统会通过
自动重置用户已授予应用的运行时敏感权限
来保护用户数据;

2.5、新增“仅限这一次”授权按钮

从 Android 11(API 级别 30)开始,当应用请求与

位置、麦克风、摄像头
相关权限时,面向用户的授权对话框会包含
仅限这一次
选项;如果用户在对话框中选择
仅限这一次
,系统会向应用授予临时的单次授权。

权限申请API使用方式不变:

private void showCameraPreview() {
// 判断是否拥有Camera权限
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED) {
// 进入Camera页面
// startCamera();
} else {
// 请求Camera权限
requestCameraPermission();
}
}

private void requestCameraPermission() {
// 判断Camera权限,之前是否已被用户"拒绝"
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.CAMERA)) {
// 弹窗告诉用户,为什么需要Camera权限
Snackbar.make(mLayout, R.string.camera_access_required,
Snackbar.LENGTH_INDEFINITE).setAction(R.string.ok, new View.OnClickListener() {
@Override
public void onClick(View view) {
// 请求Camera权限
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.CAMERA},
PERMISSION_REQUEST_CAMERA);
}
}).show();

} else {
// 请求Camera权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA}, PERMISSION_REQUEST_CAMERA);
}
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
if (requestCode == PERMISSION_REQUEST_CAMERA) {
// 用户授权Camera(用户选择"使用使用时允许"、"仅这一次允许")
if (grantResults.length == 1
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission has been granted. Start camera preview Activity.
Snackbar.make(mLayout, R.string.camera_permission_granted,
Snackbar.LENGTH_SHORT)
.show();
startCamera();
}
// 用户选择"拒绝"
else {
// Permission request was denied.
Snackbar.make(mLayout, R.string.camera_permission_denied,
Snackbar.LENGTH_SHORT)
.show();
}
}
}

源码参考: https://github.com/android/permissions-samples/tree/main/RuntimePermissionsBasic

2.6、SYSTEM_ALERT_WINDOW 权限授权方式

在 Android 11 中,

SYSTEM_ALERT_WINDOW
权限授权方式更改为:
根据请求自动向某些应用授予 SYSTEM_ALERT_WINDOW 权限

  • 系统会自动向具有
    ROLE_CALL_SCREENING
    且请求
    SYSTEM_ALERT_WINDOW
    的所有应用授予该权限。如果应用失去
    ROLE_CALL_SCREENING
    ,就会失去该权限。
    ROLE_CALL_SCREENING
    RoleManager
    中的常量类,多用于通知用户将我们的应用替换掉手机自带的预搭载应用(短信、电话拨号);
  • 系统会自动向通过
    MediaProjection
    截取屏幕且请求
    SYSTEM_ALERT_WINDOW
    的所有应用授予该权限,除非用户已明确拒绝向应用授予该权限。当应用停止截取屏幕时,就会失去该权限。此用例主要用于游戏直播应用。

SYSTEM_ALERT_WINDOW权限 官方描述: https://developer.android.google.cn/about/versions/11/privacy/permissions#system-alert

三、隐私保护

主要更改涉及以下几个方面:

  • 软件包可见性:获取其他应用信息需在
    AndroidManifest
    中增加
    <queries>
    标签;
  • 前台服务:访问位置信息、摄像头、麦克风限制;
  • 永久 SIM 卡标识符 ICCID 获取受限;
  • 新增
    AppOpsManager.OnOpNotedCallback
    监听危险权限的调用,从而保护用户的私密数据; 这样对于第三方依赖库的权限使用申请可以做一个监控

3.1、软件包可见性

  • 在 Android 11 及更高版本设备中,当应用的
    targetSdkVersion>=30
    时,如果应用希望获取其他应用的信息(比如:包名、软件名称),原有方式将无法获取到。
  • 如需获取其他应用信息,需要在
    AndroidManifest
    中增加
    <queries>
    元素标签,告知系统希望获取哪些应用的信息或者哪一类应用的信息。
  • 如果需要获取所有应用的信息(比如:Launcher应用、设备管理器应用):这种情况只需要在
    AndroidManifest
    中添加
    QUERY_ALL_PACKAGES
    权限即可。
    QUERY_ALL_PACKAGES
    权限为普通权限,不需要进行动态申请。但提交应用市场后,应用市场可能会进行审核

软件包可见性 官方描述: https://developer.android.google.cn/about/versions/11/privacy/package-visibility

<manifest package="com.xiaxl.myapp">

// 1、若知道具体应用的包名
<queries>
<package android:name="com.xiaxl.otherapp01" />
<package android:name="com.xiaxl.otherapp01" />
</queries>
// 2、不知道包名,但想知道某一类App的应用信息
<queries>
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="image/jpeg" />
</intent>
</queries>
</manifest>

3.2、前台服务:访问位置信息、摄像头、麦克风限制

当应用的

targetSdkVersion>=30
时,
前台服务
访问
位置信息、摄像头、麦克风
时,需添加
foregroundServiceType

<manifest>
// 前台服务访问:位置信息、摄像头、麦克风
<service
android:foregroundServiceType="location|camera|microphone" />
</manifest>

前台服务 官方描述: https://developer.android.google.cn/about/versions/11/privacy/foreground-services

3.3、永久 SIM 卡标识符 ICCID 获取受限

在 Android 11 及更高版本中,使用

SubscriptionInfo.getIccId()
方法访问不可重置的 ICCID 受到限制。

SubscriptionInfo.getIccId()
方法会返回一个
非null的空字符串

如需唯一标识设备上安装的 SIM 卡,请改用

getSubscriptionId()
方法。
SubscriptionId
会提供一个索引值,用于唯一识别已安装的 SIM 卡(包括实体 SIM 卡和电子 SIM 卡),除非设备恢复出厂设置,否则此标识符的值对于给定 SIM 卡是保持不变的。

3.4、监听危险权限的调用

Android 11新增

AppOpsManager.OnOpNotedCallback
为开发者提供
对应用危险权限的使用监听,从而保护用户的私密数据
。 当应用以及应用的依赖包中,申请某项危险权限时,
AppOpsManager.OnOpNotedCallback
的对应回调方法将会被调用,从而
打印申请的权限
对应的API调用栈

举例:

使用位置权限获取位置信息
时,将会回调
AppOpsManager.OnOpNotedCallback
中的
onNoted
方法,并打印
使用的权限
对应的API调用栈

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//
AppOpsManager.OnOpNotedCallback appOpsCallback =
new AppOpsManager.OnOpNotedCallback() {
private void logPrivateDataAccess(String opCode, String trace) {
Log.i("xiaxl: ", "opCode: " + opCode + "\n trace: " + trace);
}

@Override
public void onNoted(@NonNull SyncNotedAppOp syncNotedAppOp) {
Log.i("xiaxl: ", "---onNoted---");
logPrivateDataAccess(syncNotedAppOp.getOp(),
Arrays.toString(new Throwable().getStackTrace()));
}

@Override
public void onSelfNoted(@NonNull SyncNotedAppOp syncNotedAppOp) {
Log.i("xiaxl: ", "---onSelfNoted---");
logPrivateDataAccess(syncNotedAppOp.getOp(),
Arrays.toString(new Throwable().getStackTrace()));
}

@Override
public void onAsyncNoted(@NonNull AsyncNotedAppOp asyncNotedAppOp) {
Log.i("xiaxl: ", "---onAsyncNoted---");
logPrivateDataAccess(asyncNotedAppOp.getOp(),
asyncNotedAppOp.getMessage());
}
};

AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);
if (appOpsManager != null) {
appOpsManager.setOnOpNotedCallback(getMainExecutor(), appOpsCallback);
}
}

public void getLocation() {
// 创建归因
Context attributionContext = createAttributionContext("shareLocation");
// 获取位置信息
LocationManager locationManager =
attributionContext.getSystemService(LocationManager.class);
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
Location lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
}

打印日志如下:

---onNoted---
opCode: android:coarse_location
trace:
[com.xiaxl.android_test.MainActivity$1.onNoted(MainActivity.java:42),
android.app.AppOpsManager.readAndLogNotedAppops(AppOpsManager.java:8204),
android.os.Parcel.readExceptionCode(Parcel.java:2304),
android.os.Parcel.readException(Parcel.java:2279),
android.location.ILocationManager$Stub$Proxy.getLastLocation(ILocationManager.java:1225),
android.location.LocationManager.getLastKnownLocation(LocationManager.java:648),
com.xiaxl.android_test.MainActivity.getLocation(MainActivity.java:87),
com.xiaxl.android_test.MainActivity$2.onClick(MainActivity.java:70),
android.view.View.performClick(View.java:7448),
com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:967),
android.view.View.performClickInternal(View.java:7425),
android.view.View.access$3600(View.java:810),
android.view.View$PerformClick.run(View.java:28305),
android.os.Handler.handleCallback(Handler.java:938),
android.os.Handler.dispatchMessage(Handler.java:99),
android.os.Looper.loop(Looper.java:223),
android.app.ActivityThread.main(ActivityThread.java:7656),
java.lang.reflect.Method.invoke(Native Method),
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592),
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)]

从以上日志可以看出,当应用申请

ACCESS_COARSE_LOCATION
权限并
获取位置信息时
,打印了应用
申请的权限
对应的API调用栈

AppOpsManager 相关官方描述: https://developer.android.google.cn/guide/topics/data/audit-access#audit-by-attribution-tag

四、性能

  • JobScheduler使用频率进行限制

4.1、JobScheduler使用频率进行限制

Android 11 为对

JobScheduler
使用频率进行一定限制。 对于 debuggable 清单属性设置为 true 的应用,过多的调用
JobScheduler
API 将返回
RESULT_FAILURE

JobScheduler
主要用于在未来某个时间下满足一定条件时触发执行某项任务,例如:
当设备在空闲状态, 并且使用wifi时, 自动下载Apk
JobScheduler
典型的使用举例如下:

JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
ComponentName jobService = new ComponentName(this, MyJobService.class);

//任务Id等于123
JobInfo jobInfo = new JobInfo.Builder(123, jobService)
// 任务最少延迟时间
.setMinimumLatency(5000)
// 任务deadline,当到期没达到指定条件也会开始执行
.setOverrideDeadline(60000)
// 网络条件,网络无需付费时执行
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
// 是否充电
.setRequiresCharging(true)
// 是否在空闲时执行
.setRequiresDeviceIdle(true)
// 设备重启后是否继续执行
.setPersisted(true)
// 设置退避/重试策略
.setBackoffCriteria(3000,JobInfo.BACKOFF_POLICY_LINEAR)
.build();
scheduler.schedule(jobInfo);

官方描述参考: https://developer.android.google.cn/about/versions/11/behavior-changes-all

官方Demo参考: https://github.com/googlearchive/android-JobScheduler

五、安全

  • 非 SDK 接口限制

5.1、非 SDK 接口限制

官方从 Android 9(API 级别 28)开始,对应用使用的非 SDK 接口实施了限制。 如果你的APP通过引用

非 SDK 接口
或尝试
使用反射或 JNI 来获取句柄
,这些限制就会起作用。官方给出的解释是为了
提升用户体验、降低应用崩溃风险

a、非SDK接口检测工具

官方给出了一个检测工具,下载地址:veridex

veridex使用方法:

appcompat.sh --dex-file=apk.apk

b、blacklist、greylist、greylist-max-o、greylist-max-p含义

以上截图中,blacklist、greylist、greylist-max-o、greylist-max-p含义如下:

  • blacklist 黑名单:禁止使用的非SDK接口,运行时直接Crash(因此必须解决)
  • greylist 灰名单:即当前版本仍能使用的非SDK接口,但在下一版本中可能变成被限制的非SDK接口
  • greylist-max-o: 在targetSDK<=O中能使用,但是在targetSDK>=P中被禁止使用的非SDK接口
  • greylist-max-p: 在targetSDK<=P中能使用,但是在targetSDK>=Q中被禁止使用的非SDK接口

非SDK接口限制 官方描述: https://developer.android.google.cn/about/versions/11/non-sdk-11

========== THE END ==========

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: