Android 8.0:java.lang.IllegalStateException:不允许启动服务Intent

时间:2017-09-27 10:14:50

标签: android android-service android-8.0-oreo

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

  

引起:java.lang.IllegalStateException:不允许启动   服务意图{   CMP = my.app.tt / com.my.service   }:app在后台uid UidRecord {90372b1 u0a136 CEM idle procs:1   SEQ(0,0,0)}

据我所知,它与: Background execution limits

  

如果是,则startService()方法现在抛出IllegalStateException   针对Android 8.0的应用尝试在某种情况下使用该方法   它不被允许创建后台服务。

" ,在不允许的情况下" - 它究竟意味着什么?以及如何解决它。我不想将我的服务设置为"前景"

20 个答案:

答案 0 :(得分:164)

我得到了解决方案。对于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。

希望它能解决IllegalArgumentException

答案 1 :(得分:148)

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

  

在某些情况下,后台应用会被放置在临时白名单上几分钟。虽然应用程序位于白名单中,但它可以无限制地启动服务,并允许其后台服务运行。当应用程序处理用户可见的任务时,应用程序将放置在白名单中,例如:

     
      
  • 处理高优先级的Firebase云消息传递(FCM)消息。
  •   
  • 接收广播,例如短信/彩信。
  •   
  • 从通知中执行PendingIntent。
  •   
  • 在VPN应用程序将自己升级到前台之前启动VpnService。
  •   

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

因此换句话说,如果您的后台服务不符合白名单要求,则必须使用新的JobScheduler。它与后台服务基本相同,但它会定期调用,而不是在后台连续运行。

如果您使用的是IntentService,则可以更改为JobIntentService。请参阅@ kosev的answer below

答案 2 :(得分:60)

最好的方法是使用JobIntentService使用新的JobScheduler for Oreo或旧服务(如果不可用)。

在你的清单中声明:

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

答案 3 :(得分:27)

如果服务通过扩展IntentService在后​​台线程中运行,您可以将IntentService替换为作为Android支持库一部分提供的JobIntentService

使用JobIntentService的优点是,它在pre-O设备上的行为为IntentService,在O及更高版本上行为,它将其作为作业发送

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

答案 4 :(得分:11)

是的,那是因为你无法在API 26中在后台启动服务。所以你可以在API 26之上启动ForegroundService。

你必须使用

ContextCompat.startForegroundService(...)

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

答案 5 :(得分:5)

来自firebase release notes,他们表示对Android O的支持最初是在10.2.1中发布的(虽然我建议使用最新版本)。

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

this.props.userData

根据需要升级Google Play服务和Google存储库。

答案 6 :(得分:5)

Oreo 中,Android定义了limits to background services

  

为改善用户体验,Android 8.0(API级别26)强制实施   应用程序在后台运行时可以做什么的限制。

如果仍然需要始终运行的服务,则可以使用前台服务。

  

后台服务限制:当应用处于空闲状态时,存在一些限制   使用后台服务。这不适用于前台   服务,对用户来说更明显。

因此您可以进行前台服务。服务运行时,您需要显示通知用户See this answer(还有很多其他人)

一个解决方案,如果-

您不希望收到服务通知吗?

  

您可以执行定期任务,1.它启动您的服务,2.服务将完成其工作,3.使其自身停止。如此一来,您的应用就不会被视为耗电了。

您可以对Alarm ManagerJob SchedulerEvernote-JobsWork Manager使用定期任务。

我已经使用Work-Manager测试了永久运行的服务。

答案 7 :(得分:4)

我看到很多建议仅使用ForegroundService的响应。为了使用ForegroundService,必须有一个与其关联的通知。用户将看到此通知。根据情况的不同,他们可能会对您的应用程序感到烦恼,然后将其卸载。

最简单的解决方案是使用名为WorkManager的新体系结构组件。您可以在此处查看文档:{​​{3}}

您只需定义扩展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);

容易!您可以通过多种方式配置工作程序。它支持周期性工作,如果需要,您甚至可以执行诸如链接之类的复杂工作。希望这会有所帮助。

答案 8 :(得分:3)

使用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;
  }
 }

之后,该BroadCast Receiver类名为 ServiceReceiver.java

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!在没有前台服务的情况下启动后台服务

答案 9 :(得分:3)

如果您在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 );

答案 10 :(得分:3)

正如@kosev在his answer中所说,您可以使用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)
        }
    }
}

当我处理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()
    }
}

答案 11 :(得分:2)

如果您已经集成了firebase消息传递推送通知,那么

由于Background Execution Limits,为Android O(Android 8.0)添加新的/更新firebase消息传递依赖项。

if($(".app-wizard-header").length > 0) {            
  if ($(document).scrollTop() > 50) {                  
      this.setState({Headerbg:'app-wizard-header'})          
  } else {                 
      this.setState({Headerbg:'app-wizard-header no-bg'})          
  }
}

根据需要升级Google Play服务和Google存储库。

<强>更新

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

答案 12 :(得分:1)

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

答案 13 :(得分:1)

如果先前有任何意图在应用程序处于后台运行时都可以正常工作,则从Android 8及更高版本开始不再适用。仅指在应用程序处于后台时必须进行一些处理的意图。

必须遵循以下步骤:

  1. 以上提到的意图应该使用JobIntentService而不是 IntentService
  2. 扩展JobIntentService的类应实现-onHandleWork(@NonNull Intent intent)方法,并应位于 方法,它将调用onHandleWork方法:

    public static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, xyz.class, 123, work);
    }
    
  3. 从定义您的意图的类中调用enqueueWork(Context, intent)

    示例代码:

    Public class A {
    ...
    ...
        Intent intent = new Intent(Context, B.class);
        //startService(intent); 
        B.enqueueWork(Context, intent);
    }
    
  

以下类以前扩展了服务类

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.support:support-compat需要
  1. JobIntentService-我使用26.1.0 V

  2. 最重要的是确保Firebase库版本至少在10.2.1上,我在10.2.0上遇到问题-如果有的话!

  3. 您的清单应具有Service类的以下权限:

    service android:name=".B"
    android:exported="false"
    android:permission="android.permission.BIND_JOB_SERVICE"
    

希望这会有所帮助。

答案 14 :(得分:0)

这实际上是因为手机在屏幕外,或者您在启动服务时按下了电源按钮。对我有用的解决方案是 启动一个活动,当它进入 onResume 然后启动服务。 就我而言,它正在启动并启动服务。

答案 15 :(得分:0)

我也有这个问题

添加了该库

implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'

并重新安装该应用程序为我解决了此问题

答案 16 :(得分:0)

我认为解决此问题的最佳方法是在运行时检查构建版本,并以此为基础,如果该版本低于Api级别21,请继续使用startService()。 但如果更高,则应使用作业调度程序。 我想您也可以使用工作管理器,但它仍处于测试阶段

答案 17 :(得分:-2)

其他答案都是正确的,但我想指出的另一种解决方法是要求用户禁用应用程序的电池优化(除非您的应用程序是系统,否则通常不建议这样做有关)。请参阅this answer,了解如何在不禁止您的应用在Google Play中被禁止的情况下,选择退出电池优化。

您还应该通过以下方法检查接收器中的电池优化功能是否已关闭,以防止崩溃:

$_

答案 18 :(得分:-3)

- 启动应用或主要活动中的第一个调用方法:

public static void createChancelNotification(Context mContext) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationManager notificationManager =
                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
        NotificationChannel androidChannel = new NotificationChannel(Constants.ANDROID_CHANNEL_ID,
                Constants.ANDROID_CHANNEL_NAME, NotificationManager.IMPORTANCE_MAX);
        notificationManager.createNotificationChannel(androidChannel);
    }
}

- 关闭app和firebase推送时的第二个:

public class FirebaseDataReceiver extends WakefulBroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
    if (intent.getExtras() != null) {
        for (String key : intent.getExtras().keySet()) {
            Object value = intent.getExtras().get(key);
            Log.e("FirebaseDataReceiver", "Key: " + key + " Value: " + value);
        }
    }
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
        // Attach component of GCMIntentService that will handle the intent in background thread
        ComponentName componentName = new ComponentName(context.getPackageName(), MyFirebaseMessageService.class.getName());
        // Start the service, keeping the device awake while it is launching.
        startWakefulService(context, intent.setComponent(componentName));
        setResultCode(Activity.RESULT_OK);
    }

}

}

- 文件中的最后一个句柄:

public class MyFirebaseMessageService extends FirebaseMessagingService {
private static final String TAG = MyFirebaseMessageService.class.getSimpleName();

@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
    if (remoteMessage.getData() != null) {
        // Do some thing here
    }
}

答案 19 :(得分:-13)

不要在onStartCommand中使用:

return START_NOT_STICKY

只需将其更改为:

return START_STICKY

它会起作用