美文网首页AndroidWorldAPP开发经验总结Architecture Components
(一)Android官方MVVM框架实现组件化之整体结构

(一)Android官方MVVM框架实现组件化之整体结构

作者: 生椰拿铁锤 | 来源:发表于2017-11-25 00:11 被阅读4996次

    作者: Dawish_大D
    简书: http://www.jianshu.com/u/40e1f22c2c53

    (一)Android官方MVVM框架实现组件化之整体结构
    (二)Android官方MVVM框架实现组件化之ARouter串联各模块

    目前的项目结构图置顶:Demo的Github地址: https://github.com/Dawish/GoogleArchitectureDemo

    0-演示项目MVVM组件化架构图

    一、google官方MVVM框架讲解

    我前面对比了MVC和MVP《两张图看懂Android开发中MVC与MVP的区别》,可以相对于MVC我们的MVP是有多优越,但是Android开发现在已经开始流行了MVVM,前不久google官方发布了MVVM的正式库。官方的正式MVVM库主要包括下面四个:

    1-正式MVVM库组件

    其中只有ViewModel是MVVM结构中的一个组件,其他的三个都是辅助性质的。

    lifecycles 就是处理UI界面的生命周期,在26版本以后的Support库中,AppCompatActivitySupportActivity中都实现了LifecycleOwner,内部已经对UI界面的生命周期做了处理了。

    LiveData是一个抽象类,我们可以存放UI页面需要的数据,就是把数据包装在LiveData中了,我们可以观测LiveData中的数据变化,但是LiveData是跟UI的生命周期关联的,当UI页面销毁了,LiveData的数据变化回调是不会执行的。

    Room 就是一个sqlite数据持久化库,我们也可以使用别的ORM库。


    二、MVVM架构优势

    《两张图看懂Android开发中MVC与MVP的区别》 前面两张图真是了MVC和MVP的区别,我这里也来一张图看看MVVM:

    2-MVVM架构

    看上图ModelView是不会发生关系的,ViewModel是把View和Model关联起来的加工厂:

    3-ViewModel工厂

    MVVM优势总结:

    1. ViewModel双向绑定,一方的改变都会影响另一方,开发者不用再去手动修改UI的数据。额,互相自动的。

    2. 不需要findViewById也不需要butterknife,不需要拿到具体的View去设置数据绑定监听器等等,这些都可以用DataBinding完成。是不是很舒服?

    3. ViewModel的双向绑定是支持生命周期检测的,不会担心页面销毁了还有回调发生,这个由lifeCycle完成。

    4. 不会像MVC一样导致Activity中代码量巨大,也不会像MVP一样出现大量的ViewPresenter接口。项目结构更加低耦合。

    5. 更低的耦合把各个模块分开开发,分开测试,可以分给不同的开发人员来完成。


    三、MVVM组件化示例项目架构分析

    下图是项目模块和工程之间的依赖关系:


    4-MVVM组件化示例项目架构图

    下图是工程Android Studio中的目录结构:


    5-工程目录结构

    3.1 各模块和彼此之间的关系解释:

    • lib_opensource :第三方build.gradle依赖,本项目主要有supportlifecycleroomfrescoretrofitokhttpRxJavaARouter这些。

    • lib_coremodel: 存放MVVM中的ModelViewModel两个模块,就是数据的处理和数据与UI页面的绑定。依赖lib_opensource库。

    • lib_common : 公共库,主要有各种base,各种ui组件,自定义组件,公用的Activity、公用的Fragment,和公用的utils等等。依赖lib_coremodel库。

    • module_girls : 妹子功能模块,可以在libraryapplication之间切换,自己可以是一个app也可以成为别的app的一个组件模块。组件化编译时为app,反之为module。

    • module_news : 新闻功能模块,可以在libraryapplication之间切换,自己可以是一个app也可以成为别的app的一个组件模块。组件化编译时为app,反之为module。

    • app_universal : 定制版本的app,组件化编译时 module_girlsmodule_news为app,所以不能把这两个作为module加进来编译,所以组件化编译时app_universal要依赖lib_common库,反之就可以把 module_girlsmodule_news作为module加进来编译。

    • app_specific : 定制版本的app,组件化编译时 module_girlsmodule_news为app,所以不能把这两个作为module加进来编译,所以组件化编译时app_specific要依赖lib_common库,反之就可以把 module_girlsmodule_news作为module加进来编译。

    3.2 ARouter串联各个模块

    使用ARouter来跳转Activity和获取Fragment,记得看之前别人的组件化结构文章,一直都在纠结Fragment的获取问题,我想说的是有了ARouter来获取Fragment不是超级简单么?

    ARouter典型应用

    • 从外部URL映射到内部页面,以及参数传递与解析
    • 跨模块页面跳转,模块间解耦
    • 拦截跳转过程,处理登陆、埋点等逻辑
    • 跨模块API调用,通过控制反转来做组件解耦

    3.3 组件化编译和非组件化编译切换

    我们在工程根目录下的gradle.properties文件中加入一个Boolean类型的变量,通过修改这个变量来识别编译模式:

    # 每次更改“isModule”的值后,需要点击 "Sync Project" 按钮
    # isModule是“集成开发模式”和“组件开发模式”的切换开关
    isModule=false
    

    然后在 module_girlsmodule_news中的build.gradle文件中支持切换:

    if (isModule.toBoolean()) {
        //组件化编译时为application
        apply plugin: 'com.android.application'
    } else {
        //非组件化编译时为library
        apply plugin: 'com.android.library'
    }
    
    android {
        compileSdkVersion build_versions.target_sdk
        buildToolsVersion build_versions.build_tools
    
        defaultConfig {
            minSdkVersion build_versions.min_sdk
            targetSdkVersion build_versions.target_sdk
            versionCode 1
            versionName "1.0"
    
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    
            //ARouter
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = [moduleName: project.getName()]
                }
            }
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
        dataBinding {
            enabled = true
        }
        lintOptions {
            abortOnError false
        }
        sourceSets {
            main {
                if (isModule.toBoolean()) {
                    //组件化编译时为app,在对应的AndroidManifest文件中需要写ndroid.intent.action.MAIN入口Activity
                    manifest.srcFile 'src/main/module/AndroidManifest.xml'
                } else {
                    manifest.srcFile 'src/main/AndroidManifest.xml'
                    //集成开发模式下排除debug文件夹中的所有Java文件
                    java {
                        //debug文件夹中放的是Application类,非组件化时不用有此类
                        exclude 'debug/**'
                    }
                }
            }
        }
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        api project(':lib_coremodel')
        api project(':lib_common')
        implementation 'com.android.support:support-v4:26.1.0'
        annotationProcessor deps.arouter.compiler
    }
    
    

    上面看到了组件化和非组件化编译会有不用的AndroidManifest文件,组件化时需要debug文件夹下面的application类,非组件化时排除此文件夹。

    6-组件化非组件化编译切换
    • module下的AndroidManifest文件是组件化app编译时的,写了MAIN入口Activity
    • dubug下是组件化app编译时的Application类,初始化作为一个app运行时需要的资源等等。在非组件化编译在build.gradle文件中排除debug文件夹的所以东西。

    3.4 最后预告:

    后面会有一些列介绍在MVVM组件化过程中使用ARouter来跳转Activity和获取FragmentDataBinding实现数据和UI的互相绑定、Rxjava2Retrofit2动态数据获取,和AndroidViewModel的封装。

    下面贴贴一个lib_coremodel库中我封装的AndroidViewModel,用泛型来确定数据类型,并且是动态URL获取数据:

    package google.architecture.coremodel.viewmodel;
    
    import android.app.Application;
    import android.arch.lifecycle.AndroidViewModel;
    import android.arch.lifecycle.LiveData;
    import android.arch.lifecycle.MutableLiveData;
    import android.databinding.ObservableField;
    import android.support.annotation.NonNull;
    
    import java.io.IOException;
    import java.lang.reflect.ParameterizedType;
    
    import google.architecture.coremodel.datamodel.http.ApiClient;
    import google.architecture.coremodel.datamodel.http.ApiConstants;
    import google.architecture.coremodel.datamodel.http.service.DynamicApiService;
    import google.architecture.coremodel.util.JsonUtil;
    import io.reactivex.Observer;
    import io.reactivex.android.schedulers.AndroidSchedulers;
    import io.reactivex.disposables.CompositeDisposable;
    import io.reactivex.disposables.Disposable;
    import io.reactivex.schedulers.Schedulers;
    import okhttp3.ResponseBody;
    
    /**
     * Created by dxx on 2017/11/20.
     */
    
    public class BaseViewModel<T> extends AndroidViewModel {
    
        //生命周期观察的数据
        private MutableLiveData<T>  liveObservableData = new MutableLiveData<>();
        //UI使用可观察的数据 ObservableField是一个包装类
        public ObservableField<T> uiObservableData = new ObservableField<>();
    
        private final CompositeDisposable mDisposable = new CompositeDisposable();
    
        private static final MutableLiveData ABSENT = new MutableLiveData();
        {
            //noinspection unchecked
            ABSENT.setValue(null);
        }
    
    
        public BaseViewModel(@NonNull Application application, String fullUrl) {
            super(application);
            ApiClient.initService(ApiConstants.GankHost, DynamicApiService.class).getDynamicData(fullUrl).subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<ResponseBody>() {
                @Override
                public void onSubscribe(Disposable d) {
                    mDisposable.add(d);
                }
    
                @Override
                public void onNext(ResponseBody value) {
                   if(null != value){
                       try {
                           liveObservableData.setValue(JsonUtil.Str2JsonBean(value.string(), getTClass()));
                       } catch (IOException e) {
                           e.printStackTrace();
                       }
                   }
                }
    
                @Override
                public void onError(Throwable e) {
    
                }
    
                @Override
                public void onComplete() {
    
                }
            });
        }
    
        /**
         * LiveData支持了lifecycle生命周期检测
         * @return
         */
        public LiveData<T> getLiveObservableData() {
            return liveObservableData;
        }
    
        /**
         * 当主动改变数据时重新设置被观察的数据
         * @param product
         */
        public void setUiObservableData(T product) {
            this.uiObservableData.set(product);
        }
    
        public Class<T> getTClass(){
            Class<T> tClass = (Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
            return tClass;
        }
    
        @Override
        protected void onCleared() {
            super.onCleared();
            mDisposable.clear();
        }
    }
    
    

    Demo的Github地址: https://github.com/Dawish/GoogleArchitectureDemo

    相关文章

      网友评论

      • fe3c45e41166:怎么优雅的处理分页,加载更多
        fe3c45e41166:@Dawish_大D 哪个?我在适配器加了
        public void addGirlsList(final List<NewsData1.DataBean> list){
        newsList.addAll(list);
        addAll(newsList);
        }
        然后刷新到第3页就报IndexOutOfBoundsException,这段代码
        @Override
        public void OnBindViewHolder(BaseViewHolder holder, int position) {
        super.OnBindViewHolder(holder, position);
        ((GirlsViewHolder)holder).binding.setNewsItem1(newsList.get(position));//超出边界异常
        ((GirlsViewHolder)holder).binding.executePendingBindings();
        }
        生椰拿铁锤:@fe3c45e41166 官方有一个分页加载框架,你可以研究一下!
      • 0e32099de0eb:博主,想问一下,View的数据变化是怎样影响Viewmodel的值的呢?
        生椰拿铁锤:View是用来展示数据的,你的意思是操作View触发数据改变,建议看一下DataBinding支持的双向数据绑定,在xml文件中使用ViewModel中的ObservableField和LiveData容器中的数据来实现,最新的MVVM框架快支持了LiveData来双向绑定数据。
      • 木乃伊459:求教一下博主DataBinding怎么优雅的处理快速点击的情况呢
        XBaron:@木乃伊459 你要的处理快速点击的优雅方案哦
        https://www.jianshu.com/p/7b354eb8d0d3
        chenzhenlindx:引用RxBinding,自定义BindingAdapter可以处理快速点击。这是我写的一篇小结,欢迎指正:
        https://www.jianshu.com/p/20e03adf58fd
        生椰拿铁锤:@第九感 Dawish_大D: @第九感 自定义binding adapter实现点击监听后再判断吧,databinding不是天然支持你需要的这种功能,你说的功能用rxjava实现呀!
      • 我说今晚月光这么美你说是的:LZ,我觉得mvvm在布局文件中绑定数据,不如用kotlin 的扩展库,利用id绑定数据,你怎么看?而且布局文件不用写标签。
        生椰拿铁锤:@我说今晚月光这么美你说是的 recyclerview的item就会存在一样的id 我的意思是recyclerview不知道是否能够友好支持,如果能,那就挺好的。现在kotlin的库也好多,看来又要转了😪
        我说今晚月光这么美你说是的:@Dawish_大D 同一个View肯定不能有重复ID,就像变量一样,你可以在用一个页面定义相同id吗?至于数据刷新,生命周期组件可以做的
        生椰拿铁锤:@我说今晚月光这么美你说是的 kotlin我只是看过做过简单的demo,还没实际使用,所以你说的id绑定数据,那是不是id就不能重复?不知道支持不支持id跟数据互相绑定,数据改变了ui自动刷新?
      • Fritz_Xu:感觉MVVM用起来比MVP轻松些
        生椰拿铁锤:@武械 👏
        Fritz_Xu:@Dawish_大D 是的,正打算全名转入mvvm里面
        生椰拿铁锤:@武械 耦合度更低了
      • 请叫我四爷:好用吗?我还没有用过mvp,现在都已经mvvm 了吗
        生椰拿铁锤:@请叫我四爷 官方最新的稳定版框架都出了,比mvp好些。重构一下吧

      本文标题:(一)Android官方MVVM框架实现组件化之整体结构

      本文链接:https://www.haomeiwen.com/subject/imjqbxtx.html