关于android O之后前台服务crash的问题
android O 之后系统提供startForegroundService(Intent service)
来开启前台服务
官方原文:Similar to startService(android.content.Intent), but with an implicit promise that the Service will call startForeground(int, android.app.Notification) once it begins running. The service is given an amount of time comparable to the ANR interval to do this, otherwise the system will automatically stop the service and declare the app ANR.
Unlike the ordinary startService(android.content.Intent), this method can be used at any time, regardless of whether the app hosting the service is in a foreground state.
翻译:跟startService(Intent)
相似,但不同的是,service
通过这个方法启动之后需要在一定时间内马上调用startForegrond()
方法,而这个时间跟系统出现ANR
的时间一致,否则,系统将会自动停止service
,并出现ANR
。跟传统的startService()
方法不同,这个方法可以在任何时候使用,不管启动service
的应用是否在前台。
实际上,官方描述的只是出现ANR
的情况,在指定时间内(5s)未调用startForegrond()
就会出现ANR
,但是如果在调用startForegrond()
之前服务已经被杀死了,那么会出现crash:android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()
换句话说,现在无论应用处于前后台都可以通过startForegroundService()
方法来启动服务,只是需要在服务一启动的时候,就调用startForeground()
方法,也就是说,我们需要在服务的onCreate()
中调用startForeground()
,如下:
public void onCreate() {
... //创建Notification
startForeground(id, notification);
}
不过以防万一,最好在onStartCommand()
中也调用startForeground()
public void public int onStartCommand(Intent intent, int flags, int startId) { {
... //前面创建的Notification
startForeground(id, notification);
}
针对出现crash
的情况,目前谷歌官方觉得这是在预期之内的,并不算bug,但是对于没写厂商的手机,出现crash的情况还是很高的,比如小米。目前借鉴网上大佬的方法是:
- 当应用处于前台的时候,调用
startservices()
方法来启动服务 - 当应用处于后台的时候,可以创建一个一像素的
activity
讲应用换到前台,任何再调用startservices()
来启动服务
具体代码如下:
//方法一:适用于应用没有启动activity的情况,比如说,被第三方应用拉起,只执行application差不多
//判断应用是否处于前台
public static boolean isAppForeground(Context context) {
final ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
if (manager != null) {
final List<ActivityManager.RunningAppProcessInfo> taskInfoList = manager.getRunningAppProcesses();
for (int i = 0; i < taskInfoList.size(); i++) {
//"com.runningman.test"为包名
if ("com.runningman.test".equals(taskInfoList.get(i).processName)) {
if (taskInfoList.get(i).importance != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
return false;
} else {
return true;
}
}
}
}
return false;
}
//方法二:适用于应用有启动activity的情况
//也可以通过application的registerActivityLifecycleCallbacks方法来判断
boolean isAppForeground = false;
application.registerActivityLifecycleCallbacksnew SimpleActivityLifecycleCallbacks() {
@Override
public void onActivityStarted(Activity activity) {
super.onActivityStarted(activity);
isAppForeground = false;
}
@Override
public void onActivityStopped(Activity activity) {
super.onActivityStopped(activity);
isAppForeground = true;
}
}
//最后通过以下方式启动service
public static void start(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (isAppForeground(context)) {
Intent intent = new Intent(context, TestService.class);
context.startService(intent);
} else {
ServiceActivity.start(context); //一像素Activity
}
} else {
Intent intent = new Intent(context, TestService.class);
context.startService(intent);
}
}
//ServiceActivity.class
public class ServiceActivity extends AppcompatActivity {
@Override
public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
//设置1像素
Window window = getWindow();
window.setGravity(Gravity.START | Gravity.TOP);
WindowManager.LayoutParams params = window.getAttributes();
params.x = 0;
params.y = 0;
params.height = 1;
params.width = 1;
window.setAttributes(params);
}
@Override
protected void onStart() {
super.onStart();
Intent intent = new Intent(this, TestService.class);
startService(intent);
finish();
}
public static void start(Context context) {
Intent intent = new Intent(context, ServiceActivity.class);
context.startActivity(intent);
}
}
使一像素Activity
透明化
<style name="service_Activity" parent="Theme.AppCompat.Light.NoActionBar">//无标题
<item name="android:windowIsTranslucent">true</item>//透明
</style>