Android 6(M)适配
1、运行时权限
在6.0之前的版本中,我们在使用某项权限时,只需要在Manifest中声明就可以使用了,但是在6.0之后,某些权限(例如:通讯录、位置、相机)不仅需要在Manifest中声明,还要在app运行的时候动态地申请并被用户允许才能正常使用,这类权限称为危险权限(Dangerous Permission)。与其对应的是正常权限(NormalPermissions),正常权限(例如:蓝牙,网络)只需要在清单文件声明即可。
解决方式:以相机权限CAMERA 为例子
a、在androidmanifest中申明所需权限
<uses-permission android:name="android.permission.CAMERA"/>
b、调用ContextCompat.checkSelfPermission()检查权限
if(ContextCompat.checkSelfPermission(this,Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
//权限未授予,调用requestPermission()申请权限
}else{
//权限已授予
}
c、如果权限未被授予,调用ActivityCompat.requestPermissions()申请权限:
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CAMERA}, MY_REQUEST_CODE);
d、重写Activity的onRequestPermissionsResult()处理申请回调:
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_REQUEST_CODE: {
if (grantResults.length > 0 &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//用户允许该权限
} else {
//用户拒绝该权限
}
return;
}
}
}
2、取消支持 Apache HTTP 客户端 Android 6.0 版移除了对 Apache HTTP 客户端的支持。如果您的应用使用该客户端,并以 Android 2.3(API 级别 9)或更高版本为目标平台,请改用 HttpURLConnection 类。此 API 效率更高,因为它可以通过透明压缩和响应缓存减少网络使用,并可最大限度降低耗电量。要继续使用 Apache HTTP API,您必须先在 build.gradle 文件中声明以下编译时依赖项: android { useLibrary 'org.apache.http.legacy' }
3、Notification的适配 NotificationManager.java的notifyAsUser出现了以下的修改,强迫6.0及以上系统下在使用notification时一定要传入small icon。否则会出现异常。 出现异常的源码如下: public void notifyAsUser(String tag, int id, Notification notification, UserHandle user) { …… if (mContext.getApplicationInfo().targetSdkVersion
Build.VERSION_CODES.LOLLIPOP_MR1) { //如果没有setSmallIcon ,此处会抛异常 if (notification.getSmallIcon() == null) { throw new IllegalArgumentException("Invalid notification (no valid small icon): "+ notification); } }
……
}
Android 7(N)适配 1、应用间共享文件 Android 7及更高版本,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 file:// URI。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。 解决方式:使用FileProvider生成content://URI,并授予URI临时访问权限。 FileProvider使用大概分为以下几个步骤:a、androidmanifest中申明FileProvider;b、res/xml中定义对外暴露的文件夹路径;c、生成content://URI;d、给uri赋予临时访问权限;e、使用intent传递uri; 必须注意的问题: 通过FileProvider.getUriForFile()函数获取content://URI时,包名+自定义的标识必须和AndroidManifest文件中配置的一致; 如果是模块化开发,需要注意包名的一致。如果在代码不小心使用了 Uri uri = FileProvider.getUriForFile(getContext(), BuildConfig.APPLICATION_ID + ".fileProvider", file); 那BuildConfig.APPLICATION_ID在module中得到的是module的包名,而不是app的包名,这要就会导致在module中闪退。正确的方式应该是用mContext.getPackageName()来代替BuildConfig.APPLICATION_ID FileProvider重复。在模块化开发中,如果在module中也定义了FileProvider,那么就会报FileProvider重复的错误: Attribute provider#android.support.v4.content.FileProvider@authorities value=(***.fileProvider) from AndroidManifest.xml:352:13-62 is also present at ... 使用自定义的FileProvider,同时要求指向的res/xml定义的文件名需要不同 2、SharedPreferences触发SecurityException 为了提供私有文件的安全性,Android 7.0及更高版本限制了对SharedPreferences文件的访问,设置Context.MODE_WORLD_READABLE或Context.MODE_WORLD_WRITEABLE,会触发SecurityException。 解决方式:用MODE_PRIVATE替换 3、静态广播优化 在android 7及以上,将不会接收静态注册的CONNECTIVITY_ACTION广播,请使用动态注册。 同时也不能发送或接收ACTION_NEW_PICTURE和ACTION_NEW_VIDEO 广播 4、android 7.1版本上Toast BadTokenException 在7.1.1机型上概率性出现。稳定复现的步骤是,在Toast.show()之后,UI线程做了耗时的操作阻塞了Handler message的处理,如使用Thread.sleep(5000),然后这个崩溃就出现了。原因是7.1.1系统对TYPE_TOAST的Window类型做了超时限制,绑定了Window Token,最长超时时间是3.5s,如果UI在这段时间内没有执行完,Toast.show()内部的handler message得不到执行,NotificationManageService那端会把这个Toast取消掉,同时把Toast对于的window token置为无效。等App端真正需要显示Toast时,因为window token已经失效,ViewRootImpl就抛出了上面的异常。 解决方式: 5、APK signature scheme v2 a、只勾选V1签名就是传统方案签署,但是在 Android 7.0 上不会使用V2安全的验证方式。 b、只勾选V2签名7.0以下会显示未安装,Android 7.0 上则会使用了V2安全的验证方式。 c、同时勾选V1和V2则所有版本都没问题。
Android 8(O)适配
1、唤不起系统安装器
在android 8.0 上,“未知来源应用权限”的开关被移除掉,取而代之的是未知来源应用的权限管理列表。如果你想要安装某个被自己所信任的开发者的app,则需要在每一次都手动授权“安装未知应用”的许可。
解决方式:在AndroidManifest中添加安装权限
2、非全屏不透明Activity禁用设置orientation
只有全屏不透明页面设置方向,否则会抛Caused by: java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation异常;此问题在Android 9上已修复。
解决方式:对该activity的theme中使用android:windowIsTranslucent=false
3、通知没有显示
自android 8.0(26)起,Android推出了NotificationChannel机制,旨在对通知进行分类管理,如果用户App的targetSdkVersion大于等于26且未设置NotificaitonChannel,创建的通知是不会弹出的。
·解决方式:使用NotificationChannel机制,下面是关键性代码:
NotificationChannel mChannel =new NotificationChannel("channel_01","消息推送", NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(mChannel);
4、悬浮窗
当app退入后台,接收到一些广播且弹出对话框的应用场景,没有依附的activity,这个时候就可以使用悬浮窗。
在android 8中,新增了一种悬浮窗的窗口类型:TYPE_APPLICATION_OVERLAY
如果应用使用 SYSTEM_ALERT_WINDOW 权限并且尝试使用以下窗口类型之一来在其他应用和系统窗口上方显示提醒窗口:
TYPE_PHONE
TYPE_PRIORITY_PHONE
TYPE_SYSTEM_ALERT
TYPE_SYSTEM_OVERLAY
TYPE_SYSTEM_ERROR
TYPE_TOAST
这些窗口将始终显示在使用 TYPE_APPLICATION_OVERLAY 窗口类型的窗口下方。
如果在android 8及以上,应用只能使用TYPE_APPLICATION_OVERLAY窗口类型来创建悬浮窗。(其它窗口类型在8.0已经被废弃掉)
解决方式:关键代码如下:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mWindowParams.type
= WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
mWindowParams.type
= WindowManager.LayoutParams.TYPE_PHONE;
}
5、权限
在 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,则系统会立即授予该权限,而不会提示用户。
6、后台服务限制
Android 8中,将禁止从后台通过使用 startService() 函数创建后台服务,否则会引发一个IllegalStateException。
新的 Context.startForegroundService() 函数将启动一个前台服务。现在,即使应用在后台运行,系统也允许其调用 Context.startForegroundService()。不过,应用必须在创建服务后的5秒内调用该服务的 startForeground() 函数。
解决方式:
a、使用JobScheduler替代后台服务
b、使用 Context.startForegroundService() 创建前台服务,并在5秒内调用startForeground()函数。 如果应用在此时间限制内未调用startForeground(),则系统将停止此 Service 并声明此应用为 ANR。
7、静态广播禁用
在android 8后,在AndroidManifest中注册的静态广播将不会生效,如果需要,请使用动态注册。
8、SecurityException的闪退
项目使用了ActiveAndroid,在 8.0 或 8.1 系统上使用 26 或以上的版本的 SDK 时,调用 ContentResolver 的 notifyChange 方法通知数据更新,或者调用 ContentResolver 的 registerContentObserver 方法监听数据变化时,会出现上述异常。
解决方式:
a、
b、去掉这个监听刷新的方法,改为广播刷新
Android 9(P)适配 1、Android 9通话记录权限: Android 9引入了CALL_LOG权限组,包含 READ_CALL_LOG、WRITE_CALL_LOG 和 PROCESS_OUTGOING_CALLS,在该版本以前,这三个权限放在PHONE权限组。如果需要访问通话记录或者需要处理去电,则您、必须向 CALL_LOG 权限组明确请求这些权限。 否则会发生 SecurityException。 2、限制非Activity context启动Activity 在 Android 9 中,不能从非 Activity 环境中启动 Activity,除非传递的 Intent 中添加标志 FLAG_ACTIVITY_NEW_TASK。 否则,该 Activity 不会启动,并且会抛出异常。 3、应用不再能访问 xt_qtaguid 文件夹中的文件 从 Android 9 开始,不再允许应用直接读取 /proc/net/xt_qtaguid 文件夹中的文件。 这样做是为了确保与某些根本不提供这些文件的设备保持一致。 依赖这些文件的公开 API TrafficStats 和 NetworkStatsManager 继续按照预期方式运行。 然而,不受支持的 cutils函数(例如 qtaguid_tagSocket())在不同设备上可能不会按照预期方式运行 — 甚至根本不运行。 4、前台service需要添加权限 必须在AndroidManifest中添加FOREGROUND_SERVICE权限,才能使用前台服务,否则会抛出SecurityException异常。由于FOREGROUND_SERVICE是普通权限,不需要动态申请,只需要在AndroidManifest中添加: 5、限制明文的网络请求 在android 9中,限制了明文流量的网络请求,非加密的流量请求都会被系统禁止掉。所以对于使用okhttp3的应用,会抛出CLEARTEXT communication to " + host + " not permitted by network security policy。 下面是okhttp中的检查: if (!Platform.get().isCleartextTrafficPermitted(host)) { throw new RouteException(new UnknownServiceException( "CLEARTEXT communication to " + host + " not permitted by network security policy")); } 解决方式: (a)、在 res 下新建一个 xml 目录,然后创建一个名为:network_security_config.xml 文件 ,该文件内容如下: (b)、在 AndroidManifest.xml application 标签内应用上面的xml配置: 6、设备识别码 通过Build.SERIAL不再能够获取到真实数据,Build.serial:unknown,需要通过Build.getSerial()获取。同时需要用户授权READ_PHONE_STATE权限。
Android 10(Q)适配 1、应用图标 在8.0中引入了矢量图标,并提供了图标动态调整功能(视差和脉动)且这个新特性只对矢量图有效。在android Q以前的版本中,图标如果使用png,系统会自动适配。而在android Q中,图标动态调整功能强制启动,那对图标将会按照新特性来适配。也就是说如果在android Q中,图标不是使用矢量图,而是png图片,那应用的图标将会比使用了矢量图的应用的图标小。 2、HTTPS 连接变更 如果运行 Android Q 的应用将 null 传递给 setSSLSocketFactory(),现在会出现 IllegalArgumentException。在以前的版本中,将 null 传递给 setSSLSocketFactory() 与传入当前的默认 SSL 套接字工厂效果相同。 3、共享内存 Ashmem 更改了 /proc//maps 中的 dalvik 映射的格式,这会影响那些直接解析映射文件的应用。如果应用依赖于 dalvik 映射格式,则应用开发者应该在设备上测试新的 /proc//maps 格式并相应地进行解析。 以 Android Q 为目标平台的应用无法再直接使用 ashmem (/dev/ashmem),而必须通过 NDK 的 ASharedMemory 类访问共享内存。此外,应用无法直接对现有 ashmem 文件描述符进行 IOCTL,而必须改为使用 NDK 的 ASharedMemory 类或 Android Java API 创建共享内存区域。这项变更可以提高使用共享内存时的安全性和稳健性,从而提高 Android 的整体性能和安全性。 4、针对全屏 Intent 的权限变更 如果应用以 Android Q 或更高版本为目标平台并使用涉及全屏 intent 的通知,则必须在清单文件中请求 USE_FULL_SCREEN_INTENT 权限。这是普通权限,因此,系统会自动为请求权限的应用授予此权限。 如果以 Android Q 或更高版本为目标平台的应用试图创建使用全屏 intent 的通知,而不请求 USE_FULL_SCREEN_INTENT权限,则系统会忽略此全屏 intent 并输出以下日志消息:Package [pkg]: Use of fullScreenIntent requires the USE_FULL_SCREEN_INTENT permission 5、后台定位权限 在android Q中,新引入了后台定位权限ACCESS_BACKGROUND_LOCATION,且需要代码动态申请。与现有的 ACCESS_FINE_LOCATION 和 ACCESS_COARSE_LOCATION 权限不同,新权限仅会影响应用在后台运行时对位置信息的访问权。此权限的影响只对是以android Q为目标平台的应用。如果target 是android 9或者更低,权限ACCESS_BACKGROUND_LOCATION会自动添加。 若target是android Q,会需要在AndroidManifest中申明: 6、分区存储 Android Q中更改了对外部存(如/sdcard下的文件)的访问方式。 具体内容和适配方式请参照: developer.android.google.cn/preview/pri… 7、设备唯一标识符 在android Q中将禁止非系统app获取设备的不可重置标识符(包含 IMEI 和序列号)。所以如果你只是想得到唯一标识符,请不要再加入READ_PHONE_STATE权限,不会生效。 如果应用以 Android Q 为目标平台,则会发生 SecurityException。 如果应用以 Android 9(API 级别 28)或更低版本为目标平台,则相应方法会返回 null 或占位符数据(如果应用具有 READ_PHONE_STATE 权限)。否则,会发生 SecurityException。 解决方式: 使用UUID,UUID每次请求都会重新生成,所以我们申请一个UUID后,用sp把他存储起来。这样只要我们的App没有被删除,这个UUID将永远唯一的存在。这里需要注意的是,把这个值存在sd卡里,在android Q中是没有作用了的,android Q的分区存储以及访问方式的改变,特别是当应用卸载后,位于sd中的应用私有文件也会删除。 猜测:既然在sd上的私有文件会被删除,那能不能在MediaStore 的目录下保存这个uuid呢? 8、访问剪贴板数据 对于非默认输入法app或者app已经失去焦点,无法访问剪贴板数据。也就是说后台应用无法访问剪切板数据。 目前没有具体的适配方案 9、后台启动activity Android Q加强了后台启动activity的限制,不可以随意从后台拉起activity。 解决方式:使用通知