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);

海康威视公司福利 1311人发布