Android官方架构分析(四)——todo‑mvp‑rxjava

前言

这篇文章是android-architecture源码分析系列文章的第四篇,我们将对todo-mvp-rxjava项目Demo进行源码分析。todo-mvp-rxjava项目是在todo‑mvp项目的基础上添加了RxJava响应式编程,希望读者有一定的Rxjava的基础。

引入RxJava和Lambda

RxJava

RxJava是近来最火的一个响应式编程的库,在项目中引入RxJava的根本目的在于:使用RxJava可以在程序逻辑复杂的情况下,在不断做线程调度的同时依然保持代码简洁。

RxJava:https://github.com/ReactiveX/RxJava

RxAndroid:https://github.com/ReactiveX/RxAndroid

在项目中引入RxJava:

compile "io.reactivex:rxjava:$rootProject.rxjavaVersion"
compile "io.reactivex:rxandroid:$rootProject.rxandroidVersion"

在写本文时RxJava早已迭代出2.0版本,但由于todo-mvp-rxjava的Demo使用的还是1.0版本的API,这里并没有做出修改,还是使用1.0版本。

Lambda

Java8开始引入了Lambda表达式,它让代码变得更加简洁,但是也降低了代码的可读性。

在Android项目中引入Lambda时,需要在App下的 build.gradle 文件中输入以下内容:

android { 
    
    ...
    
    defaultConfig {
        jackOptions {
            enabled true
        }
    }
   
    compileOptions { 
        sourceCompatibility JavaVersion.VERSION_1_8 
        targetCompatibility JavaVersion.VERSION_1_8 
    } 

    ...

源码分析

我们开始分析todo-mvp-rxjava的代码,项目结构其实与todo-mvp差别不大,重点就是分析RxJava在项目中的使用。

todo-mvp-rxjava的包结构

从上图可以看到,util包里面添加了对RxJava的线程调度的封装,我们进一步分析具体实现类SchedulerProvider的源码。

/**
 * Provides different types of schedulers.
 */
public class SchedulerProvider implements BaseSchedulerProvider {

    @Nullable
    private static SchedulerProvider INSTANCE;

    // Prevent direct instantiation.
    private SchedulerProvider() {
    }

    public static synchronized SchedulerProvider getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new SchedulerProvider();
        }
        return INSTANCE;
    }

    @Override
    @NonNull
    public Scheduler computation() {
        return Schedulers.computation();
    }

    @Override
    @NonNull
    public Scheduler io() {
        return Schedulers.io();
    }

    @Override
    @NonNull
    public Scheduler ui() {
        return AndroidSchedulers.mainThread();
    }
}

SchedulerProvider类对外提供了三种不同的可调度的线程类型。

  • Schedulers.computation():CPU密集型计算所使用的Scheduler。
  • Schedulers.io():I/O操作(读写文件、读写数据库、网络信息交互等)所使用的 Scheduler。
  • AndroidSchedulers.mainThread():运行在Android UI线程上。

BasePresenter类也基于RxJava做了更改:

public interface BasePresenter {

    void subscribe();

    void unsubscribe();

}

Presenter需要根据Activity或者View的生命周期来调用subscribe()和unsubscribe();

我们还是以AddEditTask模块为例,接着分析AddEditTaskPresenter

/**
 * Listens to user actions from the UI ({@link AddEditTaskFragment}), retrieves the data and updates
 * the UI as required.
 */
public class AddEditTaskPresenter implements AddEditTaskContract.Presenter {

    @NonNull
    private final TasksDataSource mTasksRepository;

    @NonNull
    private final AddEditTaskContract.View mAddTaskView;

    @NonNull
    private final BaseSchedulerProvider mSchedulerProvider;

    @Nullable
    private String mTaskId;

    private boolean mIsDataMissing;

    @NonNull
    private CompositeSubscription mSubscriptions;

    /**
     * Creates a presenter for the add/edit view.
     *
     * @param taskId                 ID of the task to edit or null for a new task
     * @param tasksRepository        a repository of data for tasks
     * @param addTaskView            the add/edit view
     * @param shouldLoadDataFromRepo whether data needs to be loaded or not (for config changes)
     */
    public AddEditTaskPresenter(@Nullable String taskId, @NonNull TasksDataSource tasksRepository,
                                @NonNull AddEditTaskContract.View addTaskView, boolean shouldLoadDataFromRepo,
                                @NonNull BaseSchedulerProvider schedulerProvider) {
        mTaskId = taskId;
        mTasksRepository = checkNotNull(tasksRepository);
        mAddTaskView = checkNotNull(addTaskView);
        mIsDataMissing = shouldLoadDataFromRepo;

        mSchedulerProvider = checkNotNull(schedulerProvider, "schedulerProvider cannot be null!");

        mSubscriptions = new CompositeSubscription();
        mAddTaskView.setPresenter(this);
    }

    @Override
    public void subscribe() {
        if (!isNewTask() && mIsDataMissing) {
            populateTask();
        }
    }

    @Override
    public void unsubscribe() {
        mSubscriptions.clear();
    }

    ...

    @Override
    public void populateTask() {
        if (isNewTask()) {
            throw new RuntimeException("populateTask() was called but task is new.");
        }
        mSubscriptions.add(mTasksRepository
                .getTask(mTaskId)
                .subscribeOn(mSchedulerProvider.computation())  //执行在computation线程中
                .observeOn(mSchedulerProvider.ui())     //回调方法执行在主线程中
                .subscribe(
                        // onNext
                        task -> {
                            if (mAddTaskView.isActive()) {
                                mAddTaskView.setTitle(task.getTitle());
                                mAddTaskView.setDescription(task.getDescription());

                                mIsDataMissing = false;
                            }
                        }, // onError
                        __ -> {
                            if (mAddTaskView.isActive()) {
                                mAddTaskView.showEmptyTaskError();
                            }
                        }));
    }

   ...
   
}

CompositeSubscription就是用来持有所有的Subscriptions,然后在生命周期结束(unsubscribe方法)时取消所有的订阅。

mAddTaskView.setPresenter(this);是将Presenter赋值到View中,由View的生命周期管理Presenter的状态:

  • 在View的onResume()方法中调用Presenter的subscribe();
  • 在View的onPause()方法中调用Presenter的unsubscribe();

Observable.subscribe绑定的时候会返回一个Subscription的对象,add到mSubscriptions里面统一管理。

mTasksRepository.getTask(mTaskId)返回的是Observable<Task>。Observable 即被观察者,它决定什么时候触发事件以及触发怎样的事件。

TasksRepository.getTask(taskId)方法:

    /**
     * Gets tasks from local data source (sqlite) unless the table is new or empty. In that case it
     * uses the network data source. This is done to simplify the sample.
     */
    @Override
    public Observable<Task> getTask(@NonNull final String taskId) {
        checkNotNull(taskId);

        final Task cachedTask = getTaskWithId(taskId);

        // Respond immediately with cache if available
        if (cachedTask != null) {
            return Observable.just(cachedTask);
        }

        // Load from server/persisted if needed.

        // Do in memory cache update to keep the app UI up to date
        if (mCachedTasks == null) {
            mCachedTasks = new LinkedHashMap<>();
        }

        // Is the task in the local data source? If not, query the network.
        Observable<Task> localTask = getTaskWithIdFromLocalRepository(taskId);
        Observable<Task> remoteTask = mTasksRemoteDataSource
                .getTask(taskId)
                .doOnNext(task -> {
                    mTasksLocalDataSource.saveTask(task);
                    mCachedTasks.put(task.getId(), task);
                });

        return Observable.concat(localTask, remoteTask).first()
                .map(task -> {
                    if (task == null) {
                        throw new NoSuchElementException("No task found with taskId " + taskId);
                    }
                    return task;
                });
    }

简单描述getTask(taskId)方法的流程:

  1. 在本地数据中查询对应taskId的数据。
  2. 从远程数据中查询对应taskId的数据。
  3. 将获得的本地数据对象和远程数据对象依次发送出来。
  4. 取得满足指定条件的第一个数据。
  5. 将这条数据转换成Task对象。
  6. 返回这个Task对象。

concat:接收若干个Observables,发射数据是有序的,不会交叉。

first:获取源Observable产生的第一个数据或者满足指定条件的第一个数据。

map:事件对象的直接变换。

总结

todo-mvp-rxjava是在todo-mvp的基础上集成了RxJava库,相比之下,从程序架构看来并没有太多的不一样,主要区别还是在Model层对数据操作过程中进行了多次线程调度,使用到了RxJava的响应式编程简化了代码流程。而本文并没有对RxJava做过多的介绍,这也不是本文的初衷。

从RxJava的火爆程度来看,在Android项目架构中引入RxJava,以后或许会成为常态。

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,448评论 25 707
  • 前言我从去年开始使用 RxJava ,到现在一年多了。今年加入了 Flipboard 后,看到 Flipboard...
    占导zqq阅读 9,158评论 6 151
  • 在做播放器类似的软件时,通常会有一个计时的功能,用来表示当前歌曲还剩多久,这里的通常我们肯定是用UILabel来显...
    CoderOne阅读 680评论 0 0
  • 假设有两个表,订单表和产品表,订单跟产品的关系是一对多的关系,那么在JPA中怎样表示一对多的关系呢?实体关系一对多...
    姜小码阅读 17,084评论 0 3
  • 想要获得满意的收入得增加自己的实力和利用正确的方法赚取应得的收入进一步实现财务自由。在这之前千万不要忘了自己...
    杏儿姐阅读 370评论 0 1