前言
从Android 6.0 开始,系统更新时APP也需要进行相应的适配,在此记录下开发中需要适配的地方。
Android 6.0 (M) 适配
在Android6.0之前,申请权限只需要再清单文件中配置即可,在Android6.0之后危险权限需要动态申请。
危险权限列表
权限组 | 权限名称 |
---|---|
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 |
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 | |
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 |
在Android8.0之前,同一组内的权限,只要有一个被同意,其他的都会被同意。在 Android 8.0 之后,此行为已被纠正。系统只会授予应用明确请求的权限。然而一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准。
Android 7.0 (N) 适配
1.应用间共享文件
在Android7.0以上进行应用内升级或拍照选取图片时,会报异常原因是:
对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 file:// URI。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。
具体适配请看Android 7.0 行为变更 通过FileProvider在应用间共享文件吧
2.APK signature scheme v2
Android 7.0 引入一项新的应用签名方案 APK Signature Scheme v2,它能提供更快的应用安装时间和更多针对未授权 APK 文件更改的保护。在默认情况下,Android Studio 2.2 和 Android Plugin for Gradle 2.2 会使用 APK Signature Scheme v2 和传统签名方案来签署您的应用。
为保障应用在所有版本都可正常安装,需要在应用打包时同时勾选V1和V2。
1.只勾选V1签名就是传统方案签署,但是在 Android 7.0 上不会使用V2安全的验证方式。
2.只勾选V2签名7.0以下会显示未安装,Android 7.0 上则会使用了V2安全的验证方式。
3.同时勾选V1和V2则所有版本都没问题。
Android 8.0 (O) 适配
1.安卓8.0中PHONE权限组新增两个权限
ANSWER_PHONE_CALLS:允许您的应用通过编程方式接听呼入电话。要在您的应用中处理呼入电话,您可以使用 acceptRingingCall() 函数。
READ_PHONE_NUMBERS :权限允许您的应用读取设备中存储的电话号码。
2.通知适配
Android 8.0中,为了更好的管制通知的提醒,不想一些不重要的通知打扰用户,新增了通知渠道,用户可以根据渠道来屏蔽一些不想要的通知。
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 适配8.0
NotificationChannel channel = new NotificationChannel(notifyChannelId, notifyChannelName, NotificationManager.IMPORTANCE_HIGH);
if (notificationManager != null) {
notificationManager.createNotificationChannel(channel);
}
}
3.静态广播无法正常接收
问题原因: Android 8.0 引入了新的广播接收器限制,因此您应该移除所有为隐式广播 Intent 注册的广播接收器。
解决方案:
1.使用显示广播代替隐式广播。
2.静态隐式广播需指定包名。
4.Caused by: java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation
问题原因:Android 8.0 只有全屏不透明的activity才可以设置方向(8.1系统谷歌就去掉了这个限制)
解决方案:
1.android:windowIsTranslucent设置为false。
2.如果还是想用的话,就去掉清单文件中Activity中的android:screenOrientation=“portrait”。
5.悬浮窗适配
使用 SYSTEM_ALERT_WINDOW 权限的应用无法再使用以下窗口类型来在其他应用和系统窗口上方显示提醒窗口:
TYPE_PHONE
TYPE_PRIORITY_PHONE
TYPE_SYSTEM_ALERT
TYPE_SYSTEM_OVERLAY
TYPE_SYSTEM_ERROR
相反,应用必须使用名为 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" />
6.更新安装APK
Android8.0以上需要申请安装未知来源应用的权限
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
7.SecurityException的闪退
问题原因:项目使用了ActiveAndroid,在 8.0 或 8.1 系统上使用 26 或以上的版本的 SDK 时,调用 ContentResolver 的 notifyChange 方法通知数据更新,或者调用 ContentResolver 的 registerContentObserver 方法监听数据变化时,会出现上述异常。
解决方案:
<provider
android:name="com.activeandroid.content.ContentProvider"
android:authorities="com.ylmf.androidclient"
android:enabled="true"
android:exported="false">
</provider>
8.不允许后台应用创建后台服务
Android 8.0 引入一种全新的方法,即Context.startForegroundService()
,以在前台启动新服务。在系统创建服务后,应用有 5秒的时间来调用该服务的 startForeground()
方法以显示新服务的用户可见通知。如果应用在此时间限制内未调用 starForeground()
,则系统将停止服务并声明此应用为 ANR。
Android 9.0 (Pie) 适配
1.CLEARTEXT communication to oss.pgyer.com not permitted by network security policy
问题原因: Android P 限制了明文流量的网络请求,非加密的流量请求都会被系统禁止掉
解决方案:
在资源文件新建xml目录,新建文件
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
清单文件配置:
<application
android:networkSecurityConfig="@xml/network_security_config">
</application>
有条件最好还是使用https进行传输。
2.弃用 Apache HTTP Client
由于官方在 Android 9.0 中移除了所有 Apache HTTP Client 相关的类,因此我们的应用或是一些第三方库如果使用了这些类,就会抛出找不到类的异常:
java.lang.NoClassDefFoundError: Failed resolution of: Lorg/apache/http/conn/scheme/SchemeRegistry;
解决方案:
在 AndroidManifest.xml 中添加以下内容:
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
3.强制执行 FLAG_ACTIVITY_NEW_TASK 要求
若开发者需要通过非 Activity context 启动 Activity,就必须设置 Intent 标志 FLAG_ACTIVITY_NEW_TASK
,否则会启动失败并抛出以下异常
android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
4.前台服务权限
在 Android 9.0 中,应用在使用前台服务之前必须先申请 FOREGROUND_SERVICE 权限,否则就会抛出 SecurityException 异常。
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
5.后台对传感器的访问受限
Android 9 限制后台应用访问用户输入和传感器数据的能力。如果应用在运行 Android 9 设备的后台运行,系统将对您的应用采取一笑限制:
- 应用不能访问麦克风或摄像头
- 使用连续报告模式的传感器(例如加速度计和陀螺仪)不会接收事件
- 使用变化或一次性报告模式的传感器不会接收事件
如果您的应用需要在运行 Android 9 的设备上检测传感器事件,请使用前台服务。
6.限制访问通话记录
Android 9 引入 CALL_LOG 权限组并将 READ_CALL_LOG
、WRITE_CALL_LOG
和 PROCESS_OUTGOING_CALLS
权限移入改组。在之前的版本中,这些权限位于 PHONE权限组。
Android 10.0 (Q) 适配
1.分区存储
-
内部存储:
/data
目录。一般使用getFilesDir()
或getCacheDir()
方法获取本应用的内部存储路径,读写该路径下的文件不需要申请存储空间读写权限,且卸载应用是会自动删除。 -
外部存储:
/storage
或/mnt
目录。一般使用getExternalStorageDirectory()
方法获取的路径来存储文件。
现将外部存储空间分为了三部分:- 特定目录(App-specific),使用
getExternalFilesDir()
或getExternalCacheDir()
方法访问,无需权限,且卸载应用时会自动删除。 - 照片、视频、音频这类媒体文件,使用
MediaStore
访问,访问其他应用的媒体文件时需要READ_EXTERNAL_STORAGE
权限。 - 其他目录,使用存储访问框架SAF
- 特定目录(App-specific),使用
适配:
使用getExternalFilesDir()
,如果是缓存文件,可以放到 getExternalCacheDir()
路径下
使用MediaStore
,将文件存至对应的媒体类型中(图片:MediaStore.Images
,视频:MediaStore.Video
,音频:MediaStore.Audio
),仅限于多媒体文件。
2.权限变化
1.在后台运行时访问设备位置信息需要权限
Android 10 引入了 ACCESS_BACKGROUND_LOCATION
权限(危险权限)
该权限允许应用在后台访问位置。如果请求此权限,则还必须请求ACCESS_FINE_LOCATION
或ACCESS_COARSE_LOCATION
权限。只请求此权限无效果。
官方不推荐使用申请后台访问权的方式,这样无非就是多请求一个权限,推荐使用前台服务来实现。
2.一些电话、蓝牙和WLAN的API需要精确位置权限
下面列举了Android 10中必须具有 ACCESS_FINE_LOCATION 权限才能使用类和方法:
电话
TelephonyManager
- getCellLocation()
- getAllCellInfo()
- requestNetworkScan()
- requestCellInfoUpdate()
- getAvailableNetworks()
- getServiceState()
TelephonyScanManager
- requestNetworkScan()
TelephonyScanManager.NetworkScanCallback
- onResults()
PhoneStateListener
- onCellLocationChanged()
- onCellInfoChanged()
- onServiceStateChanged()
WLAN
WifiManager
- startScan()
- getScanResults()
- getConnectionInfo()
- getConfiguredNetworks()
WifiAwareManager
WifiP2pManager
WifiRttManager
蓝牙
BluetoothAdapter
- startDiscovery()
- startLeScan()
BluetoothAdapter.LeScanCallback
BluetoothLeScanner
- startScan()
3.后台启动Activity的限制
简单解释就是应用处于后台时,无法启动Activity。比如点开一个应用会进入启动页或者广告页,一般会有几秒的延时再跳转至首页。如果这期间你退到后台,那么你将无法看到跳转过程。而在之前的版本中,会强制弹出页面至前台。
在以下情况下不受限:
-
应用具有可见窗口,例如前台 Activity。
-
应用在前台任务的返回栈中已有的 Activity。
-
应用在
Recents
上现有任务的返回栈中已有的 Activity。Recents
就是我们的任务管理列表。 -
应用收到系统的
PendingIntent
通知。 -
应用收到它应该在其中启动界面的系统广播。示例包括
ACTION_NEW_OUTGOING_CALL
和SECRET_CODE_ACTION
。应用可在广播发送几秒钟后启动 Activity。 -
用户已向应用授予
SYSTEM_ALERT_WINDOW
权限,或是在应用权限页开启后台弹出页面的开关。
对于全屏 intent,注意设置最高优先级和添加USE_FULL_SCREEN_INTENT
权限,这是一个普通权限。比如微信来语音或者视频通话时,弹出的接听页面就是使用这一功能。
<uses-permission android:name="android.permission.USE_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")
.setPriority(NotificationCompat.PRIORITY_HIGH) // <--- 高优先级
.setCategory(NotificationCompat.CATEGORY_CALL)
// Use a full-screen intent only for the highest-priority alerts where you
// have an associated activity that you would like to launch after the user
// interacts with the notification. Also, if your app targets Android 10
// or higher, you need to request the USE_FULL_SCREEN_INTENT permission in
// order for the platform to invoke this notification.
.setFullScreenIntent(fullScreenPendingIntent, true); // <--- 全屏 intent
Notification incomingCallNotification = notificationBuilder.build();
注意:在部分手机上,直接设置setPriority
无效(或者说以渠道优先级为准)。所以需要创建通知渠道时将重要性设置为IMPORTANCE_HIGH
。
NotificationChannel channel = new NotificationChannel(channelId, "xxx", NotificationManager.IMPORTANCE_HIGH);
4.深色主题
手动适配
官方文档中提到的继承Theme.AppCompat.DayNight
或者 Theme.MaterialComponents.DayNight
的方法,但这只是将我们使用的各种View的默认样式进行了适配,并不太适用于实际项目的适配。因为具体的项目中的View都按照设计的风格进行了重定义。
其实适配的方法很简单,类似屏幕适配、国际化的操作,并不需要继承上面的主题。比如你要修改颜色,就在res
下新建 values-night
目录,创建对应的colors.xml
文件。将具体要修改的色值定义在里面。图标之类的也是一个思路,创建对应的 drawable-night
目录。
5.对不可重置的设备标识符实施了限制
受影响的方法包括:
- Build
- getSerial()
- TelephonyManager
- getImei()
- getDeviceId()
- getMeid()
- getSimSerialNumber()
- getSubscriberId()
从 Android 10 开始,应用必须具有 READ_PRIVILEGED_PHONE_STATE 特许权限才能正常使用以上这些方法。
6.限制了对剪贴板数据的访问权限
除非您的应用是默认输入法 (IME) 或是目前处于焦点的应用,否则它无法访问 Android 10 或更高版本平台上的剪贴板数据。
7.对启用和停用 WLAN 实施了限制
以 Android 10 或更高版本为目标平台的应用无法启用或停用 WLAN。WifiManager.setWifiEnabled()
方法始终返回 false。
如果您需要提示用户启用或停用 WLAN,请使用设置面板。