网上有不少关于Android架构的讨论,如MVC
, MVP
,MVVM
。本质上都是一种代码架构思想,并没有明显的孰优孰劣,关键看应用的场景以及实现的细节。或许你跟我一样,写过很多代码,也重构过很多次。项目代码往往起初是混沌状态,再渐至清晰明朗,又随着业务发展及程序员修仙等级的良莠不齐,再次进入隐忍状态。周而复始,bug不断,但也其乐无穷。
本没有架构,说的人多就有了。还记得最初写Android代码的时候,应该没有明确的架构概念。Android的四大组件已经基本规范我们的代码结构。Activity
,Fragment
负责UI显示和交互。以此为入口,引入一系列的工具类(xxUtil,xxHelper),实体类(xxModel,xxBean,xxEntity)和一些第三方类库(Okhttp,EventBus,Glide,GreenDao..),再遵循基本的设计模式,一个项目雏形就出来了。再往后发展,定会发现UI层俨然成了一个大胖子,无从下手。不行,我们要重构,要手撕大胖子。于是乎,就开始不断地总结,抽象,分层级。期间估摸会遇到如下问题:
为了解决上面的问题,各种Mxx架构就被总结并应用上了。近来有涉猎RxJava和Android官方推出新框架Android Architecture Components,发现将两者有机地结合起来,可以产生新的开发姿势。
不明白Android Architecture Components和RxJava为何物的,最好先查阅相关资料,了解基本使用就行。来!上个谷歌官方架构图,本文将会围绕该图的实现进行探讨。
这是一张MVVM(Model-View-ViewModel)结构的架构图。简介一下各个模块:
View层 绿色框中的Activity/Fragment
,继承至LifecycleActivity\LifecycleFragment
,是UI控件的宿主。核心职责是:
View层不直接处理任何业务逻辑及数据加工。尽量做到瘦身,代码逻辑简约,减轻UI线程负担。
ViewModel层 蓝色框的ViewModel
。只做业务逻辑操作,不持有任何UI控件的引用。那数据的更新如何通知到View层,这就要仰仗LiveData
,具体使用后面会提及。
Model层 橘黄色框的Repository
及其下都是Model层。Model层就是数据层。数据来源有:
Repository
是数据仓库,整合各路来源的数据,再统一暴露给ViewModel层使用。官方新框架在这一层只提供给了一个新的SQLite数据库封装类库Room,这是可选的。换言之,Model层,官方只提供指导思想,并未有具体实现方案。那问题来了:
基本了解官方新架构组件后,再换个姿势看看各个层次的依赖。
等等,这依赖图跟平常看到的有点不一样啊。通常View与ViewMode是双向绑定的,即View的改动会反馈到ViewModel,反之亦然。官方提供的Data Binding类库,可实现该功能。但在看完使用说明后,蛋蛋有点隐隐作痛,有种当年做网站使用模板引擎的既视感。在xml布局文件添加过多逻辑,不知是一种退步还是进步。这里我们使用LiveData
类实现单向绑定,对使用者相对友好。强调一点:上层只依赖临近的下层,下层不可调用上层
但是…但是新姿势在哪里?莫急,各位看客请继续往下look。
这张图Model层的实现细化了许多。Model是个容易混淆的词,可等同与JavaBean,Entity等实体类,亦可指广义上的数据层,本文改用Module代指后者。不同的业务模块归类到不同的Module类,如用户模块UserModule
,直播模块LiveModule
。那Mudule该如何封装方法给ViewModel调用?。地球人都知道UI线程不可做耗时操作。这样Module提供的方法分两种:非耗时和耗时。
非耗时调用通常是直接内存操作的。如
public interface UserModule {
...
boolean isLogin();
...
}
内存维持一个变量记录着当前的登录状态,isLogin
直接将该变量值返回就行。
耗时调用通常是与IO操作或复杂计算相关。如
public interface UserModule {
...
LoginResult login(String username, String password);
...
}
登录操作通常需要服务端验证,获取用户信息,本地用户信息更新,发出登录状态变化通知等,显然不宜在UI线程执行,那只能交付子线程执行。
子线程干完活,需要告诉调用者:回调or事件通知?这里使用回调更适合,因为事件通知是一对多的关系,而回调是一对一。一对多就意味需要做很多附加操作去确保调用的上下文,避免请求与响应错乱。
另外,若页面准备退出,而此时子线程还在干活,则需要取消任务。综上所诉,关键代码大概是这样的:
// 用户模块相关方法定义
public interface UserModule {
...
// 获取登录状态,非耗时操作
boolean isLogin();
// 进行登录,耗时操作
ModuleCall login(String username, String password) ;
...
}
// Module方法调用返回值
public class ModuleCall<T> {
...
public void cancel(){
...
}
public void enqueue(ModuleCallback callback) {
...
}
...
}
// Module方法调用的回调接口
public interface ModuleCallback<T> {
void onModuleCallback(ModuleResult result) ;
}
// Module方法调用的结果封装
public class ModuleResult<T> {
public final Throwable error;
public final T data;
public ModuleResult(T data, Throwable error) {
this.data = data;
this.error = error;
}
}
这里采用了与OkHttp
、retrofit
类似的调用封装方式。Module方法调用后返回ModuleCall对象,再利用该对象进行取消操作和异步调用(没有同步调用)。应该有看客注意到UserModule
是个接口,那该如何使用呢?
下面是UserModule
更详细的代码
// UserModule的接口定义
@ProxyTarget(UserModuleImpl.class)
public interface UserModule extends BaseModule {
boolean isLogin();
ModuleCall login(String username, String password) ;
}
// UserModule相应的实现类,注意,不是实现UserModule接口
public class UserModuleImpl extends BaseModuleImpl {
public boolean isLogin() {
return true;
}
public Observable login(String username, String password) {
// 模拟实现
LoginResult result = new LoginResult();
return Observable.just(result);
}
public String getUserName() {
return "fusang";
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ProxyTarget {
Class extends BaseModuleImpl> value();
}
public class BaseModuleImpl {
// 获取其他模块实现类
protected T getModule(Class moduleClass) {
...
}
}
public interface BaseModule {
}
// Module管理类
class ModuleManager {
// 获取指定module
public static T get(Class moduleClass) {
...
}
// 获取指定的module实现类
static T getImpl(Class moduleClass) {
...
}
}
代码贴得略多了些,容我徐徐道来。首先,是否有点诧异UserModuleImpl
居然没有实现接口UserModule
。注意login
方法的返回值类型,前者返回Observable
,而后者返回ModuleCall
,但是方法签名是一样的。而isLogin
方法又完全一样。这里有一些约定策略:
一个Module接口就有相应的一个Module实现类,两者非实现关系
Module实现类,可通过其基类BaseModuleImpl
的getModule
方法获取其他Module实现类的实例,即Module实现类之间可相互调用
Mudule接口定义的方法返回值若是ModuleCall
,则其关联的实现类必须有相同签名,返回值类型是Observable
,Flowable
,Single
,Maybe
之一的方法
Mudule接口定义的方法返回值若非ModuleCall
,则其关联的实现类必须有相同签名和返回值类型的方法
Module接口暴露给ViewModel调用,它代理了相应的实现类的方法
它们都来至于鼎鼎大名的类库RxJava。RxJava非常强大,如同胶水,可方便的组织代码。上图中最底层的数据库、文件及网络操作也可采用RxJava来封装。这样各种业务逻辑都可方便串联起来,很带劲。Observable
等也具有”UI线程回调”和”任务取消“功能,那为什么不直接将它们返回给ViewModel层,而非要转为ModuleCall
?两个原因:
接口统一,便于调用。Observable
,Single
,Flowable
,Maybe
的回调接口都是不一样的
简化代码,看ModuleCall的实现可知,不用每次手动设置observeOn(AndroidSchedulers.mainThread())
(主线程回调)及处理冗长的回调。
public class ModuleCall<T> {
...
void setObservable(Object observable) {
mObservable = observable;
}
public void cancel() {
mCanceled = true;
if (mCancelHandle instanceof Disposable) {
((Disposable) mCancelHandle).dispose();
} else if (mCancelHandle instanceof Subscription) {
((Subscription) mCancelHandle).cancel();
}
}
public void enqueue(final ModuleCallback callback) {
synchronized (this) {
if (mExecuted) {
throw new IllegalStateException("每个ModuleCall只能enqueue一次");
}
mExecuted = true;
}
if (mCanceled || mDone) {
return;
}
mModuleCallback = callback;
if (mObservable instanceof Observable) {
subscribeObservable((Observable) mObservable);
} else if (mObservable instanceof Single) {
subscribeSingle((Single) mObservable);
} else if (mObservable instanceof Flowable) {
subscribeFlowable((Flowable) mObservable);
} else {
subscribeMaybe((Maybe) mObservable);
}
}
private void subscribeObservable(Observable observable) {
observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer() {
@Override
public void onSubscribe(@NonNull Disposable d) {
mCancelHandle = d;
}
@Override
public void onNext(@NonNull T t) {
ModuleResult result = new ModuleResult<>(t, null);
doCallback(result);
}
@Override
public void onError(@NonNull Throwable e) {
ModuleResult result = new ModuleResult<>(null, e);
doCallback(result);
mDone = true;
}
@Override
public void onComplete() {
mDone = true;
}
});
}
private void subscribeSingle(Single single) {
...
}
private void subscribeFlowable(Flowable flowable) {
...
}
private void subscribeMaybe(Maybe maybe) {
...
}
private void doCallback(ModuleResult result) {
...
}
}
通过注解ProxyTarget
将Module接口与其实现类关联起来
@ProxyTarget(UserModuleImpl.class)
public interface UserModule extends BaseModule {
boolean isLogin();
ModuleCall login(String username, String password) ;
}
UserModule
是接口,UserModelImpl
是其逻辑层面而非程序语言层面的实现,那ViewModel该如何调用呢?上代码:
class ModuleManager {
private static Map, BaseModule> mModuleCache = new ConcurrentHashMap<>();
private static Map, BaseModuleImpl> mModuleImplCache = new ConcurrentHashMap<>();
// 获取module接口的代理实例(单列)
public static T get(Class moduleClass) {
BaseModule module = mModuleCache.get(moduleClass);
if (module != null) {
return (T) module;
}
synchronized (mModuleCache) {
try {
module = mModuleCache.get(moduleClass);
if (module == null) {
module = create(moduleClass);
mModuleCache.put(moduleClass, module);
}
return (T) module;
} catch (Throwable t) {
throw new RuntimeException("获取module失败 " + moduleClass.getName() + " " + t);
}
}
}
// 获取module实现类的实例(单例)
static T getImpl(Class moduleClass) {
BaseModuleImpl module = mModuleImplCache.get(moduleClass);
if (module != null) {
return (T) module;
}
synchronized (mModuleImplCache) {
try {
module = mModuleImplCache.get(moduleClass);
if (module == null) {
module = moduleClass.newInstance();
mModuleImplCache.put(moduleClass, module);
}
return (T) module;
} catch (Throwable t) {
throw new RuntimeException("获取moduleImpl失败 " + moduleClass.getName() + " " + t);
}
}
}
// 生成Module接口的代理类
private static T create(Class moduleClass) throws Exception {
ProxyTarget proxyTarget = moduleClass.getAnnotation(ProxyTarget.class);
Class extends BaseModuleImpl> targetClass = proxyTarget.value();
Method[] methods = moduleClass.getDeclaredMethods();
Method[] targetMethods = targetClass.getDeclaredMethods();
HashMap methodMap = new HashMap<>();
// TODO 完善方法的映射
for (Method method : methods) {
for (Method targetMethod : targetMethods) {
if (methodEquals(method, targetMethod)) {
methodMap.put(method, targetMethod);
break;
}
}
}
Object targetObject = getImpl(targetClass);
return (T) Proxy.newProxyInstance(moduleClass.getClassLoader(), new Class[]{moduleClass}, new ModuleInvocationHandler(targetObject, methodMap));
}
// 代理处理方法
private static class ModuleInvocationHandler implements InvocationHandler {
private Object mTargetObject;
private Map mMethodMap;
public ModuleInvocationHandler(Object targetObject, Map methodMap) {
mTargetObject = targetObject;
mMethodMap = methodMap;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = mMethodMap.get(method).invoke(mTargetObject, args);
if (ModuleCall.class.equals(method.getReturnType())) {
ModuleCall call = new ModuleCall();
call.setObservable((Observable) res);
return call;
} else {
return res;
}
}
}
// 判断签名是否一样
private static boolean methodEquals(Method method1, Method method2) {
...
}
}
调用ModuleManager.get(UserModule.class)
就可以获取UserModule的实例,该实例是使用动态代理技术生成的。通过ProxyTarget
注解,建立Moduel接口和Module实现类的方法的映射。UserModule的实例根据这份映射表,将接口方法的调用转换为UserModuleImpl的实例的调用。
至此,已经基本理清Model层的内部逻辑以及ViewModel层调用Model层的方式。但是还有一个可大可小的问题?
下面是LoginViewModel
的基本实现,继承至官方新架构类库提供的ViewModel
。当LoginViewModel
所依附的Activity\Fragment
被销毁时会自动回调其onCleared
方法,并将相关的Module方法取消。老铁,没毛病。莫急,倘若有很多ModuleCall
,那就要手动维持这些引用,并都要在onCleared
进行取消操作。有时大意,忘了取消就可能产生内存泄漏。倘若能将关于一个ViewModel
关联的所有ModuleCall
自动收集起来,在回调方法onCleared
进行统一取消,那该是多么美好的事情。
public class LoginlViewModel extends ViewModel {
public final MutableLiveData> loginResult = new MutableLiveData<>();
private ModuleCall mLoginCall;
public void login(String username, String password) {
mLoginCall = ModuleManager.getModule(UserModule.class).login(username, password);
mLoginCall.enqueue(new ModuleCallback() {
@Override
public void onModuleCallback(ModuleResult result) {
loginResult.setValue(result);
}
});
}
public boolean isLogin() {
return getModule(UserModule.class).isLogin();
}
@Override
protected void onCleared() {
if (mLoginCall!=null) {
mLoginCall.cancel();
}
}
}
解决方案还是动态代理技术。对Module接口二次代理,即不直接使用ModuleManager.getModule
返回的实例,而是再利用Module接口,生成一个新的实例。对该实例的调用都将映射对应的ModuleManager.getModule
返回的实例的调用,并将返回的ModuleCall
收集起来,便于统一取消调用。抽象出两个基础类BaseViewModel
和BaseAndroidViewModel
public class BaseViewModel extends ViewModel {
private ModuleDelegate mDelegate = new ModuleDelegate();
protected T getModule(Class moduleClass) {
return mDelegate.getModule(moduleClass);
}
@Override
protected void onCleared() {
mDelegate.cancelAll();
super.onCleared();
}
}
public class BaseAndroidViewModel extends AndroidViewModel {
private ModuleDelegate mDelegate = new ModuleDelegate();
public BaseAndroidViewModel(Application application) {
super(application);
}
protected T getModule(Class moduleClass) {
return mDelegate.getModule(moduleClass);
}
@Override
protected void onCleared() {
mDelegate.cancelAll();
super.onCleared();
}
}
再看ModuleDelegate
的实现
public class ModuleDelegate {
private static Map, BaseModule> mModuleCache = new ConcurrentHashMap<>();
private ModuleCalls mModuleCalls = new ModuleCalls();
public T getModule(Class moduleClass) {
BaseModule module = mModuleCache.get(moduleClass);
if (module != null) {
return (T) module;
}
synchronized (mModuleCache) {
try {
module = mModuleCache.get(moduleClass);
if (module == null) {
InvocationHandler invocationHandler = new ModuleInvocationHandler(ModuleManager.get(moduleClass), mModuleCalls);
module = (T) Proxy.newProxyInstance(moduleClass.getClassLoader(), new Class[]{moduleClass}, invocationHandler);
mModuleCache.put(moduleClass, module);
}
return (T) module;
} catch (Throwable t) {
throw new RuntimeException("获取module代理 失败 " + moduleClass.getName() + " " + t);
}
}
}
public void cancelAll() {
mModuleCalls.cancel();
// 清理缓存的module
synchronized (mModuleCache) {
mModuleCache.clear();
}
}
private static class ModuleInvocationHandler implements InvocationHandler {
private Object mTarget;
private ModuleCalls mModuleCalls;
public ModuleInvocationHandler(Object target, ModuleCalls moduleCalls) {
mTarget = target;
mModuleCalls = moduleCalls;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = method.invoke(mTarget, args);
if (ModuleCall.class.equals(method.getReturnType())) { // 收集返回的moduleCall
mModuleCalls.add((ModuleCall) res);
}
return res;
}
}
private static class ModuleCalls {
private List mModuleCalls;
public void add(ModuleCall call) {
...
}
public void cancel() {
...
}
}
}
这种方案有个明显的缺点:一个ViewModel调用了多少个Module,就将产生多少个Module实例,虽然ViewModel销毁后,这些实例也随之可被回收,还是有些性能代价。现在LoginViewModel
可简化为:
public class LoginlViewModel extends BaseViewModel {
public final MutableLiveData> loginResult = new MutableLiveData<>();
public void login(String username, String password) {
ModuleCall loginCall = getModule(UserModule.class).login(username, password);
loginCall.enqueue(new ModuleCallback() {
@Override
public void onModuleCallback(ModuleResult result) {
loginResult.setValue(result);
}
});
}
public boolean isLogin() {
return getModule(UserModule.class).isLogin();
}
}
官方架构提供的LiveData
可实现数据驱动UI,代码如下:
public class LoginActivity extends LifecycleActivity implements View.OnClickListener {
private LoginViewModel mLoginViewModel;
private Button mLoginBtn;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.login_activity_layout);
// step0 获取相关的viewModel
mLoginViewModel = ViewModelProviders.of(this).get(LoginViewModel.class);
// step1 获取view
mLoginBtn = (Button) findViewById(R.id.loginBtn);
// step2 设置监听
mLoginBtn.setOnClickListener(this);
// step3 绑定数据
mLoginViewModel.loginResult.observe(this, mLoginObserver);
}
private Observer> mLoginObserver = new Observer>() {
@Override
public void onChanged(@Nullable ModuleResult result) {
Toast.makeText(getApplicationContext(), "data=" + result.data() + " e=" + result.throwable(), Toast.LENGTH_SHORT).show();
}
};
public void onClick(View view) {
switch (view.getId()) {
case R.id.loginBtn:
mLoginViewModel.login("fusang", "abc123456");
break;
}
}
}
通过官方框架提供的ViewModelProviders.of(this).get(LoginViewModel.class)
可获取到LoginViewModel
。显然LoginActivity
主要做两件事情:
LiveData
),数据改变时,进行UI更新操作结合强大的RxJava和官方新架构类库,再加上对各个层级调用的约束(规范),可快速搭建一个健壮的常规App问题。这只是一个App的概貌,还有很多问题待探索:
最后的最后,想看这个扩展类库源码的可以戳这里。官方新架构类库还是alpha版,这个新姿势还未经过严格测试及生产环境验证,慎用。