Android Application Architecture

原文:https://medium.com/ribot-labs/android-application-architecture-8b6e34acda65#.g1pkqmdoh

Android Application Architecture

Our journey from standard Activities and AsyncTasks to a modern MVP-based architecture powered by RxJava.
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.

Initial architecture

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 theview 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.

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 problems

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:

  1. Call a method loadPosts(callback) in the APIProvider
  2. Wait for the APIProvider success callback and then callsavePosts(callback) in the CacheProvider.
  3. Wait for the CacheProvider success callback and then display the posts on the ListView.
  4. Separately handle the two potential errors callback from the APIProvider and CacheProvider.

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.

In summary:

  • 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.

RxJava-driven architecture

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.

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.

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.

The code below shows what a DataManager method would look like. This sample method works as follows:

  1. Call the Retrofit service to load a list of blog posts from a REST API
  2. Save the posts in a local database for caching purposes using the DatabaseHelper.
  3. Filter the blog posts written today because those are the only ones the view layer wants to display.
width="700" height="250" src="https://medium.com/media/2f062b4f85a98ee291de11d72a6b23b1?maxWidth=700" data-media-id="2f062b4f85a98ee291de11d72a6b23b1" frameborder="0" style="display: block; margin: auto; max-width: 100%; box-sizing: border-box; transform: translateZ(0px);">

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.

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, asignOut() 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.

Why was this approach better?
  • RxJava Observables and operators remove the need for having nested callbacks.
  • 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.

MVP-based architecture

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.

Below you can see what a public method in the presenter would look like. This code subscribes to the Observable returned by thedataManager.loadTodayPosts() method we defined in the previous section.

width="700" height="250" src="https://medium.com/media/38ca7c5484cec836579797d5f27cdb4e?maxWidth=700" data-media-id="38ca7c5484cec836579797d5f27cdb4e" frameborder="0" style="display: block; margin: auto; max-width: 100%; box-sizing: border-box; transform: translateZ(0px);">

The mMvpView is the view component that this presenter is assisting. Usually the MVP view is an instance of an Activity, Fragment or ViewGroup.

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 callpresenter.loadTodayPosts() from the onClick listener.

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.

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.


深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
The software architecture of Android is based on a layered approach, with each layer providing specific functionalities and services. Here are the key layers of the Android software architecture: 1. Linux Kernel: The Linux kernel is the foundation of the Android operating system. It provides core system services such as memory management, process management, security, and device drivers. 2. Hardware Abstraction Layer (HAL): The HAL provides a standardized interface between the Android platform and the underlying hardware. It allows developers to write hardware-specific code without needing to know the details of the hardware. 3. Native C/C++ Libraries: The native libraries include a set of libraries that provide core system functionalities such as graphics rendering, media playback, and networking. 4. Android Runtime: The Android runtime includes the Dalvik Virtual Machine (DVM) and the Android Runtime (ART). The DVM is used to execute Android applications, while ART is a more recent runtime that provides improved performance and other optimizations. 5. Framework Layer: The Framework layer provides a set of APIs and services that application developers can use to build their applications. This layer includes a wide range of functionalities such as UI rendering, data storage, location services, and more. 6. Applications: Finally, the top layer of the Android software architecture is the applications layer. This layer includes all the user-facing applications that users interact with, such as messaging apps, social media apps, games, and more. Overall, the Android software architecture is designed to be modular and flexible, allowing developers to build applications that take advantage of the underlying system functionalities while remaining independent of the specific hardware and software configuration of the device.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值