MVVM 最完整架构解析及 Jetpack 架构组件的使用

MVVM 架构图

谈到 MVVM 架构,不得不祭出官方的架构图,架构图能帮助我们更好地理解,如下所示:
MVVM 最完整架构解析及 Jetpack 架构组件的使用_第1张图片
MVVM 和 MVP 的区别
MVP 中 V 层和 P 层互相持有对方的引用,在V 层调用 P 层逻辑后,P 层回调V 层的相应方法更新 UI。

而在 MVVM 中,上层只依赖直接下层,不能跨层持有引用,那 View 层调用 ViewModel 处理数据后,又如何更新自己呢?

答案就在 ViewModel 中的 LiveData,这是一种可观察的数据类型,在 View 层中观察者 Observer 对需要的数据进行订阅,当数据发生变化后,观察者 Observer 的回调方法 onChanged() 中会收到新的数据,从而可以更新 UI。

Jetpack 架构组件

Jetpack 是 Google 为我们提供的架构组件,对于这些组件,我有以下理解和使用心得:

DataBinding
适用于数据繁杂的页面,可以减少大量 java 代码。xml 中可以做出类似于VUE 那样的数据双向绑定。同时可以直接调用控件id名称直接写UI逻辑代码不用写findviewById,也不用通过butterknife来注解实现findviewById的节省。
ViewModel
管理 Activity 或 Fragment 的数据
创建于Activity 或 Fragment 内,页面被销毁前,ViewModel 会一直存在
如果因配置变化导致页面销毁,ViewModel 不会销毁,它会被用于新的页面实例
一般在 ViewModel 中配合 LiveData 使用
一般用 ViewModelProviders 获取 ViewModelProvider,再用它的 get() 方法获取 ViewModel
在 get() 方法中会调用 Factory 的 create() 方法创建 ViewModel
Lifecycles
管理你的 Activity 和 Fragment 生命周期。
不用再通过写回调监听来感知Activity 和 Fragment 生命周期的变化来做相应的处理动作
LiveData
基于生命周期的消息订阅组件,不会发生内存泄漏,不用反注册。

MVVM 案例实战

下面根据我的开源项目 Jetpack_MVVM进一步讲解 MVVM 架构的运用,以下所有代码均来自于该项目

1、基类封装

专门针对MVVM模式打造的BaseActivity、BaseFragment、BaseViewModel,在View层中不再需要定义ViewDataBinding和ViewModel,直接在BaseActivity、BaseFragment上限定泛型即可使用。

public abstract class BaseActivity extends RxAppCompatActivity implements LifecycleProvider {
    protected V binding;
    protected VM viewModel;
    /**
     * 注入绑定
     */
    private void initViewDataBinding(Bundle savedInstanceState) {
        //DataBindingUtil类需要在project的build中配置 dataBinding {enabled true }, 同步后会自动关联android.databinding包
        binding = DataBindingUtil.setContentView(this, initContentView(savedInstanceState));
        viewModelId = initVariableId();
        viewModel = initViewModel();
        if (viewModel == null) {
            Class modelClass;
            Type type = getClass().getGenericSuperclass();
            if (type instanceof ParameterizedType) {
                modelClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[1];
            } else {
                //如果没有指定泛型参数,则默认使用BaseViewModel
                modelClass = BaseViewModel.class;
            }
            viewModel = (VM) createViewModel(modelClass);
        }
        ...........................
    }
    /**
     * 创建ViewModel
     *
     * @param 
     * @return
     */
    protected  T createViewModel(@NonNull Class modelClass) {
        if (mActivityProvider == null) {
            mActivityProvider = new ViewModelProvider(this);
        }
        return mActivityProvider.get(modelClass);
    }
    ...............
}

2、网络请求框架封装

retrofit+okhttp+rxJava负责网络请求;gson负责解析json数据;rxlifecycle负责管理view的生命周期;与网络请求共存亡;
RetrofitClient
NetWorkManager
这两个类分别封装了Retrofit的相关配置和具体的使用方法。
RetrofitClient 中对相关参数进行相关初始化配置,拦截配置,以及参数加密配置。

public class RetrofitClient {
    //超时时间
    private static final int DEFAULT_TIME_OUT = 10;//超时时间 70s
    private static final int DEFAULT_READ_TIME_OUT = 10;


    //缓存时间
    private static final int CACHE_TIMEOUT = 10 * 1024 * 1024;
    //服务端根路径
    public static String baseUrl = "http://rap2.taobao.org:38080/app/mock/259908/";

    private static Context mContext = App.getContext();

    private static OkHttpClient okHttpClient;
    private static Retrofit retrofit;

    private Cache cache = null;
    private File httpCacheDirectory;

    private static class SingletonHolder {
        private static RetrofitClient INSTANCE = new RetrofitClient();
    }

    public static RetrofitClient getInstance() {
        return SingletonHolder.INSTANCE;
    }

    private RetrofitClient() {
        this(baseUrl, null);
    }

    private RetrofitClient(String url, Map headers) {

        if (TextUtils.isEmpty(url)) {
            url = baseUrl;
        }

        if (httpCacheDirectory == null) {
            httpCacheDirectory = new File(mContext.getCacheDir(), "goldze_cache");
        }

        try {
            if (cache == null) {
                cache = new Cache(httpCacheDirectory, CACHE_TIMEOUT);
            }
        } catch (Exception e) {
            LogUtils.e("Could not create http cache", e);
        }
        HttpsUtils.SSLParams sslParams = HttpsUtils.getSslSocketFactory();
        okHttpClient = new OkHttpClient.Builder()
                .cookieJar(new CookieJarImpl(new PersistentCookieStore(mContext)))
                .cache(cache)
                .addInterceptor(new BaseInterceptor(headers))
                .addInterceptor(new CacheInterceptor(mContext))
                .addInterceptor(commonHeader())
                .sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager)
                .addInterceptor(new LoggingInterceptor
                        .Builder()//构建者模式
                        .loggable(BuildConfig.DEBUG) //是否开启日志打印
                        .setLevel(Level.BASIC) //打印的等级
                        .log(Platform.INFO) // 打印类型
                        .request("Request") // request的Tag
                        .response("Response")// Response的Tag
                        .addHeader("log-header", "I am the log request header.") // 添加打印头, 注意 key 和 value 都不能是中文
                        .build()
                )
                .connectTimeout(DEFAULT_TIME_OUT, TimeUnit.SECONDS)
                .readTimeout(DEFAULT_READ_TIME_OUT, TimeUnit.SECONDS)
                .writeTimeout(DEFAULT_READ_TIME_OUT, TimeUnit.SECONDS)
                .connectionPool(new ConnectionPool(8, 60, TimeUnit.SECONDS))
                // 这里你可以根据自己的机型设置同时连接的个数和时间,我这里8个,和每个保持时间为10s
                .build();
        retrofit = new Retrofit.Builder()
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl(url)
                .build();

    }

    /**
     * create you ApiService
     * Create an implementation of the API endpoints defined by the {@code service} interface.
     */
    public  T create(final Class service) {
        if (service == null) {
            throw new RuntimeException("Api service is null!");
        }
        return retrofit.create(service);
    }


    /**
     * 利用拦截器配置请求头
     *
     * @return
     */
    private Interceptor commonHeader() {

        Interceptor commonInterceptor = new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                Request.Builder requestBuilder = request.newBuilder();

                requestBuilder.addHeader("Content-Type", "application/x-www-form-urlencoded")
                        .addHeader("version", BuildConfig.VERSION_NAME)
                        .addHeader("osType", DeviceUtils.getSDKVersionCode() + "")
                        .addHeader("DeviceType", DeviceUtils.getModel())
                        .addHeader("udid",DeviceUtils.getUniqueDeviceId())
                        .build();

                Map signParams = new HashMap<>(); // 假设你的项目需要对参数进行签名
                RequestBody originalRequestBody = request.body();
                if(originalRequestBody!=null){
                    if (originalRequestBody instanceof FormBody) { // 传统表单
                        FormBody requestBody = (FormBody) request.body();
                        int fieldSize = requestBody == null ? 0 : requestBody.size();
                        for (int i = 0; i < fieldSize; i++) {
                            signParams.put(requestBody.name(i), requestBody.value(i));
                        }

                        UserBean loginBean = App.getInstance().getUserBean();
                        if(loginBean!=null){
                            String sign = getSign(signParams, loginBean.getToken());
                            requestBuilder.addHeader("sign", sign);
                            requestBuilder.addHeader("uid",loginBean.getUid());
                            requestBuilder.addHeader("username",loginBean.getUsername());
                            requestBuilder.addHeader("token",loginBean.getToken());
                        }
                        request = requestBuilder.build();
                    }
                }


                return chain.proceed(request);
            }
        };
        return commonInterceptor;
    }


    //Sign加密
    public static String getSign(Map params, String phone) {
        List list = new ArrayList();

        for (Map.Entry entry : params.entrySet()) {
            if (entry.getValue() != null) {
                Object object = entry.getValue();
//                list.add(entry.getKey() + "=" + object + "&");
                if (object instanceof HashMap) {
                    String valueStr = JSON.toJSONString(object);
                    Log.i("object", valueStr);

                    list.add(entry.getKey() + "=" + valueStr + "&");
                } else if (object instanceof ArrayList) {
                    String valueStr = JSON.toJSONString(object);
                    Log.i("object", valueStr);

                    list.add(entry.getKey() + "=" + valueStr + "&");
                } else {
                    list.add(entry.getKey() + "=" + object + "&");
                }

            }
        }
        int size = list.size();
        String[] arrayToSort = list.toArray(new String[size]);
        Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < size; i++) {
            sb.append(arrayToSort[i]);
        }

        String result = sb.toString();
        result += "key=" + phone;
        result = EncryptUtils.encryptMD5ToString(result).toUpperCase();
        return result;
    }
}

NetWorkManager 针对网络框架进行了封装,便于快速使用网络请求。同时也做了相关生命周期的绑定工作,封装了手动解除网络请求的方法、showDialog 的相关封装以及跟UI 回调显示绑定工作。

public class NetWorkManager {
    private WeakReference lifecycle;
    private static HashMap disposableHashMap;
    private UIChangeLiveData uc;
    private static class NetWorkManagerHolder {
        private static final NetWorkManager INSTANCE = new NetWorkManager();
    }

    public static final NetWorkManager getInstance() {
        if(disposableHashMap==null){
            disposableHashMap=new HashMap<>();
        }
        return NetWorkManagerHolder.INSTANCE;
    }



    public  void execute(Observable observable,DisposableObserver subscriber) {
        observable.subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .compose(RxUtils.handleGlobalError(App.getContext()))
                .observeOn(AndroidSchedulers.mainThread())
                //绑定生命周期,生命周期结束的时候解除网络订阅
                .compose(RxUtils.bindToLifecycle(lifecycle.get()))
                .subscribe(subscriber);
    }

    /**
     * 手动添加 disposable 绑定tag
     * @param disposable
     * @param tag
     */
    private void addSubscribe(Disposable disposable,String tag) {
        CompositeDisposable mCompositeDisposable = getCompositeDisposable(tag);
        mCompositeDisposable.add(disposable);
    }


    private CompositeDisposable getCompositeDisposable(String tag){
        CompositeDisposable mCompositeDisposable = disposableHashMap.get(tag);
        if (mCompositeDisposable == null) {
            mCompositeDisposable = new CompositeDisposable();
            disposableHashMap.put(tag,mCompositeDisposable);
        }
        return mCompositeDisposable;
    }


    /**
     * 取消请求
     *
     * @param tag
     */
    public void reMoveRequest(Object tag) {
        if(disposableHashMap.get(tag)!=null){
            disposableHashMap.get(tag).clear();
        }
    }


    public final class UIChangeLiveData  {
        private SingleLiveEvent showDialogEvent;
        private SingleLiveEvent dismissDialogEvent;
        private SingleLiveEvent> startActivityEvent;
        private SingleLiveEvent> startContainerActivityEvent;
        private SingleLiveEvent finishEvent;
        private SingleLiveEvent onBackPressedEvent;

        public SingleLiveEvent getShowDialogEvent() {
            return showDialogEvent = createLiveData(showDialogEvent);
        }

        public SingleLiveEvent getDismissDialogEvent() {
            return dismissDialogEvent = createLiveData(dismissDialogEvent);
        }

        public SingleLiveEvent> getStartActivityEvent() {
            return startActivityEvent = createLiveData(startActivityEvent);
        }

        public SingleLiveEvent> getStartContainerActivityEvent() {
            return startContainerActivityEvent = createLiveData(startContainerActivityEvent);
        }

        public SingleLiveEvent getFinishEvent() {
            return finishEvent = createLiveData(finishEvent);
        }

        public SingleLiveEvent getOnBackPressedEvent() {
            return onBackPressedEvent = createLiveData(onBackPressedEvent);
        }

        private  SingleLiveEvent createLiveData(SingleLiveEvent liveData) {
            if (liveData == null) {
                liveData = new SingleLiveEvent<>();
            }
            return liveData;
        }
    }

    public UIChangeLiveData getUC() {
        if (uc == null) {
            uc = new UIChangeLiveData();
        }
        return uc;
    }


    public void showDialog() {
        showDialog("请稍后...");
    }

    public void showDialog(String title) {
        uc.showDialogEvent.postValue(title);
    }

    public void dismissDialog() {
        uc.dismissDialogEvent.call();
    }


    /**
     * 注入RxLifecycle生命周期
     *
     * @param lifecycle
     */
    public void injectLifecycleProvider(LifecycleProvider lifecycle) {
        this.lifecycle = new WeakReference<>(lifecycle);
    }

    public LifecycleProvider getLifecycleProvider() {
        return lifecycle.get();
    }

}

3、具体一个完整请求的实现

我们以一个登录页面为例,首先当然是xml 文件:



    
        
    
    
        

        

        

        

        


        
        

布局使用的是谷歌最新的布局方式约束布局,有的人可能觉得约束布局拖动起来布局比较爽,但是实际使用中针对布局做一些小小的改动时确发现有一堆属性,感觉比较麻烦,其实不然,只要熟悉其中的用法和原理,我们可以像线性布局和相对布局一样进行手写xml 进行布局。这里推荐两篇帖子,大家进行参考,一个是视频,【阿里P7进阶学习】使用 ConstraintLayout 构建自适应界面。一个是文档,约束布局ConstraintLayout看这一篇就够了。
总的来说布局比较简单一个登录按钮,两个输入框。输入框分别做了数据双向绑定。布局嵌套 layout 标签后,编译器会自动生成 相关的databinding 类, 例如 activity_login.xml,会自动生成 ActivityLoginBinding 这个类。
下面我们来聊聊具体的ViewModel、DataBinding、LiveData 是如何具体的完成这一整套数据绑定和显示的过程的
LoginActivity, 由于我们封装了基类,所以只要 继承 BaseActivity 就可以直接获取DataBinding 和 ViewModel 的具体对象。 DataBinding 具体是通过DataBindingUtil 这个官方的工具类来获取的,ViewModel 是通过ViewModelProvider 来创建的。这个在基类封装里面已经做了介绍。

public class LoginActivity extends BaseActivity {

    @Override
    public int initContentView(Bundle savedInstanceState) {

        return R.layout.activity_login;
    }

    @Override
    public int initVariableId() {
        return BR.vm;
    }



    @Override
    protected void initData() {
        binding.btLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                viewModel.requestLogin(getClass().getSimpleName());
            }
        });
    }

    @Override
    public void initViewObservable() {
        String account = SPUtils.getInstance().getString("account");
        String password = SPUtils.getInstance().getString("password");
        viewModel.userName.set(account);
        viewModel.userPassword.set(password);

        viewModel.getUserBeanObservable().observe(this, new Observer>() {
            @Override
            public void onChanged(Result userBeanResult) {
                if(userBeanResult.getReturnCode()==200){
                    ToastUtils.showLong("登录成功");
                    App.getInstance().saveUserBean(userBeanResult.data);
                    SPUtils.getInstance().put("account",viewModel.userName.get());
                    SPUtils.getInstance().put("password",viewModel.userPassword.get());
                    Intent intent = new Intent(LoginActivity.this,MainActivity.class);
                    startActivity(intent);
                    finish();
                }else {
                    ToastUtils.showLong("登录失败");
                }
            }
        });
    }
}

DataBinding 首先我们使用DataBinding 需要在 app gradle文件中添加

 buildFeatures {
    dataBinding = true
}

这里要注意一下,之前的写法已经过时,

dataBinding {
    enabled true
}

其次就是我们在xml 中使用 layout 标签来包裹 xml 布局,这样我们就可以直接在Activity 中直接使用控件Id来进行Ui 逻辑编写,这里可以回头去看看 LoginActivity 的代码。

 @Override
    protected void initData() {
        binding.btLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                viewModel.requestLogin(getClass().getSimpleName());
            }
        });
    }

ViewModel和LiveData
ViewModel 中 定义一个LiveData对象 ,用来包装需要获取的登录实体类信息UserBean。 然后调取数据仓库请求登录的方法,将 LiveData 对象扔过去,当接口请求成功的时候对LiveData 进行赋值,然后LoginActivity 感知了LiveData 的数据变化,就能获取到了从网络请求下来的登录信息,然后进行登录相关操作。
LoginViewModel 代码:

public class LoginViewModel extends BaseViewModel {
    //用户名的绑定
    public ObservableField userName = new ObservableField<>("");
    public ObservableField userPassword = new ObservableField<>("");
    private MutableLiveData> userBeanObservable;

    public LoginViewModel(@NonNull Application application) {
        super(application);
    }
    public MutableLiveData> getUserBeanObservable(){
        userBeanObservable = new MutableLiveData>();
        return userBeanObservable;
    }
    public void requestLogin(){
        Map param = new HashMap();
        param.put("username",userName.get());
        param.put("password",userPassword.get());
        DataRepository.getInstance().login(param,userBeanObservable);
    }
}

DataRepository 代码:

public class DataRepository implements ILocalRequest, IRemoteRequest {
    public ApiService apiService = RetrofitClient.getInstance().create(ApiService.class);

    private static final DataRepository S_REQUEST_MANAGER = new DataRepository();
    private MutableLiveData responseCodeLiveData;


    private DataRepository() {
    }

    public static DataRepository getInstance() {
        return S_REQUEST_MANAGER;
    }

    ............................


    /**
     * @param liveData
     */

    public void login(Map param,MutableLiveData> liveData) {
        NetWorkManager.getInstance().showDialog();
        NetWorkManager.getInstance().execute(apiService.loginApi(param), new DisposableObserver>() {
            @Override
            public void onNext(Result t) {
                NetWorkManager.getInstance().dismissDialog();
                liveData.postValue(t);
            }
            @Override
            public void onError(Throwable e) {
                NetWorkManager.getInstance().dismissDialog();
            }

            @Override
            public void onComplete() {
                NetWorkManager.getInstance().dismissDialog();
            }
        });
    }
    
    ...........................
}

具体流程如下:

MVVM 最完整架构解析及 Jetpack 架构组件的使用_第2张图片
至此采用MVVM 架构的一个完整请求过程已完成。

你可能感兴趣的:(Jetpack-MVVM,android)