重大隐私权变更
隐私权变更 | 受影响的应用 | 缓解策略 | |
---|---|---|---|
分区存储 针对外部存储的过滤视图,可提供对特定于应用的文件和媒体集合的访问权限 | 访问和共享外部存储中的文件的应用 | 使用特定于应用的目录和媒体集合目录 了解详情 | |
增强了用户对位置权限的控制力 仅限前台权限,可让用户更好地控制应用对设备位置信息的访问权限 | 在后台时请求访问用户位置信息的应用 | 确保在没有后台位置信息更新的情况下优雅降级 使用 Android 10 中引入的权限在后台获取位置信息 了解详情 | |
系统执行后台 Activity 针对从后台启动 Activity 实施了限制 | 不需要用户互动就启动 Activity 的应用 | 使用通知触发的 Activity 了解详情 | |
不可重置的硬件标识符 针对访问设备序列号和 IMEI 实施了限制 | 访问设备序列号或 IMEI 的应用 | 使用用户可以重置的标识符 了解详情 | |
无线扫描权限 访问某些 WLAN、WLAN 感知和蓝牙扫描方法需要获得精确位置权限 | 使用 WLAN API 和蓝牙 API 的应用 | 针对相关使用场景请求 ACCESS_FINE_LOCATION 权限了解详情 |
- Android Q文件存储机制修改成了沙盒模式,应用只能访问自己沙盒下的文件和公共媒体文件,不可直接访问外部存储
(/sdcard)
文件,否则抛异常 - 对于Android Q以下,还是使用老的文件存储方式
- Android Q不再需要申请文件读写权限(删除了READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE两项危险权限),所以,Q以上不需要再动态申请文件读写权限。
- 谷歌官方推荐应用在沙盒内存储文件的地址为Context.getExternalFilesDir()下的文件夹。比如要存储一张图片,则应放在Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)中---》对应路径/Android/data/包名/files/Pictures
- 访问系统媒体文件:比如照片,音乐,视频等,需要使用MediaStore来进行访问,并且需要达成两个条件:
①拥有READ_EXTERNAL_STORAGE权限
②对应访问文件位于以下明确定义的媒体集合中
照片, 存储在MediaStore.Images
音频, 存储在MediaStore.Audio
视频, 存储在 MediaStore.Video
- 存储兼容 :
如果应用targetSDK<=P,并且应用安装在从 Android P 升级到 Android Q 的设备上。app的存储权限不受影响;
如果应用重新安装在Android Q设备上,不管targetSDK是否是Q,app的存储权限都会受影响,那么必须对应用进行存储权限改动的适配; - 如果targetSDK是Q,但是不想启用分区存储,可以在配置文件中配置下
<application android:requestLegacyExternalStorage="true" .....
这是为了给开发者更多时间适应这个变化,但是不知道什么时候回强制启用分区存储,还需尽快适配
2.定位权限
1.Android Q 引入了新的位置权限 ACCESS_BACKGROUND_LOCATION,该权限仅会影响应用在后台运行时对位置信息的 访问权。如果应用targetSDK<=P,请求了ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION权限,Android Q设备会自动帮你申请ACCESS_BACKGROUND_LOCATION权限。
2.如果应用以 Android 10 或更高版本为目标平台,则您必须在应用的清单文件中声明 ACCESS_BACKGROUND_LOCATION
权限并 接收用户权限,才能在应用位于后台时接收定期位置信息更新。
以下代码段展示了如何在应用中请求在后台访问位置信息:
<manifest ... >
<!--允许获得精确的GPS定位-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!--允许获得粗略的基站网络定位-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!-- 兼容10.0系统,允许App在后台获得位置信息 -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
</manifest>
3.以下代码段中显示了定位权限检查逻辑的示例:
boolean permissionAccessCoarseLocationApproved =
ActivityCompat.checkSelfPermission(this, permission.ACCESS_COARSE_LOCATION)
== PackageManager.PERMISSION_GRANTED;
if (permissionAccessCoarseLocationApproved) {
boolean backgroundLocationPermissionApproved =
ActivityCompat.checkSelfPermission(this,
permission.ACCESS_BACKGROUND_LOCATION)
== PackageManager.PERMISSION_GRANTED;
if (backgroundLocationPermissionApproved) {
// App can access location both in the foreground and in the background.
// Start your service that doesn't have a foreground service type
// defined.
} else {
// App can only access location in the foreground. Display a dialog
// warning the user that your app must have all-the-time access to
// location in order to function properly. Then, request background
// location.
ActivityCompat.requestPermissions(this, new String[] {
Manifest.permission.ACCESS_BACKGROUND_LOCATION},
your-permission-request-code);
}
} else {
// App doesn't have access to the device's location at all. Make full request
// for permission.
ActivityCompat.requestPermissions(this, new String[] {
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
},
your-permission-request-code);
}
4.如果您的应用通常需要在被置于后台后(如当用户按设备上的主屏幕按钮或关闭设备的显示屏时)访问设备的位置信息。 要在这种特定类型的用例中保留对设备位置信息的访问权,请启动您已在应用的清单中声明前台服务类型为 "location"
的 前台服务:
<service
android:name="MyNavigationService"
android:foregroundServiceType="location" ... >
...
</service>
在启动该前台服务之前,请确保您的应用仍可访问设备的位置信息:
boolean permissionAccessCoarseLocationApproved =
ActivityCompat.checkSelfPermission(this,
permission.ACCESS_COARSE_LOCATION) ==
PackageManager.PERMISSION_GRANTED;
if (permissionAccessCoarseLocationApproved) {
// App has permission to access location in the foreground. Start your
// foreground service that has a foreground service type of "location".
} else {
// Make a request for foreground-only location access.
ActivityCompat.requestPermissions(this, new String[] {
Manifest.permission.ACCESS_COARSE_LOCATION},
your-permission-request-code);
}
3.后台启动App的限制
Android10中, 当App无前台显示的Activity时,其启动Activity会被系统拦截, 导致启动无效。
对此官方给予的折中方案是使用全屏Intent(full-screen intent), 既创建通知栏通知时, 加入full-screen intent 设置, 示例代码如下(基于官方文档修改):
Intent fullScreenIntent = new Intent(this, CallActivity.class);
PendingIntent fullScreenPendingIntent = PendingIntent.getActivity(this, 0,
fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("Incoming call")
.setContentText("(919) 555-1234")
//以下为关键的3行
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_CALL)
.setFullScreenIntent(fullScreenPendingIntent, true);
NotificationManager notifyManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notifyManager.notify(notifyId, builder.build());
注意:在Target SDk为29及以上时,需要在AndroidManifest上增加USE_FULL_SCREEN_INTENT申明
//AndroidManifest中
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
测试结果:当手机处于亮屏状态时, 会显示一个通知栏, 当手机处于锁屏或者灭屏状态时,会亮屏并直接进入到CallActivity中
4.设备唯一标识符
从 Android Q 开始,应用必须具有 READ_PRIVILEGED_PHONE_STATE 签名权限才能访问设备的不可重置标识符(包含 IMEI 和序列号),然而READ_PRIVILEGED_PHONE_STATE权限只提供给系统app,所以不能通过((TelephonyManager) getActivity() .getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId()获取了
当前获取设备唯一ID的方式为使用SSAID, 若获取为空的话则使用UUID.randomUUID().toString()获得一个随机ID并存储起来, 该ID保证唯一, 但App卸载重装之后就会改变,SSAID获取方式:
String id = android.provider.Settings.Secure.getString(context.getContentResolver(), android.provider.Settings.Secure.ANDROID_ID);
5.非 SDK 接口限制
非SDK接口限制就是某些SDK中的私用方法,如private方法,你通过Java反射等方法获取并调用了。那么这些调用将在target>=P或target>=Q的设备上被限制使用,当你使用了这些方法后,会报错: