Android 笔记:sdk升级为28遇到的问题
Android 笔记
targetSdkVersion升级为26+遇到的问题及解决方案。
Android 9.0 以上修改
1.当在 Android 9.0 加载网络请求数据时,有时会抛出如下异常:
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>
<application android:usesCleartextTraffic="true"/>
原来默认为 true,但在 Android 9.0 中默认值改为了 false,因此将配置手动设为 true 即可解决明文传输被限制的问题
方法2:
在res文件夹下创建一个xml文件夹,然后创建一个network_security_config.xml文件,文件内容如下:
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config cleartextTrafficPermitted="true" /> </network-security-config>
然后在<application>标签内添加以下代码:</application>
android:networkSecurityConfig="@xml/network_security_config"
2. Android 9.0 弃用 Apache HTTP Client
由于官方在 Android 9.0 中移除了所有 Apache HTTP Client 相关的类,因此我们的应用或是一些第三方库如果使用了这些类,就会抛出找不到类的异常:
java.lang.NoClassDefFoundError: Failed resolution of: Lorg/apache/http/conn/scheme/SchemeRegistry;
若需要继续使用 Apache HTTP Client ,可通过以下方法进行适配:
在 AndroidManifest.xml 中的<application>标签内添加以下内容:</application>
<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()方法。
/** * 获取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并不管用
//卸载APP fun uninstallApp(activity: Activity, packageName: String) { val intent = Intent(Intent.ACTION_DELETE) intent.data = Uri.parse("package:$packageName") activity.startActivity(intent) }
原因是没有添加权限,解决办法:
在AnroidManifest.xml中加入
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
5. Android 9.0 取消 BottomNavigationView 动画效果的实现
在 API 28 之前,取消 BottomNavigationView 动画效果的实现方法为:
//使用 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属性即可。
<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中加入
<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 为目标平台时,必须实现一个或者多个通知渠道,以向用户显示通知。比如聊天软件,为每个聊天组设置一个通知渠道,指定特定声音、灯光等配置。
示例代码如下:
/** * 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 时报了个异常:
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版本进行兼容
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() 方法中按如下代码设置:
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权限:
<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 以后,静态注册的广播接收器还是可以接收到广播的,只要广播是通过显示方式发送的。
当广播接收器使用静态注册方式时,除了一些例外,这个接收器接收不到隐式广播。注意这个“隐式”是重点。
先定义一个简单广播:
public class MyReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context,context.toString(),Toast.LENGTH_SHORT).show(); } }
然后注册到AndroidManifest.xml中
<receiver android:name=".broadcast.MyReceiver"> <intent-filter> <action android:name="com.demo.recriver"/> </intent-filter> </receiver>
最后,在Activity中发送一个广播,intent通过设置Action为com.demo.recriver的形式发送隐式广播。
Intent intent = Intent intent = new Intent("com.demo.recriver");//隐式intent,发送隐式广播 sendBroadcast(intent);
运行后会发现在8.0以下的手机上,会有Toast显示,8.0以上的手机不会弹出,说明没有接收到广播。
原因在于这个广播 是“隐式” 发送的,8.0中,静态注册的广播接收器无法接受 隐式广播。
有两种解决方法:
在Activity或其他组件中动态注册广播
发送显式广播
发送显式广播写法:
Intent intent = new Intent(MainActivity.this,MyReceiver.class);//显示指定组件名 sendBroadcast(intent);