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

Android 如果我关闭活动,则从BroadcastReceiver.onReceive调用peekService将返回null 问题,android,android-4.3-jelly-bean,Android,Android 4.3 Jelly Bean,我为安卓4.3编写了一个程序,包含一个主活动、一个广播接收器和一个服务。活动通过其onCreate方法绑定到服务。该活动有一个按钮,用于在未来10秒内安排报警。该警报触发广播接收器.onReceive。此方法尝试保留活页夹,但在某些情况下会失败,peekService返回null 什么有效 单击按钮并等待10秒钟 单击按钮,单击主页,然后在主页屏幕中等待10秒钟 单击按钮,单击活动列表,通过向左滑动关闭活动,重新打开程序并等待10秒(您需要快速:-) 什么不起作用 点击按钮,点击活动列表,向左滑

我为安卓4.3编写了一个程序,包含一个主活动、一个广播接收器和一个服务。活动通过其
onCreate
方法绑定到服务。该活动有一个按钮,用于在未来10秒内安排报警。该警报触发
广播接收器.onReceive
。此方法尝试保留活页夹,但在某些情况下会失败,
peekService
返回
null

什么有效
  • 单击按钮并等待10秒钟
  • 单击按钮,单击主页,然后在主页屏幕中等待10秒钟
  • 单击按钮,单击活动列表,通过向左滑动关闭活动,重新打开程序并等待10秒(您需要快速:-)
  • 什么不起作用
  • 点击按钮,点击活动列表,向左滑动关闭活动,等待10秒;这本质上与(3.)类似,没有重新打开程序
  • 具体来说,如果我执行这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.
    
    最后一条消息显示测试(4.)失败,而之前的消息显示(1.)、(2.)和(3.)成功

    我知道,并回答,以及谷歌在前两页列出的几乎所有相关结果。我尝试了几件事,包括但不限于:

    • BroadcastReceiver.onReceive
      和主活动调用
      startService
      (尽管我不确定自己是否了解
      bindService
      startService
      的交互方式)
    • 玩弄意图,尤其是上下文(
      this
      VS
      getApplicationContext
      等等)
    • 将服务设置为前台(请参阅)
    我真的对发生这种情况的原因感兴趣,而不是对解决方案感兴趣

    MainActivity.java MyReceiver.java MyService.java ApplicationManifest.xml
    
    
    
  • 简而言之 解决方案包括三件事:

    • 使服务成为前台服务
    • 在活动中使用startService和绑定启动它。显然,
      peek服务
      启动服务是不够的;另外,必须有人(一项活动或可能是另一项服务)绑定到它。(对我来说)这是无法理解的
    • 在服务声明中添加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
    中删除服务代码,并将相应的
    onResume
    onStop
    方法添加到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甚至不会被触发

    <service
        android:name="it.damix.examples.servicesscce.MyService"
        android:enabled="true"
        android:isolatedProcess="true"></service>
    
    修复所有问题的最后一步是将属性
    android:isolatedService=“true”
    添加到ApplicationManifest.xml中的
    服务
    声明中

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

    +1了解damix911对问题的完整说明以及可能的解决步骤。所有代码的发布使我能够轻松地重新创建测试应用程序

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

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

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

    <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>
    
    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);
        }
    
        ...
    }
    
    @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();
    }
    
    <service
        android:name="it.damix.examples.servicesscce.MyService"
        android:enabled="true"
        android:isolatedProcess="true"></service>