Android O (8.0)(API 26) 适配
最近将我们项目的targetSdkVersion升到26了,下面是一些需要适配的问题,后面遇到其他问题再补充:
一、参考我另一篇文章 Android 8.0 通知适配
二、安装APK
1、在Android 8.0及以上系统调用安装Apk的代码会发现屏幕闪一下就结束了,并没有跳转到安装Apk的界面。因为Android 8.0中,Google 移除掉了容易被滥用的允许未知来源应用的开关,在安装应用商店之外的第三方来源Apk的时候,需要用户手动授予安装未知应用的许可。
2、适配
首先在AndroidManifest文件中添加安装未知来源应用的权限
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
上面一步是必须的,添加了上面的权限,在Android8.0及以上也可以正常的安装Apk了,安装时如果Apk未允许安装则会弹窗让用户选择允许安装,如果允许则会安装,不允许则不安装。下面还有些设置,不是必须的。canRequestPackageInstalls方法(使用注意添加上面权限)查看此Apk是否已经允许安装了,不允许安装可以跳转到安装未知应用的设置页,让用户设置,然后返回App会回到onActivityResult中。示例如下:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
boolean installAllowed = getActivity().getPackageManager().canRequestPackageInstalls();
if (installAllowed) {
installApk();
} else {
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
startActivityForResult(intent, 100);
}
} else {
installApk();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == 100) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
boolean installAllowed = getActivity().getPackageManager().canRequestPackageInstalls();
if (installAllowed) {
installApk();
}
}
}
}
参考链接:https://blog.youkuaiyun.com/qq_17766199/article/details/80965631
三、应用图标适配
1、背景
建议看一下郭神写的 Android应用图标微技巧,8.0系统中应用图标的适配,不适配会有什么问题,是否一定要适配。答案是targetSdkVersion26以下不需要适配,targetSdkVersion26及以上不适配不会引起崩溃,只是图片在Android 8.0以上的手机上会变成下面这样,会将我们原来的icon放到一个白色层上了。
2、适配
在app的res下右键->New->Image Asset,打开AS提供的编辑器就可以生成icon了
Icon Type: 保持默认,表示同时创建兼容8.0系统以及老版本系统的应用图标。下面三个页签Foreground Layer用于编辑前景层,Background Layer用于编辑背景层,Legacy用于编辑老版本系统的图标,Trim选择No。右边是预览区。设置Foreground Layer的Image path,Background Layer的Image path或color。最后选择图片后注意Resize的大小,别超出那个黑圈了,里面是安全区域,超出去可能被截掉。
3、注意:在8.0以后的手机上会使用mipmap-anydpi-v26里面的icon,ic_launcher_round是Android 7.1系统上的过渡版本,若要使用则在AndroidManifest中application下使用android:roundIcon属性设置。看下面图片,这是在Andoird8.0下的系统会使用mipmap-(x)hdpi下的图片,可以看到图片四周都是透明的,在手机上看的icon比其他app的小。如果嫌小可以将除mipmap-anydpi-v26下的ic_laucher替换为之前的。
四、后台服务运行的限制
1、官网介绍如下:
Android 8.0 还对特定函数做出了以下变更:
- 如果针对 Android 8.0 的应用尝试在不允许其创建后台服务的情况下使用 startService() 函数,则该函数将引发一个 IllegalStateException。
- 新的 Context.startForegroundService() 函数将启动一个前台服务。现在,即使应用在后台运行,系统也允许其调用 Context.startForegroundService()。不过,应用必须在创建服务后的五秒内调用该服务的 startForeground() 函数。
如需了解详细信息,请参阅后台执行限制。
2、我自己重现了一下,具体如下:
我简单重现了下,我是在Activity的onPause()中延时调用启动了启动服务,按home键进入后台,生命周期执行onPause(),我试了下我手机100s就会重现崩溃了
@Override
protected void onPause() {
super.onPause();
handler.postDelayed(new Runnable() {
@Override
public void run() {
Intent intent = new Intent(context, MyService.class);
context.startService(intent);
}
}, 100000);
}
报错如下,IllegalStateException异常
12-20 17:57:12.526 15250-15250/com.bill.test E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.bill.test, PID: 15250
java.lang.IllegalStateException: Not allowed to start service Intent { act=com.bill.test.service.action.task cmp=com.bill.test/com.bill.test.MyService (has extras) }: app is in background uid UidRecord{dd3b2cd u0a159 LAST bg:+1m40s41ms idle change:cached procs:2 seq(0,0,0)}
at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1595)
at android.app.ContextImpl.startService(ContextImpl.java:1550)
at android.content.ContextWrapper.startService(ContextWrapper.java:664)
at com.peopledaily.common.statistics.post.MyService$1.run(MyService.java:69)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6863)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
3、解决方案按照上面1中的方案:
- 启动服务时判断一下在Android 8.0后调用startForegroundService启动服务,8.0前还是用startService
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
添加完上面代码还会报下面错误,这时还需要调用startForeground
12-20 18:30:05.066 15831-15831/com.bill.test E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.bill.test, PID: 15831
android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{5ca9d62 u0 com.bill.test/com.bill.test.MyService}
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1835)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6863)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
- Google要求在调用startForegroundService之后5s内调用startForeground,我是Service的onCreate中调用的startForeground,如下:
@Override
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForeground(0x64, new Notification());
}
}