Android 9.0 以上修改
1.当在 Android 9.0 加载网络请求数据时,有时会抛出如下异常:
1 | Cause ( 1 of 1 ): class java.io.IOException: Cleartext HTTP traffic to xxxx.xxxx.xxxx not permitted |
这是因为Android 9.0版本系统默认支持一个网络访问协议:Https协议的网络,所以不支持网络访问:Http协议的网络面对这样的问题,解决办法:
方法1:
直接通过在AnroidManifest.xml中的<application>标签内添加</application>
1 | <application android:usesCleartextTraffic= "true" /> |
原来默认为 true,但在 Android 9.0 中默认值改为了 false,因此将配置手动设为 true 即可解决明文传输被限制的问题
方法2:
在res文件夹下创建一个xml文件夹,然后创建一个network_security_config.xml文件,文件内容如下:
1 2 3 4 | <?xml version= "1.0" encoding= "utf-8" ?> <network-security-config> <base-config cleartextTrafficPermitted= "true" /> </network-security-config> |
然后在<application>标签内添加以下代码:</application>
1 | android:networkSecurityConfig= "@xml/network_security_config" |
2. Android 9.0 弃用 Apache HTTP Client
由于官方在 Android 9.0 中移除了所有 Apache HTTP Client 相关的类,因此我们的应用或是一些第三方库如果使用了这些类,就会抛出找不到类的异常:
1 | java.lang.NoClassDefFoundError: Failed resolution of: Lorg/apache/http/conn/scheme/SchemeRegistry; |
若需要继续使用 Apache HTTP Client ,可通过以下方法进行适配:
在 AndroidManifest.xml 中的<application>标签内添加以下内容:</application>
1 | <uses-library android:name= "org.apache.http.legacy" android:required= "false" /> |
3. Android 9.0 Build.SERIAL 被弃用
Android 9.0 之前,开发者可以使用 Build.SERIAL 获取设备的序列号。现在这个方法被弃用了,Build.SERIAL 将始终设置为 "UNKNOWN" 以保护用户的隐私。适配的方法为先请求READ_PHONE_STATE权限,然后调用Build.getSerial()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /** * 获取android设备唯一标识码 */ fun getClientId(context: Context): String { val clientId: String val androidID = Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID) clientId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { androidID + Build.getSerial() } else { androidID + Build.SERIAL } Log.e( "" , "ClientId: $clientId" ) return clientId } |
4. Android 9.0 使用Intent卸载应用无反应
使用下面代码在安卓9.0并不管用
1 2 3 4 5 6 | //卸载APP fun uninstallApp(activity: Activity, packageName: String) { val intent = Intent(Intent.ACTION_DELETE) intent.data = Uri.parse( "package:$packageName" ) activity.startActivity(intent) } |
原因是没有添加权限,解决办法:
在AnroidManifest.xml中加入
1 | <uses-permission android:name= "android.permission.REQUEST_DELETE_PACKAGES" /> |
5. Android 9.0 取消 BottomNavigationView 动画效果的实现
在 API 28 之前,取消 BottomNavigationView 动画效果的实现方法为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | //使用 BottomNavHelper.disableShiftMode(bottomNavigationView); /** * 处理BottomNavigationView控件底部按钮超过3个文字不显示 */ public class BottomNavHelper { @SuppressLint ( "RestrictedApi" ) public static void disableShiftMode(BottomNavigationView bottomNavView) { BottomNavigationMenuView menuView = (BottomNavigationMenuView) bottomNavView.getChildAt( 0 ); try { Field shiftingMode = menuView.getClass().getDeclaredField( "mShiftingMode" ); shiftingMode.setAccessible( true ); shiftingMode.setBoolean(menuView, false ); shiftingMode.setAccessible( false ); for ( int i = 0 ; i < menuView.getChildCount(); i++) { BottomNavigationItemView itemView = (BottomNavigationItemView) menuView.getChildAt(i); itemView.setShiftingMode( false ); itemView.setChecked(itemView.getItemData().isChecked()); } } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); } } } |
然而升级 API 28 后,BottomNavigationView 的 setShiftingMode(boolean) 不能用了。
解决方法:直接设置labelVisibilityMode属性即可。
1 2 | <android.support.design.widget.BottomNavigationView app:labelVisibilityMode= "labeled" /> |
labelVisibilityMode 用于设置图标下面的文字显示,该属性对应的值为:
- auto : 当 item 小于等于3时,显示文字,item 大于3个默认不显示,选中显示文字
- labeled : 始终显示文字
- selected : 选中时显示
- unlabeled : 选中时显示
该属性对应的方法是setLabelVisibilityMode(LabelVisibilityMode.LABEL_VISIBILITY_LABELED);
Android 8.0 以上修改
1. Android 8.0 无法通过 "application/vnd.android.package-archive" 安装应用
原因:targetsdkversion 大于 25 必须声明 REQUEST_INSTALL_PACKAGES 权限
解决办法:在AnroidManifest.xml中加入
1 | <uses-permission android:name= "android.permission.REQUEST_INSTALL_PACKAGES" /> |
2. Android 8.0 的 Notification 不显示问题
原因:NotificationChannel 是 Android 8.0 新增的特性,如果 targetSdkVersion >= 26,没有设置 channel 通知渠道的话,就会导致通知无法展示。
Android O 引入了通知渠道(Notification Channels),以提供统一的系统来帮助用户管理通知,如果是针对 Android O 为目标平台时,必须实现一个或者多个通知渠道,以向用户显示通知。比如聊天软件,为每个聊天组设置一个通知渠道,指定特定声音、灯光等配置。
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /** * Notification通知设置 */ fun pushMsg(context: Context) { val channelId = "channel_id" val channelName = "channel_name" val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { val channel = NotificationChannel( channelId, channelName, NotificationManager.IMPORTANCE_HIGH) manager.createNotificationChannel(channel) } val builder = NotificationCompat.Builder(context, channelId) builder.setContentTitle( "新消息来了" ) //标题 .setContentText( "周末到了,不用上班了" ) //文本内容 .setSmallIcon(R.mipmap.ic_launcher) //小图标 .setAutoCancel( true ) //设置点击信息后自动清除通知 manager.notify( 1 , builder.build()) } |
3. Android 8.0 启动后台 service 问题
Android P 上应用在后台启动 service 时报了个异常:
1 | java.lang.IllegalStateException, Not allowed to start service Intent |
原因:Android 8.0+ 对后台服务进行了限制,如果依然采用之前startService()方式会导致问题。
Android 8.0 对特定函数做出了以下变更:
- 如果针对 Android 8.0 的应用尝试在不允许其创建后台服务的情况下使用 startService() 函数,则该函数将引发一个 IllegalStateException。
- 新的 Context.startForegroundService() 函数将启动一个前台服务。现在,即使应用在后台运行,系统也允许其调用 Context.startForegroundService()。不过,应用必须在创建服务后的五秒内调用该服务的 startForeground() 函数。
解决办法:原来的 startService() 需要根据sdk版本进行兼容
1 2 3 4 5 6 | Intent intent = new Intent(context, SampleService. class ); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(intent); } else { context.startService(intent); } |
之后在被启动的 Service 创建服务后的五秒内需要调用 startForeground(1, notification) ,如果不调用或调用时间超过5秒则会抛出一个 ANR 错误。
所以需要在 service 的 onCreat() 方法中按如下代码设置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | public class SampleService extends Service { private static final String CHANNEL_ID = "channel_id" ; private static final String CHANNEL_NAME = "channel_name" ; @Override public void onCreate() { super .onCreate(); //适配 8.0 service NotificationManager notificationManager = (NotificationManager) App.getInstance() .getSystemService(Context.NOTIFICATION_SERVICE); NotificationChannel mChannel; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { mChannel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH); if (notificationManager != null ) { notificationManager.createNotificationChannel(mChannel); } Notification notification = new Notification .Builder(getApplicationContext(), CHANNEL_ID) .build(); startForeground( 1 , notification); } } @Nullable @Override public IBinder onBind(Intent intent) { return null ; } } |
除此之外,Android 9.0 要求创建一个前台服务需要请求 FOREGROUND_SERVICE 权限,否则系统会引发 SecurityException。
解决方法:AndroidManifest.xml中添加FOREGROUND_SERVICE权限:
1 | <uses-permission android:name= "android.permission.FOREGROUND_SERVICE" /> |
4. Android 8.0 静态注册广播 行为变更
Android 8.0 在 AndroidManifest 中注册的 receiver 不能收到广播
原因:Android 8.0 引入了新的广播接收器限制,加强对匿名 receiver 的控制,以至于在 manifest 中注册的隐式 receiver 都失效了。
不过 8.0 以后,静态注册的广播接收器还是可以接收到广播的,只要广播是通过显示方式发送的。
当广播接收器使用静态注册方式时,除了一些例外,这个接收器接收不到隐式广播。注意这个“隐式”是重点。
先定义一个简单广播:
1 2 3 4 5 6 | public class MyReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context,context.toString(),Toast.LENGTH_SHORT).show(); } } |
然后注册到AndroidManifest.xml中
1 2 3 4 5 | <receiver android:name= ".broadcast.MyReceiver" > <intent-filter> <action android:name= "com.demo.recriver" /> </intent-filter> </receiver> |
最后,在Activity中发送一个广播,intent通过设置Action为com.demo.recriver的形式发送隐式广播。
1 2 | Intent intent = Intent intent = new Intent( "com.demo.recriver" ); //隐式intent,发送隐式广播 sendBroadcast(intent); |
运行后会发现在8.0以下的手机上,会有Toast显示,8.0以上的手机不会弹出,说明没有接收到广播。
原因在于这个广播 是“隐式” 发送的,8.0中,静态注册的广播接收器无法接受 隐式广播。
有两种解决方法:
-
在Activity或其他组件中动态注册广播
-
发送显式广播
发送显式广播写法:
复制代码
1 2 | Intent intent = new Intent(MainActivity. this ,MyReceiver. class ); //显示指定组件名 sendBroadcast(intent); |