使用响应式编程(RxJava)开发Android App

Android app development using the reactive programming paradigm (RxJava)

原作者:Arif Nadeem

如果你已经看过了RxJava或其他的ReactiveX库的点赞数,你一定会同意我的说法:响应式编程的学习曲线很陡峭,而之所以形成这种学习体验,则是因为没有好的学习向导和书籍。

我探究了响应式编程(尤其是RxJava)背后的基本原理。我不想从RxJava的基础知识说起,你可以从这篇博客里找到对此的介绍。我想给你展示的是怎么使用RxJava和RxAndroid开发一个基础的Android App,从中你可以体会到RxJava和RxAndroid带来的便利。

Github源码地址

为了开始在Android应用中使用RxJava,你需要使用以下的库工程:

  1. Retrofit2
  2. RxJava
  3. RxAndroid, RxJava在Android上的扩展库
  4. Gson
  5. Picasso
  6. Retrolambda,让代码更精巧,可读性更好

注意:我在工程里使用了retrolambda,这可能导致你不能直接从Android
Studio构建出apk。原因是Lambda表达式是从Java8开始支持的,而现在的Android还不支持Java8。你可以在gradle
file文件里配置java 8和java 7的路径

对于gradle文件和其他的工程设置请看我的Github工程。

为了展示如何使用上面那些库,我会用OMDB API 完成下面这些任务:

  1. 在用户输入电影或电视剧名字的同时,根据已经输入的部分字符进行匹配,提供建议列表
  2. 当用户点选了某条建议,我们通过一个API查询,显示出对应的电影详情
  3. 当用户点击了键盘上的搜索按钮,我们需要展示所有匹配的电影的详情列表
  4. 允许用户根据类型对结果进行过滤
  5. 允许用户输入多个名字,我们获取所有的结果展示给用户(使用传统的编程方法达成这一任务可不简单)

RxJava 基础:在进一步深入之前,我们要先确认一点,我们要理解在Observable(被观察者)和Subscriber(订阅者)之间的不同。
在响应式编程里,有两个有意思的概念,第一个是Observable(被观察者),第二个是Subscriber(订阅者)或Observer(观察者)。Observable负责做所有的工作,而Subscriber负责监听Observable的不同状态,一个Observable可能完成,也可能失败,这会反应到Subscriber的onComplete函数或者onError函数,还有一个叫onNext的方法,当Observable发出一个事件时它会被调用。

现在我们开始写代码,首先我们要定义一个Retrofit单例

public class RetrofitHelper {

    private static final String BASE_URL = "http://www.omdbapi.com";
    private static RetrofitHelper mRetrofitHelper;
    private Retrofit mRetrofit;

    private RetrofitHelper() {
        mRetrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())                  .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();
    }

    public static RetrofitHelper getInstance() {
        if (mRetrofitHelper == null) {
            synchronized (RetrofitHelper.class) {
                if (mRetrofitHelper == null)
                    mRetrofitHelper = new RetrofitHelper();
            }
        }
        return mRetrofitHelper;
    }

    public Retrofit getRetrofit() {
        return mRetrofit;
    }

}

这里注意,为了引入GsonConverterFactory和RxJavaCallAdapterFactory,需要在build.grable添加下面两行

compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'
compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2'

为了使用Retrofit,我们还需要为我们的API定义下面的接口

public interface OmdbApiInterface {

@GET("/")
Observable<SearchResults> getSearchResults(@Query("s") String query,
                                         @Query("plot") String plot,
                                         @Query("type") String type,
                                         @Query("r") String format);
@GET("/")
Observable<Movie> getMovie(@Query("t") String title,
                               @Query("plot") String plot,
                               @Query("type") String type,
                               @Query("r") String format);

}

第一个API用来根据用户输入的字符搜索匹配的电影列表,第二个API用来根据电影的名字查询到电影的详情。通过使用为RxJava适配的Retrofit2,我们可以方便的从请求得到一个Observable,然后可以对它进行订阅并监听它的状态变化。

现在再看我们怎么实现给用户展示搜索建议列表。

我已经实现了SearchView的OnQueryTextListener,当用户输入两个字符以上时,我开始进行API调用。为了使用RxJava,我们需要定义一个能通过查询字段获取搜索结果的Observable

public Observable<SearchResults> getSearchResultsApi(String query, String type) {
 return apiInterface.getSearchResults(query, "short", type, "json");
}

下一个任务是给上面的Observable写一个Subscriber

private Subscriber<SearchResults> searchResultsSubscriber() {
    return new Subscriber<SearchResults>() {
        @Override
        public void onCompleted() {

        }

        @Override
        public void onError(Throwable e) {
            HttpException exception = (HttpException) e;
            Log.e(MovieSearchFragment.class.getName(), "Error: " + exception.code());
        }

        @Override
        public void onNext(SearchResults searchResults) {
            MatrixCursor matrixCursor = CPUtils.convertResultsToCursor(searchResults.getSearch());
            mSearchViewAdapter.changeCursor(matrixCursor);
        }
    };
}

下面是最后一步了,当用户的输入字符超过2个的时候,我们就要生成这个订阅,把事件发出去

@Override
public boolean onQueryTextChange(String newText) {
    if (newText.length() > 2) {
        try {
            if (searchResultsSubscription != null && !searchResultsSubscription.isUnsubscribed()) {
                //Cancel all ongoing requests and change cursor
                searchResultsSubscription.unsubscribe();
                matrixCursor = CPUtils.convertResultsToCursor(new ArrayList<>());
                mSearchViewAdapter.changeCursor(matrixCursor);
            }
            String encodedQuery = URLEncoder.encode(newText, "UTF-8");
            Observable<SearchResults> observable = mOmdbApiObservables.getSearchResultsApi(encodedQuery, mFilterSelection);
            searchResultsSubscription = observable
                    .debounce(250, TimeUnit.MILLISECONDS)
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribeOn(Schedulers.io())
                    .subscribe(searchResultsSubscriber());
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
    return true;
}

完事大吉了,现在当用户输入时,我们会在下拉列表里展示搜索建议,注意这一行observeOn(AndroidSchedulers.mainThread()),我们使用了RxAndroid,来实现让观察者运行在Android UI线程的目的,我们都知道Android只允许在主线程里更新 。

确保在onDestroy()函数里对订阅解绑

if (searchResultsSubscription != null && !searchResultsSubscription.isUnsubscribed())
    searchResultsSubscription.unsubscribe();

上面的功能很容易实现,现在让我们看一下RxJava最有趣的一个功能:根据我们的需求把数据组合。

当用户点击了搜索按钮,我们应该给用户展示一个所有匹配的电影的详情列表。为了实现这个功能,我们需要对每一个匹配的电影调用getMovie(),在命令式编程范式里,我们需要为每一个请求产生一个线程,等待所有的结果返回时再把他们组合起来,然后再绑定到Adapter上。但是,但是!我们现在有了RxJava,我们得救了。

Observer(译者注: 应该是Observable吧)

public Observable<List<Movie>> getAllMoviesForSearchApi(String query, String type) {
    return apiInterface.getSearchResults(query, "short", type, "json").subscribeOn(Schedulers.newThread())
            .flatMap(searchResults -> Observable.from(searchResults.getSearch() != null ? searchResults.getSearch() : Collections.emptyList()))
            .flatMap(search -> getSingleMovieForTitleApi(search.getTitle(), type)).toList();
}

public Observable<Movie> getSingleMovieForTitleApi(String title, String type) {
    return apiInterface.getMovie(title, "short", type, "json").subscribeOn(Schedulers.newThread());
}

Subscriber 订阅者

private Subscriber<List<Movie>> moviesForSearchSubscriber() {
    return new Subscriber<List<Movie>>() {
        @Override
        public void onCompleted() {
            if (mPd.isShowing())
                mPd.dismiss();
            moviesRecyclerAdapter.notifyDataSetChanged();
        }

        @Override
        public void onError(Throwable e) {
            if (mPd.isShowing())
                mPd.dismiss();
            HttpException exception = (HttpException) e;
            Log.e(MovieSearchFragment.class.getName(), "Error: " + exception.code());
        }

        @Override
        public void onNext(List<Movie> movies) {
            if (movies == null || movies.size() == 0)
                showShortToast("No results, is your title correct?");
            for (Movie m : movies) {
                mMovies.add(m);
            }
        }
    };
}

这里我们首先调用getSearchResults API, 然后对它的调用结果searchResults构建了一个新的Observable,实现对searchResults里每一项调用getSingleMovieForTitleApi;最后把结果组合成一个List在Adapter里使用。subscribeOn()方法使请求在单独的线程执行。

这就是RxJava的神奇,通过四行代码,我们避免了模版代码和令人困惑的多线程语法,实现了开辟最佳的线程数进行高效的调用。(译者注:Schedulers.newThread()为每一个任务创建新的线程,内部用了线程池)

最后我们看一下怎么从多个查询得到结果

Observer(译者注:同上,认为应该是Observable)

public Observable<List<Movie>> getMoviesForMultipleQueries(List<String> queries, String type) {
Observable<List<Movie>> observable = Observable.from(queries).flatMap(query -> getAllMoviesForSearchApi(query.trim(), type)).subscribeOn(Schedulers.newThread());
    return observable;
}

Subscriber

private Subscriber<List<Movie>> moviesForMultiQuerySearchSubscriber() {
    return new Subscriber<List<Movie>>() {
        @Override
        public void onCompleted() {
            if (mPd.isShowing())
                mPd.dismiss();
            moviesRecyclerAdapter.notifyDataSetChanged();
        }

        @Override
        public void onError(Throwable e) {
            if (mPd.isShowing())
                mPd.dismiss();
            HttpException exception = (HttpException) e;
            Log.e(MovieSearchFragment.class.getName(), "Error: " + exception.code());
        }

        @Override
        public void onNext(List<Movie> movies) {
            if (movies == null || movies.size() == 0)
                showShortToast("No results, is your title correct?");
            for (Movie m : movies) {
                mMovies.add(m);
            }
        }
    };
}

多么简单~我们把多个查询词组合成一个列表,然后在每一个查询词上调用getAllMoviesForSearchApi,再把结果组合起来用到Adapter里。

我希望这个向导能清晰地阐明关于响应式编程的许多概念,因为我是个新手,我用RxJava实现的内容可能有更好的方式实现,请在评论里指出。(译者注:这也是毕业后第一次翻译完整的英语文章,有不合适的地方希望得到指正,谢谢)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值