官方规定:8.0以后不允许后台应用启动后台服务,
需要通过 startForegroundService() 指定为前台服务运行,或者使用 JobScheduler 替代。
问题:
- 若在 Android 8.0 设备上通过 startService 启动一个属于后台应用的后台服务,会直接崩溃:
Caused by: java.lang.IllegalStateException: Not allowed to start service Intent{ act=intent.action.ServerService pkg=com.server }: app is in background uid null
at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1577)
at android.app.ContextImpl.startService(ContextImpl.java:1532)
at android.content.ContextWrapper.startService(ContextWrapper.java:664)
总结一下处理方案。
1.使用 startForegroundService 代替
2.使用 JobScheduler 代替
3.启动服务前,先将服务所在应用从后台切换到前台
- 使用 startForegroundService 代替。去启动一个前台服务,但是这个方式,需要在调用 startForegroundService后五秒内通过startForeground发送一个可见通知
Intent intent1 = new Intent();
intent1.setClass(this, TestService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent1);
} else {
startService(intent1);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel(ID, NAME, NotificationManager.IMPORTANCE_HIGH);
manager.createNotificationChannel(channel);
Notification notification = new Notification.Builder(this, ID)
.build();
startForeground(1, notification);
}
startForeground只需要在onCreate或者onBind中启动一次就好
在服务销毁时调用 stopForeground(true);
2.使用 JobScheduler 代替,这也是官方建议的一中方式。
- 新建一个Services继承JobService;
public class MyJobService extends JobService {
public static String TAG = "MyJobService";
Context mContext;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public boolean onStartJob(final JobParameters params) {
Log.d(TAG, "onStartJob ");
new Thread(new Runnable() { // JobService默认在主线程中执行,如果操作耗时任务,需要启用新线程执行
@Override
public void run() {
// 具体业务逻辑代码
Log.d(TAG, "onStartJob run() ");
try {
Thread.sleep(3000);
jobFinished(params,false); // 如果onStartJob返回true的话需要调用此方法表示任务执行完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
return true; // 返回false表示任务执行完毕,下一个任务即将展开,true表示任务还未执行结束,需要手动调用jobFinished;
}
@Override
public boolean onStopJob(JobParameters params) { //在onStartJob()返回true的前提下, 取消cancel或者强制停止Job任务的时候才会调用到此方法
Log.d(TAG, "onStopJob");
return false; // 任务是否应该在下次继续
}
- 其次需要在manifest中申明权限:
<service
android:name=".ui.AlarmCheckJobService"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE" />
- 然后创建JobScheduler:
ComponentName jobService = new ComponentName(this, AlarmCheckJobService.class);
JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
- 最后创建Job,开启任务
JobInfo.Builder builder = new JobInfo.Builder(1, jobService);
JobInfo jobInfo = builder
.setPeriodic(15 * 60 * 1000) // 每隔15分钟运行一次
.setMinimumLatency(0) // 设置任务运行最少延迟时间
.setOverrideDeadline(60000) // 设置deadline,若到期还没有达到规定的条件也会开始执行
.setPersisted(true) // 设备重新启动之后你的任务是否还要继续运行
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) // 设置网络条件(不是蜂窝网络( 比方在WIFI连接时 )时任务才会被运行)
.setRequiresCharging(true) // 设置是否充电的条件
.setRequiresDeviceIdle(false) // 设置手机是否空闲的条件
.setRequiresCharging(true) // 这种方法告诉你的应用,仅仅有当设备在充电时这个任务才会被运行。
.setRequiresDeviceIdle(true) //这种方法告诉你的任务仅仅有当用户没有在使用该设备且有一段时间没有使用时才会启动该任务。
.build();
scheduler.schedule(jobInfo);
3.启动服务前,先将服务所在应用从后台切换到前台,主要思路如下:
如果应用处于后台,就启动一个透明的、用户无感知的 Activity,将应用切换到前台,然后再通过 startService 启动服务,随后 finish 掉透明 Activity。
调用端这样 startService :
Intent serviceIntent = new Intent();
serviceIntent.setAction("com.ahab.server.service");
serviceIntent.setPackage("com.ahab.server");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try{
context.startService(serviceIntent);
}catch (Exception e){
Intent activityIntent = new Intent();
activityIntent.setAction("com.ahab.server.TranslucentActivity");
activityIntent.setPackage("com.ahab.server");
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(activityIntent);
}
}else{
context.startService(serviceIntent);
}
启动透明 Activity 后 startService:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent serviceIntent = new Intent();
serviceIntent.setAction("com.ahab.server.service");
serviceIntent.setPackage("com.ahab.server");
context.startService(serviceIntent);
finish();
}
}
上面都是围绕 startService 启动方式来讲,没有提及 「bindService」 方式,系统并未限制 bindService 启动后台服务,所以通过 bindService 绕过 Android 8.0 startService 的限制,也是可行的。
网友评论