对Android开发有用的技术栈(一)架构篇

2,892 阅读5分钟
原文链接: android.jobbole.com

本文是介绍有关如何搭建一个可扩展、维护和测试的Android环境系列教程的第一篇在这一系列中我会涉及到一些Android开发者会用到的模式和库。

应用场景

我将基于一个简单的电影分类项目作为示例在这个项目中,分类信息可以用视图(Views)展示出来。

影片信息可通过叫做themoviedb的公共API获取,你可以在Apiary中查阅相关说明文档。

这个项目是基于MVP(Model View Presenter)模式进行设计的,以及一些Material Design指南中的实现,如传输(transitions)、结构、动画、色彩等……

所有的代码都可以从Github上获得,所以请随便查看。另外这里还有一个视频(注:需翻墙)演示。

查看图片

架构:

架构设计采用MVP模式,它由MVC模式演变而来。

此模式是将应用层中的业务逻辑抽象出来,这在Android中十分重要。因为Android本身的框架将这一层与数据层耦合在了一起,一个明显的例子就是Adapter或者CursorLoader。

这种架构的方便之处在于视图的变化无需涉及业务逻辑层和数据层的修改。可在领域内方便地进行复用,或者在不同的数据源间进行切换(如数据库或REST API)。

总览

架构可分为三个主要层次:

查看图片

展现层

展现层负责显示图形界面并且为它提供数据。

模型(数据)层

模型层负责提供信息(数据),它不需要知道领域层和展现层,它只是单纯地实现与数据库的连接和接口(与REST API或者其它任何存储方式)。

在这一层中也实现应用的实体,如影片、类别等类。

领域层

领域层与展现层完全分开,是单独存在的,它只于应用程序的业务逻辑相关。

实现

领域层与数据层作为单独的Java模块存在,展现层则表现为Android应用程序模块。此外还有一个公共模块用于共享库和工具类。

查看图片

领域模块

领域模块承载了用例和它们的实现,即应用程序的业务逻辑。

这个模块是完全独立于Android框架的。

它依赖于模型模块和公共模块。

一个用例可能是指获取各种类型影片的总排名、查看哪一类影片被请求得最多等。用例可能需要获得信息并对其进行计算,这些信息是由模型(Model)提供的。

dependencies {
    compile project (':common')
    compile project (':model')
}

模型模块

模型模块负责管理信息、选择、保存、删除等等。在第一个版本中我仅仅管理一些与影片信息API相关的内容。

另外它也实现了实体,如TvMovie —— 表示一部影片。

这个模块只与公共模块相依赖,引用的库是用来管理请求API的。这里我使用了Square的Retrofit,我会在以后的文章中再讨论Retrofit的。

dependencies {
    compile project(':common')
    compile 'com.squareup.retrofit:retrofit:1.9.0'
}

表现模块

就是Android应用程序本身,包括资源、assets、逻辑等等……

它通过运行用例的方式与领域模块进行交互,例如获得某一时间的影片列表、请求一个影片数据等。

这个模块中有展现与视图。

每个Activity、Fragment、Dialog都实现一个MVPView接口,它定义了在视图上用于显示、隐藏和“画”出信息的操作。

例如视图PopularMoviesView,定义了显示当前影片列表的操作,它被MoviesActivity所实现。

public interface PopularMoviesView extends MVPView {

    void showMovies (List movieList);

    void showLoading ();

    void hideLoading ();

    void showError (String error);

    void hideError ();
}

MVP模式指出视图应尽可能地简单,它的行为应该由展现器(presenter)来决定。

public class MoviesActivity extends ActionBarActivity implements
    PopularMoviesView, ... {

    ...
    private PopularShowsPresenter popularShowsPresenter;
    private RecyclerView popularMoviesRecycler;
    private ProgressBar loadingProgressBar;
    private MoviesAdapter moviesAdapter;
    private TextView errorTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        ...
        popularShowsPresenter = new PopularShowsPresenterImpl(this);
        popularShowsPresenter.onCreate();
    }

    @Override
    protected void onStop() {

        super.onStop();
        popularShowsPresenter.onStop();
    }

    @Override
    public Context getContext() {

        return this;
    }

    @Override
    public void showMovies(List movieList) {

        moviesAdapter = new MoviesAdapter(movieList);
        popularMoviesRecycler.setAdapter(moviesAdapter);
    }

    @Override
    public void showLoading() {

        loadingProgressBar.setVisibility(View.VISIBLE);
    }

    @Override
    public void hideLoading() {

        loadingProgressBar.setVisibility(View.GONE);
    }

    @Override
    public void showError(String error) {

        errorTextView.setVisibility(View.VISIBLE);
        errorTextView.setText(error);
    }

    @Override
    public void hideError() {

        errorTextView.setVisibility(View.GONE);
    }

    ...
}

用例将被展现器执行,它们将接收应答并且管理视图的行为。

public class PopularShowsPresenterImpl implements PopularShowsPresenter {

    private final PopularMoviesView popularMoviesView;

    public PopularShowsPresenterImpl(PopularMoviesView popularMoviesView) {

        this.popularMoviesView = popularMoviesView;
    }

    @Override
    public void onCreate() {

        ...
        popularMoviesView.showLoading();

        Usecase getPopularShows = new GetMoviesUsecaseController(GetMoviesUsecase.TV_MOVIES);
        getPopularShows.execute();
    }

    @Override
    public void onStop() {

        ...
    }

    @Override
    public void onPopularMoviesReceived(PopularMoviesApiResponse popularMovies) {

        popularMoviesView.hideLoading();
        popularMoviesView.showMovies(popularMovies.getResults());
    }
}

通信

在这个项目中我选择了一个消息总线系统(Message Bus system),这个系统对广播事件或者建立组件间的通信非常有用(尤其是对后者)。

基本上说事件就是由总线被发送出来,然后由感兴趣的类订阅并接收处理该事件。

使用这个系统会极大地降低模块间的耦合。

实现系统总线,我使用Square的Otto库。

我定义了两个总线,分别用于用例与REST API的通信,以及发送事件给展现层。

REST_BUS将使用后台线程处理事件,而UI_BUS则使用默认线程(即主线程)来发送事件。

public class BusProvider {

    private static final Bus REST_BUS = new Bus(ThreadEnforcer.ANY);
    private static final Bus UI_BUS = new Bus();

    private BusProvider() {};

    public static Bus getRestBusInstance() {

        return REST_BUS;
    }

    public static Bus getUIBusInstance () {

        return UI_BUS;
    }
}

这个类被公共模块所管理,因为所用模块都通过访问它来与总线交互。

dependencies {
    compile 'com.squareup:otto:1.3.5'
}

最后,考虑下面的示例,用户打开应用程序然后最流行的影片就被展现出来了。

当Android视图中的onCreate方法被调用时,展现器在UI_BUS上订阅所需接收的事件。当onStop被调用时取消订阅。然后,展现器运行GetMovieUseCase用例。

   @Override
    public void onCreate() {

        BusProvider.getUIBusInstance().register(this);

        Usecase getPopularShows = new GetMoviesUsecaseController(GetMoviesUsecase.TV_MOVIES);
        getPopularShows.execute();
    }

    ...

    @Override
    public void onStop() {

        BusProvider.getUIBusInstance().unregister(this);
    }
}

接收事件时,展现器必须实现一个方法——它带有与传递过来的事件所相同的数据类型参数,并且必须使用标注@Subscribe

   @Subscribe
    @Override
    public void onPopularMoviesReceived(PopularMoviesApiResponse popularMovies) {

        popularMoviesView.hideLoading();
        popularMoviesView.showMovies(popularMovies.getResults());
    }