一个实用的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

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效果的话,可以进行参考,我也会翻译出来,方便大家查找。

时间: 2024-12-24 00:19:40

一个实用的android框架(一)——架构的相关文章

Android酷炫实用的开源框架(UI框架)

前言 忙碌的工作终于可以停息一段时间了,最近突然有一个想法,就是自己写一个app,所以找了一些合适开源控件,这样更加省时,再此分享给大家,希望能对大家有帮助,此博文介绍的都是UI上面的框架,接下来会有其他的开源框架(如:HTTP框架.DB框架). 1.Side-Menu.Android分类侧滑菜单,Yalantis 出品.项目地址:https://github.com/Yalantis/Side-Menu.Android2.Context-Menu.Android可以方便快速集成漂亮带有动画效果

Android酷炫实用的开源框架(UI框架) 转

Android酷炫实用的开源框架(UI框架) 前言 忙碌的工作终于可以停息一段时间了,最近突然有一个想法,就是自己写一个app,所以找了一些合适开源控件,这样更加省时,再此分享给大家,希望能对大家有帮助,此博文介绍的都是UI上面的框架,接下来会有其他的开源框架(如:HTTP框架.DB框架). 1.Side-Menu.Android 分类侧滑菜单,Yalantis 出品. 项目地址:https://github.com/Yalantis/Side-Menu.Android 2.Context-Me

Android酷炫实用的开源框架——UI框架(转)

转载别人整理好的文章,列出了很多炫酷的UI开源设计 原文地址:http://www.androidchina.net/1992.html 1.Side-Menu.Android分类侧滑菜单,Yalantis 出品.项目地址:https://github.com/Yalantis/Side-Menu.Android 2.Context-Menu.Android可以方便快速集成漂亮带有动画效果的上下文菜单,Yalantis出品.项目地址:https://github.com/Yalantis/Con

ym——Android酷炫实用的开源框架(UI框架)(终)

转载请注明本文出自Cym的博客(http://blog.csdn.net/cym492224103),谢谢支持! 前言 好久没写博文了,最近工作比较忙,剩下的一点点时间在做自己的项目,在Android酷炫实用的开源框架(UI框架)这篇文章中提到了很多开源的UI框架,我在自己的项目开发中也使用了大部分的,但是总觉得仅仅这几个不够用啊,所以本人在此做项目期间又找到了更多优秀的开源UI框架,在此分享给大家希望能对大家有所帮助,大家记得关注我哦~!在此篇之后就给大家带来优秀的HTTP框架和DB框架了!

一个很实用的前端框架Zui

杰哥给我推荐了一个很有用的前端框架-Zui,我看着觉得很神奇的,因为有很多我都不懂.在这里分享总结一下.首先,这是一个中国自己开发的框架,比起很多外国的框架来说,有很详细的API,而且是全中文的,不需要再经过其他人的翻译了.然后,它的内容十分丰富,很系统的分为了:基础,控件,组件,JS插件,视图几大块:而且使用起来,只需要导入js,在适当的地方加上正确的class类就可以了.对于,没有什么js基础的人,也是十分容易上手的.下面我就大体的介绍一下它的各个模块的功能.基础:基础里面我觉得很有用的主要

开发一个简单实用的android紧急求助软件

之前女朋友一个人住,不怎么放心,想找一个紧急求助的软件,万一有什么突发情况,可以立即知道.用金山手机卫士的手机定位功能可以知道对方的位置状态,但不能主动发送求助信息,在网上了很多的APK,都是鸡肋功能,都需要解锁.并打开软件,真正的紧急情况可能没有时间来完成这一系列操作. 于是我自己做了一个这样的软件,在紧急情况下,连续按电源键5次即可发送求救短信和位置信息给事先指定的用户,这个操作在裤兜里就能完成.原理很简单,就是设置监听器捕获屏幕的开关,在较短的时间内屏幕开关达到一定次数后,触发手机定位,定

google官方架构MVP解析与实战-(从零开始搭建android框架系列(3))

最近更新2016.5.10(已经添加整个项目目录.更新新闻资讯) 本篇文章项目github地址:MVPCommon 本文章原地址:简书博客 1 前言 当然对于MVP的解说也是使用也是层出不穷,我也网络上也能看到各种版本的解说,之前博客也有文章的更新,里面有MVP的详细说明和项目代码->Android中的MVP模式,带实例. 本篇文章将参考 google官方android MVP架构项目的实现,来实现自己的项目.或许看了这篇文章之后,你再去梳理一下google官方架构项目,会让你收获更多.官方的实

【Java&amp;Android开源库代码剖析】のandroid-async-http(如何设计一个优雅的Android网络请求框架,同时支持同步和异步请求)开篇

在<[Java&Android开源库代码剖析]のandroid-smart-image-view>一文中我们提到了android-async-http这个开源库,本文正式开篇来详细介绍这个库的实现,同时结合源码探讨如何设计一个优雅的Android网络请求框架.做过一段时间Android开发的同学应该对这个库不陌生,因为它对Apache的HttpClient API的封装使得开发者可以简洁优雅的实现网络请求和响应,并且同时支持同步和异步请求. 网络请求框架一般至少需要具备如下几个组件:1

LogCook 一个简单实用的Android日志管理工具

众所周知,日志的管理是软件系统很重要的一部分,千万不可忽略其重要性.完整的日志将会在系统维护中起着异常重要的作用,就好像磨刀不误砍柴工一样,日志就像对系统进行分析的工具,工具便捷了,对系统分析起来就能达到事半功倍的效果.开发者必须要明白日志的价值和意义,万万不可忽略和轻视. LogCook是一款非常简洁实用的Android日记管理工具.LogCook的中文翻译是日志厨师,你可以把它看作是一个日志美食家. 特点 作为一款日志管理工具它最大的特点就是简单实用,与Android原生的日志功能相比较它具