【译】Android技术栈,1#架构

本文是如何开发一款具有扩展性,维护性和测试性的Android应用专题的第一篇。本专题将会涉及到一些设计模式和类库的使用方式,减少Android Developer日常开发的苦恼。

简介:##

作为例子,我将使用以下这个项目,事实上就是一个简单的电影概念目录,可以称之为视图或者其它。

关于电影的信息可以从一个叫做Themoviedb的公开API中获得,在这个版块中Apiary可以找到不错的文档说明。

项目基于Model View Presenter 设计模式,也参考了一些Material Design 设计规范,比如转场,(界面)结构,动画,配色等等。

所有代码都可以从Github中获得,所以请随意看,这里同样有一个视频用来展示App。

Paste_Image.png

架构:##

架构的设计基于Model View Presenter ,它是Model View Controller 设计模式的一个变种。

这种设计试图抽象Presentation层的业务逻辑,在Android中这是很重要的,因为自身Framework 提倡这两部分与数据层解耦合,一个明显的例子就是AdaptersCursorLoaders

这种架构促使业务逻辑层和数据层不再随着视图层的变换而改变,这样无论是Domain层的代码复用还是例如Database或者REST API等数据源的改变,都变得简单起来。

概述##

这种结构可以被划分为三个主要层次:

  • presentation
  • model
  • domain

Presentation
Presentation层负责提供数据并展示图形化界面。

Model
Model层将负责提供信息,这一层并不知道Presentation层和Domain,它能够与数据库,REST API或者其他可持久化数据等实现连接。

在这一层,也可以实现一些应用程序的实体类,用来代表,电影,种类等等。

Domain
Domain层完全独立于Presentation层之外,这一层专门处理业务逻辑。

实现##

Domain层和Model层被放到两个java module中,app module也就是Android应用代表Presentation层,这里还有另外一个common module,用来存放一些公共类库和工具类们。

Domain module

Domain module存放着一些usecase和它们的实现类,它们是应用程序的业务逻辑。

这个module完全独立于Android framework

依赖它的模块有model module和common module。

一个usecase可以用来获得不同类别电影的总评分,看一看哪个类别的电影最受欢迎,usecase需要获取信息然后做出计算,所有这些信息都由Model层提供。

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

Model module##

model module负责处理信息,查询,保存,删除等等,我只处理了从API获取电影详情的操作。

也实现了一些实体类,比如TvMovie,用来表现一部电影。

它目前只依赖common module,通过这个类库处理API请求,在这个例子中我使用Square出品的Retrofit,我将在接下来的博客中介绍Retrofit。

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

Presentation module##

就是Android应用自身,包括resources, assets, 逻辑等等。

它与执行usecaseDomain进行交互,比如可以用来获取某一时段的电影列表,或者从某部电影中获取特殊的数据。

这个模块只包含PresenterView

每一个ActivityFragmentDialog都实现MVPView接口,它指定了一些在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变得尽可能的简单,由Presenter决定它们的行为。(译者注:View层应体现KISS原则,感兴趣的同学可以了解一下Keep it simple stupid

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上的表现。

通信##

对于这个项目,我选择了Message Bus(译者注:消息总线)系统,这个系统对于广播事件,或者在两个组件之间建立通信是非常有用的,尤其特别适用于后者。

基本上,通过Bus发送事件,对事件感兴趣的类,需要订阅Bus,才能消费那个事件。

适用这个系统可以降低模块间的耦合度。

为了实现这个系统总线,我使用Square出品的Otto类库。

我定义了两个Bus,一个用来使usecase和REST API进行通信,另一个用来发送事件至Presentation
层。

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

这个类通过common module管理。因为所有的模块都需要访问它,从而与Bus进行交互。

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

最后,想象一下这个场景,当用户打开应用,显示最受欢迎的电影。

View调用onCreate()方法时,Presenter订阅UI_BUS接收事件。当onStop()方法被调用的时候Presenter取消订阅。Presenter运行GetMoviesUseCase这个usecase

@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需要实现一个方法,这个方法所接受参数的数据类型必须与Bus发送的事件的数据类型一致,兵器必须使用注解:@Subscribe

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

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

资源:##

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,498评论 25 707
  • 作者:李旺成### 时间:2016年4月3日### 上篇 5. 最佳实践# 好了终于要点讲自己的东西了,有点小激动...
    diygreen阅读 30,243评论 54 493
  • 5. 最佳实践 好了终于要点讲自己的东西了,有点小激动。下面这些仅表示个人观点,非一定之规,各位看官按需取用,有说...
    SnowDragonYY阅读 2,383评论 4 36