3月份快结束了,天气也渐渐变暖,希望2020年android行情也能够逐渐回暖吧,等待之余,我们也要不断的提升自己的技术,跟上技术更新的步伐。这不眼看着2020年Google IO大会即将发布新的功能,android10.0系统还没有玩熟,android11即将来临。是不是很可怕,是不是瞬间感觉android开发好痛苦,不要慌!!!饭要一口一口吃,路要一步一步走。是不是有些同学,对AAC(android架构组件)还没有真正的使用到项目中,是不是还在使用MVP设计模式开发。读过这篇文章,就会让你从此爱上MVVM模式。
前不久看到一篇文章,说到一个完整的app应该包括4个"现代化"
我认为这种说法是正确的,区分这些能够加快代码的开发,以及功能之间的解耦。
这里其实最重要的就是gradle的配置,自定义config.gradle。把通用的gradle配置和第三方依赖放进来:
ext {
//定以变量,决定当前环境是集成环境还是组件化环境
//集成环境:把所有的module作为library打包到app中,不可单独运行
//组件化环境:就是可以单独运行的module
isRelease = true
//配置defaultConfig下的信息
versionConfig = [
"compileSdkVersion": 29,
"buildToolsVersion": "29.0.2",
"minSdkVersion" : 21,
"targetSdkVersion" : 29,
"versionCode" : 1,
"versionName" : "1.0"
]
//配置appId 就是applicationId
appId = [
"app" : "com.xinyartech.mymvvmmaster",
"main": "com.xinyartech.main",
]
appcompatVersion = "1.1.0"
retrofitVersion = "2.6.2"
arouteVersion = "1.4.1"
glideVersion = "4.11.0"
//测试版本和发布版本url地址
url = [
"debug" : "http://www.baidu.com",
"release": "http://www.google.com"
]
//配置第三方依赖
dependencies = [
"appcompat" : "androidx.appcompat:appcompat:$rootProject.appcompatVersion",
//网络
"rxjava2" : 'io.reactivex.rxjava2:rxjava:2.2.12',
"rxandroid" : 'io.reactivex.rxjava2:rxandroid:2.1.1',
"retrofit2_adapter" : "com.squareup.retrofit2:adapter-rxjava2:$rootProject.retrofitVersion",
"okhttp_log_interceptor": 'com.squareup.okhttp3:logging-interceptor:3.9.0',
"retrofit2_convert" : "com.squareup.retrofit2:converter-gson:$rootProject.retrofitVersion",
"retrofit2" : "com.squareup.retrofit2:retrofit:$rootProject.retrofitVersion",
//解析
"gson" : 'com.google.code.gson:gson:2.8.6',
"commonstool" : 'org.apache.commons:commons-lang3:3.7',
//动画
"lottie" : 'com.airbnb.android:lottie:2.8.0',
//错误界面
"loadsir" : 'com.kingja.loadsir:loadsir:1.3.6',
//生命周期
"lifecycle_extensions" : 'android.arch.lifecycle:extensions:1.1.1',
"lifecycle_viewmodel" : 'androidx.lifecycle:lifecycle-viewmodel:2.2.0',
//ARouter
"aroute" : "com.alibaba:arouter-api:$rootProject.arouteVersion",
//glide
"glide" : "com.github.bumptech.glide:glide:$rootProject.glideVersion",
//recyclerView
"recyclerview" : "androidx.recyclerview:recyclerview:$rootProject.appcompatVersion"
]
apts = [
"aroute_compiler": 'com.alibaba:arouter-compiler:1.2.2',
"glide_compiler" : "com.github.bumptech.glide:compiler:$rootProject.glideVersion"
]
}
然后不要忘记在工程的build.gradle文件第一行添加一句
apply from: "config.gradle"
buildscript{
xxx
}
allprojects{
xxx
}
这样才能在各个模块下使用ext扩展名。如何使用呢? 比如我在主模块app下使用,在app的build.gradle中添加如下:
def versionConfig = rootProject.ext.versionConfig
xxx
android {
compileSdkVersion versionConfig.compileSdkVersion
xxx
}
这样就可以通过自定义变量进行赋值了。
首先看一下整个app模块的截图:
这一块也是我们研究的重中之重。
这里主要是对Okhhtp、rxjava以及retrofit进行了二次封装,方便调用。举个例子,调用一个网络请求接口:
NetworkApi
.getInstance()
.getService(IMainService.class)
.getNewsList("5572a108b3cdc86cf39001cd", "国内焦点", "1")
.compose(
NetworkApi.getInstance().applySchedulers(new BaseObserver<MainBean>(this,this))
);
入口类是 NetworkApi.java,伪代码如下:
public class NetworkApi{
getInstance(){
xxx
}
public <T> T getService(Class<T> service) {
return getInstance().getRetrofit(service).create(service);
}
}
getService返回的是一个接口。负责retrofit相关的网络请求路径及参数进行统一配置。这里重点关注applySchedulers 和 BaseObserver。
(1)applySchedulers
/**
* 统一处理错误 、线程切换
*/
public <T> ObservableTransformer<T, T> applySchedulers(final BaseObserver<T> observer) {
return new ObservableTransformer<T, T>() {
@Override
public ObservableSource<T> apply(Observable<T> upstream) {
Observable<T> observable = (Observable<T>) upstream
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(getAppErrorHandler())
.onErrorResumeNext(new HttpErrorHandler<T>());
observable.subscribe(observer);
return observable;
}
};
}
其实就是统一处理线程的切换,以及统一错误处理。最终异常的数据流向都会到达HttpErrorHandler中。
(2)HttpErrorHandler
/**
* 处理错误类型
* 1、http请求相关的错误,例如:404,403,socket timeout等等;
* 2、应用数据的错误会抛RuntimeException,最后也会走到这个函数来统一处理;
*/
public class HttpErrorHandler<T> implements Function<Throwable, Observable<T>> {
@Override
public Observable<T> apply(Throwable throwable) throws Exception {
return Observable.error(ExceptionHandler.handleException(throwable));
}
}
最终交于ExceptionHandler进行异常处理。
(3)BaseObserver
public class BaseObserver<T> extends DisposableObserver<T> {
//model基类
private BaseModel model;
//回调网络数据到model层
private IBaseModelListener<T> modelListener;
public BaseObserver(BaseModel model, IBaseModelListener<T> listener) {
this.model = model;
this.modelListener = listener;
model.addDisposable(this);
}
@Override
public void onNext(T t) {
Log.e("onNext>>>>>>>",t.toString());
this.modelListener.onSuccess(t);
}
@Override
public void onError(Throwable e) {
if(e instanceof ExceptionHandler.ResponseThrowable){
this.modelListener.onFail(e);
} else {
this.modelListener.onFail(new ExceptionHandler.ResponseThrowable(e, ExceptionHandler.ERROR.UNKNOWN));
}
}
@Override
public void onComplete() {
Log.e("onComplete>>>>>>>","onCompleteonCompleteonCompleteonCompleteonCompleteonCompleteonCompleteonComplete");
}
}
这个类主要负责数据的回调,通过IBaseModelListener接口回调给model层数据。另外通过
model.addDisposable(this);
这一行代码,把相关操作添加到CompositeDisposable管理者中,以便在合适的时机,将相关请求移除,防止内存泄漏。代码如下:
/**
* 订阅
*/
public void addDisposable(Disposable disposable) {
if (disposable == null) {
return;
}
if (compositeDisposable == null) {
compositeDisposable = new CompositeDisposable();
}
compositeDisposable.add(disposable);
}
这个模块可以说是核心模块了,所有的MVVM设计都是基于这个模块开展而来,先瞅一下包含了哪些功能:
(1)bindAdapter
不说了,主要是DataBinding自定义属性。注意RecyclerViewBindAdapter.java类。其实之前有篇文章已经对RecycleView实现DataBinding做了实现,recyclerView+dataBinding
(2)loadSir
主要是展示异常界面,比如界面没有数据,需要显示一个自定义空数据界面,loadSir可以帮你完成,使用起来很方便,强力推荐。
public void register(IBaseViewModelListener listener) {
if (listener == null) {
return;
}
...
WeakReference<IBaseViewModelListener> weakListener =
new WeakReference<IBaseViewModelListener>(listener, mReferenceQueue);
mWeakListenerArrayList.add(weakListener);
}
}
存的是IBaseViewModelListener ,用于数据回调给ViewModel层。回调如下:
/**
* 成功:有需要可以在此处保存到本地缓存
*
* @param data 数据
*/
protected void loadSuccess(D data) {
if (mWeakListenerArrayList == null || mReferenceQueue == null) {
return;
}
if (mWeakListenerArrayList.size() > 0) {
for (WeakReference<IBaseViewModelListener> reference :
mWeakListenerArrayList) {
IBaseViewModelListener listener = reference.get();
if (listener != null) {
listener.onLoadSuccess(this, data);
}
}
}
}
public abstract class BaseViewModel<M extends BaseModel, D> extends ViewModel implements LifecycleObserver, IBaseViewModelListener<D> {
protected M model;
//model层回复数据
public MutableLiveData<D> returnData = new MutableLiveData<>();
//界面状态数据,比如成功、正在加载等
public MutableLiveData<ViewStatus> viewData = new MutableLiveData<>();
//model层回复错误信息
public MutableLiveData<String> errorMsg = new MutableLiveData<>();
xxx
@Override
public void onLoadSuccess(BaseModel model, D data) {
returnData.setValue(data);
returnData.postValue(returnData.getValue());
viewData.setValue(ViewStatus.SHOW_CONTENT);
viewData.postValue(viewData.getValue());
}
@Override
public void onLoadFail(BaseModel model, String msg) {
viewData.setValue(ViewStatus.REFRESH_ERROR);
viewData.postValue(viewData.getValue());
errorMsg.setValue(msg);
}
xxx
}
public abstract class BaseActivity<V extends ViewDataBinding, VM extends BaseViewModel> extends AppCompatActivity implements Observer, IBaseView {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dataBinding = DataBindingUtil.setContentView(this, getLayoutId());
Log.d(getStringTag(), "Activity: " + this + " : " +
"onCreate");
//观察viewModel
viewModel = getViewModel();
//同步生命周期到ViewModel
getLifecycle().addObserver(viewModel);
viewModel.returnData.observe(this, this);
viewModel.errorMsg.observe(this,this);
viewModel.viewData.observe(this,this);
}
@Override
public void onChanged(Object obj) {
xxx
}
xxx
}
(6)recyclerView
public abstract class BaseRecyclerCustomView<VM extends BaseRecyclerViewModel, B extends ViewDataBinding> extends LinearLayout implements IBaseRecyclerCustomView<VM> {
xxx
private void init() {
LayoutInflater inflater =(LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (getLayoutId() != 0 && inflater != null) {
dataBinding = DataBindingUtil.inflate(inflater, getLayoutId(), this, false);
this.addView(dataBinding.getRoot());
}
}
@Override
public void setDataToView(VM data) {
viewModel = data;
//交给子类实现
setData(viewModel);
if (dataBinding != null) {
dataBinding.executePendingBindings();
}
}
}
public abstract class BaseRecyclerViewAdapter<VH extends BaseRecyclerViewHolder,
VM extends BaseRecyclerViewModel> extends RecyclerView.Adapter<VH> {
protected ObservableList<VM> mItems;
@NonNull
@Override
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return null;
}
@Override
public void onBindViewHolder(@NonNull VH holder, int position) {
holder.setData(mItems.get(position));
}
@Override
public int getItemCount() {
return mItems.size();
}
public void setItems(ObservableList<VM> items) {
mItems = items;
notifyDataSetChanged();
}
@Override
public int getItemViewType(int position) {
//符合开闭原则
return mItems.get(position).getItemType();
}
}
public class BaseRecyclerViewHolder<VM extends BaseRecyclerViewModel> extends RecyclerView.ViewHolder {
private IBaseRecyclerCustomView view;
public BaseRecyclerViewHolder(@NonNull IBaseRecyclerCustomView itemView) {
super((View) itemView);
this.view = itemView;
}
public void setData(VM data){
this.view.setDataToView(data);
}
}
public interface IBaseRecyclerCustomView<D extends BaseRecyclerViewModel> {
@LayoutRes
int getLayoutId();
void setDataToView(D data);
}
具体的两个方法交于子类实现。
public abstract class BaseRecyclerViewModel implements Serializable, IBaseRecyclerItemType {
public String tag;
@Override
public int getItemType() {
return Integer.MAX_VALUE;
}
}
具体itemType交于子类实现。
为什么要这样设计adapter,搞了那么多类出来???
我们普遍的做法是把ViewHolder都写在adapter类中,如果有多个itemType,就要写多个ViewHolder。这样就违背了SOLID原则中的单一职责原则。adapter的职责是适配功能,真正的刷新界面应该交给对应的viewHodlder去实现。那么究竟如何使用呢?在common模块中会体现。
公共库,所有人都可以在里面进行修改,增加功能。比如ARouter的路径配置
public class ARoutePath {
/**
* 主界面
*/
public static final class Main{
//路由组名
static final String GROUP_NAME = "main";
//主界面
public static final String PATH_MAIN = "/"+GROUP_NAME+"/MainActivity";
}
/**
* 登录界面
*/
public static final class Login{
//路由组名
static final String GROUP_NAME = "login";
//主界面
public static final String PATH_LOGIN = "/"+GROUP_NAME+"/LoginActivity";
}
}
这里重点说一下针对不同的itemType,是如何自定义viewHolder的?在BaseRecyclerViewHolder类中,需要将IBaseRecyclerCustomView类型作为参数,传递给BaseRecyclerViewHolder。那么IBaseRecyclerCustomView具体实现类是谁呢?这里就是AdapterImageTextView和AdapterTextView。
AdapterImageTextView代码:
public class AdapterImageTextView extends BaseRecyclerCustomView<AdapterImageTextViewModel, AdapterImageTextviewBinding> {
public AdapterImageTextView(Context context) {
super(context);
}
@Override
public int getLayoutId() {
return R.layout.adapter_image_textview;
}
@Override
public void setData(AdapterImageTextViewModel viewModel) {
getDataBinding().setVm(viewModel);
}
}
具体的数据定义交给AdapterImageTextViewModel。代码如下:
public class AdapterImageTextViewModel extends BaseRecyclerViewModel {
public String title;
public String imageUrl;
public String content;
public static final int type1 = 1;
@Override
public int getItemType() {
return type1;
}
}
通过AdapterImageTextView 的setData() 方法,将布局与viewModel进行绑定,达到数据的更新。
其实上面自定义AdapterImageTextView以及AdapterTextView已经实现了自定义view,也是属于控件化的。
使用ARouter实现。
可以参考支付宝、微信等优秀app。里面大量集成了插件,也可以参考文章架构师学习–插件化之Hook方式(5.0版本~9.0版本)
这里用Main模块做个演示。
<layout
xxx
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/adapter"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
rv:dataList="@{dataList}"
rv:adapter="@{adapter}" />
xxx
</layout>
@Route(path = ARoutePath.Main.PATH_MAIN)
public class MainActivity extends BaseActivity<ActivityMainBinding,MainViewModel> {
@Override
public int getLayoutId() {
return R.layout.activity_main;
}
@Override
protected MainViewModel getViewModel() {
ViewModelProvider viewModelProvider = new ViewModelProvider(this);
viewModel = viewModelProvider.get(MainViewModel.class);
dataBinding.setViewModel(viewModel);
//模拟数据
ObservableList<BaseRecyclerViewModel> nameClassObservableList = new ObservableArrayList<>();
...
dataBinding.setVariable(BR.adapter,new MyAdapter());
dataBinding.setVariable(BR.dataList,nameClassObservableList);
return viewModel;
}
}
实际的网络发起请求在MainViewModel的构造方法中。
public class MainViewModel extends BaseViewModel<MainModel, MainBean>{
public MainViewModel() {
super(MainModel.class);
model.loadData();
}
@Override
public void onLoadSuccess(BaseModel model, MainBean data) {
super.onLoadSuccess(model, data);
}
}
最终交给MainModel完成网络操作
public class MainModel extends BaseModel<MainBean> {
...
@Override
public void loadLocal() {
Observable<MainBean> ob =
NetworkApi.getInstance().getService(IMainService.class).getNewsList(
"5572a108b3cdc86cf39001cd", "国内焦点", "1")
.compose(NetworkApi.getInstance().applySchedulers(new BaseObserver<MainBean>(this
, this)));
}
...
@Override
public void onSuccess(MainBean data) {
loadSuccess(data);
}
@Override
public void onFail(Throwable e) {
loadFail(e.getMessage());
}
}
结果回调给onSuccess()方法。调用父类的loadSuccess()方法,最后回调给MainViewModel。
protected void loadSuccess(D data) {
...
IBaseViewModelListener listener = reference.get();
if (listener != null) {
listener.onLoadSuccess(this, data);
}
...
}
列表适配器
public class MyAdapter extends BaseRecyclerViewAdapter {
@NonNull
@Override
public BaseRecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == AdapterTextViewModel.type0) {
IBaseRecyclerCustomView adapterTextView = new AdapterTextView(parent.getContext());
return new BaseRecyclerViewHolder(adapterTextView);
} else if (viewType == AdapterImageTextViewModel.type1) {
IBaseRecyclerCustomView adapterImageTextView =
new AdapterImageTextView(parent.getContext());
return new BaseRecyclerViewHolder(adapterImageTextView);
}
return super.onCreateViewHolder(parent, viewType);
}
根据不同的itemType,传入不同的自定义view。具体操作交于自定义view实现。
这样的操作,是不是很简单,谁都会用!!!
mvvm+rxjava2+retrofit+ARouter