Android 6.0-14.0系统兼容
1、新增运行时权限。2、Android 6.0 版本删除了对 Apache HTTP 客户端的支持,要继续使用 Apache HTTP API,必须在build.gradle文件中声明以下编译时依赖项
目录
4、如何在Android 10.0及以上系统正确的读写文件?
1.3、如何在Android 11.0系统上声明你的应用与其他应用交互
1、Display.getRealSize 和 getRealMetrics:废弃和限制
8、在 BluetoothAdapter 中强制执行 BLUETOOTH_CONNECT 权限
一、Android 6.0系统
官方文档:Android 6.0
1、新增运行时权限。
权限分为普通权限和危险权限,危险权限除了在清单文件即AndroidManifest.xml文件中注册,还需要在需要权限的代码位置动态申请,以下为危险权限清单:
分组 | 名字 | 分割线 |
---|---|---|
PHONE | android.permission.READ_PHONE_STATE | |
android.permission.CALL_PHONE | ||
android.permission.READ_CALL_LOG | ||
android.permission.ADD_VOICEMAIL | ||
android.permission.WRITE_CALL_LOG | ||
android.permission.USE_SIP | ||
android.permission.PROCESS_OUTGOING_CALLS | ||
CALENDAR | android.permission.READ_CALENDAR | |
android.permission.WRITE_CALENDAR | ||
CAMERA | android.permission.CAMERA | |
CONTACTS | android.permission.READ_CONTACTS | |
android.permission.WRITE_CONTACTS | ||
android.permission.GET_ACCOUNTS | ||
LOCATION | android.permission.ACCESS_FINE_LOCATION | |
android.permission.ACCESS_COARSE_LOCATION | ||
MICROPHONE | android.permission.RECORD_AUDIO | |
SENSORS | android.permission.BODY_SENSORS | |
SMS | android.permission.SEND_SMS | |
android.permission.RECEIVE_SMS | ||
android.permission.READ_SMS | ||
android.permission.RECEIVE_WAP_PUSH | ||
android.permission.RECEIVE_MMS | ||
STORAGE | android.permission.READ_EXTERNAL_STORAGE | |
android.permission.WRITE_EXTERNAL_STORAGE |
<!-- 危险权限 start -->
<!--PHONE-->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<uses-permission android:name="android.permission.ADD_VOICEMAIL"/>
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
<uses-permission android:name="android.permission.USE_SIP"/>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<!--CALENDAR-->
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
<!--CAMERA-->
<uses-permission android:name="android.permission.CAMERA"/>
<!--CONTACTS-->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<!--LOCATION-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!--MICROPHONE-->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<!--SENSORS-->
<uses-permission android:name="android.permission.BODY_SENSORS"/>
<!--SMS-->
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_WAP_PUSH"/>
<uses-permission android:name="android.permission.RECEIVE_MMS"/>
<!--STORAGE-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 危险权限 Permissions end -->
2、取消对Apache HTTP 客户端的支持
Android 6.0 版本删除了对 Apache HTTP 客户端的支持,要继续使用 Apache HTTP API,必须在build.gradle
文件中声明以下编译时依赖项
android {
useLibrary 'org.apache.http.legacy'
}
3、APK 验证
对 APK 执行更严格的验证。如果文件在清单中声明但在 APK 本身中不存在,则认为 APK 已损坏。如果删除了任何内容,则必须重新签署 APK。
4、USB 连接
通过 USB 端口连接的设备现在默认设置为仅充电模式。要通过 USB 连接访问设备及其内容,用户必须明确授予此类交互的权限。如果您的应用支持通过 USB 端口与设备进行用户交互,请考虑必须显式启用交互。
二、Android 7.0系统
官方文档:Android 7.0
1、增加JIT即时编译前
在 Android 7.0 中,添加了即时 (JIT) 编译器,对 ART 进行代码分析,让它可以在应用运行时持续提升 Android 应用的性能。JIT 编译器对 Android 运行组件当前的 Ahead of Time (AOT) 编译器进行了补充,有助于提升运行时性能,节省存储空间,加快应用更新和系统更新速度。
2、随时随地低电耗模式
屏幕关闭片刻后,设备在使用电池时,低电耗模式将限制网络访问,同时延迟作业和同步。在短暂的维护时间范围后,其允许应用访问网络,并执行延迟的作业/同步。打开屏幕或将设备插入电源会使设备退出低电耗模式。
3、SurfaceView
SurfaceView
类可减少屏幕合成对电池的消耗,因为它是在专用硬件中合成,与应用窗口内容分隔开。因此,它产生的中间副本少于 TextureView
。从 Android 7.0 开始,强烈建议您使用 SurfaceView
代替 TextureView
,以实现省电。
4、APK v2签名
Android 7.0 引入一项新的应用签名方案 APK Signature Scheme v2,它能提供更快的应用安装时间和更多针对未授权 APK 文件更改的保护。在默认情况下,Android Studio 2.2 和 Android Plugin for Gradle 2.2 会使用 APK Signature Scheme v2 和传统签名方案来签署您的应用。
android {
...
defaultConfig { ... }
signingConfigs {
release {
storeFile file("myreleasekey.keystore")
storePassword "password"
keyAlias "MyReleaseKey"
keyPassword "password"
v2SigningEnabled true //打开v2签名配置
}
}
}
5、作用域目录访问
Android 使用的文件系统类似于其他平台上基于磁盘的文件系统。该系统提供了以下几种保存应用数据的选项:
- 应用专属存储空间:存储仅供应用使用的文件,可以存储到内部存储卷中的专属目录或外部存储空间中的其他专属目录。使用内部存储空间中的目录保存其他应用不应访问的敏感信息。
- 共享存储:存储你的应用打算与其他应用共享的文件,包括媒体、文档和其他文件。
- 偏好设置:以键值对形式存储私有原始数据。
- 数据库:使用 Room 持久性库将结构化数据存储在专用数据库中。
内容类型 | 访问方法 | 所需权限 | 其他应用是否可以访问? | 卸载应用时是否移除文件? | |
---|---|---|---|---|---|
应用专属文件 | 仅供您的应用使用的文件 | 从内部存储空间访问,可以使用 getFilesDir() 或 getCacheDir() 方法从外部存储空间访问,可以使用 getExternalFilesDir() 或 getExternalCacheDir() 方法 | 从内部存储空间访问不需要任何权限 如果应用在搭载 Android 4.4(API 级别 19)或更高版本的设备上运行,从外部存储空间访问不需要任何权限 | 否 | 是 |
媒体 | 可共享的媒体文件(图片、音频文件、视频) | MediaStore API | 在 Android 11(API 级别 30)或更高版本中,访问其他应用的文件需要 READ_EXTERNAL_STORAGE 在 Android 10(API 级别 29)中,访问其他应用的文件需要 READ_EXTERNAL_STORAGE 或 WRITE_EXTERNAL_STORAGE 在 Android 9(API 级别 28)或更低版本中,访问所有文件均需要相关权限 | 是,但其他应用需要 READ_EXTERNAL_STORAGE 权限 | 否 |
文档和其他文件 | 其他类型的可共享内容,包括已下载的文件 | 存储访问框架 | 无 | 是,可以通过系统文件选择器访问 | 否 |
应用偏好设置 | 键值对 | Jetpack Preferences 库 | 无 | 否 | 是 |
数据库 | 结构化数据 | Room 持久性库 | 无 | 否 | 是 |
应用间共享文件适配参考:Android 7.0 行为变更 通过FileProvider在应用间共享文件吧
三、Android 8.0系统
官方文档:Android 8.0
1、提醒窗口
使用 SYSTEM_ALERT_WINDOW 权限的应用无法再使用以下窗口类型来在其他应用和系统窗口上方显示提醒窗口:
- TYPE_PHONE
- TYPE_PRIORITY_PHONE
- TYPE_SYSTEM_ALERT
- TYPE_SYSTEM_OVERLAY
- TYPE_SYSTEM_ERROR
相反,应用必须使用名为 TYPE_APPLICATION_OVERLAY 的新窗口类型。
使用 TYPE_APPLICATION_OVERLAY 窗口类型显示应用的提醒窗口时,请记住新窗口类型的以下特性:
- 应用的提醒窗口始终显示在状态栏和输入法等关键系统窗口的下面。
- 系统可以移动使用 TYPE_APPLICATION_OVERLAY 窗口类型的窗口或调整其大小,以改善屏幕显示效果。
- 通过打开通知栏,用户可以访问设置来阻止应用显示使用 TYPE_APPLICATION_OVERLAY 窗口类型显示的提醒窗口。
适配方式为需要在之前的基础上加上判断:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mWindowParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
}else {
mWindowParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
}
同时需要添加权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
2、权限
在 Android 8.0 之前,如果应用在运行时请求权限并且被授予该权限,系统会错误地将属于同一权限组并且在清单中注册的其他权限也一起授予应用。
对于针对 Android 8.0 的应用,此行为已被纠正。系统只会授予应用明确请求的权限。然而,一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准。例如,假设某个应用在其清单中列出 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE。应用请求 READ_EXTERNAL_STORAGE,并且用户授予了该权限。如果该应用针对的是 API 级别 24 或更低级别,系统还会同时授予 WRITE_EXTERNAL_STORAGE,因为该权限也属于同一 STORAGE 权限组并且也在清单中注册过。如果该应用针对的是 Android 8.0,则系统此时仅会授予 READ_EXTERNAL_STORAGE;不过,如果该应用后来又请求 WRITE_EXTERNAL_STORAGE,则系统会立即授予该权限,而不会提示用户。
3、Android_ID
Android 8.0 改变了标识符的处理方式。
- 对于在 OTA 之前安装到某个版本 Android 8.0(API 级别 26)的应用,除非在 OTA 后卸载并重新安装,否则 ANDROID_ID 的值将保持不变。要在 OTA 后在卸载期间保留值,开发者可以使用密钥/值备份关联旧值和新值。
- 对于安装在运行 Android 8.0 的设备上的应用,ANDROID_ID 的值现在将根据应用签署密钥和用户确定作用域。应用签署密钥、用户和设备的每个组合都具有唯一的 ANDROID_ID 值。因此,在相同设备上运行但具有不同签署密钥的应用将不会再看到相同的 Android ID(即使对于同一用户来说,也是如此)。
- 只要签署密钥相同(并且应用未在 OTA 之前安装到某个版本的 O),ANDROID_ID 的值在软件包卸载或重新安装时就不会发生变化。
- 即使系统更新导致软件包签署密钥发生变化,ANDROID_ID 的值也不会变化。
要借助一个简单的标准系统实现应用获利,请使用广告 ID。广告 ID 是 Google Play 服务针对广告服务提供的唯一 ID,此 ID 可由用户重置。
4、允许安装未知来源应用
针对 8.0 的应用需要在 AndroidManifest.xml 中声明 REQUEST_INSTALL_PACKAGES 权限,否则将无法进行应用内升级。
<uses-permissionandroid:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
5、设置activity屏幕方向
只有不透明的全屏Activity可以自主设置界面方向,在全透明的Activity属性中设置横竖屏,会直接闪退,比如:
<activity android:name=".TranslucentCrashTestActivity"
android:theme="@style/TranslucentCrashTestTheme"
android:screenOrientation="portrait"/>
此为谷歌8.0上的bug,在8.1版本上已修复。
6、取消隐式广播
移除掉了所有的隐式广播(即清单文件中注册的广播),所以请使用显示广播进行注册(即代码注册)。
7、通知消息需要设置通知渠道才可显示
//只需要将渠道加进去,并且在NotificationCompat.Build中加入渠道id
NotificationManager systemService = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
NotificationChannel notificationChannel = new NotificationChannel("001", "测试", NotificationManager.IMPORTANCE_MAX);
systemService.createNotificationChannel(notificationChannel);
mBuilder = new NotificationCompat.Builder(this, "001");
}else {
mBuilder = new NotificationCompat.Builder(this);
}
Notification build = mBuilder.setContentTitle("标题").setContentText("内容").setWhen(System.currentTimeMillis()).setSmallIcon(R.mipmap.ic_launcher).build();
systemService.notify(1,build);
8、新增权限
- ANSWWER_PHONE_CALLS:接听呼入电话权限(需要动态申请),acceptRingingCall()进行接听操作
- READ_PHONE_NUMBERS:读取设备存储的电话号码(需要动态申请)
9、SharedPreferences闪退
8.0以后不能使用 MODE_WORLD_READABLE 方式创建SharedPreferences对象,否则会闪退,比如:
SharedPreferences read = getSharedPreferences(RELEASE_POOL_DATA, MODE_WORLD_READABLE)
需要将第二个参数修改成 MODE_PRIVATE。
10、后台服务限制
处于空闲状态时的应用不可以通过startService()启动服务,当然如果是前台服务则不会被限制,可以通过Context.startForegroundService() 来启动一个前台服务,即使应用在后台运行,系统也允许其调用 Context.startForegroundService()。不过,应用必须在创建服务后的五秒内调用该服务的
startForeground() 函数。
所以在Android 8.0 上启动服务时需要做如下判断:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(service);
}else {
startService(service);
}
四、Android 9.0系统
官方文档:Android 9.0
1、HTTP接口请求限制
Android 9.0 限制了明文流量(即HTTP接口)的网络请求,非加密的流量请求都会被系统禁止,或者直接使用HTTPS接口。
解决:
方式一:在Application属性中添加如下配置:
<application
android:usesCleartextTraffic="true">
方式二:在res目录下新建xml文件夹,并创建network_security_config.xml文件,内容如下:
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
之后在Application属性中配置 :
<application
android:networkSecurityConfig="@xml/network_security_config">
2、废弃Apache库
Android 9.0 彻底废弃Apache HTTP库,如果想要继续使用,需要在清单文件中添加如下配置:
<application>
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
</application>
3、新引入CALL_LOG权限组
将 READ_CALL_LOG、WRITE_CALL_LOG 和 PROCESS_OUTGOING_CALLS 权限移入该组。 在之前的 Android 版本中,这些权限位于 PHONE 权限组。
对于需要访问通话敏感信息(如读取通话记录和识别电话号码)的应用,该 CALL_LOG 权限组为用户提供了更好的控制和可见性。
如果您的应用需要访问通话记录或者需要处理去电,则您必须向 CALL_LOG 权限组明确请求这些权限。 否则会发生 SecurityException 异常。
4、限制访问电话号码
在未首先获得 READ_CALL_LOG 权限的情况下,除了应用的用例需要的其他权限之外,运行于 Android 9.0 上的应用无法读取电话号码或手机状态。
要通过 PHONE_STATE Intent 操作读取电话号码,同时需要 READ_CALL_LOG 权限和 READ_PHONE_STATE 权限。
要从 onCallStateChanged() 中读取电话号码,只需要 READ_CALL_LOG 权限。 不需要 READ_PHONE_STATE 权限。
5、限制非Activity场景启动Activity
从Android 9.0开始,只有当Intent flag中指定了FLAG_ACTIVITY_NEW_TASK,才允许在非Activity场景启动Activity。如果不在Intent添加FLAG_ACTIVITY_NEW_TASK,将无法通过非Activity的Context启动一个Activity,并且会抛异常。
6、限制后台对传感器的访问
App后台运行时,以下行为受限:
- 访问麦克风和摄像头。
- 使用连续报告模式的传感器(加速度计、陀螺仪等)不会接收事件。
- 使用变化或一次性报告模式的传感器不会接收事件。
7、 限制访问WiFi位置和连接信息
getConnectionInfo() 函数返回的 WifiInfo 对象受限,只有当App具有以下权限时,才能获得SSID和BSSID:
- ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION
- ACCESS_WIFI_STATE
- 还需要在设备上启用位置服务(Settings > Location)
8、从 Wi-Fi 服务函数中移除的信息
在 Android 9.0 中,下列事件和广播不接收用户位置或个人可识别数据方面的信息:
- WifiManager 中的 getScanResults() 和 getConnectionInfo() 函数。
- WifiP2pManager 中的 discoverServices() 和 addServiceRequest() 函数。
- NETWORK_STATE_CHANGED_ACTION 广播。
Wi-Fi 的 NETWORK_STATE_CHANGED_ACTION系统广播不再包含 SSID(之前为 EXTRA_SSID)、BSSID(之前为 EXTRA_BSSID)或连接信息(之前为 EXTRA_NETWORK_INFO)。 如果应用需要此信息,请改为调用 getConnectionInfo()。
9、电话信息依赖设备定位设置
如果用户在运行 Android 9.0上的设备停用设备定位,则以下函数不提供结果:
- getAllCellInfo()
- listen()
- getCellLocation()
- getNeighboringCellInfo()
10、前台服务
Android 8.0 增加startForegroundService()来创建一个前台服务,在Android 9.0 调用该函数时,还需要额外添加FOREGROUND_SERVICE权限,这是普通权限,否则会发生 SecurityException 异常。
11、对使用非 SDK 接口的限制
为帮助确保应用稳定性和兼容性,Android 9.0 对某些非 SDK 函数和字段的使用进行了限制;无论你是直接访问这些函数和字段,还是通过反射或 JNI 访问,这些限制均适用。 在 Android 9.0 中,你的应用可以继续访问这些受限的接口;该平台通过 toast 和日志条目提醒你注意这些接口。 如果你的应用显示这样的 toast,则必须寻求受限接口之外的其他实现策略。下表说明了当你的应用尝试访问屏蔽名单中的非 SDK 接口时可能会出现的预期行为:
访问方式 | 结果 |
---|---|
Dalvik 指令引用某个字段 | 抛出 NoSuchFieldError |
Dalvik 指令引用某个方法 | 抛出 NoSuchMethodError |
通过 Class.getDeclaredField() 或 Class.getField() 进行反射 | 抛出 NoSuchFieldException |
通过 Class.getDeclaredMethod() 、Class.getMethod() 进行反射 | 抛出 NoSuchMethodException |
通过 Class.getDeclaredFields() 、Class.getFields() 进行反射 | 结果中未获取到非 SDK 成员 |
通过 Class.getDeclaredMethods() 、Class.getMethods() 进行反射 | 结果中未获取到非 SDK 成员 |
通过 env->GetFieldID() 进行 JNI 调用 | 返回 NULL ,抛出 NoSuchFieldError |
通过 env->GetMethodID() 进行 JNI 调用 | 返回 NULL ,抛出 NoSuchMethodError |
12、设备硬件序列号弃用
在 Android 9.0 中,Build.SERIAL 始终设置为 "UNKNOWN" 以保护用户的隐私。如果你的应用需要访问设备的硬件序列号,你应改为请求 READ_PHONE_STATE 权限,然后调用 getSerial()。
13、多进程使用WebView注意无法共用同一数据目录
为改善 Android 9.0 中的应用稳定性和数据完整性,应用无法再让多个进程共用同一 WebView 数据目录。 此类数据目录一般存储 Cookie、HTTP 缓存以及其他与网络浏览有关的持久性和临时性存储。
在大多数情况下,你的应用只应在一个进程中使用 android.webkit 软件包中的类,例如 WebView 和 CookieManager。 例如,你应该将所有使用 WebView 的 Activity 对象移入同一进程。 你可以通过在应用的其他进程中调用 disableWebView(),更严格地执行“仅限一个进程”规则。 该调用可防止 WebView 在这些其他进程中被错误地初始化,即使是从依赖内容库进行的调用也能防止。
如果你的应用必须在多个进程中使用 WebView 的实例,则必须先利用 WebView.setDataDirectorySuffix() 函数为每个进程指定唯一的数据目录后缀,然后再在该进程中使用 WebView 的给定实例。 该函数会将每个进程的网络数据放入其在应用数据目录内自己的目录中。
注:即使你使用 setDataDirectorySuffix(),系统也不会跨应用的进程界限共享 Cookie 以及其他网络数据。 如果应用中的多个进程需要访问同一网络数据,你需要自行在这些进程之间复制数据。 例如,你可以调用 getCookie() 和 [setCookie()]
14、与加密有关的其他变更
- 使用 PBE 密钥时,如果 Bouncy Castle 需要初始化矢量 (IV),而您的应用未提供 IV,则会收到一条警告消息。
- ARC4 加密的 Conscrypt 实现允许您指定 ARC4/ECB/NoPadding 或 ARC4/NONE/NoPadding。
- Crypto Java 加密架构 (JCA) 提供程序现已被移除。 因此,如果你的应用调用 SecureRandom.getInstance("SHA1PRNG", "Crypto"),将会发生 NoSuchProviderException异常。
- 如果您的应用从大于密钥结构的缓冲区中解析 RSA 密钥,将不会再发生异常。
15、应用不再能访问 xt_qtaguid 文件夹中的文件
从 Android 9.0 开始,不再允许应用直接读取 /proc/net/xt_qtaguid 文件夹中的文件。 这样做是为了确保与某些根本不提供这些文件的设备保持一致。依赖这些文件的公开 API TrafficStats 和 NetworkStatsManager 继续按照预期方式运行。 但是,不受支持的 cutils 函数(例如 qtaguid_tagSocket())在不同设备上可能不会按照预期方式运行 , 甚至根本不运行。
16、Canvas变更
if (Build.VERSION.SDK_INT >= 28) {
canvas.clipPath(mPath);
} else {
canvas.clipPath(mPath, Region.Op.REPLACE);
}
17、新增V3签名方案
Android 9.0 新增了对 APK Signature Scheme v3 的支持。该架构提供的选择可以在其签名块中为每个签名证书加入一条轮转证据记录。 利用此功能,应用可以通过将 APK 文件过去的签名证书链接到现在签署应用时使用的证书,从而使用新签名证书来签署应用。
注:运行 Android 8.1(API 级别 27)或更低版本的设备不支持更改签名证书。 如果应用的 minSdkVersion 为 27 或更低,除了新签名之外,可使用旧签名证书来签署应用。
详细了解如何使用 apksigner 轮转密钥。
五、Android 10.0系统
官方文档:Android 10.0
1、HTTPS 连接变更
如果在 Android 10 上运行的应用将 null 传递给 setSSLSocketFactory(),则会出现 IllegalArgumentException。在以前的版本中,将 null 传递给 setSSLSocketFactory() 与传入当前的默认 SSL 套接字工厂效果相同。
2、android.preference 库已弃用
从 Android 10 开始,将弃用 android.preference 库。开发者应该改为使用 AndroidX preference 库,这是 Android Jetpack 的一部分。
3、存储权限
Android 10.0(Q) 在外部存储设备中为每个应用提供了一个“隔离存储沙盒”(例如 /sdcard)。任何其他应用都无法直接访问你应用的沙盒文件。由于文件是你应用的私有文件,因此你不再需要任何权限即可在外部存储设备中访问和保存自己的文件。此变更可让你更轻松地保证用户文件的隐私性,并有助于减少应用所需的权限数量。
沙盒,简单而言就是应用专属文件夹,并且访问这个文件夹无需权限。谷歌官方推荐应用在沙盒内存储文件的地址为Context.getExternalFilesDir()下的文件夹。比如要存储一张图片,则应放在Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)中。
1、以下将按访问的目标文件的地址介绍如何适配。
- 访问自己文件:Q中用更精细的媒体特定权限替换并取消了 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE权限,并且无需特定权限,应用即可访问自己沙盒中的文件。
- 访问系统媒体文件:Q中引入了一个新定义媒体文件的共享集合,如果要访问沙盒外的媒体共享文件,比如照片,音乐,视频等,需要申请新的媒体权限:READ_MEDIA_IMAGES、READ_MEDIA_VIDEO、READ_MEDIA_AUDIO,申请方法同原来的存储权限。
- 访问系统下载文件:对于系统下载文件夹的访问,暂时没做限制,但是,要访问其中其他应用的文件,必须允许用户使用系统的文件选择器应用来选择文件。
- 访问其他应用沙盒文件:如果你的应用需要使用其他应用在沙盒内创建的文件,请点击使用其他应用的文件,本文不做介绍。
所以请判断当应用运行在Q平台上时,取消对READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE两个权限的申请。并替换为新的媒体特定权限。
2、内部存储、外部存储、共享存储空间
Android 提供两类物理存储位置:内部存储空间和外部存储空间。在大多数设备上,内部存储空间小于外部存储空间。不过,所有设备上的内部存储空间都是始终可用的,因此在存储应用所依赖的数据时更为可靠。
可移除卷(例如 SD 卡)在文件系统中属于外部存储空间。Android 使用路径(例如 /sdcard)表示这些存储设备。默认情况下,应用本身存储在内部存储空间中。不过,如果你的 APK 非常大,也可以在应用的清单文件中指明偏好设置,以便将应用安装到外部存储空间:
<manifest ...
android:installLocation="preferExternal">
...
</manifest>
- 内部存储空间目录:这些目录既包括用于存储持久性文件的专属位置,也包括用于存储缓存数据的其他位置。系统会阻止其他应用访问这些位置,并且在 Android 10(API 级别 29)及更高版本中,系统会对这些位置进行加密。这些特征使得这些位置非常适合存储只有应用本身才能访问的敏感数据。但是请注意,这些目录的空间通常比较小。在将应用专属文件写入内部存储空间之前,应用应查询设备上的可用空间。如果内部存储空间不足以存储应用专属文件,请考虑改为使用外部存储空间。
- 外部存储空间目录:这些目录既包括用于存储持久性文件的专属位置,也包括用于存储缓存数据的其他位置。虽然其他应用可以在具有适当权限的情况下访问这些目录,但存储在这些目录中的文件仅供你的应用使用。如果你明确打算创建其他应用能够访问的文件,你的应用应改为将这些文件存储在外部存储空间的共享存储空间部分。
- 共享存储空间:如果用户数据可供或应可供其他应用访问,并且即使在用户卸载应用后也可对其进行保存,请使用共享存储空间。例如,如果应用允许用户拍摄照片,用户会希望即使卸载应用后仍可访问这些照片。因此,你应改为使用共享存储空间将此类文件保存到适当的媒体集合中。
共享存储空间又分为以下类型:
- 媒体内容:系统提供标准的公共目录来存储这些类型的文件,这样用户就可以将所有照片保存在一个公共位置,将所有音乐和音频文件保存在另一个公共位置,依此类推。你的应用可以使用此平台的 MediaStore API 访问此内容。
- 文档和其他文件:系统有一个特殊目录,用于包含其他文件类型,例如 PDF 文档和采用 EPUB 格式的图书。你的应用可以使用此平台的存储访问框架访问这些文件。
- 数据集:在 Android 11(API 级别 30)及更高版本中,系统会缓存多个应用可能使用的大型数据集。这些数据集可为机器学习和媒体播放等用例提供支持。应用可以使用 BlobStoreManager API 访问这些共享数据集。
3、如何访问内部存储、外部存储和共享存储空间
3.1、内部存储空间:
//getFilesDir() = /data/data/应用包名/files
File file = new File(context.getFilesDir(), filename);
//getCacheDir() = /data/data/应用包名/cache
File file = new File(context.getCacheDir(), filename);
注:getFilesDir和getCacheDir目录,为应用的内部存储专属目录,其他应用不可访问,应用自己本身读写不需要权限。
3.2、外部存储空间:
//getExternalFilesDir = /storage/emulated/0/Android/data/应用包名/files
File externalFileDir = new File(context.getExternalFilesDir(null), filename);
//getExternalCacheDir = /storage/emulated/0/Android/data/应用包名/cache
File externalCacheFile = new File(context.getExternalCacheDir(), filename);
注:在Android 10.0及以后,getExternalFilesDir和getExternalCacheDir目录被划分为应用的外部存储空间的专属目录,应用自己本身读写不需要权限。
3.3、共享存储空间:
//getRootDirectory = /system
File file = new File(Environment.getRootDirectory().getAbsolutePath());
//getDataDirectory = /data
File file = new File(Environment.getDataDirectory().getAbsolutePath());
//getStorageDirectory = /storage
File file = new File(Environment.getStorageDirectory().getAbsolutePath());
//getExternalStorageDirectory = /storage/emulated/0
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath());
//getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) = /storage/emulated/0/Download
//该目录属于getExternalStorageDirectory()的子目录
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath());
注:getRootDirectory()、getDataDirectory()、getStorageDirectory()目录应用在任何Android版本都不可访问。
getExternalStorageDirectory()目录访问权限分为以下三种情况:
1、Android 9.0及以下:在申请到READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE权限后可以进行读写。
2、Android10.0:在申请到READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE权限,并且在清单文件配置停止分区存储后可以进行读写,两个条件缺一不可。配置停止分区存储方式如下:
<!-- 停止分区存储只限制在Android 10.0起作用,在Android 11.0及以上版本该属性无效,会默认开启分区存储 -->
<application
android:requestLegacyExternalStorage="true">
</application>
3、Android10.0以上:无论是否申请到READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE权限,或者已配置停止分区存储,应用都不可以访问。如果想要继续访问getExternalStorageDirectory()目录中的文件,需要改为申请另一种权限:MANAGE_EXTERNAL_STORAGE,该权限拥有对共享存储空间(即:/storage/emulated/0)目录下的中所有文件的读写访问权限。申请方式如下为:
- 在清单中声明 MANAGE_EXTERNAL_STORAGE 权限。
- 使用Intent为 ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION 的方式将用户引导至一个系统设置页面,在该页面上,用户可以为你的应用启用以下选项:授予所有文件的管理权限。如需确定应用是否已获得 MANAGE_EXTERNAL_STORAGE 权限,可以调用 Environment.isExternalStorageManager()进行判断。
获得 MANAGE_EXTERNAL_STORAGE 权限后,getExternalStorageDirectory(即:/storage/emulated/0)目录下的文件除了 /storage/emulated/0/Android/data/ 目录外,应用都拥有写入和读写权限,此写入权限包括文件路径访问权限。但是获得此权限的应用仍然无法访问属于其他应用的应用专属目录,因为这些目录在存储卷上显示为 /storage/emulated/0/Android/data/ 的子目录。
4、如何在Android 10.0及以上系统正确的读写文件?
4.1、浏览设备上的图库,并返回图片地址
可以使用照片选择器。照片选择器是作为使用媒体库的替代方案,Android 照片选择器工具为用户提供了安全的内置媒体文件选择方式,让用户无需向应用授予对整个媒体库的访问权限。此功能仅适用于支持的设备。具体使用请参阅照片选择器指南。
4.2、读写图片、音频、视频
读写图片和音视频分为两种情况:
1、读写自己应用创建的媒体文件
在搭载 Android 10 或更高版本的设备上,无需任何存储相关权限即可访问和修改你自己应用拥有的媒体文件,包括 MediaStore.Downloads 集合中的文件。例如,如果你正在开发一款相机应用,则无需请求存储相关权限,因为你的应用拥有你将写入媒体库的图片。
2、读写其他应用创建的媒体文件
如需访问其他应用创建的媒体文件,你必须声明适当的存储相关权限,并且文件必须位于以下任一媒体集合中:
- MediaStore.Images
- MediaStore.Video
- MediaStore.Audio
这三个媒体目录所对应的权限为:
<!-- 仅当你的应用程序需要访问其他应用程序创建的图像或照片时才需要 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<!-- 仅当你的应用程序需要访问其他应用程序创建的视频时才需要 -->
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<!-- 仅当你的应用程序需要访问其他应用程序创建的音频时才需要 -->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
注:Android 10.0以下系统,则需要请求READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE权限才能访问任何媒体文件,Android 10.0及以上系统不再请求这两个权限。
4.3、读写非媒体文件
读写非媒体文件,比如创建和读写文档类型的文件,可以使用Android 10.0提供的存储访问框架,在该框架支持下,创建、修改、访问你所创建的文档文件,不需要任何系统权限。
存储访问框架支持以下访问文件和其他文档的用例。
- 创建新文件:ACTION_CREATE_DOCUMENT intent 操作支持用户将文件保存在特定位置。
- 打开文档或文件:ACTION_OPEN_DOCUMENT intent 操作支持用户选择要打开的特定文档或文件。
- 授予对目录内容的访问权限:ACTION_OPEN_DOCUMENT_TREE intent 操作在 Android 5.0(API 级别 21)及更高版本中提供,支持用户选择特定目录,授予应用对该目录中所有文件和子目录的访问权限。
具体使用请参考:从共享存储空间访问文档和其他文件
4、在后台运行时访问设备位置信息需要权限
为了让用户更好地控制应用对位置信息的访问权限,Android 10.0 引入了 ACCESS_BACKGROUND_LOCATION 权限。
与 ACCESS_FINE_LOCATION 和 ACCESS_COARSE_LOCATION 权限不同,ACCESS_BACKGROUND_LOCATION 权限仅会影响应用在后台运行时对位置信息的访问权限。除非符合以下条件之一,否则应用将被视为在后台访问位置信息:
- 应用的某个 Activity 可见。
- 应用正在运行前台服务。
如果你的应用在 Android 10 或更高版本上运行,但其目标平台是 Android 9(API 级别 28)或更低版本,则该平台具有以下行为:
- 如果你的应用为 ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION 声明了 <uses-permission> 元素,则系统会在安装期间自动为 ACCESS_BACKGROUND_LOCATION 添加 <uses-permission> 元素。
- 如果你的应用请求了 ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION,系统会自动将 ACCESS_BACKGROUND_LOCATION 添加到请求中。
简而言之:如果应用有后台服务,并且需要访问位置信息,比如滴滴,那么就需要申请ACCESS_BACKGROUND_LOCATION 权限。
5、针对从后台启动 Activity 的限制
在启动新的Activity时,只有在满足以下条件至少一种时才可启动,具体可参考:限制启动Activity的例外情况
6、随机分配 MAC 地址
默认情况下,在搭载 Android 10 或更高版本的设备上,系统会传输随机分配的 MAC 地址。
7、对 /proc/net 文件系统的访问权限实施了限制
在搭载 Android 10 或更高版本的设备上,应用无法访问 /proc/net,其中包含与设备的网络状态相关的信息。需要访问这些信息的应用(如 VPN)应使用 NetworkStatsManager 或 ConnectivityManager 类。
8、对不可重置的设备标识符实施了限制
从 Android 10 开始,应用必须具有 READ_PRIVILEGED_PHONE_STATE
特许权限才能访问设备的不可重置标识符(包含 IMEI 和序列号)。
受影响的方法包括:
- Build
- getSerial()
- TelephonyManager
- getImei()
- getDeviceId()
- getMeid()
- getSimSerialNumber()
- getSubscriberId()
如果您的应用没有该权限,但你仍尝试查询不可重置标识符的相关信息,则平台的响应会因目标 SDK 版本而异:
- 如果应用以 Android 10 或更高版本为目标平台,则会发生 SecurityException。
- 如果应用以 Android 9(API 级别 28)或更低版本为目标平台,则相应方法会返回 null 或占位符数据(如果应用具有 READ_PHONE_STATE 权限)。否则,会发生 SecurityException。
9、限制了对剪贴板数据的访问权限
除非您的应用是默认输入法 (IME) 或是目前处于焦点的应用,否则它无法访问 Android 10 或更高版本平台上的剪贴板数据。
10、保护 USB 设备序列号
如果您的应用以 Android 10 或更高版本为目标平台,则该应用只能在用户授予其访问 USB 设备或配件的权限后才能读取序列号。
12、面向用户的权限检查(针对旧版应用)
如果您的应用以 Android 5.1(API 级别 22)或更低版本为目标平台,则用户首次在搭载 Android 10 或更高版本的平台上使用您的应用时,系统会向其显示当前应用所使用到的权限列表界面,此界面让用户有机会撤消系统先前在安装时向应用授予的访问权限。
13、其他不常用到的更新
- 对访问摄像头详情和元数据的权限实施了限制。
- 对启用和停用 WLAN 实施了限制。
- 对直接访问已配置的 WLAN 网络实施了限制
- 一些电话 API、蓝牙 API 和 WLAN API 需要精确位置权限
- 限制对屏幕内容的访问
具体参考官方文档:Android 10.0 中的隐私权变更
六、Android 11.0系统
官方文档:Android 11.0
1、软件包可见性
Android 11.0 更改了应用查询用户已在设备上安装的其他应用以及与之交互的方式。使用 <queries> 元素,应用可以定义一组自身可访问的其他软件包。通过告知系统应向你的应用显示哪些其他软件包,此元素有助于鼓励最小权限原则。
如果你的应用以 Android 11 或更高版本为目标平台,你可能需要在应用的清单文件中添加 <queries> 元素。在 <queries> 元素中,你可以按软件包名称、intent 签名或提供程序授权指定软件包。
1.1、自动可见的应用
即使你的应用以 Android 11(API 级别 30)或更高版本为目标平台,以下类型的应用也始终对你的应用可见:
- 你自己的应用。
- 实现 Android 核心功能的某些系统软件包,如媒体提供程序。详细了解如何在运行你应用的设备上确定自动显示哪些软件包。
- 安装了你应用的应用。
- 使用 startActivityForResult() 方法在你的应用中启动 activity 的任何应用,正如如何获取 activity 的结果这一指南中所述。
- 启动或绑定到你应用中的某项服务的任何应用。
- 访问你应用中的 Content Provider 的任何应用。
- 具有 Content Provider 的任何应用,其中你的应用已被授予 URI 权限来访问该 Content Provider。
- 接收你应用的输入的任何应用。这种情况仅适用于你的应用作为输入法应用提供输入。
此外,你可以使用隐式或显式 intent 来启动另一应用的 activity,无论这个应用是否对你的应用可见。
1.2、自动可见的系统软件包
实现 Android 核心功能的某些系统软件包会自动对你的应用可见,即使您的应用以 Android 11.0(API 级别 30)或更高版本为目标平台也是如此。这组特定的软件包取决于运行你应用的设备。
如需查看特定设备的完整软件包列表,请在开发机器上的终端中运行以下命令:
adb shell dumpsys package queries
1.3、如何在Android 11.0系统上声明你的应用与其他应用交互
如果您的应用以 Android 11(API 级别 30)或更高版本为目标平台,并且需要与应用(自动可见的应用除外)交互,请在您应用的清单文件中添加 <queries> 元素。
- 按包名添加:
<manifest package="com.example.game">
<queries>
<package android:name="com.example.store" />
<package android:name="com.example.services" />
</queries>
...
</manifest>
- 按Intent过滤器添加:
<manifest package="com.example.game">
<queries>
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="image/jpeg" />
</intent>
</queries>
...
</manifest>
- 按授权添加:
<manifest package="com.example.suite.enterprise">
<queries>
<provider android:authorities="com.example.settings.files" />
</queries>
...
</manifest>
1.4、常见用例
- Android 11.0 跳转系统浏览器需要添加一下配置:
<queries>
<!-- Android 11.0 系统跳转系统浏览器时需要 -->
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
</queries>
- 打开文件
如果应用需要处理文件或附件,例如需要检查设备能否打开给定文件,通常最简单的办法是尝试启动可以处理该文件的 Activity。为此,请使用包含 ACTION_VIEW intent 操作和代表特定文件的 URI 的 intent。如果设备上没有可用的应用,你的应用可捕获 ActivityNotFoundException。在异常处理逻辑中,你可以显示错误或尝试自行处理相关文件。
如果你的应用必须事先知道其他应用能否打开给定文件,请在以下代码段中将 <intent> 元素作为 <queries> 元素的一部分添加到清单中。如果你在编译时已经知道文件类型是什么,请包括文件类型。然后,你可以使用 intent 调用 resolveActivity()
来检查是否有可用应用。
<!-- Place inside the <queries> element. -->
<intent>
<action android:name="android.intent.action.VIEW" />
<!-- If you don't know the MIME type in advance, set "mimeType" to "*/*". -->
<data android:mimeType="application/pdf" />
</intent>
具体用例可参考官网:根据用例配置软件包可见性
注:使用<queries>
元素需具备以下条件:
- 安装 Android Studio 指定版本3.3.3、3.4.3,或者3.6.1 及以上版本。
- 安装 Android Studio 支持的最新版 Gradle。
- 将应用的 targetSdkVersion 设为 30。
2、强制执行分区存储
在 Android 11.0 上运行但以 Android 10(API 级别 29)为目标平台的应用仍可请求 requestLegacyExternalStorage 属性。应用可以利用此标记暂时停用与分区存储相关的变更,例如授予对不同目录和不同类型的媒体文件的访问权限。当你将应用更新为以 Android 11 为目标平台后,系统会忽略 requestLegacyExternalStorage 标记,强制开启分区存储。
注意:如果在Android 11.0(API 级别 30)上运行,但是应用覆盖安装的话,可以增加android:preserveLegacyExternalStorage=“true”属性,可以暂时关闭分区存储,但是一旦用户卸载重装,该属性就会失效,重新安装后的应用会开启分区存储。
3、外部存储设备上的应用专属目录
从 Android 11 开始,应用无法在外部存储设备上创建自己的应用专用目录。如需访问系统为您的应用提供的目录,请调用 getExternalFilesDirs()。如果访问系统中的应用专属缓存目录,请调用getExternalCacheDir()。并且在 Android 11.0 上,应用无法再访问外部存储设备中的任何其他应用的专用于特定应用的目录中的文件。
4、支持并发使用多个摄像头
Android 11 添加了 API 以查询对同时使用多个摄像头(包括前置摄像头和后置摄像头)的支持。
如需在运行应用的设备上检查支持情况,请使用以下方法:
- getConcurrentCameraIds() 可返回摄像头 ID 组合 Set,这些组合可与有保证的数据流组合并发进行流式传输(如果它们是由同一应用进程配置的)。
- isConcurrentSessionConfigurationSupported() 可查询摄像头设备是否可以并发支持相应的会话配置。
5、应用进程退出原因
Android 11.0 引入了 ActivityManager.getHistoricalProcessExitReasons() 方法,用于报告近期任何进程终止的原因。应用可以使用此方法收集崩溃诊断信息,例如进程终止是由于 ANR、内存问题还是其他原因所致。此外,你还可以使用新的 setProcessStateSummary() 方法存储自定义状态信息,以便日后进行分析。
getHistoricalProcessExitReasons() 方法会返回 ApplicationExitInfo 类的实例,该类包含与应用进程终止相关的信息。通过对此类的实例调用 getReason(),你可以确定应用进程终止的原因。例如,返回值为 REASON_CRASH 表示你的应用中发生了未处理的异常。如果您的应用需要确保退出事件的唯一性,它可以维护一个应用专用的标识符,如基于来自 getTimestamp() 方法的时间戳的哈希值。
注意:某些设备无法报告内存不足终止事件。在这些设备上,getHistoricalProcessExitReasons() 方法会返回 REASON_SIGNALED 而不是 REASON_LOW_MEMORY,并且 getStatus() 的返回值为 SIGKILL。
如需检查设备是否可以报告内存不足终止事件,请调用 ActivityManager.isLowMemoryKillReportSupported()。
6、APK 签名方案 v4
Android 11.0 添加了对 APK 签名方案 v4 的支持。此方案会在单独的文件 (apk-name.apk.idsig) 中生成一种新的签名,但在其他方面与 v2 和 v3 类似。没有对 APK 进行任何更改。此方案支持 ADB 增量 APK 安装,这样会加快 APK 安装速度。
7、永久 SIM 卡标识符
在 Android 11.0 及更高版本中,使用 getIccId() 方法访问不可重置的 ICCID 受到限制。该方法会返回一个非 null 的空字符串。如需唯一标识设备上安装的 SIM 卡,请改用 getSubscriptionId() 方法。订阅 ID 会提供一个索引值(从 1 开始),用于唯一识别已安装的 SIM 卡(包括实体 SIM 卡和电子 SIM 卡)。除非设备恢复出厂设置,否则此标识符的值对于给定 SIM 卡是保持不变的。
8、电话号码相关权限
Android 11.0 更改了应用在读取电话号码时使用的与电话相关的权限。
- TelecomManager 类中的 getLine1Number() 方法
- TelecomManager 类中的 getMsisdn() 方法
也就是当用到这两个API的时候,原来的READ_PHONE_STATE权限不管用了,需要添加READ_PHONE_NUMBERS权限并且动态获取之后才行。
9、自定义消息框视图(Toast)被屏蔽
Android 11.0上不允许后台显示自定义视图的Toast,如果想要从后台显示Toast,可以使用普通Toast。
10、强制使用V2签名方案
对于以 Android 11.0(API 级别 30)为目标平台,且目前仅使用 APK 签名方案 v1 签名的应用,现在还必须使用 APK 签名方案 v2 或更高版本进行签名。用户无法在搭载 Android 11 的设备上安装或更新仅通过 APK 签名方案 v1 签名的应用。
11、媒体intent操作需要系统默认相机
从 Android 11.0 开始,只有预装的系统相机应用可以响应以下 intent 操作:
- android.media.action.VIDEO_CAPTURE
- android.media.action.IMAGE_CAPTURE
- android.media.action.IMAGE_CAPTURE_SECURE
也就是说,如果我调用intent唤起照相机,使用VIDEO_CAPTURE的action,只有系统的相机能够响应,而第三方的相机应用不会响应了。
//无法唤起第三方相机了,只能唤起系统相机
Intent intent = new Intent();
intent.setAction(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
startActivity(intent);
官方给的建议是如果要使用特定的第三方相机应用来代表其捕获图片或视频,可以通过为intent设置软件包名称或组件来使这些intent变得明确。
12、前台位置信息访问权限
如果应用的功能在下列某种情况下访问设备的当前位置信息,系统就会认为应用需要使用前台位置信息:
- 属于应用的某个 Activity 可见。
- 应用的某个前台服务正在运行中。当有前台服务在运行时,系统会显示一条常驻通知来提醒用户注意。当应用被置于后台时(例如当用户按设备上的主屏幕按钮或关闭设备的显示屏时),其位置信息访问权限会得到保留。
此外,建议你声明 location 的前台服务类型,如以下代码段所示。在 Android 10(API 级别 29)及更高版本中,你必须声明此前台服务类型。
<service
android:name="MyNavigationService"
android:foregroundServiceType="location">
</service>
当应用请求 ACCESS_COARSE_LOCATION 权限或 ACCESS_FINE_LOCATION 权限时(如以下代码段所示),就是在声明需要获取前台位置信息:
<manifest ... >
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 仅当你的应用程序用于精确位置访问时添加 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>
13、后台位置信息访问权限
除了前台位置信息部分所述的两种情况之外,如果应用在任何其他情况下访问设备的当前位置信息,系统就会认为应用需要使用后台位置信息。后台位置信息精确度与前台位置信息精确度相同,具体取决于应用声明的位置信息权限。
在 Android 10.0(API 级别 29)及更高版本中,你必须在应用的清单中声明 ACCESS_BACKGROUND_LOCATION 权限,以便请求在运行时于后台访问位置信息。在较低版本的 Android 系统中,当应用获得前台位置信息访问权限时,也会自动获得后台位置信息访问权限。
<manifest ... >
<!-- 仅在请求后台位置访问时才需要,且是在Android 10(API级别29)及更高版本。 -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
</manifest>
注意:申请后台位置权限,要在获取前台权限之后,顺序不能乱。
14、文档访问限制
Android 10.0(api 29)上通可以过SAF(存储访问框架–Storage Access Framework)来访问外部存储上的公共目录,但是Android 11.0(api 30)再次升级,部分目录和文件不能访问了,具体如下:
无法再使用 ACTION_OPEN_DOCUMENT_TREE intent 操作请求访问以下目录:
- 内部存储卷的根目录。
- 设备制造商认为可靠的各个 SD 卡卷的根目录,无论该卡是模拟卡还是可移除的卡。可靠的卷是指应用在大多数情况下可以成功访问的卷。
- Download 目录。
无法再使用 ACTION_OPEN_DOCUMENT_TREE 或 ACTION_OPEN_DOCUMENT intent 操作请求用户从以下目录中选择单独的文件:
- Android/data/ 目录及其所有子目录。
- Android/obb/ 目录及其所有子目录。
15、设备到设备文件传输
如果你的应用以 Android 11.0(api 30) 为目标平台,你将无法再使用 allowBackup 属性停用应用文件的设备到设备迁移。系统会自动启用此功能。不过,即使你的应用以 Android 11.0(api 30) 为目标平台,你也可以通过将 allowBackup 属性设置为 false 来停用应用文件的云端备份和恢复。
android:allowBackup属性:
- 代表是否允许应用参与备份和恢复基础架构。如果将此属性设为 false,则永远不会为该应用执行备份或恢复,即使是采用全系统备份方法也不例外(这种备份方法通常会通过 adb 保存所有应用数据)。此属性的默认值为 true。
16、单次授权
从 Android 11.0(API 级别 30)开始,每当你的应用请求与位置、麦克风或相机相关的权限时,面向用户的权限对话框都会包含仅限这一次选项,如果用户在对话框中选择此选项,系统会向应用授予临时的单次授权。然后,应用可以在一段时间内访问相关数据,具体时间取决于应用的行为和用户的操作:
- 当应用的 activity 可见时,应用可以访问相关数据。
- 如果用户将应用转为后台运行,应用可以在短时间内继续访问相关数据。
- 如果您在 activity 可见时启动了一项前台服务,并且用户随后将您的应用转到后台,那么您的应用可以继续访问相关数据,直到该前台服务停止。
如果用户撤消单次授权(例如在系统设置中撤消),无论你是否启动了前台服务,应用都无法访问相关数据。与任何权限一样,如果用户撤消了应用的单次授权,应用进程就会终止。当用户下次打开应用并且应用中的某项功能请求访问位置信息、麦克风或摄像头时,系统会再次提示用户授予权限。简单的说,就是在申请与位置信息、麦克风或摄像头相关的权限时,系统会自动提供一个单次授权的选项,只供这一次权限获取。然后用户下次打开app的时候,系统会再次提示用户授予权限。
七、Android 12.0系统
官方文档:Android 12.0
1、Display.getRealSize 和 getRealMetrics:废弃和限制
在 Android 11.0 中,我们引入了 WindowMetrics API 并废弃了以下方法:
- Display.getSize()
- Display.getMetrics()
在 Android 12.0 中,我们继续建议使用 WindowMetrics,并且正在逐步废弃以下方法:
- Display.getRealSize()
- Display.getRealMetrics()
2、大致位置权限
在搭载 Android 12.0 或更高版本的设备上,用户可以要求你的应用只能访问大致位置信息。
- 如果你的应用请求 ACCESS_COARSE_LOCATION 但未请求 ACCESS_FINE_LOCATION,则此变更不会影响你的应用。
- 如果你的应用请求 ACCESS_FINE_LOCATION 运行时权限,你还应请求 ACCESS_COARSE_LOCATION 权限,以便处理用户授予应用大致位置访问权限的情形。你应该在单个运行时请求中包含这两项权限。
粗略位置: 精确到2平方公里的位置值,请求 ACCESS_COARSE_LOCATION 权限可以获得。
精确位置: 精确到50米以内的位置值,请求 ACCESS_FINE_LOCATION 权限可以获得。
3、蓝牙权限
Android 12.0 引入了 BLUETOOTH_SCAN、BLUETOOTH_ADVERTISE 和 BLUETOOTH_CONNECT 权限。这些权限可让以 Android 12.0 为目标平台的应用更轻松地与蓝牙设备互动,尤其是不需要访问设备位置信息的应用。
4、应用无法关闭系统对话框
为了加强用户与应用和系统互动时的控制,从 Android 12.0 开始,弃用了 ACTION_CLOSE_SYSTEM_DIALOGS intent 操作。除了一些特殊情况之外,当应用尝试调用包含此操作的 intent 时,系统会基于应用的目标 SDK 版本执行以下操作之一:
- 如果应用以 Android 12 或更高版本为目标平台,则会发生 SecurityException。
- 如果应用以 Android 11(API 级别 30)或更低版本为目标平台,则系统不会执行 intent,并且 Logcat 中会显示以下消息:
E ActivityTaskManager Permission Denial: \
android.intent.action.CLOSE_SYSTEM_DIALOGS broadcast from \
com.package.name requires android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS, \
dropping broadcast.
在以下情况下,应用仍然可以在 Android 12.0 或更高版本上关闭系统对话框:
- 你的应用运行的是插桩测试。
- 你的应用以 Android 11.0 或更低版本为目标平台,并在抽屉式通知栏顶部显示一个窗口。
- 你的应用以 Android 11.0 或更低版本为目标平台。此外,用户已与通知互动,可能使用了通知的操作按钮,你的应用正在处理服务或广播接收器来响应该用户操作。
- 你的应用以 Android 11.0 或更低版本为目标平台并且具有有效的无障碍服务。如果你的应用以 Android 12.0 为目标平台并且想要关闭通知栏,请改用 GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE 无障碍操作。
5、activity 生命周期
Android 12.0 更改了在按下“返回”按钮时系统对为其任务根的启动器 activity 的默认处理方式。在以前的版本中,系统会在按下“返回”按钮时finsh这些 activity。在 Android 12.0 中,现在系统会将 activity 及其任务移到后台,而不是finsh activity。当使用主屏幕按钮或手势从应用中导航出应用时,新行为与当前行为一致。
注意:系统仅会将新行为应用于为其任务根的启动器 activity,即使用 ACTION_MAIN 和 CATEGORY_LAUNCHER 声明 intent 过滤器的 activity。对于其他 activity,在按下“返回”按钮时,系统会像以前一样finsh activity。
对于大多数应用而言,此变更意味着使用“返回”按钮退出应用的用户可以更快地从温状态恢复应用,而不必从冷状态完全重启应用。
建议你针对此变更测试你的应用。如果你的应用目前替换 onBackPressed() 来处理返回导航并finsh Activity,请更新你的实现来调用 super.onBackPressed() 而不是finsh Activity。调用 super.onBackPressed() 可在适当时将 activity 及其任务移至后台,
6、圆角 API
Android 12 引入了 RoundedCorner 和 WindowInsets.getRoundedCorner(int position),它们可以提供圆角的半径和中心点。
具体参考:https://developer.android.google.cn/guide/topics/ui/look-and-feel/rounded-corners
7、隐藏应用叠加窗口
为了让开发者能够更好地控制用户在与开发者的应用互动时会看到什么内容,Android 12.0 引入了隐藏由具有 SYSTEM_ALERT_WINDOW 权限的应用绘制的叠加窗口的功能。
声明 HIDE_OVERLAY_WINDOWS 权限后,应用可以调用 setHideOverlayWindows() 以指明当应用自己的窗口可见时所有 TYPE_APPLICATION_OVERLAY 类型的窗口都应隐藏。在显示敏感屏幕(如交易确认流程)时,应用可能会选择这样做。显示 TYPE_APPLICATION_OVERLAY 类型窗口的应用应考虑可能更适合其用例的替代方案,如画中画或气泡。
8、设备芯片组信息
Android 12.0 向 android.os.Build 添加了两个常量,它们可通过 SDK 公开 SoC 芯片组供应商和型号信息。您可以通过分别调用 Build.SOC_MANUFACTURER 和 Build.SOC_MODEL 来检索此信息。
9、麦克风和摄像头切换开关
从Android 12.0 开始,用户可以通过状态栏下拉菜单中两个新增的切换开关选项,一键启用/停用摄像头和麦克风使用权限。
这里的「使用权限」针对的是设备上的所有App,是全局的,不要和Android 6.0的「运行时权限」混淆。如果用户主动关闭了摄像头或麦克风的使用权限,那么当下次App再需要启动摄像头或麦克风时,系统就会提醒用户,相关硬件的使用权限已关闭,并申请重新开启。
10、更安全的组件导出
以Android 12.0 为目标平台的App,如果其包含的四大组件中使用到了 intent 过滤器(intent-filter),则必须显示声明 android:exported 属性,否则App将无法在 Android 12.0 及更高系统版本的设备上安装。
11、PendingIntent可变性
以 Android 12.0 为目标平台的App,在构建PendingIntent时,需要指定Flag为FLAG_IMMUTABLE
(建议)或FLAG_MUTABLE
二者之一,否则App将崩溃。
12、前台服务启动限制
Android 12.0 对应用从后台启动前台服务的行为做出限制,除了后台启动限制的豁免 等少数情况外,如果应用尝试在后台运行时启动前台服务,系统会抛出ForegroundServiceStartNotAllowedException异常。应用可以使用WorkManager的加急工作来执行后台任务。
13、精确的闹钟权限
Android 12系统引入了新的权限 android.permission.SCHEDULE_EXACT_ALARM,设置AlarmManager 精准闹钟的应用必须在 Manifest中请求 SCHEDULE_EXACT_ALARM 权限。
新增了一个新的API — — canScheduleExactAlarms(),用于检查应用的精准闹钟权限状态。
14、应用启动画面
从Android 12.0 开始,所有的App在每次启动时(特指冷启动与温启动),系统都会为我们加上一个默认的启动画面。
该启动画面主要由以下4个元素组成,分别为:
- 应用图标:应该是矢量可绘制对象,它可以是静态或动画形式。虽然动画的时长可以不受限制,但建议不超过1000毫秒。默认情况下,使用启动器图标。
- 图标背景:可选,在图标与窗口背景之间需要更高的对比度时图标背景很有用。
- 前景遮罩:可选,前景的三分之一被遮盖。
- 窗口背景:由不透明单色组成。如果窗口背景已设置且为纯色,则未设置相应的属性时默认使用该背景。
适配方案:
在SplashScreen API之前,我们通常是利用 SplashActivity 的背景图 android:windowBackground 来实现应用启动转场效果,这个大家都很熟悉了。如果你不做任何适配,那么根据你配置的 windowBackground 资源值,在 Android 12 上会有不同的效果:
- windowBackground 采用 @color/单色,则系统会使用该单色和应用的启动图标来构成启动效果,这可能与预期效果不符。
- windowBackground 采用@drawable/图片,则系统会继续使用该图片来构成启动效果,这个体验与低版本系统一致。
因此,如果你的应用采用的是 windowBackground 为图片资源的方式,那么你不适配也没有问题。需要升级启动效果的话,可以参考:https://developer.android.google.cn/guide/topics/ui/splash-screen
15、应用休眠
Android 11.0 就已经引入了应用休眠机智,如果用户有几个月没有与应用交互,那么系统会将应用置于休眠/冬眠状态,Android 12.0 在此基础上扩展了应用休眠机智:
- Android 11.0:重置已授予的运行时敏感权限;
- Android 12.0:重置已授予的运行时敏感权限;无法从后台运行任务;无法接受推送通知;应用缓存文件会被删除。
八、Android 13.0系统
官方文档:Android 13.0
1、通知运行时权限
在Android13之前,App只需要使用NotificationManager即可向终端用户推送通知栏消息。Android13则引入了新的运行时通知权限:android.permission.POST_NOTIFICATIONS。对此,App开发者需要予以重点关注。
适配:
1、声明权限
<manifest ...>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application ...>
...
</application>
</manifest>
2、代码动态申请
3、弹出通知权限对话框,对话框内容分为以下三种:
- 选择允许
- 选择不允许
- 滑开对话框,不按任何一个按钮
a、如果用户选择允许选项,你的应用可以执行以下操作:
- 发送通知。可以使用所有通知渠道。
- 发送与前台服务相关的通知。这些通知会显示在抽屉式通知栏中。
b、如果用户选择“不允许”
如果用户选择不允许选项,你的应用将无法发送通知,除非该应用符合豁免条件。除了几个特定角色之外,所有通知渠道都会被屏蔽。这类似于用户在系统设置中手动关闭应用的所有通知后发生的行为。
注意:如果你的应用以 12L 或更低版本为目标平台,并且用户点按不允许(即使仅点按一次),那么系统不会再次提示用户,直到发生以下任一情况:
- 用户卸载并重新安装你的应用。
- 你将应用更新为以 Android 13 或更高版本为目标平台。
c、如果用户滑开对话框(即他们既没有选择允许,也没有选择不允许),通知权限的状态不会变化。
对新安装的应用的影响:
如果用户在搭载 Android 13.0 或更高版本的设备上安装您的应用,应用的通知默认处于关闭状态。在你请求新的权限且用户向你的应用授予该权限之前,你的应用都将无法发送通知。
权限对话框的显示时间取决于应用的目标 SDK 版本:
- 如果你的应用以 Android 13 或更高版本为目标平台,应用将可以完全自行控制权限对话框的显示时间。你可以借此机会向用户说明应用需要此权限的原因,进而鼓励他们授予该权限。
- 如果你的应用以 12L(API 级别 32)或更低版本为目标平台,在你创建通知渠道后您的应用首次启动 activity 时,或在你的应用启动一个 activity,然后创建它的第一个通知渠道时,系统会显示该权限对话框。这通常是在应用启动时。
对现有应用更新的影响:
- 为了最大限度地减少与通知权限相关的中断,当用户将其设备升级到 Android 13 或更高版本后,系统会自动向所有符合条件的应用预先授予相应权限。换言之,这些应用可以继续向用户发送通知,而用户不会看到运行时权限提示。
预先授予权限的资格条件:
- 你的应用要获得自动预先授权必须满足以下条件:应用必须已具有通知渠道,并且用户未在搭载 12L 或更低版本的设备上明确停用应用的通知。如果用户在搭载 12L 或更低版本的设备上停用了应用的通知,当设备升级到 Android 13 或更高版本后,该停用会继续有效。
2、通知权限会影响前台服务的显示
如果用户拒绝授予通知权限,就不会在抽屉式通知栏中看到与前台服务相关的通知。 不过,无论是否授予通知权限,用户都会在 前台服务(FGS) 任务管理器 中看到与前台服务相关的通知。
3、前台服务 (FGS) 任务管理器
无论应用采用何种目标 SDK 版本,Android 13.0(API 级别 33)都允许用户从抽屉式通知栏中停止前台服务。这项新功能称为前台服务 (FGS) 任务管理器,它会显示当前正在运行前台服务的应用列表。此列表的标签为使用中的应用。每个应用旁边都有一个停止按钮。抽屉式通知栏底部有一个按钮,用于指示当前在后台运行的应用的数量。按此按钮时,系统会显示一个对话框,其中会列出不同应用的名称。每个应用的右侧都有一个“停止”按钮。当用户在 FGS 任务管理器中按您应用旁边的停止按钮时,系统会停止您的整个应用,而不仅仅是正在运行的前台服务。当用户按停止按钮时,系统不会向您的应用发送任何回调。
比较“向上滑动”用户操作和“强行停止”用户操作的行为:
FGS 任务管理器 | 向上滑动 | 强行停止 | |
---|---|---|---|
立即从内存中移除应用 | ✔ | ✔ | |
停止媒体播放 | ✔ | ✔ | |
停止 FGS/移除关联的通知 | ✔ | ✔ | |
移除 activity 返回堆栈 | ✔ | ✔ | ✔ |
从历史记录中移除应用 | ✔ | ✔ | |
取消预定作业 | ✔ | ||
取消闹钟 | ✔ |
以下应用可以运行前台服务,而完全不会显示在任务管理器中:
- 系统级应用
- 安全应用,即具有 ROLE_EMERGENCY 角色的应用
- 处于演示模式的设备上的应用
当以下类型的应用运行前台服务时,它们会显示在 FGS 任务管理器中,但应用名称旁边没有可以供用户按的停止按钮:
- 设备所有者应用
- 资料所有者应用
- 常驻应用
- 具有 ROLE_DIALER 角色的应用
4、WiFi权限变更
在以前的 Android 版本中,用户需要向您的应用授予 ACCESS_FINE_LOCATION 权限,应用才能完成一些常见的 Wi-Fi 用例。
由于用户很难将位置信息权限与 Wi-Fi 功能相关联,因此 Android 13(API 级别 33)在 NEARBY_DEVICES 权限组中引入了运行时权限,适用于管理设备与附近 Wi-Fi 接入点连接情况的应用。此权限 (NEARBY_WIFI_DEVICES) 可满足以下 Wi-Fi 用例:
- 查找或连接到附近的设备,如打印机或媒体投射设备。通过该工作流,您的应用可以完成以下类型的任务:
- 通过带外方式(例如通过 BLE)接收 AP 信息。
- 使用仅限本地使用的热点,通过 Wi-Fi 感知和连接功能发现并连接到设备。
- 通过 Wi-Fi 直连发现和连接到设备。
- 发起与已知 SSID(例如汽车或智能家居设备)的连接。
- 开启仅限本地使用的热点。
- 连接到附近的 Wi-Fi 感知设备。
如果你的应用(targetSdk == 33)已经声明不会根据 WiFi信息推导设备的物理位置信息,那就不再需要声明 ACCESS_FINE_LOCATION 权限。另外,如果应用在Android13上只使用WiFi API而不使用位置信息,那开发者可以在AndroidManifest.xml中增加NEARBY_WIFI_DEVICES权限,并将usesPermissionFlags属性设为neverForLocation,给ACCESS_FINE_LOCATION权限增加maxSdkVersion="32"的限制,代码如下:
<manifest ...>
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
android:usesPermissionFlags="neverForLocation"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
android:maxSdkVersion="32" />
</manifest>
5、细化的媒体权限
如果你的应用以 Android 13.0 或更高版本为目标平台,并且需要访问其他应用已经创建的媒体文件,你必须请求以下一项或多项细化的媒体权限,而不是 READ_EXTERNAL_STORAGE 或者 WRITE_EXTERNAL_STORAGE 权限:
媒体类型 | 请求权限 |
---|---|
图片和照片 | READ_MEDIA_IMAGES |
视频 | READ_MEDIA_VIDEO |
音频文件 | READ_MEDIA_AUDIO |
单独请求READ_MEDIA_IMAGES、单独请求 READ_MEDIA_VIDEO和同时请求READ_MEDIA_IMAGES& READ_MEDIA_VIDEO,系统均将只显示一个授权弹窗。 另外,如果App(targetSdk == 33)已经申请了读的权限,那App同时也就有了写的权限,无需再额外声明 WRITE_EXTERNAL_STORAGE权限,代码如下:
<manifest ...>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
</manifest>
6、在后台使用身体传感器需要新的权限
Android 13.0 中引入了“在使用时”访问身体传感器(例如心率、体温和血氧饱和度)的概念。此访问模式与 Android 10.0(API 级别 29)系统为位置信息引入的模式非常相似。
如果你的应用以 Android 13 为目标平台,并且在后台运行时需要访问身体传感器信息,那么除了现有的 BODY_SENSORS 权限外,您还必须声明新的 BODY_SENSORS_BACKGROUND 权限。
7、精确的闹钟权限
为了节省系统资源,Android 12.0 引入了SCHEDULE_EXACT_ALARM权限进行“闹钟和提醒”功能的授权管理。Android 13.0 则又引入了新的闹钟权限USE_EXACT_ALARM。
和Android 12.0 的SCHEDULE_EXACT_ALARM权限不同,如果App已经申请使用了USE_EXACT_ALARM新权限,那么用户是不能在设置页面里关闭授权的。
对于日程管理、时间管理等类型的App来讲,Android 13.0 引入的USE_EXACT_ALARM权限能够带来一定便利。相比Android 12.0 的SCHEDULE_EXACT_ALARM权限,使用新权限的应用将不再需要频繁打扰用户进行授权,能够更高效地为用户提供闹钟、日程提醒等服务。
不过,为了防止新权限被滥用,GooglePlay设置了严格的上架审核机制。开发者要注意,一旦使用了USE_EXACT_ALARM权限,App在上架GooglePlay时将会被平台严格审查。除非App属于闹钟、计时器、日历等类型的应用或者在已被列入到应用市场的白名单里,否则GooglePlay将不会允许使用该权限的应用上架。
8、广告 ID 需要权限
使用 Google Play 服务广告 ID 且以 Android 13(API 级别 33)及更高版本为目标平台的应用必须在其清单文件中声明常规 AD_ID权限,如下所示:
<manifest ...>
<!-- 仅当你的应用程序针对Android 13或更高版本时才需要 -->
<uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
<application ...>
...
</application>
</manifest>
如果你的应用以 Android 13.0 或更高版本为目标平台且未声明此权限,系统会自动移除广告 ID 并将其替换为一串零。
9、BroadcastReceiver变更
以往的Android系统下,应用动态注册的BroadcastReceiver广播接收器会接收到任何应用发送的广播(除非该接收器使用了应用签名权限保护),这会使动态注册的广播接收器存在安全风险。
Android 13.0 要求,应用动态注册的广播接收器必须以显著的方式指出是否允许其他应用访问,即其他应用是否可以向其发送广播。否则,在动态注册时系统将抛出安全异常SecurityException。
目前该增强措施并非默认生效,开发者需启用 DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED兼容性框架,并在动态注册广播时指定是否接受其他应用的广播:
context.registerReceiver(receiver, intentFilter, RECEIVER_EXPORTED)
context.registerReceiver(receiver, intentFilter, RECEIVER_NOT_EXPORTED)
注意:系统广播不受RECEIVER_NOT_EXPORTED影响。
10、IntentFilter变更
在之前版本的Android系统中,开发者只需将android:exported设为true就可以跨应用显式启动Activity和Service,即使intent-filter中的action或者type不匹配,也能够启动。为避免上述漏洞,Android 13.0 增强了intent-filter的匹配过滤逻辑。在接收方的targetSdk == 33的情况下,如果intent-filter匹配命中,无论发送方的targetSdk版本如何,intent都将生效。
以下几种情况不需要遵循intent-filter的匹配过滤逻辑:
- 组件没有声明
- 同一个App里的intent
- 系统或Root进程发出的intent
11、照片选择器
Android 13.0(API 级别 33)及更高版本引入了照片选择器体验。应用启动照片选择器时,用户可选择与应用分享特定图片和视频(如个人资料照片),而不是授予应用查看整个媒体库的权限。这是访问用户照片和视频的建议方法。
照片选择器可为用户提供更好的隐私保护,因为你的应用无需声明任何运行时权限。此外,照片选择器还为应用提供内置标准化界面,从而打造更一致的用户体验。
12、APK 签名方案 v3.1
Android 13.0 可支持 APK 签名方案 v3.1,此方案在现有的 APK 签名方案 v3 的基础上进行了改进,解决了 APK 签名方案 v3 的一些已知问题。具体而言,v3.1 签名方案允许应用在单个 APK 中同时支持原始签名者和轮替签名者。此外,v3.1 签名方案还支持 SDK 版本定位功能,这会允许轮替定位到更高版本的平台。
v3.1 签名方案使用在 12L 或更低版本中无法识别的分块 ID。因此,平台会应用以下签名者行为:
- 搭载 Android 13.0 或更高版本的设备使用 v3.1 分块中的轮替签名者。
- 搭载旧版 Android 的设备会忽略轮替签名者,而使用 v3.0 分块中的原始签名者。
尚未轮替其签名密钥的应用无需执行任何其他操作。每当这些应用选择轮替时,系统都会默认应用 v3.1 签名方案。
九、Android 14.0系统
官方文档:Android 14.0
1、当应用进入缓存时,上下文注册的广播将加入队列
在 Android 14 中,当应用处于缓存状态时,系统可能会将上下文注册的广播放入队列中。这与 Android 12(API 级别 31)为异步 binder 事务引入的队列行为类似。在清单中声明的广播不会加入队列,并且应用会从缓存状态中移除以进行广播传递。
当应用离开缓存状态(例如返回前台)时,系统会传递所有已加入队列的广播。某些广播的多个实例可以合并为一个广播。根据其他因素(例如系统运行状况),系统可能会将应用从缓存状态中移除,并且系统会传送之前加入队列的所有广播。
2、应用只能终止自己的后台进程
从 Android 14 开始,当您的应用调用 killBackgroundProcesses() 时,该 API 只能终止您自己应用的后台进程。如果您传入另一个应用的软件包名称,此方法对该应用的后台进程没有影响,并且 Logcat 中会显示以下消息:
Invalid packageName: com.example.anotherapp
您的应用不应使用 killBackgroundProcesses() API,也不得以其他方式尝试影响其他应用的进程生命周期,即使在旧版操作系统上也是如此。Android 旨在让缓存应用在后台运行,并在系统需要内存时自动终止它们。如果您的应用会不必要地终止其他应用,则由于之后需要完全重启这些应用,因此可能会降低系统性能并增加耗电量,这比恢复现有缓存应用所消耗的资源要多得多。
3、授予对照片和视频的部分访问权限
Android 14 引入了“所选照片访问权限”,可让用户授权应用访问其媒体库中的特定图片和视频,而不是授予对给定类型的所有媒体内容的访问权限。
如果您还没有使用照片选择器,我们建议您在应用中实现该选择器,以便在选择图片和视频时能够获得一致的体验,同时还可以加强用户隐私保护,而无需请求任何存储权限。
如果您使用存储权限维护自己的图库选择器,并且需要全面控制您的实现,请调整您的实现以使用新的 READ_MEDIA_VISUAL_USER_SELECTED 权限。如果您的应用不使用新权限,系统会在兼容模式下运行您的应用。
4、关于不可关闭通知用户体验方式的变更
如果您的应用向用户显示不可关闭的前台通知,请注意:Android 14 已更改此行为,允许用户关闭此类通知。
这项变更适用于通过 Notification.Builder#setOngoing(true) 或 NotificationCompat.Builder#setOngoing(true) 设置 Notification.FLAG_ONGOING_EVENT 来阻止用户关闭前台通知的应用。FLAG_ONGOING_EVENT 的行为已发生变化,使用户实际上能够关闭此类通知。
在以下情况下,此类通知仍不可关闭:
- 当手机处于锁定状态时
- 如果用户选择全部清除通知操作(有助于防止意外关闭)
此外,这一新行为不适用于以下用例中的通知:
- CallStyle 通知
- 企业设备政策控制器 (DPC) 和支持软件包
5、最低可安装的目标 API 级别
从 Android 14 开始,targetSdkVersion 低于 23 的应用无法安装。要求应用满足这些最低目标 API 级别要求有助于提高用户的安全性和隐私性。
恶意软件通常会以较旧的 API 级别为目标平台,以绕过在较新版本 Android 中引入的安全和隐私保护机制。例如,有些恶意软件应用使用 targetSdkVersion 22,以避免受到 Android 6.0 Marshmallow(API 级别 23)在 2015 年引入的运行时权限模型的约束。这项 Android 14 变更使恶意软件更难以规避安全和隐私权方面的改进限制。尝试安装以较低 API 级别为目标平台的应用将导致安装失败,并且 Logcat 中会显示以下消息:
INSTALL_FAILED_DEPRECATED_SDK_VERSION: App package must target at least SDK version 23, but found 7
在升级到 Android 14 的设备上,targetSdkVersion 低于 23 的所有应用都将继续保持安装状态。如果您需要测试以旧版 API 级别为目标平台的应用,请使用以下 ADB 命令:
adb install --bypass-low-target-sdk-block FILENAME.apk
6、媒体所有者软件包名称可能会隐去
媒体库支持查询 OWNER_PACKAGE_NAME 列,该列表示存储特定媒体文件的应用。从 Android 14 开始,除非满足以下条件之一,否则系统会隐去此值:
- 存储媒体文件的应用有一个软件包名称始终对其他应用可见。
- 查询媒体库的应用会请求 QUERY_ALL_PACKAGES 权限。
7、检测用户何时截取设备屏幕截图
为了打造更加标准化的屏幕截图检测体验,Android 14 引入了可保护隐私的屏幕截图检测 API。借助此 API,应用可以按 activity 注册回调。如果用户在该 activity 可见时截取屏幕截图,系统会调用这些回调并通知用户。
注意:该回调未提供实际屏幕截图的图片。用户截取屏幕截图后,屏幕上的内容将由您的应用决定。
8、在 BluetoothAdapter 中强制执行 BLUETOOTH_CONNECT 权限
对于以 Android 14(API 级别 34)为目标平台的应用,Android 14 会在调用 BluetoothAdapter getProfileConnectionState() 方法时强制执行 BLUETOOTH_CONNECT 权限。
此方法已需要 BLUETOOTH_CONNECT 权限,但未被强制执行。请确保应用在应用的 AndroidManifest.xml 文件中声明 BLUETOOTH_CONNECT(如以下代码段所示),并在调用 getProfileConnectionState 之前检查用户是否已授予权限。
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
9、对隐式 intent 和待处理 intent 的限制
对于以 Android 14 为目标平台的应用,Android 会通过以下方式限制应用向内部应用组件发送隐式 intent:
- 隐式 intent 只能传送到android:exported="true"组件。应用必须使用显式 intent 传送到android:exported="false"的组件,或将该组件标记为android:exported="true"。
- 如果应用通过未指定组件或软件包的 intent 创建可变待处理 intent,系统现在会抛出异常。
这些变更可防止恶意应用拦截意在供应用内部组件使用的隐式 intent。例如,下面是可以在应用的清单文件中声明的 intent 过滤器:
<activity
android:name=".AppActivity"
android:exported="false">
<intent-filter>
<action android:name="com.example.action.APP_ACTION" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
如果应用尝试使用隐式 intent 启动此 activity,则系统会抛出异常:
// Throws an exception when targeting Android 14.
context.startActivity(new Intent("com.example.action.APP_ACTION"));
如需启动android:exported="false"的 activity,应用应改用显式 intent:
// This makes the intent explicit.
Intent explicitIntent = new Intent("com.example.action.APP_ACTION")
explicitIntent.setPackage(context.getPackageName());
context.startActivity(explicitIntent);
10、在运行时注册的广播接收器必须指定导出行为
以 Android 14 为目标平台并使用上下文注册的接收器的应用和服务必须指定以下标志,以指明接收器是否应导出到设备上的所有其他应用:RECEIVER_EXPORTED 或 RECEIVER_NOT_EXPORTED。此要求有助于利用 Android 13 中引入的这些接收器的功能,来保护应用免受安全漏洞的影响。
context.registerReceiver(receiver, intentFilter, RECEIVER_EXPORTED)
context.registerReceiver(receiver, intentFilter, RECEIVER_NOT_EXPORTED)
例外:如果您的应用仅通过 Context#registerReceiver 方法针对系统广播注册接收器,则不受上诉限制。
11、针对从后台启动 activity 的其他限制
对于以 Android 14 为目标平台的应用,系统会进一步限制允许应用在后台启动 activity 的时间:
- 现在,当应用使用 PendingIntent#send() 或类似方法发送 PendingIntent 时,如果它想要授予自己的后台 activity 启动待处理 intent 的启动特权,则必须选择启用。如需选择启用,应用应通过 setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED) 传递 ActivityOptions 软件包。
- 当可见应用使用 bindService() 方法绑定其他在后台应用的服务时,如果可见应用想要授予自己的后台 activity 对绑定服务的启动特权,则必须选择启用。如需选择启用,应用应在调用 bindService() 方法时包含 BIND_ALLOW_ACTIVITY_STARTS 标志。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)