Android数据层架构的实现 下篇

接上篇:Android数据层架构的实现 上篇

4.外观模式实现数据处理引擎框架暴露出来的api

我们在使用各种开源框架的时候,大多数时候都不会对框架内部的实现进行细究,所以一个好的框架需要一个简单的入口来对整个框架进行使用,我接下来就是要讲解本框架的入口类。

public class DataEngine {
public static String TAG="DataEngine";
public static Context nowContext;
public static ObservableBoolean nowIsShowProgressBar;

public static final List<Interceptor> mInterceptors=new ArrayList<>();
public static final DataEngine sInstance;
static  {
    synchronized (DataEngine.class){
        sInstance=new DataEngine();
    }
}

private final Service memoryCacheService=new MemoryCacheServiceImpl(true);
private final Service mLocalDataService =new LocalDataServiceImpl(true) ;
private final Service networkService=new NetworkServiceImpl(true);
private DataEngine() {
    mInterceptors.add(new MemoryCacheInterceptor(memoryCacheService));
    mInterceptors.add(new NewThreadInterceptor());
    mInterceptors.add(new LocalDataInterceptor(mLocalDataService));
    mInterceptors.add(new NetworkInterceptor(networkService));
}

/**
 * 所有的数据请求,都是从这个方法开始。本方法在{@link LocalDataEngine}和{@link NetworkDataEngine}中使用。
 * @param request 传入的数据请求
 * @return
 */
public Observable<Response> request(final Request request){
    final RealInterceptorChain realInterceptorChain=RealInterceptorChain.obtain(request);
    Object response;
    try {
        response = realInterceptorChain.proceed();
    } catch (Exception e) {
        e.printStackTrace();
        // 此时抛出的异常,那么就是请求失败了,应该是内存缓存服务有问题,由于发生了未知的错误所以不应该继续运行这个请求了。
        FLog.e(TAG,"应该是内存缓存服务有问题",e);
        return Observable.just(Response.getFailedResponse(e))
                .compose(((RxAppCompatActivity)nowContext).<Response>bindToLifecycle())
                .filter(new Func1<Response, Boolean>() {
                    @Override
                    public Boolean call(Response appConfigObjectObjectResponse) {
                        //此时拦截链已经调用完毕,所以可以将RealInterceptorChain回收
                        realInterceptorChain.recycle();
                        Toast.makeText(nowContext, "操作失败,发生未知错误", Toast.LENGTH_SHORT).show();
                        if (nowIsShowProgressBar!=null)nowIsShowProgressBar.set(false);
                        return appConfigObjectObjectResponse.isSuccess();
                    }
                });
    }

    Observable<?> finalResponseObservable;
    if (response instanceof Observable){
        // 这里表示需要从其他线程获取数据数据,一般是本地储存或者网络上的数据
        finalResponseObservable = (Observable<?>) response;
    } else {
        // 这里表示直接从本线程获取数据,一般是内存中的数据
        finalResponseObservable = Observable.just(response);
    }
    return finalResponseObservable
            .compose(((RxAppCompatActivity)nowContext).bindToLifecycle())
            .observeOn(AndroidSchedulers.mainThread())
            .filter(new Func1<Object, Boolean>() {
                @Override
                public Boolean call(Object o) {
                    //此时拦截链已经调用完毕,所以可以将RealInterceptorChain回收
                    realInterceptorChain.recycle();
                    if (o instanceof Response){
                        // 这里表示在 本地储存或者网络请求或者想内存缓存存数据的时候 发生了问题,所以直接返回了请求错误的结果,
                        // 由于发生了未知的错误所以不应该继续运行这个请求了。
                        Toast.makeText(nowContext, "操作失败,发生未知错误", Toast.LENGTH_SHORT).show();
                        if (nowIsShowProgressBar!=null)nowIsShowProgressBar.set(false);
                        return ((Response) o).isSuccess();
                    }
                    return true;
                }
            }).map(new Func1<Object, Response>() {
                @Override
                public Response call(Object o) {
                    FLog.v(TAG, request.getRequestFlag()+"请求成功");
                    FLog.v("                                         ","                                     ");
                    return Response.getCommonResponseOne(o);
                }
            });
}
}

由于本框架适配了Rxjava,所以需要大家对Rxjava比较熟悉

  • 1.前三个静态变量不需要细究,List<Interceptor> mInterceptors是静态final变量,在DataEngine单例创建的时候会把拦截器放入其中。DataEngine sInstance是静态final单例,使用同步的方式在类被加载的时候初始化,由于DataEngine的构造器是private的,所以这个类不能再创建其他对象了。
  • 2.我们由代码可以看出,所有的Service和List<Interceptor> mInterceptors都是被多线程共用的,所以此时就会出现线程安全问题。由于mInterceptors中的拦截器都是不可变类,所以这个不需要担心。但是所有Service的实现就需要对某些操作进行同步了,Service的实现和同步会在后面讲到。
  • 3.再下来我们就可以看见,惟一一个public的方法request(final Request request):
    • 1.方法首先会去RealInterceptorChain的对象池中获取一个对象,然后调用realInterceptorChain#proceed()获取结果。注意这里的调用都是在主线程进行的,如果大家对前面的MemoryCacheInterceptor还有印象的话,就会知道这里要么返回数据类Object,要么返回一个Observable。数据类Object是从内存中获取的不会在其他线程,而Observable并没有调用subscribe(),所以其只是对一个请求的封装,真正的请求还没被调用。
    • 2.在获取到response之后,要判断这个response到底是数据类Object还是Observable,如果是数据类Object将其再用Observable封装。
    • 3.再进行了上面的操作之后,返回的请求就都变成Observable了,此时我们先将线程切换为主线程,然后用一个filter过滤掉出现异常的请求,最后将成功获取的数据映射成一个Response。注意:此时返回的是Observable,真正的调用会由客户端触发

5.服务的实现

前面我们在讲拦截链的时候,使用的都是Service接口,那么每个拦截器的具体实现是怎么样的呢?接下来我就来讲解一下我们app登录时,每个服务的实现。

@ThreadSafe

public class MemoryCacheServiceImpl implements Service{
private final MemoryCache<CacheKey,Object> mainMemoryCache=new CountingLruMapMemoryCacheImpl<CacheKey, Object>(200,new DefaultCacheListener());
private final MemoryCache minorMemoryCache=null;
private boolean enable;

public MemoryCacheServiceImpl(boolean enable) {
    this.enable = enable;
}

@Override
public Object in(Request request, Object in) throws Exception {
    return mainMemoryCache.cache(request.getCacheKey(),in==null?request.getParam():in);
}

@Override
public Object out(Request request) throws Exception {
    return mainMemoryCache.get(request.getCacheKey());
}

@Override
public boolean isEnabled() {
    return enable;
}

@Override
public void setEnable(boolean enable) {
    this.enable=enable;
}

}
  • 1.MemoryCacheServiceImpl是内存服务的实现类。
      1. MemoryCache<CacheKey,Object> mainMemoryCache:是一个接口,由于我们可以使用第三方类库实现缓存,也可以写一个自制缓存,如果在内存缓存服务中使用实际的类,那么在以后要进行更换内存缓存实现的时候就需要改动很多地方。而MemoryCache<CacheKey,Object>就是我定义了一组内存缓存组件的标准,只要实现了这个标准的内存缓存组件都能用于这个内存缓存服务。
    • 2.MemoryCache minorMemoryCache:是副缓存,目前没有赋值。
    • 3.接下来的四个方法就是对Service的实现,可以看见对于in()代码中就直接调用的是缓存组件的cache()来对,传入的参数或者返回的结果进行缓存。out()中就是直接从缓存组件中根据Request中的CacheKey获取缓存的数据。

    代码 public interface ToLocalDataRequest<OUT,IN> { OUT updateAndGetAppConfig(Request request, IN in)throws Exception; OUT getAppConfig(Request request, IN in)throws Exception ; OUT updateAndGetUserConfig(Request request, IN in) throws Exception; OUT getUserConfig(Request request, IN in)throws Exception ; } public interface ToNetworkRequest<OUT,IN> { OUT patrolAccountAction_login(Request request, IN in)throws Exception ; OUT initAppData(Request request, IN in)throws Exception ; }

  • 2.在讲本地储存服务和网络服务的时候,先来介绍两个接口。ToLocalDataRequest<OUT,IN>和ToNetworkRequest<OUT,IN>。我们都知道内存缓存中存取数据是比较简单的,但是到了硬盘和服务器上就不一样了。有时我们会用数据库实现硬盘缓存,有时候会用文件系统实现硬盘缓存,在这个时候对于不同种类的请求要进行的操作是不同的,同样对于向服务器的请求也是一样的。所以我们需要对本地存储请求和服务器请求分别创建接口,然后让本地存储服务和网络请求服务去实现他们,最后根据不同的请求来调用不同的被覆盖的方法。

@ThreadSafe

public class LocalDataServiceImpl implements Service,ToLocalDataRequest,ToNetworkRequest{
private static String TAG="LocalDataServiceImpl";

private final DaoMaster daoMaster = new DaoMaster(new DaoMaster.DevOpenHelper(MyApplication.myApplication, "my.db", null).getWritableDb());
private final DiskCache<SimpleCacheKey> sharePreferenceDiskCache =new SharePreferenceDiskCacheImpl<>(MyApplication.myApplication,new DefaultCacheListener());
private boolean enable;

public LocalDataServiceImpl(boolean enable) {
    this.enable = enable;
}

@Override
public Object in(Request request,Object in) throws Exception {
    return TransformDataMethodDispatch.dispatch(this,this,request,in);
}

@Override
public Object out(Request request) throws Exception {
    return TransformDataMethodDispatch.dispatch(this,this,request,null);
}

@Override
public Object patrolAccountAction_login(Request request, Object in) throws IOException {
    return null;
}

@Override
public Object initAppData(Request request, Object in) throws IOException {
    if (in==null)return null;
    FlushData flushData=(FlushData)in;
    ArrayList<MissionInfoEntity> mInfoEntityArrayList=flushData.getFlushData().getInfoEntityArrayList();
    ArrayList<PolicePeopleEntity> mPeopleArrayList=flushData.getFlushData().getPeopleArrayList();
    ArrayList<UnitBaseEntity> mUnitBaseArrayList=flushData.getFlushData().getUnitBaseArrayList();

    if (mInfoEntityArrayList!=null&&mInfoEntityArrayList.size()!=0){
        for (MissionInfoEntity m:mInfoEntityArrayList){
            daoMaster.newSession().getMissionInfoDao().insert(new MissionInfo(m));
        }
    }

    if (mPeopleArrayList!=null&&mPeopleArrayList.size()!=0){
        for (PolicePeopleEntity p :mPeopleArrayList){
            daoMaster.newSession().getPolicePeopleDao().insert(new PolicePeople(p));
        }
    }

    if (mUnitBaseArrayList!=null&&mUnitBaseArrayList.size()!=0){
        for (UnitBaseEntity u :mUnitBaseArrayList){
            daoMaster.newSession().getUnitBaseDao().insert(new UnitBase(u));
        }
    }
    request.setCacheToMemory(false);
    FLog.v(TAG,"向数据库初始化数据成功");
    return in;
}

@Override
public Object updateAndGetAppConfig(Request request, Object in) throws Exception {
    if (request.getParam()==null){
        FLog.e(TAG,"appConfig插入时为null");
        throw new NullPointerException();
    }
    AppConfig appConfig=(AppConfig)request.getParam();
    List<DBMap> dbMapList=appConfig.transformToDBMap();
    daoMaster.newSession().getDBMapDao().insertOrReplaceInTx(dbMapList);
    FLog.v(TAG,"向数据库更新appConfig成功");
    return appConfig;
}

@Override
public Object getAppConfig(Request request, Object in){
    AppConfig appConfig = null;
    List<DBMap> dbMapList=daoMaster.newSession().getDBMapDao().loadAll();
    if (dbMapList==null||dbMapList.size()==0)return null;
    appConfig=new AppConfig(dbMapList);
    FLog.v(TAG,"从数据库获取的appConfig");
    return appConfig;
}

@Override
public Object updateAndGetUserConfig(Request request, Object in) throws Exception {
    if (request.getParam()==null){
        FLog.e(TAG,"userConfig插入时为null");
        throw new NullPointerException();
    }
    UserConfig userConfig=(UserConfig) request.getParam();
    sharePreferenceDiskCache.cache((SimpleCacheKey) request.getCacheKey(),JsonUtil.objectToJson(userConfig));
    FLog.v(TAG,"向sharePreferenceDiskCache更新userConfig成功");
    return userConfig;
}

@Override
public Object getUserConfig(Request request, Object in) throws IOException {
    UserConfig userConfig= JsonUtil.jsonToObject((String) sharePreferenceDiskCache.get((SimpleCacheKey) request.getCacheKey()),UserConfig.class);
    FLog.v(TAG,"从sharePreferenceDiskCache获取的userConfig");
    return userConfig;
}

@Override
public boolean isEnabled() {
    return enable;
}

@Override
public void setEnable(boolean enable) {
    this.enable=enable;
}
}
  • 3.LocalDataServiceImpl是本地存储服务的实现类,可以看见这个类实现了Service,ToLocalDataRequest,ToNetworkRequest这三个接口,Service不必多说。可能有人要问为什么要实现ToNetworkRequest呢?那是因为在一些向服务器的请求之中,可能在本地中已经有缓存了,那么此时并不需要去服务器中取数据。我们在前面的本地储存拦截器中也有介绍到这一点。
    • 1.可以看见这个类中有GreenDao(一种数据库的ORM)和DiskCache<SimpleCacheKey> sharePreferenceDiskCache这两个实现,GreenDao内部实现不是由我们实现的所以不需要抽象为接口。而sharePreferenceDiskCache是一个本地储存抽象出来的接口和前面讲的内存缓存接口是一个道理。我目前用的是sharePreference,当然以后有需要还可以换成用文件系统实现,换起来也很方便。
    • 2.接下来是实现了Service的in()和out()方法,本来是需要在这两个方法中根据请求的种类使用switch将请求分派到各个方法中的,这里我使用了TransformDataMethodDispatch来分派,内部实现是一样的。
    • 3.接下来的几个方法就是对ToLocalDataRequest,ToNetworkRequest这两个接口的实现了,这里需要根据具体的业务逻辑进行考虑。

@ThreadSafe

public class NetworkServiceImpl implements Service,ToNetworkRequest{
private boolean enable;

public NetworkServiceImpl(boolean enable) {
    this.enable = enable;
}

@Override
public Object in(Request request, Object in) throws Exception {
    return null;
}

@Override
public Object out(Request request) throws Exception {
    return TransformDataMethodDispatch.dispatch(this,null,request,null);
}

@Override
public Object patrolAccountAction_login(Request request, Object in) throws IOException {
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    LoginEntity.Response response=new LoginEntity.Response(new LoginEntity.UserEntity(1,"13030512957","1",(byte) 1,"男"),"杭州",1,4,"");
    return response;
}

@Override
public Object initAppData(Request request, Object in) throws IOException {
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return null;
}

@Override
public boolean isEnabled() {
    return enable;
}

@Override
public void setEnable(boolean enable) {
    this.enable = enable;
}
}
  • 4.NetworkServiceImpl就是网络请求服务的实现了
    • 1.这里我只是模拟了网络请求,大家可以使用任何网络框架比如:Retrofit Okhttp之类的
    • 2.注意这里的in()方法是空实现,是不会被调用到的,前面在介绍拦截器的时候已经说过了,不再赘述。
    • 3.和本地存储服务一样,out()方法中也使用了TransformDataMethodDispatch进行请求的分派。

以上就是Service的各个实现类,大家可能发现了,我并没有讲解类似于内存缓存,本地储存以及网络请求的具体实现,但是整个逻辑依旧是非常清晰的。所以看到这里我想有些同学已经理解了,到底该如何设计一个框架:框架的每个层次都需要使用接口进行抽象,能使用接口进行交互的地方就别使用实现类,在改变了一个具体子功能的实现之后其他模块并不会受到影响。 能做到前面说的几点,我想写出来的代码应该算是一个比较容易扩展的架构了。

6.更加上层的封装

我们前面将了DataEngine就算是我们的暴露给客户端的api了,但是这个类用起来还是有点麻烦的,比如说我们的Reponse的泛型参数还没有用起来,这样可能在某些地方需要我们手动进行类型转化,一不小心可能还会将类型转错了。所以接下来我们再在DataEngine之上封装一层api。

public class Transform<Response1,Response2 ,Response3> implements ToLocalDataRequest<Observable<Response<Response1,Response2,Response3>>,Observable<Response>>,
    ToNetworkRequest<Observable<Response<Response1,Response2,Response3>>,Observable<Response>> {

public static <Response1> Observable<Response<Response1,Object,Object>> transformOne(Request request) {
    Transform<Response1,Object,Object> transform=new Transform<>();
    try {
        return TransformDataMethodDispatch.dispatch(transform,transform,request, DataEngine.sInstance.request(request));
    } catch (Exception e) {
        e.printStackTrace();
        return Observable.just(Response.<Response1,Object,Object>getFailedResponse(e));
    }
}

public static <Response1,Response2> Observable<Response<Response1,Response2,Object>> transformTwo(Request request) {
    Transform<Response1,Response2,Object> transform=new Transform<>();
    try {
        return TransformDataMethodDispatch.dispatch(transform,transform,request, DataEngine.sInstance.request(request));
    } catch (Exception e) {
        e.printStackTrace();
        return Observable.just(Response.<Response1,Response2,Object>getFailedResponse(e));
    }
}
public static <Response1,Response2,Response3> Observable<Response<Response1,Response2,Response3>> transformThree(Request request) {
    Transform<Response1,Response2,Response3> transform=new Transform<>();
    try {
        return TransformDataMethodDispatch.dispatch(transform,transform,request, DataEngine.sInstance.request(request));
    } catch (Exception e) {
        e.printStackTrace();
        return Observable.just(Response.<Response1,Response2,Response3>getFailedResponse(e));
    }
}


@Override
public Observable<Response<Response1, Response2, Response3>> updateAndGetAppConfig(Request request, Observable<Response> responseObservable) throws Exception {
    return responseObservable
            .map(new Func1<Response, Response<Response1, Response2, Response3>>() {
                @Override
                public Response<Response1, Response2, Response3> call(Response response) {
                    return response;
                }
            });
}

@Override
public Observable<Response<Response1, Response2, Response3>> getAppConfig(Request request, Observable<Response> responseObservable) throws Exception {
    return responseObservable
            .map(new Func1<Response, Response<Response1, Response2, Response3>>() {
                @Override
                public Response<Response1, Response2, Response3> call(Response response) {
                    return response;
                }
            });
}

@Override
public Observable<Response<Response1, Response2, Response3>> updateAndGetUserConfig(Request request, Observable<Response> responseObservable) throws Exception {
    return responseObservable
            .map(new Func1<Response, Response<Response1, Response2, Response3>>() {
                @Override
                public Response<Response1, Response2, Response3> call(Response response) {
                    return response;
                }
            });
}

@Override
public Observable<Response<Response1, Response2, Response3>> getUserConfig(Request request, Observable<Response> responseObservable) throws Exception {
    return responseObservable
            .map(new Func1<Response, Response<Response1, Response2, Response3>>() {
                @Override
                public Response<Response1, Response2, Response3> call(Response response) {
                    return response;
                }
            });
}

@Override
public Observable<Response<Response1, Response2, Response3>> patrolAccountAction_login(Request request, Observable<Response> responseObservable) throws Exception {
    return responseObservable
            .map(new Func1<Response, Response<Response1, Response2, Response3>>() {
                @Override
                public Response<Response1, Response2, Response3> call(Response response) {
                    return response;
                }
            });
}

@Override
public Observable<Response<Response1, Response2, Response3>> initAppData(Request request, Observable<Response> responseObservable) throws Exception {
    return responseObservable
            .map(new Func1<Response, Response<Response1, Response2, Response3>>() {
                @Override
                public Response<Response1, Response2, Response3> call(Response response) {
                    return response;
                }
            });
}
}
  • 1.这个类就是对DataEngine的封装,这个封装可以将DataEngine#request()中返回的数据类,转换成多个界面上可以直接使用的数据类。当由于不同种类的请求用到的数据类型和数量各不相同,所以这个类需要实现ToLocalDataRequest和ToNetworkRequest这两个接口,对不同的请求返回的结果,进行不同的转换。
    • 1.transformOne()、transformTwo()、transformThree()这三个方法,分别表示最后转换的结果是一个、两个、三个。
    • 2.后面的方法都是两个接口的实现,我举的例子中都是不需要转换的数据请求。

7.例子代码

我前面已经给出了一个app登录界面的整套运行流程,项目已经测试过了问题应该不大有问题可以加我QQ,内部有数据引擎的完整代码,项目是用MVVM+Rxjava+GreenDao写的,大家有兴趣可以去下载看看,能给个star就更好了,没兴趣的同学给大家欣赏一下我们登录界面的链式代码。不得不说Rxjava让代码逻辑变得非常清晰,而MVVM的databinding则完美的与数据引擎结合了起来。

`   isShowProgressBar.set(true);
    LocalDataEngine.getAppConfig()
            .flatMap(new Func1<AppConfig, Observable<AppConfig>>() {
                @Override
                public Observable<AppConfig> call(AppConfig appConfig) {
                    //如果为null表示第一次打开app,所以进行全局数据初始化
                    if (appConfig==null) appConfig=new AppConfig(true,"13030512957",false,false);
                    FLog.v(TAG,String.valueOf(appConfig.isFirstOpenApp()));
                    //如果不是第一次打开app,将appConfig传入下面一个环节
                    appConfig.setAutoLogin(true);
                    appConfig.setRememberPassword(true);
                    if (appConfig.isFirstOpenApp()){
                        return LocalDataEngine.updateAndGetAppConfig(appConfig);
                    }else {
                        return Observable.just(appConfig);
                    }
                }
            }).flatMap(new Func1<AppConfig, Observable<AppConfig>>() {
                @Override
                public Observable<AppConfig> call(AppConfig appConfig) {
                    //如果是第一次打开app,那么去服务器拉去数据表
                    if (appConfig.isFirstOpenApp()){
                        return NetworkDataEngine.initAppData(appConfig);
                    }else {
                        //否则,将appConfig传入下面一个环节
                        return Observable.just(appConfig);
                    }
                }
            }).filter(new Func1<AppConfig, Boolean>() {
                @Override
                public Boolean call(AppConfig appConfig) {
                    //此时如果是第一次打开app,那么数据已经全部初始化完毕,更变第一次打开app的flag
                    //同时AppConfig已经准备好,可以进行接下来的操作了。
                    if (appConfig.isFirstOpenApp())
                        appConfig.setFirstOpenApp(false);
                    return true;
                }
            }).filter(new Func1<AppConfig, Boolean>() {
                @Override
                public Boolean call(AppConfig appConfig) {
                    //设置界面上的:记住密码 和 自动登录 的checkBox
                    isRememberPassword.set(appConfig.isRememberPassword());
                    isAutoLogin.set(appConfig.isAutoLogin());
                    //如果全局设置中,既不需要记住密码,也不需要自动登录,那么就不去获取默认的用户信息,直接显示登录界面。
                    if ((!appConfig.isRememberPassword())&&(!appConfig.isAutoLogin())){
                        isShowProgressBar.set(false);
                        return false;
                    }
                    return true;
                }
            }).flatMap(new Func1<AppConfig, Observable<UserConfig>>() {
                @Override
                public Observable<UserConfig> call(AppConfig appConfig) {
                    //根据全局配置获取默认用户的帐号,然后去SharePreference中获取默认用户数据
                    return LocalDataEngine.getUserConfig(appConfig.getNowUserPhone());
                }
            }).filter(new Func1<UserConfig, Boolean>() {
                @Override
                public Boolean call(UserConfig userConfig) {
                    // 如果默认用户数据为null,说明用户数据缓存被清空了,或者app是第一次被打开,此时app不需要自动登录。
                    if (userConfig==null){
                        //弹出toast说明自动登录失败的原因,并将登录界面显示出来
                        Toast.makeText(mBaseActivity, "用户缓存被清空,请手动登录", Toast.LENGTH_SHORT).show();
                        isShowProgressBar.set(false);
                        return false;
                    }else {
                        //数据准备就绪,进行自动登录
                        usernameObservable.set(userConfig.getRememberedPhone());
                        passwordObservable.set(userConfig.getRememberedPassword());
                        username=usernameObservable.get();
                        password=passwordObservable.get();
                        return true;
                    }
                }
            }).subscribe(new Action1<UserConfig>() {
                @Override
                public void call(UserConfig userConfig) {
                    login();
                }
            });`

本篇博客只是给大家一个启示,告诉大家如何实现一个数据引擎的架构,内部必然会出现一些纰漏,有兴趣的同学可以和我在QQ上交流。 最后大家对MVVM架构有兴趣的可以看看我的这两篇博客 MVVM架构篇之databinding源码解析 MVVM架构之自动增删改的极简RecycleView的实现

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Golang语言社区

GoLang并发控制(下)

context的字面意思是上下文,是一个比较抽象的词,字面上理解就是上下层的传递,上会把内容传递给下,在go中程序单位一般为goroutine,这里的上下文便是...

34030
来自专栏Android知识点总结

1-AIV--使用ContentProvider获取短信

15120
来自专栏向治洪

系统捕获异常并发送到服务器

大家都知道,现在安装Android系统的手机版本和设备千差万别,在模拟器上运行良好的程序安装到某款手机上说不定就出现崩溃的现象,开发者个人不可能购买所有设备逐个...

21070
来自专栏JackieZheng

探秘Tomcat——启动篇

tomcat作为一款web服务器本身很复杂,代码量也很大,但是模块化很强,最核心的模块还是连接器Connector和容器Container。具体请看下图: ? ...

50070
来自专栏飞雪无情的博客

Go语言实战笔记(二十)| Go Context

控制并发有两种经典的方式,一种是WaitGroup,另外一种就是Context,今天我就谈谈Context。

21430
来自专栏Flutter知识集

Flutter 实践 MVVM

在做Android或iOS开发时,经常会了解到MVC,MVP和MVVM。MVVM在移动端一度被非常推崇,虽然也有不少反对的声音,不过MVVM确实是不错的设计架构...

3.4K50
来自专栏移动端开发

Android学习--跨程序共享数据之内容提供其探究

      跨程序共享数据之内容提供器,这是个什么功能?看到这个名称的时候最能给我们提供信息的应该是“跨程序”这个词了,是的重点就是这个词,这个内容提供器的作用...

13430
来自专栏向治洪

Universal-Image-Loader源码分析,及常用的缓存策略

讲到图片请求,主要涉及到网络请求,内存缓存,硬盘缓存等原理和4大引用的问题,概括起来主要有以下几个内容: 原理示意图     主体有三个,分别是UI,缓存模...

22190
来自专栏屈定‘s Blog

设计模式--适配器模式的思考

个人认为适配器模式是一种加中间层来解决问题的思想,为的是减少开发工作量,提高代码复用率.另外在对于第三方的服务中使用适配器层则可以很好的把自己系统与第三方依赖解...

12550
来自专栏游戏杂谈

bat与jscript开发工具时遇到的一些问题

之前使得bat调用luac进行编译时,会弹出一个“黑色的界面”,闪烁一下,感觉不太好。而脚本vbs或者jscript调用bat是可以利用Run方法,将其第二个参...

13220

扫码关注云+社区

领取腾讯云代金券