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



问题

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

什么有效

  1. 点击按钮并等待10秒
  2. 单击按钮,单击主页,然后在主屏幕中等待10秒钟
  3. 点击按钮,点击活动列表,向左滑动关闭活动,重新打开程序,等待10秒(你需要快速:-)

什么不起作用

  1. 点击按钮,点击活动列表,向左滑动关闭活动,等待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(尽管我不确定我是否理解bindServicestartService是如何交互的)
  • 玩弄意图,特别是上下文(this VS getApplicationContext等等)
  • 将服务设置为前台(请参阅此)

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

主活动.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!");
    }
}

我的服务.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>

链接

  • 完成了
  • 这也是
  • 是的
  • 啊啊
  • 其他没有帮助的事情

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

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

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

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

简而言之

解决方案包括三件事:

  • 使服务成为前台服务
  • 在使用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中删除服务代码,并向MainActivity.java:添加适当的onResumeonStop方法

@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>

相关内容

  • 没有找到相关文章

最新更新