如何从后台服务向UI片段发布消息?

5

我遇到了关于Greenrobot的EventBus的问题。

我正在尝试从我的同步适配器的后台服务中发布一个事件,并在片段中捕获它以更新UI。

问题是,当我尝试从同步适配器中发布事件时,在调试日志中出现以下内容:

o.lexii.muraviov.ua.simplerssreader.event.UpdateUIEvent类没有订阅者注册
org.greenrobot.eventbus.NoSubscriberEvent类没有订阅者注册

我在onResume中注册片段并在onPause取消注册。

@Override
public void onResume() {
    super.onResume();
    EventBus.getDefault().register(this);
    if (mDebug) Log.d(LOG_TAG, EventBus.getDefault().isRegistered(this) + "");
}

@Override
public void onPause() {
    super.onPause();
    EventBus.getDefault().unregister(this);
}

在onResume中的日志语句显示片段已成功注册。

下面是onEvent方法:

@Subscribe
public void onEvent(UpdateUIEvent event) {
    if (mSwipeRefreshLayout.isRefreshing())
        mSwipeRefreshLayout.setRefreshing(false);
}

我尝试使用后台线程模式调用onEvent方法,但并没有帮助。

之后我尝试使用handler来发布事件,但是EventBus仍然找不到已注册的事件订阅者。

 new Handler(Looper.getMainLooper()).post(new Runnable() {
     @Override
     public void run() {
         EventBus.getDefault().post(new UpdateUIEvent());
     }
 });

这是我的同步适配器的onPerform方法:

    @Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
    // Fetch data from server
    } finally {
       // close everything
       new Handler(Looper.getMainLooper()).post(new Runnable() {
           @Override
           public void run() {
               Log.d(LOG_TAG, "Update event");
               EventBus.getDefault().post(new UpdateUIEvent());
           }
       });
    }
}

如何使用Greenrobot的EventBus从同步适配器发送事件到片段(fragment)?

尝试将事件处理程序从onEvent更改为onEventMainThread。您是否检查了片段是否也经历了onPause? - Francesc
我尝试更改方法名称并尝试将线程模式MAIN添加到注释中,但没有帮助。是的,我已经检查过了。@Francesc - Olexii Muraviov
你的片段暂停了吗?如果已经执行了onPause,它显然无法接收任何内容。 - Francesc
不,片段在 post 方法调用期间无法工作。@Francesc - Olexii Muraviov
1个回答

8

tl;dr

您无需担心从哪个线程发布事件,但需要注意从哪个进程发布。您的同步适配器在与片段不同的进程中运行 - EventBus仅在创建它的进程内工作。

长篇版本

除了订阅事件的位置外,您永远不需要启动不同的线程或担心自己在哪个线程上。

有关更多详细信息,请查看 http://greenrobot.org/eventbus/documentation/delivery-threads-threadmode/ ,但语法非常易懂。

@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void thisMethodWillBeRunInABackgroundThread(DoLoginEvent event)
{
    //Run some code that makes an HTTP request
}

在UI上运行某些东西:

@Subscribe(threadMode = ThreadMode.MAIN)
public void thisMethodWillBeRunOnTheUIThread(LoginSuccessfulEvent event)
{
    //Run some code that touches the UI
}

当您调用post()来触发DoLoginEventLoginSuccessfulEvent时,您所在的线程绝对不重要。
现在是您真正遇到的问题:订阅对象似乎无法捕获事件。
我怀疑这是因为您的同步适配器在不同的进程中运行。EventBus仅在创建它的进程内运行。
像BroadcastReceiver或Service一样,同步适配器在单独的进程中运行,以允许您与服务器同步数据而无需与应用本身进行任何交互。这意味着即使应用关闭,您也可以与服务器同步,并将其保存到数据库中,然后当应用启动时,它会检查该数据库(快速)而不必与服务器同步(慢)。但是,如果您的应用程序在同步适配器运行时处于活动状态,则希望保存到数据库,但还要告诉应用程序有一些新信息,对吧?
因此,为了允许您的同步适配器与应用程序通信,建议您在片段/活动中设置BroadcastReceiver,然后从同步适配器广播意图。

这种机制与EventBus非常类似,你甚至可以发送粘性意图!您的片段会在活动时监听这些意图。如果您的同步适配器在应用程序关闭时发送广播,则不会发生任何事情。但是当该片段启动并注册这些意图时,它将获取最后一个粘性

public class LogFragment extends Fragment {

    private LogReceiver mReceiver;

    public class LogReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            //you can call the EventBus from in here
        }
    }


    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mReceiver = new LogReceiver();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        getActivity().registerReceiver(mReceiver, new IntentFilter("com.whatever.whatever"));
        return view;
    }

    @Override
    public void onDestroyView() {
        getActivity().unregisterReceiver(mReceiver);
        super.onDestroy();
    }
}

然后从您的同步适配器中

Intent sendIntent = new Intent("com.whatever.whatever");
sendIntent.putExtra("SYNC_ADAPTER_EXTRA", "Your sync adapter says hi");
context.sendBroadcast(sendIntent);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接