一个实用的android框架(一)——架构

原文出处:http://saulmm.github.io/2015/02/02/A%20useful%20stack%20on%20android%20%231,%20architecture/

原码github地址:https://github.com/saulmm/Material-Movies

作者:Saúl Molinero

译者注:这是最近接触到的一个关于安卓架构的项目,也是基于MVP的,分包上的想法和我比较契合。另外,该项目也是使用了Material Design,感觉比较新颖实用。因此,决定将该项目对应的blog翻译过来,供大家参考。

系列文章:

这是这篇文章的第一个章节,描述为了开发一个可扩展,可维护以及可测试的android项目,如何去搭建一个基础环境。在这个章节中,我会介绍一些使用到的模式和工具库。这些模式和库能够保证你的项目不会因为每天的开发而变失去控制。

场景

我会将下面这个项目作为示例来进行讲解。这个项目是一个简单的电影分类app,支持某一电影进行多种方式的标记(已读和即将上映)。

电影的数据是从一个叫做themoviedb公共的API获取的。关于API的描述,你可以从Apiary获取到一个完整的文档。

这个项目是基于Model-View-Presenter模式的,同时也实现了Material Design的一些特性,如过渡效果,UI结构,动画,色彩等。

所有的代码都在Github上,可以随意下载或者阅读。同时,也有一个视频来展示app的效果。

效果图

架构

这个架构的设计是基于MVP的。MVP是一个MVC架构的变换。

MVP架构试图将业务逻辑从展示层(Presenter)中抽离出来。在android中,这是一个很重要的过程。因为android本身的架构也在促进业务逻辑和展示部分的分离,这两者之间通过数据层面连接。几个典型的例子就是Adapter和CursorLoader。

这个架构可以使得对视图的修改变不需要影响到业务逻辑层和数据层。也使得负责转换多个数据源(数据库或者REST APIT)的domain和转换工具可以很轻松的被重用。

概览

总体架构可以被分成三个部分 :

  • Presentation
  • Model
  • Domain

MVP架构

Presentation

Presentation层负责展示图形接口,并填充数据。

Model

Model层负责提供数据。Model层不会知道任何关于Domain和Presentation的数据。它可以用来实现和数据源(数据库,REST API或者其他源)的连接或者接口。

这个层面同时也实现了整个app所需要的实体类,例如用来表示电影或者分类的类。

Domain

Domain层相对于Presentation层完全独立,它会实现app的业务逻辑。(译者注:这里所谓的业务逻辑可能会于Presenter的功能概念上有点混淆。打个比方,假如usecase接收到的是一个json串,里面包含电影的列表,那么把这个json串转换成json以及包装成一个ArrayList,这个应当是由usecase来完成。而假如ArrayList的size为0,即列表为空,需要显示缺省图,这个判断和控制应当是由presenter完成的。)

实现

Domain和Model层分别放在两个java模块中(译者注:意味着不会有android依赖),Presentation层则为默认的app模块,也就是所谓的android应用。同时,我增加了一个通用的模块,用来在各个模块直接共享支持库和工具类。

项目结构

Domain模块

Domain模块存放了一些Usecase以及它们的实现。这些都是应用业务逻辑的实现。这个模块相对于android框架来说完全独立。它的依赖只是来源于model模块和通用模块。

一个usecase可以用来获取各个电影分类的总评分,从而用来获取最最热门的分类。要实现这个功能,usecase可能需要获取数据并进行计算。而数据则是由model模块提供的。

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

Model模块

Model模块是负责管理数据的,例如获取,保存,删除等操作。在第一个版本中,我只是通过REST API来实现对电影数据的管理。

同时,这个模块也实现了一些实体类。例如TvMovie就是用来表示一个电影的。

它的依赖仅仅是通用模块和用来发起网络请求的支持库。关于这一个功能,我使用了Square开发的Retrofit。我会在下一篇博客中介绍一下Retrofit。

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

Presentation模块

这个模块就是android应用本身,包括他的resource,asset以及逻辑等。它也同时通过运行usecase来和domain层进行交互。例如:获取一段时间范围内的电影列表,请求一个电影的具体数据。

这个模块包含了presenter和view。每一个ActivityFragmentDialog都实现了一个MVP中的View接口。这个接口指定了一个View需要支持的操作,包括显示,隐藏以及展示数据等。例如:PopularMoviesView定义了接口来展示当前的电影列表,而具体的实现是由MoviesActivity完成的。

public interface PopularMoviesView extends MVPView {

    void showMovies (List<TvMovie> movieList);

    void showLoading ();

    void hideLoading ();

    void showError (String error);

    void hideError ();
}

MVP模式的设计初衷就是:View应当越简单越好,View的行为应当是由Presenter决定的,而不是View本身。

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

    ...
}

Usecase会在presenter中被执行,presenter会接受到返回的数据,并根据数据来控制view的行为。

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

通信

在这个项目中,我选择了消息总线(MessageBus)系统来。这个系统十分有利于发送广播事件或者在各个模块中建立通信。而这正是我们所需要的。简单来说,事件会被送到总线(Bus)上,而需要处理这个事件的类就必须向总线订阅事件。使用这个系统可以大大降低模块之间的耦合。

为了实现这个系统的总线,我使用了Square开发的库Otto。我声明了两个总线,一个(REST_BUS)用来实现usecase和REST API直接的通信,另一个(UI_BUS)则发送事件到展示(presentation)层中。其中,REST_BUS将使用任何可用的线程来处理事件,而UI_BUS只会将事件发送到默认的线程上去,即UI主线程。

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'
}

最后,来思考这样一种情况:当用户打开了应用,最热门的电影首先被展示。

onCreate方法被调用的时候,presenter将订阅UI_BUS来监听事件。然后,presenter就会执行GetMoviesUseCase来发起请求。presenter会在onStop被调用的时候取消订阅。

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

为了接收到事件,presenter必须实现一个方法。这个方法的参数必须和送入总线的事件的参数一致。并且这个方法必须用注解@Subsribe进行标注

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

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

参考

译者总结

这个项目的作者也是参考了几个著名的安卓架构后,总结出来的一套模板。在架构和分包上,都和我的想法Android架构实战(一)—— 核心思想比较契合,比一套完整的流程精简了不少,也更加适合小型团队(1-3个人)开发。

不同的是,这个项目采用了事件总线的方式来实现模块间通信。关于事件总线和RxJava的对比,个人觉得事件总线属于比较简单易懂的。但是事件总线表现出来的缺陷就是依赖关系较弱,即你没办法轻易的找到一个事件到底是由谁发起的。这个效果,一方面可以理解成低耦合,一方面也可能造成维护的时候”跟丢“的现象。因此,我还是更偏向于RxJava的设计模式。

关于此项目,后续还有两个章节,主要是关于Material Design和其兼容性的一些问题。讲得不是特别深,不过如果需要相同的UI效果的话,可以进行参考,我也会翻译出来,方便大家查找。

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值