主目录见:Android高级进阶知识(这是总目录索引)
mvvm框架地址:MarsArchitecture
从谷歌发布新的框架组件,试用之后,不仅开始设想着改进之前的mvp模式架构,谷歌新框架新组件ViewModel,LiveData,Room,确实如谷歌所推荐的,从我感受来看,确实能省不少代码,代码也会比较优雅,包括加上前面的DataBinding,以及在mvp框架中用的流行框架rxjava,retrofit。那么我们就一起来封装下,当然大家可以改进,还是有很多空间的。下面我们看下下面的整体架构图片:
一.基本使用
1.我们设想,每个界面需求最大的就是获取数据的接口,所以我们封装一个公用的接口,使用如下:
mGirlsViewModel = ViewModelProviders.of(MainActivity.this).get(DynamicGirlsViewModel.class);
mGirlsViewModel.getELiveObservableData(Constants.GIRLS_URL, "3").observe(MainActivity.this, girlsData -> {
if (null != girlsData){
List<GirlsData.ResultsBean> resultsBeans = girlsData.getResults();
}
});
2.基本的使用是下面这种:
tvTest.setOnClickListener(view -> mGirlsViewModel.getGirlsData("2","3").observe(MainActivity.this, girlsData -> {
if (null != girlsData){
List<GirlsData.ResultsBean> resultsBeans = girlsData.getResults();
}
}));
二.封装
这几个组件怎么使用我这里就不用写了,不然文章可能逻辑会有点混乱,我这里直接就从封装开始,从图中可以知道,我们界面Activity和fragment和数据的交互是跟ViewModel进行的,我们这里封装了一个BaseViewModel
,里面有公共的访问数据接口,这个方法是BaseViewModel#getELiveObservableData()
方法:
public LiveData<T> getELiveObservableData(String url, String... params) {
IRequest request = (IRequest) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{IRequest.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object object = null;
try {
String url = redirectParseMethodUrl(args);
if (null != url){
object = getLiveObservableData(url);
}
}catch (Exception e){
e.printStackTrace();
}
return object;
}
});
request.getLiveObservableData(url,params);
return liveObservableData;
}
这个方法的第一个参数是网络访问的url,这个url可以是全url地址,或者是接在base url后面的地址,第二个参数是要传到后台服务接口的参数,我们都知道,我们传到后台,后台要知道我们传的参数对应的键是什么,所以我们这里定了一个url的规则(这里我们封装的get的请求方式),url有两种方式,一种是接在url中的,另一种是接在后面key=value的方式,所以如果是第一种,我们会在url中加上{**},{}中的是对应的key,然后在最后加上一个标识/path,第二种是就不用接后面的标识/path,如下面:
public static final String PATH_URL = "/Path";
public static final String BASE_URL = "http://gank.io";
public static final String GIRLS_URL = "api/data/%E7%A6%8F%E5%88%A9/20/{index}/"+PATH_URL;
然后我们继续看BaseViewModel#getELiveObservableData()
方法,其实这个方法可以不用动态代理来做,但是当初设想着另外一种做法,后来就干脆不改,大家也可以不用,然后我们看invoke()
方法里面,首先第一个方法redirectParseMethodUrl()
方法就是为了解析传进来的url的,方法实现如下:
private String redirectParseMethodUrl(Object[] args){
if (null != args && args.length > 0){
String url = (String) args[0];
boolean isPath = false;
if (null != url){
isPath = url.contains(PATH_URL);
if (isPath){
return parseMethodUrlPath(args);
}else{
return parseMethodUrlKey(args);
}
}
}
return "";
}
这个方法首先判断url中是否含有/path标识,如果有就用path的拼凑方式,如果没有就用key=value的形式拼凑,首先看path的方式:
private String parseMethodUrlPath(Object[] args) {
String url = "";
if (null != args && args.length > 0){
url = (String) args[0];
url = url.substring(0,url.lastIndexOf("/") - 1);
Matcher matcher = pattern.matcher(url);
for (int i = 0;matcher.find();i++){
Object[] value = (Object[]) args[1];
String key = matcher.group();
if (null != value){
url = url.replace(key,(String)value[i]);
}
}
}
return url;
}
这个方法首先取出除path以外的url,然后用模式匹配出{}
部分,循环替换{}
部分的内容为真正的值。另外一种key=value的形式如下:
private String parseMethodUrlKey(Object[] args){
String baseUrl = "";
if (null != args && args.length > 0){
String url = (String) args[0];
int index = url.indexOf("{");
if (index > 0){
baseUrl = url.substring(0,index-1);
Matcher matcher = pattern.matcher(url);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("?");
for (int i = 0;matcher.find();i++){
String key = matcher.group();
String realKey = key.substring(key.indexOf("{")+1,key.lastIndexOf("}"));
Object[] value = (Object[]) args[1];
if (value != null){
stringBuilder.append(realKey+"="+value[i]+ "&");
}
}
baseUrl += stringBuilder.toString();
}
}
return baseUrl;
}
这个方法截取出{}
中的内容作为key,作为给后台的标识,最后拼凑出来的地址为baseurl?key=value&key1=value1&**
形式。然后得到url之后传给BaseViewModel#getLiveObservableData()
:
public LiveData<T> getLiveObservableData(String url,String...params) {
DataRepository.getDynamicData(url,getTClass())
.subscribeWith(new DisposableSubscriber<T>() {
@Override
public void onNext(T value) {
if(null != value){
liveObservableData.setValue(value);
}
}
@Override
public void onError(Throwable t) {
}
@Override
public void onComplete() {
}
});
return liveObservableData;
}
在这个方法里面我们调用了DataRepository
里面的方法,*Repository
中主要是获取数据,我们获取数据的方式一般有两种,从网络获取数据,从本地数据库获取数据,在*Repository
中正好隔离了这两种数据获取方式,这个地方先看从网络获取数据:
public static <T> Flowable getDynamicData(String url, final Class<T> clazz) {
return ApiServiceModule.getInstance().getNetworkService()
.getDynamicData(url)
.compose(SwitchSchedulers.applySchedulers())
.map(new Function<ResponseBody, T>() {
@Override
public T apply(ResponseBody responseBody) throws Exception {
return GsonHelper.getIntance().str2JsonBean(responseBody.string(),clazz);
}
});
}
这里网络访问用的是retrofit,retrofit这里做了下封装,retrofit我们知道访问的时候restful api是在接口中的,所以我们可以用动态代理的形式来做些网络访问错误重试,如下所示:
private ApiService provideApiService(Retrofit retrofit){
return getByProxy(ApiService.class,retrofit);
}
private ApiService getByProxy(Class<? extends ApiService> apiService, Retrofit retrofit){
ApiService api = retrofit.create(apiService);
return (ApiService) Proxy.newProxyInstance(apiService.getClassLoader(),new Class<?>[] { apiService },new ResponseErrorProxy(api,retrofit.baseUrl().toString()));
}
具体的动态代理invoke方法内容如下:
@Override
public Object invoke(Object proxy, final Method method, final Object[] args) {
return Flowable.just("")
.flatMap((Function<String, Flowable<?>>) s -> (Flowable<?>) method.invoke(mProxyObject, args))
.retryWhen(throwableFlowable -> throwableFlowable.flatMap((Function<Throwable, Publisher<?>>) throwable -> {
ResponseError error = null;
if (throwable instanceof ConnectTimeoutException
|| throwable instanceof SocketTimeoutException
|| throwable instanceof UnknownHostException
|| throwable instanceof ConnectException) {
error = new ResponseError(HTTP_NETWORK_ERROR, "当前网络环境较差,请稍后重试!");
} else if (throwable instanceof HttpException) {
HttpException exception = (HttpException) throwable;
try {
error = new Gson().fromJson(exception.response().errorBody().string(), ResponseError.class);
} catch (Exception e) {
if (e instanceof JsonParseException) {
error = new ResponseError(HTTP_SERVER_ERROR, "抱歉!服务器出错了!");
} else {
error = new ResponseError(HTTP_UNKNOWN_ERROR, "抱歉!系统出现未知错误!");
}
}
} else if (throwable instanceof JsonParseException) {
error = new ResponseError(HTTP_SERVER_ERROR, "抱歉!服务器出错了!");
} else {
error = new ResponseError(HTTP_UNKNOWN_ERROR, "抱歉!系统出现未知错误!");
}
if (error.getStatus() == HTTP_UNAUTHORIZED) {
return refreshTokenWhenTokenInvalid();
} else {
return Flowable.error(error);
}
}));
}
我们看到这里有出错重试机制,具体的错误类型如方法中所示。最后我们网络访问返回的数据经Gson
处理得到具体实体类,最终设置给LiveData
包装的数据,这样在Activity中监听得到数据的变化。网络访问处理成为实体类的时候我们也可以封装一个公用的类,因为每个接口的json返回数据都有可能有code,message,data这些,这里没有写,要是想看可以去看LArtemis这个工程。到这里网络访问的方式已经完成,讲的有点粗略请见谅,然后我们开始讲数据库获取数据的方式,也是从Respository里面获取的,如下所示:
public static Flowable<User> getUserData(Application application){
return AppDatabase.getInstance(application,mAppExecutors)
.userDao().getUser();
}
从方法里面可以看出,这里是获取AppDatabase
实例,然后获取到dao实例,最后取出前端数据库的数据,AppDatabase
实例获取方式如下:
public static AppDatabase getInstance(final Context context, final AppExecutors executors) {
if (sInstance == null) {
synchronized (AppDatabase.class) {
if (sInstance == null) {
sInstance = buildDatabase(context.getApplicationContext(), executors);
sInstance.updateDatabaseCreated(context.getApplicationContext());
}
}
}
return sInstance;
}
private static AppDatabase buildDatabase(final Context appContext,
final AppExecutors executors) {
return Room.databaseBuilder(appContext, AppDatabase.class, DATABASE_NAME)
.addCallback(new Callback() {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
executors.diskIO().execute(() -> {
AppDatabase database = AppDatabase.getInstance(appContext, executors);
// Time consuming task
// notify that the database was created and it's ready to be used
database.setDatabaseCreated();
});
}
}).build();
}
这里是从google的例子中借鉴过来的,这是单例模式获取这个实例。然后后面就是Room
的标准操作方法,就不细说了。到这里已经讲完大概的一个流程,这也是简单的一个封装,如果有什么好的改进方法,可以提出来哈,可以交流交流。