嘿!一段时间(收到很多的反馈意见)后,我认为是时候回到这个主题。这篇文章将给你另一种尝试,一种在我看来是设计现代移动应用架构的好方法(这里指的是Android 平台)。
在开始之前,假定你已经读过我的前面推送的文章Android设计架构 — 简洁之道 。如果没有读过,这是一个阅读的好机会,有助于更好地理解接下来的文章内容。
架构进化
进化(Evolution)代表一个渐进的过程,事物在这个过程演变成不同的形式,通常是更复杂或更好的形式。
如此说来,软件会随着时间改变,最终进化成为架构。其实一个好的软件设计必须保持其健壮性来帮助我们成长,拓展我们的解决方案,而不是重写代码(虽然有些情况下,重写代码是更好的方式,但是这应该是另一篇文章的主题。所以相信我,我们应该更多关注前面指出的问题)。
在这篇文章中,我将会带你检阅我认为必要和重要的关键点,以保持我们 Android 代码库的健壮性。记住这张图,我们开始吧。
响应式方法:RxJava
我不准备在此讨论 RxJava 的好处(我想你早已经尝试过它了, RxJava)。该技术已经有很多文章和大牛,他们做了出色的工作!而我将指出它在 Android 应用开发方面的有趣之处,以及它是怎样帮助我在简洁的架构上踏出第一步。
首先,我通过转化用例(use cases, 在简洁构架命名规则中称作“交互器”, interactors)选择了一个响应式模型返回 Obervables<T>。这意味着所有底层也将随着调用链返回 Obervables<T>。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public abstract class UseCase { private final ThreadExecutor threadExecutor; private final PostExecutionThread postExecutionThread; private Subscription subscription = Subscriptions.empty(); protected UseCase(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) { this.threadExecutor = threadExecutor; this.postExecutionThread = postExecutionThread; } protected abstract Observable buildUseCaseObservable(); public void execute(Subscriber UseCaseSubscriber) { this.subscription = this.buildUseCaseObservable() .subscribeOn(Schedulers.from(threadExecutor)) .observeOn(postExecutionThread.getScheduler()) .subscribe(UseCaseSubscriber); } public void unsubscribe() { if (!subscription.isUnsubscribed()) { subscription.unsubscribe(); } } } |
如你所见,所有的用例都继承自这个抽象类并且实现了抽象方法 buildUseCaseObservable() ,这个方法将设定一个 Observable<T> 用以处理复杂逻辑并返回所需的数据。
值得一提的是 execute() 方法,我们要确保 Observable<T> 在一个单独的线程里执行,因此极大程度上避免了我们 Android 主线程的阻塞。结果由 Android 主线程调度器发送给主线程。
至此,我们的 Observable<T> 已经启动并且运行了。但是如你所知,必须有人关注由此发送的数据序列。为了实现这个功能,我把展示器(presenters, 表示层 presentation Layer 的 MVP 模式中的一部分)发展成 订阅者(Subscribers)。它将“响应(react)”这些用例发出的信息,用以更新用户界面。
如下代码所示即为订阅者:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
private final class UserListSubscriber extends DefaultSubscriber<List<User>> { @Override public void onCompleted() { UserListPresenter.this.hideViewLoading(); } @Override public void onError(Throwable e) { UserListPresenter.this.hideViewLoading(); UserListPresenter.this.showErrorMessage(new DefaultErrorBundle((Exception) e)); UserListPresenter.this.showViewRetry(); } @Override public void onNext(List<User> users) { UserListPresenter.this.showUsersCollectionInView(users); } } |
每个订阅者是都是嵌套在各自的展示器里的内部类,并且实现了 DefaultSubscriber<T>,用于默认错误处理。
在所有的组件都就绪后,你可以通过下图来了解整个想法:
让我们列举一下基于 RxJava方法的一些好处:
- 低耦合的 观察者和 订阅者:提高了【可】维护性,并使测试更简单;
- 简化的异步任务(asynchronous tasks):在多于单个层面的异步执行是必须的情况下, Java 中的 Thread 和Future 就变得维护复杂、难以同步。通过调度程序我们可以轻松地在后台和主线程之间跳转(不需要额外的功夫),在需要更新 UI 的时候更是格外简单。同时也避免了我们说的“回调地狱(Callback Hell)”,它将使我们的代码无法阅读并且难以跟进。
- 数据转化与组成:在不影响客户端的情况下可以组合多个 Observables<T>,这让我们的方案更具有扩展性。
- 错误处理:当任何 Observable<T> (的实现类)出现错误时,都有一个信号发送给消费者。
我的观点里有一个缺陷,事实上这个方案需要付出的代价——那就是不熟悉这个概念的开发者必须要付出努力完成学习曲线。然而,你将从中获得无价的东西。响应式方法必胜!(译者注:学习曲线 Learning curve,表示了经验与效率之间的关系)
依赖(dependency)注入:Dagger 2
我不准备过多讨论依赖注入的问题,因为我已经写了一篇完整的文章了。我非常推荐你读这篇文章,这样你就能跟上这里讨论的内容。
值得一提的是,通过实现一个像 Dagger 2 这样的依赖注入框架,我们可以得到:
- 组件复用, 因为依赖注入和配置独立于组件之外。
- 得益于对象的初始化驻留在一个孤立且耦合性低的位置。当注入抽象方法的时候,我们只需要修改对象的实现方法,而不用大改代码库。
- 依赖可以注入到一个组件中:完全可以注入这些依赖的模拟实现,这使测试更加简单了。
Lambda 表达式:Retrolambda
没有人会抱怨利用 Java 8 的 Lambad 表达式,尤其当他们简化了代码并且摆脱了很多模式的时候。可以看看以下代码:
1 2 3 4 5 6 |
private final Action1<UserEntity> saveToCacheAction = userEntity -> { if (userEntity != null) { CloudUserDataStore.this.userCache.put(userEntity); } }; |
然而,我却对此有复杂的感情,我会对此进行解释。原因是我和 @SoundCloud 有过一个关于Retrolambda讨论,主要围绕是否使用Retrolambda,结果如下:
- 优点:
- Lambda 表达式和方法的引用;
- Try with语句可以直接在资源上使用;
- 开发的业报(Dev karma)。
- 缺点:
- Java 8 API使用的频率很低;
- 第三方库非常具有侵入性;.
- 依赖第三方 Gradle 插件使之工作在 Android 上。 。