澳门新葡萄京官网注册 21

澳门新葡萄京官网注册Android 架构演化之路

嘿!经过一段时间收集了大量反馈意见后,我认为应该来说说这个话题了。我会在这里给出我认为构建现代移动应用(Android)的好方法,这会是另一番体味。

Architecting Android…The evolution
澳门新葡萄京官网注册,更多 Android 博文请关注我的博客
http://xuyushi.github.io
Architecting Android…The evolution

大家好! 过了一好阵子了(在此期间我收到了大量的读者反馈)
我决定是时候回到手机程序架构这个话题上了(这里用android代码举例),
给大家另一个我认为好的解决方案.

开始之前,假设你已经阅读过我之前撰写的文章“ Architecting Android…The
clean
way
?”。如果还没有阅读过,为了更好地理解这篇文章,应借此机会读一读:

本文为翻译,在原文的基础上略有改动

在开始之前,
我这里假设大家都读过了我之前用简洁的办法架构Android程序一文.
如果你还没有读过, 现在应该去读一下那篇文章,
读过之后可以更好的理解我下面要讲的内容.

澳门新葡萄京官网注册 1

http://fernandocejas.com/2015/07/18/architecting-android-the-evolution/

澳门新葡萄京官网注册 2

架构演变

演变意味着一个循序渐进的过程,由某些状态改变到另一种不同的状态,且新状态通常更好或更复杂。

照这么一说,软件是随着时间发展和改变的,是架构上的发展和改变。实际上,好的软件设计必须能够帮助我们发展和扩充解决方案,保持其健壮性,而不
必每件事都重写代码(虽然在某些情况下重写的方法更好,但是那是另一篇文章的话题,所以相信我,让我们聚焦在前面所讨论的话题上)。

在这篇文章中,我将讲解我认为是必需的和重要的要点,为了保持基本代码条理清晰,要记住下面这张图片,我们开始吧!

澳门新葡萄京官网注册 3

在开始之前,你最好阅读过这篇文章 (
http://xuyushi.github.io/2016/07/19/Android%20clean%20architecting/

架构的演化

演化是指一个事物变化成为另一个不同的事物的一个平缓过程,
通常情况下会变得更加复杂或者变成更好.

软件开发一直在进化和改变. 实际上, 一个好的代码结构必须帮助我们成长,
这意味着不用重新写所有代码就可以扩展功能.

(尽管有些情况下应该大量的重写代码, 但那又是另一回事了, 这里先不做探讨).

这篇文章的重点是如何保持android代码的清晰直观, 为了阐述这一问题,
我将会带着大家看几个我认为重要的关键点. 记住下面这个图我们就可以开始了.

澳门新葡萄京官网注册 4

响应式方法:RxJava

因为已经
有很多这方面的文章
,还有这方面 做得很好、令人景仰的人
,所以我不打算在这里讨论RxJava的好处( 我假设您已经对它有所体验了
)。但是,我将指出在Android应用程序开发方面的有趣之处,以及如何帮助我形成第一个清晰的架构的方法。

首先,我选择了一种响应式的模式通过转换usecase(在这个清晰的架构命名规则中,其被称为interactor)返回Observables<T>,表示所有底层都遵循这一链条,也返回Observables<T>

澳门新葡萄京官网注册 5

正如你所看到的,所有用例继承这个抽象类,并实现抽象方法buildUseCaseObservable()。该方法将建立一个Observables<T>,它承担了繁重的工作,还要返回所需的数据。

需要强调是,在execute()方法中,要确保Observables<T>
是在独立线程执行,因此,要尽可能减轻阻止android主线程的程度。其结果就是会通过android主线程调度程序将主线程压入线程队列的尾部(push
back)。

到目前为止,我们的Observables<T>启动并且运行了。但是,正如你所知,必须要观察它所发出的数据序列。要做到这一点,
我改进了presenters(MVP模式表现层的一部分),把它变成了观察者(Subscribers),它通过用例对发出的项目做出“react”,
以便更新用户界面。

观察者是这样的:

澳门新葡萄京官网注册 6

每个观察者都是每个presenter的内部类,并实现了一个Defaultsubscriber<T>接口,创建了基本的默认错误处理。

将所有的片段放在一起后,通过下面的图,你可以获得完整的概念:

澳门新葡萄京官网注册 7

让我们列举一些摆脱基于RxJava方法的好处:

在观察者(Subscribers)与被观察者(Observables)之间去耦合:更加易于维护和测试。

  • 简化异步任务:如果要求多个异步执行时,如果需要一个以上异步执行的级别,Java的thread和future的操作和同步比较复杂,因此
    通过使用调度程序,我们可以很方便地(不需要额外工作)在后台与主线程之间跳转,特别是当我们需要更新UI时。还可以避免“回调的坑”——
    它使我们代码可读性差,且难以跟进。
  • 数据转换/组成:在不影响客户端情况下,我们能够整合多个Observables<T>,使解决方案更灵活。
  • 错误处理:在任何Observables<T>内发生错误时,就向消费者发出信号。

从我的角度看有一点不足,甚至要为此需要付出代价,那些还不熟悉概念的开发人员还是要遵循学习曲线。但你从中得到了极有价值的东西。为了成功而reactive起来吧!

澳门新葡萄京官网注册 8

反应式方法: RxJava

在这里我就不讲RxJava的好处了(我猜大家都已经自己体会过了) ,
因为已经有很多文章和坏蛋们都讲过了,
而且讲的还都不错.
这里我要讲的是它是怎么使得android开发变得非常有趣的,
还有它是如何帮助我完成搭建第一个干净简洁的架构的.

首先, 我选择一个反应式模式让用例(在简洁的架构命名规范中叫做interactor)
都返回Observables

      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

需要注意的是execute()这个方法,
我们保证了Observable让自己执行在一个单独的线程中
,
这样的话就可以最小限度的减少在主线程耗时.
然后通过主线程的scheduler机制把Observable的执行结果返回给主线程.

到现在为止, 我们已经有了Observable , 但是它产生的数据得有人来处理
. 所以我这里将presenter(MVP
三层架构中的presentation层的一部分)改为了 Subscribers
,当用例产生数据后可以及时更新UI.

也就是下面这样的subscriber:

       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只是简单实现了对错误的处理,
每一个subscriber都是presenter中的一个继承自 DefaultSubscriber
的内部类.

从下面这张图中, 你可以得到一个比较完整的思路.

澳门新葡萄京官网注册 9

我们来总结一下RxJava带给我们的好处:

  • 实现了Observables 和 Subscribers的解耦:
    保持了结构稳定并简化了测试.
  • 使得异步任务变得简单: 多层异步任务被执行时,
    java的thread和future的操作和同步会变得非常复杂, 使用 scheduler
    可以让我们在异步线程和主线程之间跳转变得非常简单(省去了多余的步骤),
    特别是我们需要更新UI界面的时候.
    同时也避免了使代码变成非常难以理解的”回调地狱”.
  • 数据的传递/组合:
    我们可以使多个Observables组合起来而不影响到client端,
    这样提高了整套解决方案的可扩展性.
  • 异常处理: 任何一个Observable.出现异常都会通知到consumer.

从我的角度看这里有一个小问题, 也是必须要付出的代价,
就是对这一概念不太熟悉的开发者的学习过程.
但是你会从中学到非常有价值的内容. 为了成功学习Reactive吧!

依赖注入:Dagger 2

关于依赖注入,
因为我已经写了一篇完整的文章
,我不想说太多。强烈建议你阅读它,这样我们就可以接着说下面的内容了。

值得一提的是,通过实现一个像Dagger 2那样的依赖注入框架我们能够获得:

  • 组件重用,因为依赖的对象可以在外部注入和配置。
  • 当注入对象作为协作者(collaborators)时,由于对象的实例存在于在一个隔离和解耦地方,这样在我们的代码库中,就不需要做很多的改变,就可以改变任何对象的实现。
  • 依赖可以注入到一个组件:这些将这些模拟实现的依赖对象注入成为可能,这使得测试更容易。

Architecture evolution

进化是一个循序渐进的过程,其中的一部分变的不同通常是变得更复杂更好

可以这么说,软件的发展很大程度上是软件architecture的发展。事实上,一个好的软件设计能帮助我们成长,同时能在不需要重写的情况下,帮助我们更好的扩展

在这篇文章中,我将指出我认为重要点和关键点,让你对 Android
的代码结构更清晰,请记住这张图片,let’s stared.

澳门新葡萄京官网注册 10

依赖注入: Dagger 2

我这里不会讲太多关于依赖注入的例子,
因为我之前写过一篇专门说依赖注入的文章,
为了跟上我这里的脚步, 强烈推荐大家读一读这篇文章.

值得强调的是, 像Dagger 2一样的依赖注入框架可以带给我们:

  • 组件的重用, 因为依赖可以被从外部配置和注入.
  • 最为合作者对抽象进行依赖注入时, 我们可以单单修改任何对象的实现,
    而不用大量修改底层代码,
    因为类的实现对象独立而解耦的存在于另一个地方.
  • 依赖可以被注入到组件中去: 注入依赖的测试实现是有可能的,
    这就使得测试变得更加容易.

Lambda表达式:Retrolambda

没有人会抱怨在代码中使用Java
8的lambada表达式,甚至在简化并摆脱了很多样板代码以后,使用得更多,如你看到这段代码:

澳门新葡萄京官网注册 11

然而,我百感交集,为什么呢?我们曾在
@SoundCloud 讨论
Retrolambada
,主要是是否使用它,结果是:

  1. 赞成的理由:
  • Lambda表达式和方法引用
  • “try-with-resources”语句
  • 使用karma做开发
  1. 反对的理由:
  • Java 8 API的意外使用
  • 十分令人反感的第三方库
  • 要与Android一起使用的第三方插件Gradle

最后,我们认定它不能为我们解决任何问题:你的代码看起来很好且具有可读性,但这不是我们与之共存的东西,由于现在所有功能最强大的IDE都包含代码折叠式选项,这就涵盖这一需求了,至少是一个可接受的方式。

说实话,尽管我可能会在业余时间的项目中使用它,但在这里使用它的主要原因是尝试和体验Android中Lambda表达式。是否使用它由你自己决定。在这里我只是展示我的视野。当然,对于这样一个了不起的工作,这个
库 的作者值得我的称赞。

Reactive approach: RxJava

这里我不再过多说 Rxjava
的好处(你可以看看这个
),现在有很多关于 rxjava
的教程
,我会指出它在 android 开发中的亮点,已经它是如何帮助我进化
clean架构的。

首先,我选择了响应式编程(也就是 rxjva,译者注)来使得use
cases(也被称为 interactors)返回
Observables<T>,也就是说所有的层都会成一条链式 返回
Observables<T>

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();
    }
  }
}

可以看到,所有的 cases
都继承自这个抽象类,并且实现了其中的抽象方法buildUseCaseObservable(),这个方法干着实际的业务活最后返回
Observable<T>

有一点需要强调一下,我们需要确保 execute()方法工作在独立的线程中。
这样能最小程度的阻塞 Android 的 主线程,

到目前为止,我们的 Observable<T>
已经启动并运行,必须有人来观测它发射的数据
,为了达成这个目标,我进化了
presenters(MVP
的一部分),使得其包含了Subscribers,使其来响应Observable 发射的数据

subscriber 长这样

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);
  }
}

每个subscriber都是presenter中的一个内部类,并且继承自DefaultSubscriber<T>。DefaultSubscriber<T>包含了一些默认的错误处理

当了解所有的细节之后,可以通过下面这张图了解整体的思路:

澳门新葡萄京官网注册 12

让我们来列举一下通过 Rxjava 我们能获得的好处

  • ObservablesSubscribers解耦。使得维护和测试更方便
  • 简化了异步的任务。java切换线程的操作还是比较复杂的。而且在
    Android
    中,我们需要在非主线程处理事务,在主线程更新UI,很容易出门”callbackhell”,使得代码难以阅读
  • 数据的传输和组织。我们在不影响客户端的情况下,可以轻易合并多个Observables<T>。这使得我们的解决方案十分灵活
  • 错误处理。所有的错误都可以在 subscribe 中处理。

在我看来,还是有缺点的。就是对于不了解这些概念的开发者需要一定的学习曲线。但是这是非常值得的。

Lambda 表达式: Retrolambda

没有人会反对在我们的代码中使用Java 8 的 Lambdas,
使用Lambdas可以省去大量的样板代码, 就像下面的代码块:

    private final Action1<UserEntity> saveToCacheAction =
            userEntity -> {
                 if (userEntity != null) {
                     CloudUserDataStore.this.userCache.put(userEntity);
                 }
           };

但是这个问题我非常纠结.
在我们@SoundCloud,
曾经有过一次关于Retrolambda的讨论,
主要的分歧是要不要用它 讨论的结果是:

利:

  • Lambda 和方法的引用
  • 尝试着用资源的方式.
  • Dev karma

弊:

  • java 8新特性的意外使用.
  • 第三方jar包很扰人.
  • 要在Android工程中使用它,必须引入第三方gradle插件.

最终我们决定Retrolambda并不是一个能解决我们任何问题的库:
使用了Retrolambda后代码的确好看易理解, 但这对我们来说并不是必须的,
因为现在大部分的IDE已经可以是实现这一功能, 至少是以可以接受的方式

老实说, 我在这里提到Retrolambda的主要原因是想用一用它,
体验一把在Android代码里使用lambda是什么感觉.
也许在我的业余项目中可能会用到这个库. 我只是把我的想法放在这里,
用不用它的最终决定权在大家手里. 当然了,
该作者创造了这么伟大的一个库也非常值得赞扬

测试方法

在测试方面,与示例的第一个版本相关的部分变化不大:

  • 表现层:用Espresso 2和Android Instrumentation测试框架测试UI。
  • 领域层:JUnit + Mockito —— 它是Java的标准模块。
  • 数据层:将测试组合换成了Robolectric 3 + JUnit +
    Mockito。这一层的测试曾经存在于单独的Android模块。由于当时(当前示例程序的第一个版本)没有内置单元测试的支持,也没有建立像
    robolectric那样的框架,该框架比较复杂,需要一群黑客的帮忙才能让其正常工作。

幸运的是,这都是过去的一部分,而现在所有都是即刻可用,这样我可以把它们重新放到数据模块内,专门为其默认的测试路径:src/test/java。

Dependency Injection: Dagger 2

我不会过多的介绍依赖注入,因为在这篇文章已经介绍过了(
http://xuyushi.github.io/2016/07/16/Android%20Application%20从零开始%202%20——DI/
),这篇文章我强烈建议你读一读。我一下就简单的介绍一下

值得一提的是,通过引入像 dagger2 这样的依赖注入工具我们可以:

  • 代码的复用,因为依赖关系可以注入和外部配置。
  • 当注入对象时,我们可以改变对象的实现即可,不需要再调用的代码处做修改。因为对象的构造和使用已经分离解耦
  • 依赖可注入一个组件,它可能注入这些依赖它使测试更容易的mock实现

测试途径

说到测试, 和之前的例子并没有什么太大的不同.

  • Presentation层 : 用Espresso 2 和 Android Instrumentation
    测试UI界面.
  • Domain 层: 因为只是正常的java模块,所以用JUnit +
    Mockito测试就好了.
  • Data层: 用 Robolectric 3 + JUnit + Mockito做迁移测试.
    因为以前(案例第一个版本的时候)没有内置单元测试支持,手动构造一个类似robolectric的框架非常复杂而且为了使它正常工作,还要一系列hack操作.

庆幸的是那都过去了, 现在所有的东西都可以直接用,
所以我重新把它们放在了数据模块中, 特别是可以放在默认的测试文件夹
src/test/java 下.

包的组织

我认为一个好的架构关键因素之一是代码/包的组织:程序员浏览源代码遇到的第一件事情就是包结构。一切从它流出,一切依赖于它。

我们能够辨别出将应用程序封装进入包(package)的2个路径:

  • 按层分包:每一个包(package)中包含的项通常不是彼此密切相关的。这样包的内聚性低、模块化程度低,包之间偶合度高。因此,编辑某个特性要编辑来自不同包的文件。另外,单次操作几乎不可能删除掉某个功能特性。
  • 按特性分包:用包来体现特性集。把所有相关某一特性(且仅特性相关)的项放入一个包中。这样包的内聚性高,模块化程度高,包之间偶合度低。紧密相关的项放在一起。它们没有分散到整个应用程序中。

我的建议是去掉按特性分包,会带来的好处有以下主要几点:

  • 模块化程度更高
  • 代码导航更容易
  • 功能特性的作用域范围最小化了

如果与功能特性团队一起工作(就像我们在@SoundCloud的所作所为),也会是非常有趣的事情。代码的所有权会更容易组织,也更容易被模块化。在许多开发人员共用一个代码库的成长型组织当中,这是一种成功。

澳门新葡萄京官网注册 13

如你所见,我的方法看起来就像按层分包:这里我可能会犯错(例如,在“users”下组织一切),但在这种情况下我会原谅自己,因为这是个以学习为目的的例子,而且我想显示的是清晰架构方法的主要概念。领会其意,切勿盲目模仿:-)。

Lambda expressions: Retrolambda

在我们的代码中使用 java8 的 lambdas
,相信没人会抱怨。他简化了我们的代码,比如

private final Action1<UserEntity> saveToCacheAction =
    userEntity -> {
      if (userEntity != null) {
        CloudUserDataStore.this.userCache.put(userEntity);
      }
    };

不过,我在这里百感交集,我来解释下原因。事实证明,在@SoundCloud我们有大约Retrolambda的讨论,主要是是否要使用它,结果是:

  1. 优点:
    • lambda表达式和方法的引用。
    • Try with resources.
    • Dev karma.(???)
  2. 缺点:
    • Accidental use of Java 8 APIs.
    • 3rd part lib, quite intrusive.
    • 3rd part gradle plugin to make it work with Android.

最后的决定取决于是否能为我们解决问题:你的代码看起来更好,更具可读性,但这些都是可有可无的。因为现在大部分的牛逼的
IDEs 都包含了这些功能。自动了折叠了这些代码

包结构组织

我认为代码/包的组织是一个良好架构的关键要素之一: 包结构是一个程序员看项目代码时最先注意到的
其他的一切要素都由它而来,也都取决于它.

下面是组织包结构常见的两种方式:

  • 根据层级关系的不同:
    单独看每个包下面的代码通常情况下并没有什么联系,
    这就降低了单个包里的内聚性和模块性,而提高了包与包之间的耦合程度.
    修改一个功能需要同时修改多个包下的多个文件.而且,
    要删除一个功能也变得不是那么简单.
  • 根据功能的不同: 根据不同的包名可以找到对应的功能,
    将功能(而且是只有此功能)下的所有组件全都放在了一起.
    这就提高了包里的内举性和模块性,而降低了包与包之间的耦合程度.
    将协同工作的代码放在了一起,而不是将它们分布在程序的各个地方.

我的建议是根据功能的不同来组织包结构, 可以带来下面这些好处:

  • 更完善的模块化
  • 代码更加容易查阅
  • 最小化代码的作用域

有趣的是, 如果你在一个所谓的 功能性团队
工作,(比如@SoundCloud),
代码结构的分配会变得更加容易更加模块化,
如果许多工程师在同样的基础代码上开发时这个优点就变得格外明显.

澳门新葡萄京官网注册 14

大家可以看出, 我的包结构看起来像是根据层级关系组织的:
这里可能有举例不太恰当的地方(比如将一切都放在’user’下面)
但是我会原谅自己这一次,
因为举这个例子是为了供大家学习,为了表达我的观点,主要目的是包含简洁的架构思路.
照我说的做, 而不是照我做的做

还需要做的事:组织构建逻辑

我们都知道,房子是从地基上建立起来的。软件开发也是这样,我想说的是,从我的角度来看,构建系统(及其组织)是软件架构的重要部分。

在Android平台上,我们采用Gradle,它事实上是一种与平台无关的构建系统,功能非常强大。这里的想法是通过一些提示和技巧,让你组织构建应用程序时能够得到简化。

  • 在单独的gradle构建文件中按功能对内容进行分组

澳门新葡萄京官网注册 15

澳门新葡萄京官网注册 16

因此,你可以用“apply from:
‘buildsystem/ci.gradle’”插入到任何Gradle建立的文件中进行配置。不要把所有都放置在一个build.gradle文件中,否则就是去创建一个怪物,这是教训。

  • 创建依赖关系图

澳门新葡萄京官网注册 17

澳门新葡萄京官网注册 18

如果想在项目的不同模块间重用相同的组件版本,这很好;否则就要在不同的模块间使用不同的版本的组件依赖。另外一点,你是在同一个地方控制依赖关系,像组件版本发生冲突这样的事情一眼就能看出来。

Testing approach

在测试中,在这个例子的第一个版本没有关系大变动:

  • Presentation Layer: 使用 android instrumentatioespresso
    做集成 和功能测试
  • Domain Layer: 使用JUnitmockito做单元测试
  • Data Layer: 使用Robolectric(因为这层有 Android 的依赖)和
    junit、mockito做集成和单元测试。

附加彩蛋: 组织你的打包逻辑

我们都知道房子都是从地基开始修筑的. 软件开发也是一个道理,
我这里要强调的是, 代码架构中,
打包系统(及其组织架构)是非常重要的一部分

在Android开发中, 我们使用一个叫做gradle的非常强大的打包系统. 这里有
一系列窍门 帮助大家在组织打包脚本时变得格外轻松:

根据功能的不同, 将打包系统分成多个脚本文件.

澳门新葡萄京官网注册 19

ci.gradle:

     def ciServer = 'TRAVIS'
        def executingOnCI = "true".equals(System.getenv(ciServer))

        // Since for CI we always do full clean builds, we don't want to pre-dex
        // See http://tools.android.com/tech-docs/new-build-system/tips
        subprojects {
          project.plugins.whenPluginAdded { plugin ->
            if ('com.android.build.gradle.AppPlugin'.equals(plugin.class.name) ||
                'com.android.build.gradle.LibraryPlugin'.equals(plugin.class.name)) {
              project.android.dexOptions.preDexLibraries = !executingOnCI
            }
          }
        }

build.gradle:

      apply from: 'buildsystem/ci.gradle'
        apply from: 'buildsystem/dependencies.gradle'

        buildscript {
          repositories {
            jcenter()
          }
          dependencies {
            classpath 'com.android.tools.build:gradle:1.2.3'
            classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
          }
        }

        allprojects {
          ext {
            ...
          }
        }
        ...

如上图, 你可以利用 “apply from: ‘buildsystem/ci.gradle’
将配置文件导入任意build脚本中.
不要将所有打包脚本写在一个build.gradle文件中,
否则你会慢慢制造出一个怪物. 我已经受过教训了
.

将依赖整合到map中

dependencies.gradle:

       ...

        ext {
          //Libraries
          daggerVersion = '2.0'
          butterKnifeVersion = '7.0.1'
          recyclerViewVersion = '21.0.3'
          rxJavaVersion = '1.0.12'

          //Testing
          robolectricVersion = '3.0'
          jUnitVersion = '4.12'
          assertJVersion = '1.7.1'
          mockitoVersion = '1.9.5'
          dexmakerVersion = '1.0'
          espressoVersion = '2.0'
          testingSupportLibVersion = '0.1'

          ...

          domainDependencies = [
              daggerCompiler:     "com.google.dagger:dagger-compiler:${daggerVersion}",
              dagger:             "com.google.dagger:dagger:${daggerVersion}",
              javaxAnnotation:    "org.glassfish:javax.annotation:${javaxAnnotationVersion}",
              rxJava:             "io.reactivex:rxjava:${rxJavaVersion}",
          ]

          domainTestDependencies = [
              junit:              "junit:junit:${jUnitVersion}",
              mockito:            "org.mockito:mockito-core:${mockitoVersion}",
          ]

          ...

          dataTestDependencies = [
              junit:              "junit:junit:${jUnitVersion}",
              assertj:            "org.assertj:assertj-core:${assertJVersion}",
              mockito:            "org.mockito:mockito-core:${mockitoVersion}",
              robolectric:        "org.robolectric:robolectric:${robolectricVersion}",
          ]
        }

build.gradle:

       apply plugin: 'java'

        sourceCompatibility = 1.7
        targetCompatibility = 1.7

        ...

        dependencies {
          def domainDependencies = rootProject.ext.domainDependencies
          def domainTestDependencies = rootProject.ext.domainTestDependencies

          provided domainDependencies.daggerCompiler
          provided domainDependencies.javaxAnnotation

          compile domainDependencies.dagger
          compile domainDependencies.rxJava

          testCompile domainTestDependencies.junit
          testCompile domainTestDependencies.mockito
        }

如果你希望在不同的模块中重复利用相同的依赖的版本,上面的建议就会变得非常有用
或者你想将不同的依赖版本放到不同的模块中去也是一样的. 另一个好处是
可以在一个地方控制所有的依赖

结语

到目前为止讲了那么多,一句话,要记住没有灵丹妙药。但好的软件架构会帮助代码保持清晰和健壮,还可以保持代码的可扩展性,易于维护。

我想指出一些事情。面对软件存在的问题,要报以本应当解决的态度:

  • 遵守SOLID原则
  • 不要过度思考(不过度工程化)
  • 务实
  • 尽可能降低(Android)框架中模块的依赖性

Package organization

我认为代码/包的组织良好的架构的关键因素之一:封装结构是浏览源代码时由程序员遇到的第一件事情。一切都从它流。一切都依赖于它。

我们可以把你的应用程序划分成包2路径区分:

  • 按层分包,每包中的内容通常不是密切相关,这导致低内聚 和
    低模块化,包与包之间的高耦合。这将导致很多问题。例如,修改会发生在不同包的文件中,删除一个功能也会涉及到很多文件
  • 按功能分包,试图将功能相关的文件放在同一封装中,这导致在高凝聚力和高度模块化,包和包带之间的最小耦合。每个文件紧密的放在一起,而不是分散在项目的个个角落

我的建议是按照功能分包,这有以下几个优点

  • 模块化程度高
  • 更简单的代码导航
  • 范围最小

当你在团队合作时,代码所有权会更容易组织,更模块化,这是一个不断发展的组织的一场胜利,许多开发人员在同一代码库工作。

澳门新葡萄京官网注册 20

-w458

可以看到,我的项目结构看起来就像按层分包一样,我可能已经知道我错在哪里了。但在这种情况下我会原谅自己,因为这个例子的主旨是学习,是介绍
clean架构方法的主要概念。照我说的做,不要像我一样:)
(貌似作者对其项目的分包不是很满意,不过我到觉得没啥不好,挺清晰的)

总结

这差不多就是我要说的了, 大家要记住 并没有包治百病的药,
但是一个好的程序架构可以帮助我们保持代码的整洁和健康,
同时也保证整了灵活性和可维护性

下面是一些我想指出的当你遇到程序问题时应有的态度:

  • 遵守 SOLID 原则
  • 不要想的太多(不要过度开发)
  • 要实际
  • 最大限度的在工程中减少对 android 框架的依赖

源代码

  1. Clean architecture github repository – master
    branch
  2. Clean architecture github repository –
    releases

Extra ball: organizing your build logic

我们都知道,万丈高楼平地起,房子的地基很重要。软件开发也一样。构建系统(及其组织)是一个软件体系结构的一个重要的一步

在 Android 中 我们使用 gradle 来构建我们的项目。gradle
是一个与平台无关很牛逼的一个构建工具。这里说一下使用 gradle构建
应用时的一些技巧。

澳门新葡萄京官网注册 21

-w214

def ciServer = 'TRAVIS'
def executingOnCI = "true".equals(System.getenv(ciServer))

// Since for CI we always do full clean builds, we don't want to pre-dex
// See http://tools.android.com/tech-docs/new-build-system/tips
subprojects {
  project.plugins.whenPluginAdded { plugin ->
    if ('com.android.build.gradle.AppPlugin'.equals(plugin.class.name) ||
        'com.android.build.gradle.LibraryPlugin'.equals(plugin.class.name)) {
      project.android.dexOptions.preDexLibraries = !executingOnCI
    }
  }
}

apply from: 'buildsystem/ci.gradle'
apply from: 'buildsystem/dependencies.gradle'

buildscript {
 repositories {
   jcenter()
 }
 dependencies {
   classpath 'com.android.tools.build:gradle:1.2.3'
   classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
 }
}

allprojects {
 ext {
   ...
 }
}
...

这样你就可以使用apply from: ‘buildsystem/ci.gradle来设置你的 gradle
文件了。不要把所有的配置都写在一个build.gradle文件中,不然你的项目会变得难以维护

创建依赖

...

ext {
  //Libraries
  daggerVersion = '2.0'
  butterKnifeVersion = '7.0.1'
  recyclerViewVersion = '21.0.3'
  rxJavaVersion = '1.0.12'

  //Testing
  robolectricVersion = '3.0'
  jUnitVersion = '4.12'
  assertJVersion = '1.7.1'
  mockitoVersion = '1.9.5'
  dexmakerVersion = '1.0'
  espressoVersion = '2.0'
  testingSupportLibVersion = '0.1'

  ...

  domainDependencies = [
      daggerCompiler:     "com.google.dagger:dagger-compiler:${daggerVersion}",
      dagger:             "com.google.dagger:dagger:${daggerVersion}",
      javaxAnnotation:    "org.glassfish:javax.annotation:${javaxAnnotationVersion}",
      rxJava:             "io.reactivex:rxjava:${rxJavaVersion}",
  ]

  domainTestDependencies = [
      junit:              "junit:junit:${jUnitVersion}",
      mockito:            "org.mockito:mockito-core:${mockitoVersion}",
  ]

  ...

  dataTestDependencies = [
      junit:              "junit:junit:${jUnitVersion}",
      assertj:            "org.assertj:assertj-core:${assertJVersion}",
      mockito:            "org.mockito:mockito-core:${mockitoVersion}",
      robolectric:        "org.robolectric:robolectric:${robolectricVersion}",
  ]
}

apply plugin: 'java'

sourceCompatibility = 1.7
targetCompatibility = 1.7

...

dependencies {
  def domainDependencies = rootProject.ext.domainDependencies
  def domainTestDependencies = rootProject.ext.domainTestDependencies

  provided domainDependencies.daggerCompiler
  provided domainDependencies.javaxAnnotation

  compile domainDependencies.dagger
  compile domainDependencies.rxJava

  testCompile domainTestDependencies.junit
  testCompile domainTestDependencies.mockito
}

如果你希望在其他 module 中也使用同一的
version,或者使用同样的依赖,这样做很有效。另一个好处就是你可以在一个地方控制所有的依赖

源代码

  1. Clean architecture github repository – master
    branch
  2. Clean architecture github repository –
    releases

延伸阅读

  1. Architecting Android..the clean
    way
  2. Tasting Dagger 2 on
    Android
  3. The Mayans Lost Guide to RxJava on
    Android
  4. It is about philosophy: Culture of a good
    programmer

Wrapping up

记住,没有银弹,但是每个好的软件架构会帮我们的代码结构保持整洁和健康,方便扩展便于维护

**我现在根据这个架构为模板做一个开源 APP,完成以后会开源,详见请见
http://xuyushi.github.io/tags/从零开始/
**

更多阅读:

  1. Architecting Android..the clean
    way
  2. Tasting Dagger 2 on
    Android
  3. The Mayans Lost Guide to RxJava on
    Android
  4. It is about philosophy: Culture of a good
    programmer

参考资料

  1. RxJava wiki by Netflix
  2. Framework bound by Uncle
    Bob
  3. Gradle user
    guide
  4. Package by feature, not
    layer

引用

  1. RxJava wiki by Netflix
  2. Framework bound by Uncle
    Bob
  3. Gradle user
    guide
  4. Package by feature, not
    layer

发表评论

电子邮件地址不会被公开。 必填项已用*标注