Android Application Architecture 安卓APP架构[译]
其实我们在开发中也遇到过，Android入门门槛较低，如果前期对APP规划不清晰，Coder们对未来变化把握不准，技术架构经验不够强大，最 终导致就是一个Activity几千行，里面写了大量的Private方法，拆成几个Fragment、封装出来几个类都是无法解决，结果就是看 Activity难受的要死，纠结，看了不爽改也不是不改也不是，严重影响看的人的心情。并且怨天尤人这个是产品人员规划App不好，没有前瞻性，改来改 去。。。
Android Application Architecture
Our journey from standard Activities and AsyncTasks to a modern MVP-based architecture powered by RxJava.
这篇文章主要目的是讲述如何将传统的Activities 与 AsyncTasks 模式向目前主流的MVP架构基础的响应式编程框架过度。
Different parts of a software codebase should be independent, yet perfectly work together like a well-oiled machine — photo by Chester Alvarez.
The Android dev ecosystem moves very quickly. Every week new tools are created, libraries are updated, blog posts are written and talks are given. If you go on holiday for a month, by the time you come back there will be a new version of the support library and/or Play Services.
I’ve been making Android apps with the ribot team for over three years. During this time, the architecture and technologies we’ve used to build Android apps have been continuously evolving. This article will take you through this journey by explaining our learnings, mistakes and the reasoning behind these architectural changes.
The old times
Back in 2012 our codebases used to follow a basic structure. We didn’t use any networking library and AsyncTasks were still our friends. The diagram below shows approximately how the architecture was.
>The code was structured in two layers: the data layer that was in charge of retrieving/saving data from REST APIs and persistent data stores; and the view layer, whose responsibility was handling and displaying the data on the UI.
The APIProvider provides methods to enable Activities and Fragments to easily interact with the REST API. These methods use URLConnection and AsyncTasks to perform network calls in a separate thread and return the result to the Activities via callbacks.
代码分为两层，Data与View,Data层主要是用来从API获取数据，保存到持久化的db当中。View层主要就是把Data的数据显示到 UI上。APIProvider提供方法出来，用于在Activity或者Fragment中方便的进行控制与交互。技术上将，使用 URLConnection与AsyncTasks实现了一个异步的网络请求并将结果返回到调用的回调方法里面。
In a similar way, the CacheProvider contains methods that retrieve and store data from SharedPreferences or a SQLite database. It also uses callbacks to pass the result back to the Activities.
The main issue with this approach was that the View layer had too many responsibilities. Imagine a simple common scenario where the application has to load a list of blog posts, cache them in a SQLite database and finally display them on a ListView. The Activity would have to do the following:
- Call a method loadPosts(callback) in the APIProvider
- Wait for the APIProvider success callback and then call savePosts(callback) in the CacheProvider.
- Wait for the CacheProvider success callback and then display the posts on the ListView.
- Separately handle the two potential errors callback from the APIProvider and CacheProvider.
- 分别书写代码处理2 3 两步的错误回调内容。
This is a very simple example. In a real case scenario the REST API will probably not return the data like the view needs it. Therefore, the Activity will have to somehow transform or filter the data before showing it. Another common case is when the loadPosts() method takes a parameter that needs to be fetched from somewhere else, for example an email address provided by the Play Services SDK. It’s likely that the SDK will return the email asynchronously using a callback, meaning that we now have three levels of nested callbacks. If we keep adding complexity, this approach will result into what is known as callback hell.
这还是一个比较简单的例子，在一些真实的场景中，远程的API可能没有返回程序的必须值，但是activity必须把数据处理完成之后才能显示结 果。再一个例子就是如果loadPosts方法需要借助一些其他地方的返回参数时，类似用多线程去实现同步请求，为保证数据正常请求，意味着必须做一个三 层的回调，如果再复杂一些，想理清楚这些回调就是很蛋疼的事情。
Activities and Fragments become very large and difficult to maintain
Too many nested callbacks means the code is ugly and difficult to understand so painful to make changes or add new features.
Unit testing becomes challenging, if not impossible, because a lot of the logic lives within the Activities or Fragments that are arduous to unit test.
A new architecture driven by RxJava
We followed the previous approach for about two years. During that time, we made several improvements that slightly mitigated the problems described above. For example, we added several helper classes to reduce the code in Activities and Fragments and we started using Volley in the APIProvider. Despite these changes, our application code wasn’t yet test-friendly and the callback hell issue was still happening too often.
It wasn’t until 2014 when we started reading about RxJava. After trying it on a few sample projects, we realised that this could finally be the solution to the nested callback problem. If you are not familiar with reactive programming you can read this introduction. In short, RxJava allows you to manage data via asynchronous streams and gives you many operators that you can apply to the stream in order to transform, filter or combine the data.
Taking into account the pains we experienced in previous years, we started to think about how the architecture of a new app would look. So we came up with this.
Similar to the first approach, this architecture can be separated into a data and view layer. The data layer contains the DataManager and a set of helpers. The view layer is formed by Android framework components like Fragments, Activities, ViewGroups, etc.
与第一种方法相似，这个架构也是分为Data层与View层，Data层包含DataManager与一堆Helper；View层是包含Fragments, Activities, ViewGroups等。
Helper classes (third column on diagram) have very specific responsibilities and implement them in a concise manner. For example, most projects have helpers for accessing REST APIs, reading data from databases or interacting with third party SDKs. Different applications will have a different number of helpers but the most common ones are:
- PreferencesHelper: reads and saves data in SharedPreferences.
- DatabaseHelper: handles accessing SQLite databases.
- Retrofit services: perform calls to REST APIs. We started using Retrofit instead of Volley because it provides support for RxJava. It’s also nicer to use.
- 类似与square的Retrofit服务，也就是Http Client,我们用Restrofit替代了Volley因为他支持Rxjava,并且更吊。
Most of the public methods inside helper classes will return RxJava Observables.
The DataManager is the brain of the architecture. It extensively uses RxJava operators to combine, filter and transform data retrieved from helper classes. The aim of the DataManager is to reduce the amount of work that Activities and Fragments have to do by providing data that is ready to display and won’t usually need any transformation.
RxJava最核心的两个东西是Observables（被观察者，事件源）和Subscribers（观察者）,在Helper类中的 Public方法，一般都会返回一个RxJava的Observables;DataManager是整个架构的大脑，他大量的使用Rxjava的 operators对Helper返回来的数据进行的整合过滤、二次处理。
The code below shows what a DataManager method would look like. This sample method works as follows:
- Call the Retrofit service to load a list of blog posts from a REST API
- Save the posts in a local database for caching purposes using the DatabaseHelper.
- Filter the blog posts written today because those are the only ones the view layer wants to display.
Components in the view layer such as Activities or Fragments would simply call this method and subscribe to the returned Observable. Once the subscription finishes, the different Posts emitted by the Observable can be directly added to an Adapter in order to be displayed on a RecyclerView or similar.
Observables发出一系列事件，Subscribers（例如 Activities or Fragments）处理这些事件,可以直接将数据显示到一些可以回收、重用的View上面。
The last element of this architecture is the event bus. The event bus allows us to broadcast events that happen in the data layer, so that multiple components in the view layer can subscribe to these events. For example, a signOut() method in the DataManager can post an event when the Observable completes so that multiple Activities that are subscribed to this event can change their UI to show a signed out state.
这个架构的另外一个模块是event bus，event bus可以让我们在Data层发出广播（不是Android的Broadcast）然后不同的模块去注册并接收不同的广播事件
The DataManager takes over responsibilities that were previously part of the view layer. Hence, it makes Activities and Fragments more lightweight.
Moving code from Activities and Fragments to the DataManager and helpers means that writing unit tests becomes easier.
Clear separation of responsibilities and having the DataManager as the only point of interaction with the data layer, makes this architecture test-friendly. Helper classes or the DataManager can be easily mocked.
What problems did we still have?
For large and very complex projects the DataManager can become too bloated and difficult to maintain.
Although view layer components such as Activities and Fragments became more lightweight, they still have to handle a considerable amount of logic around managing RxJava subscriptions, analysing errors, etc.
Integrating Model View Presenter
In the past year, several architectural patterns such as MVP or MVVM have been gaining popularity within the Android community. After exploring these patterns on a sample project and article, we found that MVP could bring very valuable improvements to our existing approach. Because our current architecture was divided in two layers (view and data), adding MVP felt natural. We simply had to add a new layer of presenters and move part of the code from the view to presenters.
The data layer remains as it was but it’s now called model to be more consistent with the name of the pattern.
Presenters are in charge of loading data from the model and calling the right method in the view when the result is ready. They subscribe to Observables returned by the data manager. Therefore, they have to handle things like schedulers and subscriptions. Moreover, they can analyse error codes or apply extra operations to the data stream if needed. For example, if we need to filter some data and this same filter is not likely to be reused anywhere else, it may make more sense to implement it in the presenter rather than in the data manager.
之前的Data层就是现在的MVP中的Model，Presenter现在负责从Model中加载数据，加载完成后后再去调用左边的在 Activity、ViewGroup中的方法。Presenters的subscribe去接收data manager中的Observables广播出来的数据。
Below you can see what a public method in the presenter would look like. This code subscribes to the Observable returned by the dataManager.loadTodayPosts() method we defined in the previous section.
The mMvpView is the view component that this presenter is assisting. Usually the MVP view is an instance of an Activity, Fragment or ViewGroup.
MVP的View并不是指的Android的View，而是一个界面组件的的实例，例如Activity, Fragment ， ViewGroup 在注册presenter的时候，需要把自己当前的实例传递进去。
// Activity onCreate 中的代码段 if (presenter == null) presenter = new Presenter1(); presenter.onTakeView(this);
Like the previous architecture, the view layer contains standard framework components like ViewGroups, Fragments or Activities. The main difference is that these components don’t subscribe directly to Observables. They instead implement an MvpView interface and provide a list of concise methods such as showError() or showProgressIndicator(). The view components are also in charge of handling user interactions such as click events and act accordingly by calling the right method in the presenter. For example, if we have a button that loads the list of posts, our Activity would call presenter.loadTodayPosts() from the onClick listener.
这个架构与上一个架构不同的是，ViewLayer 也就是Activity这些，不会直接去订阅接收Observables发出的这些事件。而是只在Activity实现几个简单的显示错误、显示进度的方 法（用接口interface来规范统一），然后把当前实例以参数形式传递给到对应事件的Presenter，由Presenter去执行这些显示错误、 显示进度的方法。
If you want to see a full working sample of this MVP-based architecture, you can check out our Android Boilerplate project on GitHub. You can also read more about it in the ribot’s architecture guidelines.
Why is this approach better?
- Activities and Fragments become very lightweight. Their only responsibilities are to set up/update the UI and handle user events. Therefore, they become easier to maintain.
- We can now easily write unit tests for the presenters by mocking the view layer. Before, this code was part of the view layer so we couldn’t unit test it. The whole architecture becomes very test-friendly.
- If the data manager is becoming bloated, we can mitigate this problem by moving some code to the presenters.
What problems do we still have?
Having a single data manager can still be an issue when the codebase becomes very large and complex. We haven’t reached the point where this is a real problem but we are aware that it could happen.
It’s important to mention that this is not the perfect architecture. In fact, it’d be naive to think there is a unique and perfect one that will solve all your problems forever. The Android ecosystem will keep evolving at a fast pace and we have to keep up by exploring, reading and experimenting so that we can find better ways to continue building excellent Android apps.
如果想有个完美的架构解决你所有问题是不可能的。TMD Android的整个生态圈变化太快，又TM的不标准，就导致我们不断的去探索探索。。。以致于去找到更吊的方法去做Android apps。
I hope you enjoyed this article and you found it useful. If so, don’t forget to click the recommend button. Also, I’d love to hear your thoughts about our latest approach.