如果我关闭活动,则从BroadcastReceiver.onReceive调用peekService将返回null

时间:2015-02-05 20:34:10

标签: android android-4.3-jelly-bean

问题

我为Android 4.3编写了一个程序,包括主要活动,广播接收器和服务。活动以onCreate方法绑定到服务。该活动有一个按钮,可以在将来10秒内安排警报。警报触发BroadcastReceiver.onReceive。此方法试图抓住活页夹,但有一种情况会失败,peekService会返回null

什么有效

  1. 单击按钮并等待10秒
  2. 点击按钮,点击主页,然后在主屏幕中等待,直到10秒钟。
  3. 单击按钮,单击活动列表,通过向左滑动关闭活动,重新打开程序并等待10秒(您需要快速: - )。
  4. 什么不起作用

    1. 单击按钮,单击活动列表,向左滑动关闭活动,等待10秒钟;这基本上就像(3.)没有重新打开程序。
    2. 具体来说,如果我执行这4个测试,我会得到以下日志:

      02-05 20:53:29.992: D/ServiceSSCCE.MyService(476): I've been bound.
      02-05 20:53:30.179: D/ServiceSSCCE.MainActivity(476): Service connected.
      02-05 20:53:43.265: D/ServiceSSCCE.MyReceiver(476): Awesome, let's get this **** done!
      02-05 20:53:55.460: D/ServiceSSCCE.MyReceiver(476): Awesome, let's get this **** done!
      02-05 20:54:08.531: D/ServiceSSCCE.MyService(764): I've been bound.
      02-05 20:54:08.663: D/ServiceSSCCE.MainActivity(764): Service connected.
      02-05 20:54:10.890: D/ServiceSSCCE.MyReceiver(764): Awesome, let's get this **** done!
      02-05 20:54:23.593: D/ServiceSSCCE.MyReceiver(788): I just received a null binder.
      

      最后一条消息显示test(4.)失败,而之前的消息显示(1.),(2.)和(3.)成功。

      我了解thisthisthis回答,以及Google在前两页中列出的几乎任何相关结果。我尝试了几件事,包括但不限于:

      • 来自startService和主要活动的BroadcastReceiver.onReceive来电(虽然我不确定我是否了解bindServicestartService如何互动)
      • 摆弄意图,特别是上下文(this VS getApplicationContext等)。
      • 将服务设置为前景(请参阅this

      我真的对为什么这种情况感兴趣,而不是我对解决方案感兴趣。

      MainActivity.java

      package it.damix.examples.servicesscce;
      
      import android.app.Activity;
      import android.app.AlarmManager;
      import android.app.PendingIntent;
      import android.content.ComponentName;
      import android.content.Context;
      import android.content.Intent;
      import android.content.ServiceConnection;
      import android.os.Bundle;
      import android.os.IBinder;
      import android.util.Log;
      import android.view.View;
      
      public class MainActivity extends Activity {
      
          private ServiceConnection mConnection = new ServiceConnection() {
              @Override
              public void onServiceDisconnected(ComponentName name) {
                  Log.d("ServiceSSCCE.MainActivity", "Service disconnected.");
              }
      
              @Override
              public void onServiceConnected(ComponentName name, IBinder service) {
                  Log.d("ServiceSSCCE.MainActivity", "Service connected.");
              }
          };
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
      
              bindService(new Intent(this, MyService.class), mConnection, Context.BIND_AUTO_CREATE);
          }
      
          public void scheduleAlarm(View view) {
              AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
              PendingIntent pi = PendingIntent.getBroadcast(this, 101, new Intent(this, MyReceiver.class), 0);
              am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 10000, pi);
          }
      }
      

      MyReceiver.java

      package it.damix.examples.servicesscce;
      
      import android.content.BroadcastReceiver;
      import android.content.Context;
      import android.content.Intent;
      import android.os.IBinder;
      import android.util.Log;
      
      public class MyReceiver extends BroadcastReceiver {
          @Override
          public void onReceive(Context context, Intent intent) {
              IBinder binder = peekService(context, new Intent(context, MyService.class));
      
              if (binder == null)
                  Log.d("ServiceSSCCE.MyReceiver", "I just received a null binder.");
              else
                  Log.d("ServiceSSCCE.MyReceiver", "Awesome, let's get this **** done!");
          }
      }
      

      MyService.java

      package it.damix.examples.servicesscce;
      
      import android.app.Service;
      import android.content.Intent;
      import android.os.Handler;
      import android.os.IBinder;
      import android.os.Messenger;
      import android.util.Log;
      
      
      public class MyService extends Service {
      
          static class MyHandler extends Handler {
          }
      
          @Override
          public IBinder onBind(Intent arg0) {
              Log.d("ServiceSSCCE.MyService", "I've been bound.");
              return new Messenger(new MyHandler()).getBinder();
          }
      
          @Override
          public boolean onUnbind(Intent intent) {
              Log.d("ServiceSSCCE.MyService", "I've been unbound.");
              return super.onUnbind(intent);
          }
      
          @Override
          public void onRebind(Intent intent) {
              Log.d("ServiceSSCCE.MyService", "I've been rebound.");
              super.onRebind(intent);
          }
      }
      

      ApplicationManifest.xml

      <?xml version="1.0" encoding="utf-8"?>
      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="it.damix.examples.servicesscce"
          android:versionCode="1"
          android:versionName="1.0" >
      
          <uses-sdk
              android:minSdkVersion="18"
              android:targetSdkVersion="18" />
      
          <application
              android:allowBackup="true"
              android:icon="@drawable/ic_launcher"
              android:label="@string/app_name"
              android:theme="@style/AppTheme" >
              <activity
                  android:name=".MainActivity"
                  android:label="@string/app_name" >
                  <intent-filter>
                      <action android:name="android.intent.action.MAIN" />
      
                      <category android:name="android.intent.category.LAUNCHER" />
                  </intent-filter>
              </activity>
      
              <receiver android:name="it.damix.examples.servicesscce.MyReceiver" android:enabled="true"></receiver>
              <service android:name="it.damix.examples.servicesscce.MyService" android:enabled="true"></service>
          </application>
      
      </manifest>
      

      activity_main.xml中

      <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:paddingBottom="@dimen/activity_vertical_margin"
          android:paddingLeft="@dimen/activity_horizontal_margin"
          android:paddingRight="@dimen/activity_horizontal_margin"
          android:paddingTop="@dimen/activity_vertical_margin"
          tools:context="it.damix.examples.servicesscce.MainActivity" >
      
          <Button
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="Click me to schedule an alarm..."
              android:onClick="scheduleAlarm"/>
      
      </RelativeLayout>
      

      链接

2 个答案:

答案 0 :(得分:1)

+1 damix911非常完整的问题陈述和可能解决的步骤。发布所有代码使我可以轻松地重新创建测试应用程序。

对于案例#4失败的问题的简短回答在于peekService()行为的一些未记录的细节。在Android框架工程师Dianne Hackborn的this post中,她解释说,对于peekService()返回IBinder,某些组件必须先前已绑定到该服务,从而导致系统创建IBinder 。该帖子是我找到获得IBinder描述的附加条件的唯一地方。

此处,对于#1到#3的情况,MainActivity的实例存在并绑定到服务,创建IBinder。当接收器运行并调用peekService()时,它将获得IBinder。对于案例#4,从最近的任务列表中滑动应用程序会杀死整个应用程序进程:活动和绑定服务。当警报随后触发时,将为接收方重新创建应用程序进程,但未启动活动,没有绑定到服务的请求,并且未创建服务,因此peekService()返回null。

我无法重现damix911的解决方案。当我修改服务属性以使其作为isolatedProcess运行时,我得到了一个安全异常(KitKat设备)。我质疑如何启动服务并使其成为前台服务会改变任何事情。启动它并返回默认(超级)模式 会导致服务变得粘滞,因此在刷卡后重新创建进程时,将重新创建服务。但是仍然没有任何约束力,因此IBinder没有peekService()可以返回。

答案 1 :(得分:0)

简而言之

解决方案涉及三件事:

  • 使服务成为前景服务。
  • 使用startService 绑定到活动中的活动。显然,peekService开始提供服务还不够;显然某人(一项活动或可能是另一项服务)必须受到约束。为什么会出现这种情况(对我而言)。
  • 在服务声明中添加android:isolatedService="true"

详细

MyReceiver.java 是正确的。

MyService.java 中声明的服务确实需要是前台:

public class MyService extends Service {

    ...

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        Notification notification = new Notification.Builder(this)
        .setTicker("Bla bla...")
        .setContentTitle("Title...")
        .setContentText("Text...")
        .build();
        startForeground(1234, notification);

        return super.onStartCommand(intent, flags, startId);
    }

    ...
}

这项服务的变化至少有一次导致我的活动提出连接泄漏警告(虽然我无法重现这种行为)。但是,通过正确解除绑定连接来解决问题;这意味着从onCreate中删除服务代码,并将相应的onResumeonStop方法添加到 MainActivity.java

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

@Override
protected void onResume() {
    startService(new Intent(this, MyService.class));
    bindService(new Intent(this, MyService.class), mConnection, 0);
    super.onResume();
}

@Override
protected void onStop() {
    unbindService(mConnection);
    super.onStop();
}

我们差不多完成了。此时(1。),(2。)和(3.)仍然有效,(4。)仍然无法正常工作。病人实际上情况更糟:现在BroadcastReceive.onReceive甚至没有被触发

修复所有内容的最后一步是将属性android:isolatedService="true"添加到 ApplicationManifest.xml 中的service声明。

<service
    android:name="it.damix.examples.servicesscce.MyService"
    android:enabled="true"
    android:isolatedProcess="true"></service>