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

Posted

技术标签:

【中文标题】如果我关闭活动,从 BroadcastReceiver.onReceive 调用 peekService 将返回 null【英文标题】:Calling peekService from BroadcastReceiver.onReceive returns null if I close the activity 【发布时间】:2015-02-05 20:34:10 【问题描述】:

问题

我为 android 4.3 编写了一个程序,其中包含一个主 Activity、一个广播接收器和一个服务。活动在其onCreate 方法中绑定到服务。该活动有一个按钮,可在未来 10 秒内安排警报。警报触发BroadcastReceiver.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.) 成功。

我知道this、this 和this 的答案,以及Google 在前两页中列出的几乎所有相关结果。我尝试了几件事,包括但不限于:

BroadcastReceiver.onReceive 和主要活动调用startService(虽然我不确定我是否理解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_
    android:layout_
    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_
        android:layout_
        android:text="Click me to schedule an alarm..."
        android:onClick="scheduleAlarm"/>

</RelativeLayout>

链接

Done that This too Yep Ah-ah Something else that doesn't help

【问题讨论】:

【参考方案1】:

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

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

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

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

【讨论】:

【参考方案2】:

总之

解决方案涉及三件事:

使服务成为前台服务。 在使用 startService 绑定到它的 Activity 中启动它。显然,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>

【讨论】:

以上是关于如果我关闭活动,从 BroadcastReceiver.onReceive 调用 peekService 将返回 null的主要内容,如果未能解决你的问题,请参考以下文章

从活动结果意图按下后退按钮时应用程序关闭[关闭]

如何为多个活动注册制作网站? [关闭]

从通知中导航到父活动

如何创建设置活动以从底部显示屏幕的一半? [关闭]

BroadcastReceiver - 无法从关闭或后台应用程序启动活动

将数据从活动 1 传递到活动 2,并在函数中计算给定数据 [关闭]