2018/4/16 15:08:06当前位置推荐好文程序员浏览文章

主目录见:Android高级进阶知识(这是总目录索引)
mvvm框架地址:MarsArchitecture

 从谷歌发布新的框架组件,试消耗之后,不仅开始设想着改进之前的mvp模式架构,谷歌新框架新组件ViewModel,LiveData,Room,的确如谷歌所推荐的,从我感受来看,的确可以省不少代码,代码也会比较优雅,包括加上前面的DataBinding,以及在mvp框架中消耗的流行框架rxjava,retrofit。那么我们就一起来封装下,当然大家能改进,还是有很多空间的。下面我们看下下面的整体架构图片:


MVVM架构体系思考.png

一.基本使消耗

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 its ready to be used                            database.setDatabaseCreated();                        });                    }                }).build();    }

这里是从google的例子中借鉴过来的,这是单例模式获取这个实例。而后后面就是Room的标准操作方法,就不细说了。到这里已经讲完大概的一个流程,这也是简单的一个封装,假如有什么好的改进方法,能提出来哈,能交流交流。

网友评论