在Android MVP中,BroadcastReceiver应该放在哪里?

19

我有一个BroadcastReceiver实现,接收网络连接事件。它在AndroidManifest.xml中声明,并由Android自动调用以处理网络事件。

BroadcastReceiver:

public class ConnectivityChangeReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.v(TAG, "action: " + intent.getAction());
        Log.v(TAG, "component: " + intent.getComponent());
    }
}

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.test">

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        ...
        <receiver
            android:name=".ConnectivityChangeReceiver"
            android:enabled="true">
            <intent-filter>
                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
            </intent-filter>
        </receiver>
    </application>

</manifest>

我想在我的应用程序中使用谷歌在这里描述的MVP样例架构:

https://github.com/googlesamples/android-architecture/tree/todo-mvp/

使用上述架构,有几个问题需要澄清:

  1. 我的BroadcastReceiver应该放在哪里?

  2. 如果我的BroadcastReceiver需要写入数据库,最佳方法是什么?

  3. 如果我的BroadcastReceiver需要更新UI,最佳方法是什么?


使用EventBus怎么样?https://github.com/greenrobot/EventBus - Chintan Soni
@Rory,你觉得使用一个需要在应用程序流程中显式启动和停止的意图服务怎么样? - Sreehari
@ChintanSoni,那些混乱的代码呢?他试图构建一些架构,而不是通过添加“组件”来快速而肮脏地解决问题,这些组件将与每个人交流并听取每个人的意见,而没有任何义务或契约。 - Ewoks
1
@Ewoks 希望改变看法能对你有所帮助 ;) - Chintan Soni
只是想知道 MVP(或其他架构)中 EventBus 属于哪个部分...思考这个问题改变了我的观点。 - Ewoks
5个回答

12
  1. 个人认为,BroadcastReceiver 的事件应该传递给 Presenter。
  2. 基于声明 1,Presenter 持有应处理数据库操作的 Interactor/Contract/Use case 的引用。

    BroadcastReceiver --事件--> Presenter --> Interactor ---> Repository

  3. 基于声明 1,Presenter 应消费事件并调用 View。

    BroadcastReceiver --事件--> Presenter --> (可能会进行一些业务逻辑) ---> View

这是一个简单的示例代码片段,它概括了我所说的内容:

  private class NetworkBroadcastReceiver23 extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            //... redacted code.../
            boolean connected = activeNetworkInfo != null && activeNetworkInfo.isConnected();
            mPresenter.onConnectionChanged(activeNetworkInfo,connected);
        }
    }
将接收器放置在活动中,因为您可以从那里向展示器流式传输事件。这将使测试展示器的连接更加容易。难以实现平台事件的关注分离,我希望保持我的层不受Android SDK组件和类的影响。Alex Shutov指出的另一种方法是,如果您考虑将BroadcastReceiver视为外部实体而不是事件源,则可以混合MVP和观察者模式。 是的,我同意,通过摆脱NetworkInfo参数可以改进该方法。

3
在MVP设计模式中,Model具有与外部世界连接的所有实体(例如,Repository用于获取数据并在本地持久化)。BroadcastReceiver是外部事件的输入,最终将修改模型。很好的比较是六边形架构中的“输入端口”。
Presenter定义了从模型显示数据的方式,但所有业务逻辑,包括对其他系统或用户事件的反应,都应该在模型内部。
View和Presenter可以根据程序运行的模式动态更改,比如说,你想使用另一个版本的UI,或者更简单的UI行为,但所有逻辑都必须保持不变,包括对外部事件的反应。这就是为什么BroadcastReceiver应该放置在模型内部。
永远不要将BroadcastReceiver放置在Activity中,因为Activity是(View)的系统容器,至少如果您遵循MVP模式的话。如果您的项目非常复杂,请考虑通过某些“ExternalInput”接口抽象出BroadcastReceiver,并在测试期间轻松模拟它并在模型内部使用它。

你应该把你的两个答案合并起来,而不是分别发布。 - Sreehari

0

如果您按功能打包,只需在功能包内创建receiver包,并将BroadcastReceiver类放入其中。如果您没有子包,只需将BroadcastReceiver类放入您的功能包中。


-3

你的广播应该是View。然后,它调用Presenter方法,改变一些NetworkStateService(这是Model级别)的状态。当NetworkStateService的状态改变时,它会通知Presenters,网络可用,他们可以发出请求。这些Presenters应该更新UI。所有这些Presenters都应该作为NetworkStateService中的监听器。

对于长时间的操作,例如与数据库或网络的工作,你应该启动Service。原因是广播将在接收后10秒钟被杀死。你应该将Presenter放入这个Service中,并从这个Presenter中处理Model


你的广播应该是View - 我不知道在哪个宇宙这是正确的,但在这个宇宙里不是。 - Tim
@TimCastelijns,你能解释一下你的宇宙吗?在我的宇宙中,所有的输入都可以放在视图层,因为它是输入。同时,你也可以将其放在领域层。这取决于输入类型。如果用户关闭网络,那么它就是用户输入的一部分。%) - senneco

-3
如果您需要进行数据库操作/更新UI,则应将BroadcastReceiver放置在相应的活动中。
以下是示例代码。
public class YourActivity extends AppCompatActivity  {

    @Override
    protected void onResume() {
        super.onResume();
        registerReceiver(mConnectivityReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
      // public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
    }

    @Override
    protected void onPause() {
        super.onPause();
        unregisterReceiver(mConnectivityReceiver);
    }


    public BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            checkIntent(intent);
        }

        private void checkIntent(Intent intent) {
            Bundle bundle = intent.getExtras();
             if (bundle != null) {
                 NetworkInfo networkInfo = (NetworkInfo) bundle.get("networkInfo");
                    doYourStuff(networkInfo.isConnected());
            }
        }
    };

    private void doYourStuff(boolean isNetworkConnected) {
        //Update your UI here
        //Do database Operations here
    }
}

在doYourStuff()函数中根据网络连接执行你的操作。

通过这种方法,你无需在Menifest.xml文件中注册BroadcastReceiver。


我相信在代码中注册BroadcastReceiver而不是在Activity中注册意味着当我的应用程序未运行时,它将不会接收广播,这正是我想要的。 - Rory
是的,这个应用程序不会接收广播,因为调用unregisterReceiver将会:1:注销先前注册的BroadcastReceiver。2:已经为此BroadcastReceiver注册的过滤器将被移除。 - waqas ali
如果这个回答对你有帮助,请将其标记为最佳答案,以便其他人参考。 - waqas ali
活动(Activity)是Android中的一个纯UI系统容器,因此如果广播接收器需要修改模型状态,则绝不能将其放在其中。 - Alex Shutov
这取决于需求。如果您的应用程序需要在前台使用BroadcastReceiver,那么您应该选择这种方法,因为它是必要的。http://stackoverflow.com/a/7636675/5124050 - waqas ali

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