上网查,使用MVP、RX和Retrofit时放在哪里
Internet check, where to place when using MVP, RX and Retrofit
我经历了 and post。所以我真的同意第二个 post,即主持人不应该知道 android 特定的事情。所以我在想的是将互联网检查放在服务层中。
我正在使用 Rx Java 进行网络调用,所以我可以在进行服务调用之前进行网络检查,所以这样我需要手动抛出 IOException 因为我需要在网络上显示错误页面不可用,另一个选项是我自己创建错误 class for no internet
Observable<PaginationResponse<Notification>> response = Observable.create(new Observable.OnSubscribe<PaginationResponse<Notification>>() {
@Override
public void call(Subscriber<? super PaginationResponse<Notification>> subscriber) {
if (isNetworkConnected()) {
Call<List<Notification>> call = mService.getNotifications();
try {
Response<List<Notification>> response = call.execute();
processPaginationResponse(subscriber, response);
} catch (IOException e) {
e.printStackTrace();
subscriber.onError(e);
}
} else {
//This is I am adding manually
subscriber.onError(new IOException);
}
subscriber.onCompleted();
}
});
我想到的另一种方法是向 OkHttpClient 添加拦截器并将其设置为 retrofit
OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
builder.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
if (!isNetworkConnected()) {
throw new IOException();
}
final Request.Builder builder = chain.request().newBuilder();
Request request = builder.build();
return chain.proceed(request);
}
});
现在第二种方法更具可扩展性,但我不确定它是否有效,因为我会不必要地调用服务方法和 call.execute() 方法。
有什么建议应该使用哪种方式吗?
另外我判断方式的参数是
效率
可扩展性
通用:我希望在遵循 MVP 和 Repository/DataProvider(可能提供来自 network/db 的数据)[=13 的类似架构的应用程序中使用相同的逻辑=]
如果你们已经在使用任何其他方式,也欢迎提出其他建议。
首先我们创建一个用于检查互联网连接的实用程序,我们可以通过两种方式创建该实用程序,一种是该实用程序仅发出一次状态,如下所示,
public class InternetConnection {
public static Observable<Boolean> isInternetOn(Context context) {
ConnectivityManager connectivityManager
= (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
return Observable.just(activeNetworkInfo != null && activeNetworkInfo.isConnected());
}
}
创建此实用程序的另一种方法是,实用程序在连接状态发生变化时不断发出连接状态,如下所示,
public class InternetConnection {
public Observable<Boolean> isInternetOn(Context context) {
final IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
return Observable.create(new Observable.OnSubscribe<Boolean>() {
@Override
public void call(final Subscriber<? super Boolean> subscriber) {
final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
subscriber.onNext(netInfo != null && netInfo.isConnected());
}
};
context.registerReceiver(receiver, filter);
subscriber.add(unsubscribeInUiThread(() -> context.unregisterReceiver(receiver)));
}
}).defaultIfEmpty(false);
}
private Subscription unsubscribeInUiThread(final Action0 unsubscribe) {
return Subscriptions.create(() -> {
if (Looper.getMainLooper() == Looper.myLooper()) {
unsubscribe.call();
} else {
final Scheduler.Worker inner = AndroidSchedulers.mainThread().createWorker();
inner.schedule(() -> {
unsubscribe.call();
inner.unsubscribe();
});
}
});
}
}
接下来,在您的数据源或 Presenter 中使用 switchMap 或 flatMap 在执行任何网络操作之前检查互联网连接,如下所示,
private Observable<List<GitHubUser>> getGitHubUsersFromRetrofit() {
return isInternetOn(context)
.filter(connectionStatus -> connectionStatus)
.switchMap(connectionStatus -> gitHubApiInterface.getGitHubUsersList()
.map(gitHubUserList -> {
gitHubUserDao.storeOrUpdateGitHubUserList(gitHubUserList);
return gitHubUserList;
}));
}
请注意,我们使用的是 switchMap 而不是 flatMap。为什么要切换地图?因为,我们这里有 2 个数据流,第一个是互联网连接,第二个是 Retrofit。首先我们将获取连接状态值(true/false),如果我们有活动连接,我们将创建一个新的 Retrofit 流并 return 开始获取结果,如果连接状态发生变化, switchMap 将首先停止现有的 Retrofit 连接,然后决定我们是否需要启动一个新连接或忽略它。
编辑:
这是示例之一,可能会更清晰 https://github.com/viraj49/Realm_android-injection-rx-test/blob/master/app-safeIntegration/src/main/java/tank/viraj/realm/dataSource/GitHubUserListDataSource.java
编辑 2:
所以你的意思是 switch map 会在互联网恢复后自行尝试?
Yes and No,先看看flatMap和switchMap的区别。假设我们有一个 editText 并且我们根据用户类型从网络中搜索一些信息,每次用户添加一个新角色我们都必须进行一个新查询(可以通过去抖减少),现在有这么多网络调用只有最新结果很有用,使用 flatMap 我们将收到我们对网络进行的所有调用的所有结果,另一方面,使用 switchMap,在我们进行查询的那一刻,所有先前的调用都将被丢弃。
现在这里的解决方案由两部分组成,
我们需要一个不断发射当前网络状态的 Observable,上面的第一个 InternetConnection 发送一次状态并调用 onComplete(),但第二个有一个广播接收器,它将继续发送 onNext () 当网络状态改变时。如果您需要制定反应性解决方案,请选择 case-2
假设您选择InternetConnection case-2,在这种情况下使用switchMap(),因为当网络状态发生变化时,我们需要停止Retrofit,无论它在做什么,然后根据状态网络要么拨打新电话,要么不拨打电话。
我如何让我的视图知道错误是 internet 的,这是否可以扩展,因为我需要处理每个网络调用,关于编写包装器有什么建议吗?
编写包装器将是一个很好的选择,您可以创建自己的自定义响应,它可以从一组可能的响应中获取多个条目,例如SUCCESS_INTERNET、SUCCESS_LOGIN、ERROR_INVALID_ID
EDIT3:请在此处找到更新的 InternetConnectionUtil https://github.com/viraj49/Realm_android-injection-rx-test/blob/master/app-safeIntegration/src/main/java/tank/viraj/realm/util/InternetConnection.java
有关同一主题的更多详细信息,请参见此处:https://medium.com/@Viraj.Tank/android-mvp-that-survives-view-life-cycle-configuration-internet-changes-part-2-6b1e2b5c5294
EDIT4:我最近使用 Android 架构组件创建了一个 Internet 实用程序 - LiveData,您可以在此处找到完整的源代码,
https://github.com/viraj49/Internet-Utitliy-using-AAC-LiveData
代码的详细说明在这里,
https://medium.com/@Viraj.Tank/internet-utility-using-android-architecture-components-livedata-e828a0fcd3db
我经历了
Observable<PaginationResponse<Notification>> response = Observable.create(new Observable.OnSubscribe<PaginationResponse<Notification>>() {
@Override
public void call(Subscriber<? super PaginationResponse<Notification>> subscriber) {
if (isNetworkConnected()) {
Call<List<Notification>> call = mService.getNotifications();
try {
Response<List<Notification>> response = call.execute();
processPaginationResponse(subscriber, response);
} catch (IOException e) {
e.printStackTrace();
subscriber.onError(e);
}
} else {
//This is I am adding manually
subscriber.onError(new IOException);
}
subscriber.onCompleted();
}
});
我想到的另一种方法是向 OkHttpClient 添加拦截器并将其设置为 retrofit
OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
builder.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
if (!isNetworkConnected()) {
throw new IOException();
}
final Request.Builder builder = chain.request().newBuilder();
Request request = builder.build();
return chain.proceed(request);
}
});
现在第二种方法更具可扩展性,但我不确定它是否有效,因为我会不必要地调用服务方法和 call.execute() 方法。
有什么建议应该使用哪种方式吗? 另外我判断方式的参数是
效率
可扩展性
通用:我希望在遵循 MVP 和 Repository/DataProvider(可能提供来自 network/db 的数据)[=13 的类似架构的应用程序中使用相同的逻辑=]
如果你们已经在使用任何其他方式,也欢迎提出其他建议。
首先我们创建一个用于检查互联网连接的实用程序,我们可以通过两种方式创建该实用程序,一种是该实用程序仅发出一次状态,如下所示,
public class InternetConnection {
public static Observable<Boolean> isInternetOn(Context context) {
ConnectivityManager connectivityManager
= (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
return Observable.just(activeNetworkInfo != null && activeNetworkInfo.isConnected());
}
}
创建此实用程序的另一种方法是,实用程序在连接状态发生变化时不断发出连接状态,如下所示,
public class InternetConnection {
public Observable<Boolean> isInternetOn(Context context) {
final IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
return Observable.create(new Observable.OnSubscribe<Boolean>() {
@Override
public void call(final Subscriber<? super Boolean> subscriber) {
final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
subscriber.onNext(netInfo != null && netInfo.isConnected());
}
};
context.registerReceiver(receiver, filter);
subscriber.add(unsubscribeInUiThread(() -> context.unregisterReceiver(receiver)));
}
}).defaultIfEmpty(false);
}
private Subscription unsubscribeInUiThread(final Action0 unsubscribe) {
return Subscriptions.create(() -> {
if (Looper.getMainLooper() == Looper.myLooper()) {
unsubscribe.call();
} else {
final Scheduler.Worker inner = AndroidSchedulers.mainThread().createWorker();
inner.schedule(() -> {
unsubscribe.call();
inner.unsubscribe();
});
}
});
}
}
接下来,在您的数据源或 Presenter 中使用 switchMap 或 flatMap 在执行任何网络操作之前检查互联网连接,如下所示,
private Observable<List<GitHubUser>> getGitHubUsersFromRetrofit() {
return isInternetOn(context)
.filter(connectionStatus -> connectionStatus)
.switchMap(connectionStatus -> gitHubApiInterface.getGitHubUsersList()
.map(gitHubUserList -> {
gitHubUserDao.storeOrUpdateGitHubUserList(gitHubUserList);
return gitHubUserList;
}));
}
请注意,我们使用的是 switchMap 而不是 flatMap。为什么要切换地图?因为,我们这里有 2 个数据流,第一个是互联网连接,第二个是 Retrofit。首先我们将获取连接状态值(true/false),如果我们有活动连接,我们将创建一个新的 Retrofit 流并 return 开始获取结果,如果连接状态发生变化, switchMap 将首先停止现有的 Retrofit 连接,然后决定我们是否需要启动一个新连接或忽略它。
编辑: 这是示例之一,可能会更清晰 https://github.com/viraj49/Realm_android-injection-rx-test/blob/master/app-safeIntegration/src/main/java/tank/viraj/realm/dataSource/GitHubUserListDataSource.java
编辑 2:
所以你的意思是 switch map 会在互联网恢复后自行尝试?
Yes and No,先看看flatMap和switchMap的区别。假设我们有一个 editText 并且我们根据用户类型从网络中搜索一些信息,每次用户添加一个新角色我们都必须进行一个新查询(可以通过去抖减少),现在有这么多网络调用只有最新结果很有用,使用 flatMap 我们将收到我们对网络进行的所有调用的所有结果,另一方面,使用 switchMap,在我们进行查询的那一刻,所有先前的调用都将被丢弃。
现在这里的解决方案由两部分组成,
我们需要一个不断发射当前网络状态的 Observable,上面的第一个 InternetConnection 发送一次状态并调用 onComplete(),但第二个有一个广播接收器,它将继续发送 onNext () 当网络状态改变时。如果您需要制定反应性解决方案,请选择 case-2
假设您选择InternetConnection case-2,在这种情况下使用switchMap(),因为当网络状态发生变化时,我们需要停止Retrofit,无论它在做什么,然后根据状态网络要么拨打新电话,要么不拨打电话。
我如何让我的视图知道错误是 internet 的,这是否可以扩展,因为我需要处理每个网络调用,关于编写包装器有什么建议吗?
编写包装器将是一个很好的选择,您可以创建自己的自定义响应,它可以从一组可能的响应中获取多个条目,例如SUCCESS_INTERNET、SUCCESS_LOGIN、ERROR_INVALID_ID
EDIT3:请在此处找到更新的 InternetConnectionUtil https://github.com/viraj49/Realm_android-injection-rx-test/blob/master/app-safeIntegration/src/main/java/tank/viraj/realm/util/InternetConnection.java
有关同一主题的更多详细信息,请参见此处:https://medium.com/@Viraj.Tank/android-mvp-that-survives-view-life-cycle-configuration-internet-changes-part-2-6b1e2b5c5294
EDIT4:我最近使用 Android 架构组件创建了一个 Internet 实用程序 - LiveData,您可以在此处找到完整的源代码, https://github.com/viraj49/Internet-Utitliy-using-AAC-LiveData
代码的详细说明在这里, https://medium.com/@Viraj.Tank/internet-utility-using-android-architecture-components-livedata-e828a0fcd3db