下面用一张图来描述下该架构:
这里借用网上的一张图了,望大家勿喷~~
总的来说分为三层,
View
层,ViewModel层
、Repository
层,其中Repository
层又可以分两块来处理,一块是数据库的分支,一块是网络的分支。
本来是打算自己做个该框架的demo,由于当前只是讲解部分,因此这里直接拿公司的项目说事了,先偷个懒哈:
Activity、Fragment或者View中请求相应model中的方法
private void loadConfig() {
viewModel.loadConfig().observe(this, configResource -> {
LogUtils.d("Config loaded " + configResource);
assert configResource != null;
switch (configResource.status) {
case EMPTY:
break;
case ERROR:
break;
case LOADING:
case SUCCESS:
//这里看不懂没关系咯,知道成功后处理页面就行了
if (configResource.data != null) {
String surveyUrl = configResource.data.getSurveyUrl();
if (!TextUtils.isEmpty(surveyUrl)) {
LogUtils.v("Config loaded: survey: " + surveyUrl);
viewModel.setSurveyUrl(surveyUrl);
invalidateOptionsMenu();
}
FeatureConfiguration.setConfig(configResource.data);
}
break;
}
});
}
其实刚看到该写法的时候,我自己也是挺不习惯的,网上查了说是RxJava
的lambda
表达式。
好吧,说到这的时候,可能大家都尴尬了。先不管了,咋们看下该方法的链式结构:
viewModel.loadConfig()
返回的是LiveData<Resource<Config>>
这里就涉及到了lifecycle
的LiveData
数据结构,为什么它也有observe
方法呢。知道Rxjava
的小伙伴们,也知道Rxjava
有个observe
方法。其中
observe
方法中需要两个参数LifecycleOwner和Observer。其实咋们要处理的逻辑都是在Observer
的onChange
方法中,上面代码如果不用lambda
表达式的话,就是下面的样子了:
private void loadConfig() {
viewModel.loadConfig().observe(this, new Observer<Resource<Config>>() {
@Override
public void onChanged(@Nullable Resource<Config> configResource) {
LogUtils.d("Config loaded " + configResource);
assert configResource != null;
switch (configResource.status) {
case EMPTY:
break;
case ERROR:
break;
case LOADING:
case SUCCESS:
if (configResource.data != null) {
String surveyUrl = configResource.data.getSurveyUrl();
if (!TextUtils.isEmpty(surveyUrl)) {
LogUtils.v("Config loaded: survey: " + surveyUrl);
viewModel.setSurveyUrl(surveyUrl);
invalidateOptionsMenu();
}
FeatureConfiguration.setConfig(configResource.data);
}
break;
}
}
});
}
lambda
表达式看上去就是简洁,可读性更高了。好了,咋们要去model
层的loadConfig
方法了:
public LiveData<Resource<Config>> loadConfig() {
return configRepository.reload();
}
好吧,model
层只是调用了repository
层的方法而已。这里要提一点configRepository
生成的方式:
private ConfigRepository configRepository;
@Inject
MainViewModel(@NonNull Application application, ConfigRepository configRepository, DeviceRepository
deviceRepository, StorageRepository storageRepository, OrderRepository orderRepository) {
super(application);
//省略代码
this.configRepository = configRepository;
//省略代码
}
这里在构造器上面加了@Inject
注解,用到了dagger2
注解部分,加上该注解,相当于不需要传该实参,该注解会去找到该类的实例。那咋们去看看ConfigRepository
的reload
方法吧:
public LiveData<Resource<Config>> reload() {
String imei = DeviceInfoUtils.getImei();
return new NetworkBoundResource<Config, Config>() {
@Override
protected void saveCallResult(@NonNull Config config) {
LogUtils.i(logTag, "configure saved " + config);
configDao.save(config);
}
@Override
protected boolean shouldFetch(@Nullable Config config) {
return config == null
|| config.shouldFetch()
|| (!TextUtils.isEmpty(imei) && !imei.equals(config.getImei()));
}
@NonNull
@Override
protected LiveData<Config> loadFromDb() {
return configDao.loadLiveConfig();
}
@NonNull
@Override
protected LiveData<Resource<Config>> createCall(Config config) {
LogUtils.v(logTag, "load launch config with imei: " + imli);
MutableLiveData<Resource<Config>> result = new MutableLiveData<>();
LaunchConfigMessage message = new LaunchConfigMessage(DeviceInfoUtils.getModel(), imei, DeviceInfoUtils.getCountryCode());
ServerConnector.sendMessage(message)
.subscribe(new Observer2<LaunchConfigMessage>() {
@Override
public void onError(Throwable e) {
result.setValue(Resource.error(e.getMessage(), null));
}
@Override
public void onNext(LaunchConfigMessage launchConfigMessage) {
Config config = new Config();
config.setImei(imli);
config.setShowChat(launchConfigMessage.isShowOnlineChat());
config.setSurveyUrl(launchConfigMessage.getSurveyUrl());
config.setChatUrl(launchConfigMessage.getChatUrl());
config.setEmailUrl(launchConfigMessage.getEmailUrl());
config.setChatAPIUsername(launchConfigMessage.getUsername());
config.setChatAPIPassword(launchConfigMessage.getPassword());
config.setShowEmail(launchConfigMessage.isShowEmail());
config.setShowMessenger(launchConfigMessage.isShowMessenger());
config.setShowTwitter(launchConfigMessage.isShowTwitter());
config.setShowPricing(launchConfigMessage.isShowPricing());
config.setLastSyncedAt(new Date());
result.setValue(Resource.success(config));
}
});
return result;
}
}.getAsLiveData();
}
其实拿到Repository
层代码的时候,也是一头雾水。那咱们对照着开篇的图找找哪部分是走网络的,哪部分是走数据库的。这里看返回数据先是new了一个NetworkBoundResource
对象,然后调用了getAsLiveData
方法,最后返回了LiveData<Resource<Config>>
数据类型。既然NetworkBoundResource
重写了几个方法,想必是一个抽象类了,咱们也进去瞧瞧该类:
@MainThread
protected NetworkBoundResource() {
reload();
}
这里走了reload()
方法:
@MainThread
public void reload() {
//设置状态,表示正在loading
result.setValue(Resource.loading(null));
result.removeSource(dbSource);
dbSource = loadFromDb();
result.addSource(dbSource, data -> {
LogUtils.d(logTag,"reload changed");
result.removeSource(dbSource);
if (shouldFetch(data)) {
LogUtils.d(logTag,"fetch from network");
fetchFromNetwork(dbSource, data);
} else {
LogUtils.d(logTag,"fetch from db");
fetchFromDb(dbSource);
}
});
}
首先一上来就是loadFromDb
方法了,然后把该dbSource
通过result
对象添加到返回的data
中了,然后传给了shouldFetch
方法,这里如果shouldFetch
返回true,那么就走网络,否则去走本地了。咋们先看fetchFromDb
方法:
private void fetchFromDb(final LiveData<ResultType> dbSource) {
result.addSource(dbSource,
newData -> {
if (newData == null ||
(newData instanceof Collection && ((Collection)newData).size() == 0)) {
result.setValue(Resource.empty());
} else {
result.setValue(Resource.success(newData));
}
});
}
其实这里只是将数据转化成newData
后,在判断是不是空了,如果是空对result
设置空的状态,否则返回成功状态,并返回数据。
咱们再回头看看子类NetworkBoundResource
中loadFromDb
方法:
@NonNull
@Override
protected LiveData<Config> loadFromDb() {
return configDao.loadLiveConfig();
}
这里就是走数据库的查询方法了:
@Dao
public interface ConfigDao {
// table will have one record. The id is always set to 1
@Query("SELECT * FROM config where id = 1")
LiveData<Config> loadLiveConfig();
@Insert(onConflict = REPLACE)
void save(Config config);
}
private void fetchFromNetwork(final LiveData<ResultType> dbSource, ResultType data) {
LiveData<Resource<RequestType>> apiResponse = createCall(data);
// 重新附加 dbSource 作为新的来源,
// 它将会迅速发送最新的值。
result.addSource(dbSource,
newData -> result.setValue(Resource.loading(newData)));
result.addSource(apiResponse, response -> {
result.removeSource(apiResponse);
result.removeSource(dbSource);
if (response == null || response.status == Status.SUCCESS
|| response.status == Status.EMPTY) {
saveResultAndReInit(response == null ? Resource.empty() : response);
} else {
Assert.assertTrue(response.status == Status.ERROR);
onFetchFailed();
result.addSource(dbSource,
newData -> result.setValue(
Resource.error(response.message, newData)));
}
});
}
上面方法中第一句就创建了我们网络的实例,那咱们去看看createCall
方法吧:
@NonNull
@Override
protected LiveData<Resource<Config>> createCall(Config config) {
LogUtils.v(logTag, "load launch config with imei: " + imli);
MutableLiveData<Resource<Config>> result = new MutableLiveData<>();
LaunchConfigMessage message = new LaunchConfigMessage(DeviceInfoUtils.getModel(), imei, DeviceInfoUtils.getCountryCode());
//这里就是走网络部分的方法了
ServerConnector.sendMessage(message)
.subscribe(new Observer2<LaunchConfigMessage>() {
@Override
public void onError(Throwable e) {
result.setValue(Resource.error(e.getMessage(), null));
}
@Override
public void onNext(LaunchConfigMessage launchConfigMessage) {
Config config = new Config();
config.setImei(imli);
config.setShowChat(launchConfigMessage.isShowOnlineChat());
config.setSurveyUrl(launchConfigMessage.getSurveyUrl());
config.setChatUrl(launchConfigMessage.getChatUrl());
config.setEmailUrl(launchConfigMessage.getEmailUrl());
config.setChatAPIUsername(launchConfigMessage.getUsername());
config.setChatAPIPassword(launchConfigMessage.getPassword());
config.setShowEmail(launchConfigMessage.isShowEmail());
config.setShowMessenger(launchConfigMessage.isShowMessenger());
config.setShowTwitter(launchConfigMessage.isShowTwitter());
config.setShowPricing(launchConfigMessage.isShowPricing());
config.setLastSyncedAt(new Date());
result.setValue(Resource.success(config));
}
});
return result;
}
大家定位到注释部分,看看是如何生成Observable
对象的:
public static <T extends APIDefinition> Observable<T> sendMessage(final T message) {
return Observable.create((Observable.OnSubscribe<T>) subscriber -> {
doSendMessage(subscriber, message);
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}
不难看出被观察者里面调用了doSendMessage
方法:
private static <T extends APIDefinition> void doSendMessage(Subscriber<? super T> subscriber, final T message) {
if (!NetworkUtil.isConnected()) {
subscriber.onError(new NetworkException());
}
String resultString = null;
try {
for (String method : message.methods()) {
if (method.equalsIgnoreCase("post")) {
//网络的关键地方
resultString = doPost(message);
break;
}
if (method.equalsIgnoreCase("get")) {
//网络的关键地方
resultString = doGet(message);
break;
}
}
parseResult(resultString, message);
subscriber.onNext(message);
subscriber.onCompleted();
} catch (RequestFailException e) {
subscriber.onError(e);
} catch (IOException e) {
subscriber.onError(new NetworkException());
} catch (JSONException e) {
LogUtils.e("fail to parse result for " + message.api());
LogUtils.e("Invalid Json response: " + resultString);
subscriber.onError(new Exception("internal error", e));
} catch (ParameterCheckFailException e) {
subscriber.onError(new Exception("internal error " + e.getMessage(), e));
} catch (Exception e) {
subscriber.onError(new Exception("response format error", e));
}
}
可以看到此处调用了doGet
或是doPost
方法,后面就不说明了,都是些okHttp
的代码。走完了网络的代码,那什么时候将数据保存到数据库的呢?咋们还是去看fetchFromNetwork
方法中调用了saveResultAndReInit
方法:
@SuppressLint("StaticFieldLeak")
@MainThread
private void saveResultAndReInit(@NonNull Resource<RequestType> response) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
if (response.data != null) {
saveCallResult(response.data);
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
if (response.data != null) {
// We need recreate the dbSource here, so subclass have a chance to change
// the load policy
result.removeSource(dbSource);
dbSource = loadFromDb();
fetchFromDb(dbSource);
}
}
}.execute();
}
可以看到doInBackground
方法中调用了saveCallResult
方法,咋们还要回到子类去看怎么保存到数据库的:
@Override
protected void saveCallResult(@NonNull Config config) {
LogUtils.i(logTag, "configure saved " + config);
configDao.save(config);
}
看到了没,这里才是保存数据库的地方。最后可以看到saveResultAndReInit
方法中的AsyncTask
会调用fetchFromDb
方法,将最后的状态交给了LiveData
。
整个的流程就是这么回事了,关于页面上的databing
部分,详情看后续的demo部分
数据都是来自知乎api,仅用来学习,无商业目的