5

因此,我正在研究将回调接口更改为本地广播以进行一些长期运行的网络操作的可行性。由于 Activity 生命周期为需要修改 UI 的异步请求创建了各种复杂性(将 Activity 从回调中断开onDestroy(),不要修改FragmentTransactions afteronSaveInstanceState()等),我认为使用本地广播更有意义,我们可以在生命周期事件中注册/注销接收器。

但是,当 Activity 在配置更改期间被销毁并重新创建时,广播接收器将不会被注册(onPause()/onResume()例如介于两者之间)。因此,例如,如果我们在onCreate()if中启动一个异步请求savedInstanceState == null(例如,对于 Activity 的第一次启动),如果用户在操作完成之前更改其设备方向,完成时发送的广播是否会丢失? ? (即接收者在onPause()中注销,然后操作完成,然后接收者在onResume()中重新注册)

如果是这种情况,那么它会增加很多额外的复杂性,我们需要添加对它的支持,而且可能不值得切换。我研究了其他东西,例如 Otto EventBus 库,但我不确定它是否有同样的问题需要担心。

4

4 回答 4

3

onRetainNonConfigurationInstance()方法中记载,系统在重启过程中Activity禁用主线程中的消息队列处理。Activity这确保了发布到主线程的事件将始终在Activity.

sendBroadcast()但是, 的方法似乎存在设计缺陷,因为它在将广播排队以在主线程上传递之前评估来自发布线程LocalBroadcastManager的注册s ,而不是在广播时在主线程上评估它们送货。虽然这使它能够报告传递的成功或失败,但它没有提供适当的语义来允许s 暂时从主线程中安全地注销,而不会丢失潜在的广播。BroadcastReceiverBroadcastReceiver

对此的解决方案是使用 aHandler从主线程发布广播,使用该sendBroadcastSync()方法使广播立即交付而不是重新发布。这是一个实现此功能的示例实用程序类:

public class LocalBroadcastUtils extends Handler {
    private final LocalBroadcastManager manager;

    private LocalBroadcastUtils(Context context) {
        super(context.getMainLooper());
        manager = LocalBroadcastManager.getInstance(context);
    }

    @Override
    public void handleMessage(Message msg) {
        manager.sendBroadcastSync((Intent) msg.obj);
    }

    private static LocalBroadcastUtils instance;

    public static void sendBroadcast(Context context, Intent intent) {
        if (Looper.myLooper() == context.getMainLooper()) {
            // If this is called from the main thread, we can retain the
            // "optimization" provided by the LocalBroadcastManager semantics.
            // Or we could just revert to evaluating matching BroadcastReceivers
            // at the time of delivery consistently for all cases.
            LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
        } else {
            synchronized (LocalBroadcastUtils.class) {
                if (instance == null) {
                    instance = new LocalBroadcastUtils(context);
                }
            }
            instance.sendMessage(instance.obtainMessage(0, intent));
        }
    }
}
于 2014-02-26T21:53:42.153 回答
2

为了克服这个问题,您需要一个即使在配置更改时重新创建活动时也能保持活动状态的组件。您可以使用Application单例或保留的 Fragment

如果您使用OttoEventBus,那么您可以创建一个事件总线实例作为应用程序的一个字段,它将与设备配置更改(如方向更改)保持分离。您的活动将需要在其中注册事件侦听器,onStart()它将接收最新事件。

如果您使用保留的 Fragment,则 Fragment 将保持活动状态,直到活动未完成。配置更改也不会释放保留片段的实例。使保留的 Fragment 不可见也是一种很好的做法(nullonCreateView()方法返回)。在onStart()您的活动中,您始终可以从该片段中获取最新状态。

您可以将 LocalBroadcastManager 与其中一种方法一起使用,但这并不能真正解决问题。它就像任何其他事件总线一样,但 API 丑陋且不方便;)

于 2014-02-26T20:49:27.920 回答
1

我发现android loader在这种情况下非常有用。

在我的情况下,我需要从另一个应用程序接收广播并在我的应用程序中管理片段转换。

所以我喜欢下面。

/**
 * LoaderManager callbacks
 */
private LoaderManager.LoaderCallbacks<Intent> mLoaderCallbacks =
        new LoaderManager.LoaderCallbacks<Intent>() {

            @Override
            public Loader<Intent> onCreateLoader(int id, Bundle args) {
                Logger.v(SUB_TAG + " onCreateLoader");
                return new MyLoader(MyActivity.this);
            }

            @Override
            public void onLoadFinished(Loader<Intent> loader, Intent intent) {
                Logger.i(SUB_TAG + " onLoadFinished");
                // Display our data
                if (intent.getAction().equals(INTENT_CHANGE_SCREEN)) {
                    if (false == isFinishing()) {
                        // handle fragment transaction 
                        handleChangeScreen(intent.getExtras());

                    }
                } else if (intent.getAction().equals(INTENT_CLOSE_SCREEN)) {
                    finishActivity();
                }
            }

            @Override
            public void onLoaderReset(Loader<Intent> loader) {
                Logger.i(SUB_TAG + " onLoaderReset");
            }
};

 /**
 * Listening to change screen commands. We use Loader here because
 * it works well with activity life cycle.
 * eg, like when the activity is paused and we receive command, it
 * will be delivered to activity only after activity comes back.  
 * LoaderManager handles this.
 */
private static class MyLoader extends Loader<Intent> {

    private Intent mIntent;
    BroadcastReceiver mCommadListner;
    public MyLoader(Context context) {
        super(context);
        Logger.i(SUB_TAG + " MyLoader");
    }

    private void registerMyListner() {
        if (mCommadListner !=  null) {
            return;
        }
        Logger.i(SUB_TAG + " registerMyListner");
        mCommadListner = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (action == null || action.isEmpty()) {
                    Logger.i(SUB_TAG + " intent action null/empty returning: ");
                    return;
                }
                Logger.i(SUB_TAG + " intent action: " + action);
                mIntent = intent;
                deliverResult(mIntent);
            }
        };
        IntentFilter filter = new IntentFilter();
        filter.addAction(INTENT_CHANGE_SCREEN);
        getContext().registerReceiver(mCommadListner, filter);
    }

    @Override
    protected void onStartLoading() {
        Logger.i(SUB_TAG + " onStartLoading");
        if (mIntent != null) {
            deliverResult(mIntent);
        }
        registerMyListner();
    }

    @Override
    protected void onReset() {
        Logger.i(SUB_TAG + "Loader onReset");
        if (mCommadListner != null) {
            getContext().unregisterReceiver(mCommadListner);
            mCommadListner = null;
        }
    }
}

Activity#onCreate or Fragment@onActivityCreated()
@Override
protected void onCreate(Bundle savedInstanceState) {
    // Listening to change screen commands from broadcast listner. We use Loader here because
    // it works well with activity life cycle.
    // eg, like when the activity is paused and we receive intent from broadcast, it will delivered
    // to activity only after activity comes back. LoaderManager handles this.
    getLoaderManager().initLoader(0, null, mLoaderCallbacks);
}
于 2017-04-04T22:08:38.340 回答
0

如果您的活动将被暂停或重新创建,则正常广播将丢失。您可以使用粘性广播,但它不起作用LocalBroadcastManager,您必须记住通过调用手动删除粘性广播Context.removeStickyBroadcast()。系统会保留粘性广播(即使您的活动已暂停),直到您决定将其删除。

EventBus 提供 postSticky() 方法,其工作方式类似于粘性广播。

于 2014-02-26T20:25:30.273 回答