如何从后台服务 post 消息到 UI 片段?

How to post a message from a background service to a UI fragment?

我对来自 Greenrobot 的 EventBus 有疑问。

我正在尝试 post 来自我的同步适配器的后台服务事件,并将其捕捉到片段中以更新 UI。

问题是,当我尝试 post 来自同步适配器的事件时,我在调试日志中得到以下信息:

No subscribers registered for event class olexiimuraviov.ua.simplerssreader.event.UpdateUIEvent
No subscribers registered for event class 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 方法,但它没有帮助。

在此之后,我尝试使用处理程序来处理 post 事件,但事件总线仍然找不到任何已注册的事件订阅者。

 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 将事件从同步适配器发送到片段?

tl;dr

您永远不必担心您 post 来自哪个 线程 事件,但您确实需要担心哪个 进程 你post它来自。您的同步适配器 运行 在与您的片段不同的进程中 - 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 仅在其创建的 Process 中运行。

同步适配器,如 BroadcastReceiver 或服务 运行s 在一个单独的进程中,允许您与服务器同步数据 而无需 与您的应用程序进行任何交互本身。这个想法是,即使应用程序关闭,您也可以与服务器同步,将任何内容保存到数据库中,然后当应用程序启动时,它会检查该数据库(快速),而不必与服务器同步(慢速)。但是,如果您的应用在同步适配器 运行 时处于活动状态,那么您想要保存到数据库的同时还要告诉应用有一些新信息对吗?

因此,为了让您的同步适配器能够与您的应用程序进行通信,我建议您在 fragment/activity 中设置一个 BroadcastReceiver,然后从您的同步适配器广播一个 Intent。

这个机制和EventBus的概念很相似,你甚至可以发送sticky intents!您的片段会监听这些意图,但仅限于它处于活动状态时。如果您的同步适配器在您的应用程序关闭时发送广播,则什么也不会发生。但是当片段启动并注册这些意图时,它将获得最后一个 sticky

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);