FrameWork层源码的分析-Google新组件下的架构思考

94 篇文章 0 订阅
94 篇文章 0 订阅

从谷歌发布新的框架组件,试用之后,不仅开始设想着改进之前的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 and it's ready to be used
                            database.setDatabaseCreated();
                        });
                    }
                }).build();
    }

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

 

 

+qq群:853967238。获取以上高清技术思维图,以及相关技术的免费视频学习资料。

+qq群:853967238。获取以上高清技术思维图,以及相关技术的免费视频学习资料。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值