在应用程序启动时,应用程序启动应该执行一些网络任务的服务。 在目标API级别26后,我的应用程序无法在Android 8.0后台启动服务。

导致原因:java.lang.IllegalStateException:不允许启动 服务意图{ cmp = my.app.tt / com.my.service }: app是在后台uid UidRecord{90372b1 u0a136 CEM空闲procs:1 seq (0, 0, 0)}

我的理解是: 后台执行限制

startService()方法现在抛出一个IllegalStateException 针对Android 8.0的应用程序尝试使用这种方法 不允许创建后台服务。

“在不被允许的情况下”——这实际上是什么意思?以及如何修复它。我不想把我的服务设置为前台


允许的情况是一个临时白名单,其中后台服务的行为与Android O之前相同。

Under certain circumstances, a background app is placed on a temporary whitelist for several minutes. While an app is on the whitelist, it can launch services without limitation, and its background services are permitted to run. An app is placed on the whitelist when it handles a task that's visible to the user, such as: Handling a high-priority Firebase Cloud Messaging (FCM) message. Receiving a broadcast, such as an SMS/MMS message. Executing a PendingIntent from a notification. Starting a VpnService before the VPN app promotes itself to the foreground.

来源:https://developer.android.com/about/versions/oreo/background.html

换句话说,如果您的后台服务不符合白名单要求,您必须使用新的JobScheduler。它基本上与后台服务相同,但它周期性地被调用,而不是持续地在后台运行。

如果你使用的是IntentService,你可以改成JobIntentService。请看下面@kosev的回答。


如果你有集成的firebase消息推送通知,

由于后台执行限制,为android O (android 8.0)添加新的/更新firebase消息依赖项。

compile 'com.google.firebase:firebase-messaging:11.4.0'

如果需要,升级谷歌播放服务和谷歌存储库。

更新:

 compile 'com.google.firebase:firebase-messaging:11.4.2'

我有办法了。对于8.0版本之前的设备,你必须使用startService(),但是对于7.0版本之后的设备,你必须使用startForgroundService()。下面是启动服务的代码示例。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        context.startForegroundService(new Intent(context, ServedService.class));
    } else {
        context.startService(new Intent(context, ServedService.class));
    }

在服务类中,请添加以下代码进行通知:

@Override
public void onCreate() {
    super.onCreate();
    startForeground(1,new Notification());
}

其中O为Android版本26。

如果你不想让你的服务在前台运行,而想让它在后台运行,发布Android O,你必须将服务绑定到如下连接:

Intent serviceIntent = new Intent(context, ServedService.class);
context.startService(serviceIntent);
context.bindService(serviceIntent, new ServiceConnection() {
     @Override
     public void onServiceConnected(ComponentName name, IBinder service) {
         //retrieve an instance of the service here from the IBinder returned 
         //from the onBind method to communicate with 
     }

     @Override
     public void onServiceDisconnected(ComponentName name) {
     }
}, Context.BIND_AUTO_CREATE);

如果服务通过扩展IntentService运行在后台线程中,你可以用JobIntentService替换IntentService, JobIntentService是Android支持库的一部分

使用JobIntentService的优点是,它在pre-O设备上表现为IntentService,在O或更高的设备上,它作为作业分派

JobScheduler还可以用于定期/按需作业。但是,确保处理向后兼容性,因为JobScheduler API仅从API 21可用


从firebase发布说明中,他们指出对Android O的支持首次发布于10.2.1(尽管我建议使用最新版本)。

请为android O添加新的firebase消息依赖项

compile 'com.google.firebase:firebase-messaging:11.6.2'

如果需要,升级谷歌播放服务和谷歌存储库。


使用startForegroundService()代替startService() 并且不要忘记创建startForeground(1,new Notification());在您的服务5秒内开始服务。


最好的方法是使用JobIntentService,它为Oreo使用新的JobScheduler,如果不可用则使用旧的服务。

在舱单上申报:

<service android:name=".YourService"
         android:permission="android.permission.BIND_JOB_SERVICE"/>

在你的服务中,你必须用onHandleWork替换onHandleIntent:

public class YourService extends JobIntentService {

    public static final int JOB_ID = 1;

    public static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, YourService.class, JOB_ID, work);
    }

    @Override
    protected void onHandleWork(@NonNull Intent intent) {
        // your code
    }

}

然后你开始你的服务:

YourService.enqueueWork(context, new Intent());

是的,那是因为在API 26上你不能在后台启动服务了。所以你可以在API 26之上启动ForegroundService。

你必须使用

ContextCompat.startForegroundService(...)

并在处理泄漏时发布通知。


由于对这个答案的争议投票(+4/-4),请先看看其他答案,并仅作为最后的手段。我只在一个以root身份运行的网络应用程序中使用过一次,我同意一般的意见,即在正常情况下不应该使用这种解决方案。

原答案如下:

其他答案都是正确的,但我想指出的是,另一种解决这个问题的方法是要求用户禁用应用程序的电池优化(这通常不是一个好主意,除非你的应用程序与系统相关)。看看这个答案如何请求选择退出电池优化而不让你的应用程序在谷歌播放被禁止。

你也应该检查电池优化是否在你的接收器关闭,以防止崩溃通过:

if (Build.VERSION.SDK_INT < 26 || getSystemService<PowerManager>()
        ?.isIgnoringBatteryOptimizations(packageName) != false) {
    startService(Intent(context, MyService::class.java))
} // else calling startService will result in crash

如果你在8.0上运行你的代码,那么应用程序将会崩溃。因此,在前台启动服务。如果低于8.0,使用以下命令:

Intent serviceIntent = new Intent(context, RingtonePlayingService.class);
context.startService(serviceIntent);

如果在8.0以上,那么使用这个:

Intent serviceIntent = new Intent(context, RingtonePlayingService.class);
ContextCompat.startForegroundService(context, serviceIntent );

我看到很多回复都建议只使用前台服务。为了使用ForegroundService,必须有一个与之关联的通知。用户将看到此通知。根据不同的情况,他们可能会对你的应用感到厌烦并卸载它。

最简单的解决方案是使用名为WorkManager的新的体系结构组件。您可以在这里查看文档:https://developer.android.com/topic/libraries/architecture/workmanager/

您只需定义扩展worker的worker类。

public class CompressWorker extends Worker {

    public CompressWorker(
        @NonNull Context context,
        @NonNull WorkerParameters params) {
        super(context, params);
    }

    @Override
    public Worker.Result doWork() {

        // Do the work here--in this case, compress the stored images.
        // In this example no parameters are passed; the task is
        // assumed to be "compress the whole library."
        myCompress();

        // Indicate success or failure with your return value:
        return Result.SUCCESS;

        // (Returning RETRY tells WorkManager to try this task again
        // later; FAILURE says not to try again.)
    }
}

然后您可以安排您想要运行它的时间。

    OneTimeWorkRequest compressionWork = 
        new OneTimeWorkRequest.Builder(CompressWorker.class)
            .build();
    WorkManager.getInstance().enqueue(compressionWork);

简单!有很多方法可以配置worker。它支持重复作业,如果需要的话,你甚至可以做一些复杂的事情,比如链接。希望这能有所帮助。


如果任何意图之前工作正常时,应用程序是在后台,这将不再是Android 8及以上的情况下。只引用意图,当app在后台时,它必须做一些处理。

必须遵循以下步骤:

Above mentioned intent should be using JobIntentService instead of IntentService. The class which extends JobIntentService should implement the - onHandleWork(@NonNull Intent intent) method and should have below the method, which will invoke the onHandleWork method: public static void enqueueWork(Context context, Intent work) { enqueueWork(context, xyz.class, 123, work); } Call enqueueWork(Context, intent) from the class where your intent is defined. Sample code: Public class A { ... ... Intent intent = new Intent(Context, B.class); //startService(intent); B.enqueueWork(Context, intent); }

下面的类以前扩展了Service类

Public Class B extends JobIntentService{
...

    public static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, B.class, JobId, work);
    }

    protected void onHandleWork(@NonNull Intent intent) {
        ...
        ...
    }
}

com.android。支持:支持compat需要JobIntentService -我使用26.1.0 V。 最重要的是确保Firebase库的版本至少是10.2.1,我在10.2.0有问题——如果你有的话! 你的manifest应该具有Service类的以下权限: 服务android: name = "。B” android:出口= " false " android:许可= " android.permission.BIND_JOB_SERVICE "

希望这能有所帮助。


正如@kosev在他的回答中所说,你可以使用JobIntentService。 但是我使用了另一种解决方案——捕获IllegalStateException并将服务作为前台启动。 例如,这个函数启动我的服务:

@JvmStatic
protected fun startService(intentAction: String, serviceType: Class<*>, intentExtraSetup: (Intent) -> Unit) {
    val context = App.context
    val intent = Intent(context, serviceType)
    intent.action = intentAction
    intentExtraSetup(intent)
    intent.putExtra(NEED_FOREGROUND_KEY, false)

    try {
        context.startService(intent)
    }
    catch (ex: IllegalStateException) {
        intent.putExtra(NEED_FOREGROUND_KEY, true)
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            context.startForegroundService(intent)
        }
        else {
            context.startService(intent)
        }
    }
}

当我处理意图时,我做了这样的事情:

override fun onHandleIntent(intent: Intent?) {
    val needToMoveToForeground = intent?.getBooleanExtra(NEED_FOREGROUND_KEY, false) ?: false
    if(needToMoveToForeground) {
        val notification = notificationService.createSyncServiceNotification()
        startForeground(notification.second, notification.first)

        isInForeground = true
    }

    intent?.let {
        getTask(it)?.process()
    }
}

备选方案使用JobScheduler,它可以定时在后台启动服务。

首先将类命名为Util.java

import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;

public class Util {
// schedule the start of the service every 10 - 30 seconds
public static void schedulerJob(Context context) {
    ComponentName serviceComponent = new ComponentName(context,TestJobService.class);
    JobInfo.Builder builder = new JobInfo.Builder(0,serviceComponent);
    builder.setMinimumLatency(1*1000);    // wait at least
    builder.setOverrideDeadline(3*1000);  //delay time
    builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);  // require unmetered network
    builder.setRequiresCharging(false);  // we don't care if the device is charging or not
    builder.setRequiresDeviceIdle(true); // device should be idle
    System.out.println("(scheduler Job");

    JobScheduler jobScheduler = null;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
        jobScheduler = context.getSystemService(JobScheduler.class);
    }
    jobScheduler.schedule(builder.build());
   }
  }

然后,将JobService类命名为TestJobService.java

import android.app.job.JobParameters;
import android.app.job.JobService;
import android.widget.Toast;
 
  /**
   * JobService to be scheduled by the JobScheduler.
   * start another service
   */ 
public class TestJobService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
    Util.schedulerJob(getApplicationContext()); // reschedule the job
    Toast.makeText(this, "Bg Service", Toast.LENGTH_SHORT).show();
    return true;
}

@Override
public boolean onStopJob(JobParameters params) {
    return true;
  }
 }

在名为servicerreceiver .java的BroadCast Receiver类之后

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

 public class ServiceReceiver extends BroadcastReceiver {
 @Override
public void onReceive(Context context, Intent intent) {
    Util.schedulerJob(context);
 }
}

用服务和接收器类代码更新清单文件

<receiver android:name=".ServiceReceiver" >
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
    </receiver>
    <service
        android:name=".TestJobService"
        android:label="Word service"
        android:permission="android.permission.BIND_JOB_SERVICE" >

    </service>

将main_intent启动器留在默认创建的mainActivity.java文件中,mainActivity.java文件中的更改是

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Util.schedulerJob(getApplicationContext());
  }
 }

WOOAAH ! !后台服务启动时没有前台服务

[编辑]:你可以使用工作管理器的任何类型的Android后台任务。


我也有这个问题

添加了这个库

implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'

重新安装了应用程序帮我解决了这个问题


它实际上发生了,因为手机在屏幕外,或者你在启动服务时按了电源键。解决这个问题的方法对我来说是 启动一个活动,当它将进入onResume然后启动服务。 在我的例子中,它是启动和启动服务。


我对这里的答案很不满意。如果前台服务和WorkManager都适合用例呢? 我已经找到了一个解决方案,我使用流程作用域,并确保在日志逻辑中不包括作用域取消异常。 像这样:

with(ProcessLifecycleOwner.get()) {
  lifecycleScope.launch {
    lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
      try {
        context.startService(context, Service::class.java)
      } catch (ex: CancellationException) {
        // app minimized, scope cancelled, do not log as error
      } catch (ex: IllegalStateException) {
        logToFirebase(ex)
      }
    }
  }
}

更多详细信息请参见本文https://medium.com/@lepicekmichal/android-background- serviceswith-hiccup -501e4479110f


您可以尝试此代码以避免崩溃。正如谷歌开发者在问题跟踪器中所说。

private val activityManager by lazy { getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager }

//due to https://issuetracker.google.com/issues/113122354
private fun isInForegroundByImportance(): Boolean {
    val importanceState = activityManager.runningAppProcesses.find {
        it.pid == android.os.Process.myPid()
    }?.importance ?: return false
    return importanceState >= RunningAppProcessInfo.IMPORTANCE_FOREGROUND
}

和使用

override fun onResume() {
    super.onResume()
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || isInForegroundByImportance()) {
        val intent = Intent(this, BluetoothScannerService::class.java)
        this.startService(intent)
    }
}