【问题标题】:Service crashing and restarting服务崩溃并重新启动
【发布时间】:2013-07-21 04:04:40
【问题描述】:

关于它有几个问题,但我总是读到同样的话:“如果系统需要资源,服务将被终止”或“你不能构建一个永远运行的服务,因为它在后台运行的越多,越多容易被系统杀死”等等。

我面临的问题是:我的服务运行良好,正如预期的那样,如果我运行我的应用程序然后退出它,我的服务仍在运行,但是当我终止我的应用程序时(通过转到“最近的应用程序”并将其扫开)服务停止。此时,如果我转到设置>>应用程序>>运行,我会看到服务正在重新启动。过了一会儿,它又回来了,我的服务运行没有问题。

我用谷歌搜索了一些我可以做的事情,但让我们先看看我的代码:

我通过这种方式启动我的服务(单击按钮后):

Intent intent = new Intent (MainActivity.this, MyService.class);
startService(intent);

我还额外添加了 3 个整数,所以我有这样的东西:

final Integer i, i2, i3;
i = 5; //for example
i2 = 10; //for example
i3 = 15; //for example
final Intent intent = new Intent (MainActivity.this, MyService.class);
intent.putExtra("INTEGER1", i);
intent.putExtra("INTEGER2", i2);
intent.putExtra("INTEGER3", i3);
startService(intent);

在 MyService 中,我有以下内容:

public class MyService extends Service
{

  AlarmManager am;
  BroadcastReceiver br;
  PendingIntent pi;
  Integer i, i2, i3;

  @Override
  public void onCreate()
  {
    super.onCreate();
    am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
    pi = PendingIntent.getBroadcast(this, 0, new Intent("anyany"); 0) //Why those zeros?

    br = new BroadcastReceiver ()
    {
      public void onReceive (Context context, Intent i) {
        new thread(new Runnable()
        {
          public void run()
          {
            //do something
          }
        }).start();
      }
    };
  }

  @Override
  public void onStartCommand(Intent intent, int flags, int startId)
  {
    super.onStartCommand(intent, flags, startId);
    try
    {
      i = intent.getIntExtra("INTENT1", 0) // I don't understant yet why this zero are here
      i2 = intent.getIntExtra("INTENT2", 0)
      i3 = intent.getIntExtra("INTENT3", 0);
    }
    catch(NullPointerException e) {}

    this.registerReceiver(br, new IntentFilter("anyany"));

    new thread(new Runnable()
    {
    public void run()
      {
      am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock. elapsedRealtime() + i*1000, i2*1000, pi);
      }
    }).start();
  return START_REDELIVER_INTENT; //so I can get my Extra even with my Activity closed
}

我的 onDestroy:

@Override
public void onDestroy()
{
  unregisterReceiver(br);
  super.onDestroy();
}

我也有 onBind() 方法(没有@Override),但它返回 null。 我谷歌了很多,我试图在前台运行服务,所以我这样做了(在 de onStartCommand 内部):

Notification n = new Notification(R.drawable.ic_laucher), getText(R.string.app_name), System.currentTimeMillis());
PendingIntent npi = PendingIntent.getActivity(this, MainActivity.class);
n.setLatestEventInfo(this, getText(R.string.notification_title), getText(R.string.notification_message), npi);
startForeground(3563, n);

我的通知出现了,当我点击它时,我的应用程序运行,但我的服务的问题没有得到解决(我相信它仍然没有在前台运行)。通知也重新启动。

我还删除了 Try catch 并为整数定义了一个值(所以我没有使用 getIntExtra() 方法),但没有任何改变

经过几次测试后,我尝试查看日志,当我杀死我的应用程序时,我收到以下消息:正在计划重新启动崩溃的服务。

那么,由于某种原因,当我的 MainActivity 死亡时,我的服务崩溃了,为什么?这里的目的不是将服务转换为无法杀死的上帝(我认为这根本不是不可能的,WhatsApp 运行了 105 小时!)而是防止我的服务在我的应用程序死后不崩溃.

我不知道这是否有帮助,但这是我在 Manifest.xml 中添加的内容

<Activity android:name = ".MyService"/>
<service android:name ="Myservice" android:enabled="true" android: exported="false"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>

最小 API = 9,目标 API = 17。 运行时服务大小:约 3MB。

希望我的英语很清楚,对不起。

PS:整个代码按预期运行,所以如果您看到任何 sintax 错误,请随时编辑它。

编辑

如果我在 AndroidManifest.xml 的 &lt;service&gt; 中添加 android:isolatedProcess="true",我会在 logCat 中收到此错误:java.lang.RuntimeException: Unable to create a service in com.mycompany.myapp.myservice: java.lang.SecurityException: Isolated process not allow ed to call getIntentSender

当我使用它启动我的服务时,MainActivity 没有显示任何错误,只有服务崩溃。

【问题讨论】:

    标签: java android service crash kill


    【解决方案1】:

    我终于找到了解决办法!我从服务中删除了 AlarmManager,服务不再兑现,但我必须使用它

    问题是用户从最近的应用程序中轻扫应用程序后服务崩溃,所以我所做的是阻止应用程序出现在该窗口中。将以下内容添加到您的 AndroidManifest.xml 作为 &lt;activity&gt; 的子项

    android:excludeFromRecents="true"
    

    现在当用户退出你的应用时,它不会出现在最近的应用窗口中,这意味着系统在你退出后立即杀死它,所以它不会浪费任何资源。

    PS:不要忘记将服务设置为在单独的进程中运行,将以下内容添加到您的 AndroidManifest.xml 中,作为 &lt;service&gt; 的子项

    android:process=":remote"
    

    编辑 - 找到真正的解决方案

    经过大量研究和研究(数月的研究),我深入研究了 android API,这是一个发现,这是仅在 API 16+ 时发生的预期行为,android arquiteture 的更改改变了PendingIntents 被系统广播的方式,所以谷歌添加了标志FLAG_RECEIVER_FOREGROUND,你必须将此标志作为PendingIntent.getBroadcast()上的参数传递给你正在使用的意图,这里是一个例子:

    if(Build.VERSION.SDK_INT >= 16)     //The flag we used here was only added at API 16    
        myIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        //use myIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); if you want to add more than one flag to this intent;
    
    
    
    PendingIntent pi = PendingIntent.getBroadcast(context, 1, myIntent, 0); // the requestCode must be different from 0, in this case I used 1;
    

    早于 API 16 的 Android 版本将按预期运行,如果您从“最近的应用程序”页面轻扫该应用程序,该服务不会崩溃。

    【讨论】:

    • 如果我在后台运行服务时向左滑动我的应用程序会崩溃。有什么帮助吗?
    • 如果您发送广播,这对我有用。但是,例如使用 Google Cloud Messaging 的广播,我无法添加标志并且服务仍然崩溃......
    • @matteolel - 我遇到了同样的问题。你是如何为 GCM 解决的?
    • 最近应用列表中没有显示应用,但如果用户清除所有最近应用列表,服务仍然崩溃。
    【解决方案2】:

    正如文档所述,Service 在其被调用者的主线程中运行,通常是 UI 线程。所以发生的事情是,当你杀死你的应用程序时,你杀死了你的应用程序进程,因此服务也被杀死了。

    您可以通过在 Manifest.xml 文件中的 &lt;service&gt; 标记中使用 android:process 在不同的进程中创建 Service 来解决此问题。

    不过,如果Service 需要独立于被调用方并且它可能被不同的应用程序使用,那么通常你会在自己的进程中启动Service。如果您的 Service 仅供您自己的应用程序使用,请坚持使用默认行为,不要杀死您的应用程序。

    编辑 1:

    android:isolatedProcess 的文档说:

    如果设置为 true,此服务将在一个特殊进程下运行,即 与系统的其余部分隔离,并且没有其权限 自己的。与它的唯一通信是通过服务 API (绑定和启动)。

    【讨论】:

    • 我添加了这个:android:process=":remote" 但没有任何改变。
    • 那么您可能正在杀死整个 DVM 环境,而不仅仅是一个进程。每个应用程序都在自己的 DVM 上运行,因此您可能需要在完全不同的应用程序(即全新的 .apk 包)中实现您的服务。
    • 那么该服务会是我应用程序的某种“插件”吗? ?我将搜索为什么整个 DVM 被杀死,看看是否有什么可以解决的。
    • 如果您的服务需要独立于您的应用程序主线程运行,那么它必须是一个独立的应用程序。
    • 看这里,有错误的来源:android.googlesource.com/platform/frameworks/base/+/…。我的猜测是 registerReceiver 或 setRepeating 抛出错误...尝试评论行!
    【解决方案3】:

    从另一个 SO 答案 (Link),这是预期的行为。但可以肯定的是,这里有人会有解决方法或解决方案。

    您的代码问题:

    pi = PendingIntent.getBroadcast(this, 0, new Intent("anyany"); 0) //为什么是那些零?

    您看到的第一个零被提及为requesCode,并被描述为当前未使用:

    requestCode:发件人的私人请求代码(当前未使用)。

    第二个零实际上应该是给定(here) 的标志之一。

    i = intent.getIntExtra("INTENT1", 0) // 我还不明白为什么 这个零在这里

    Intent 的getIntExtra(String, int) 方法不需要将0 作为第二个参数:它可以是任何整数。 getIntExtra(String, int) 返回与您提供的字符串键对应的整数。如果该键不再存在(或从未存在),getIntExtra(String, int) 返回我们作为第二个参数传递的整数。是key失效时的默认值。

    【讨论】: