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 getSearchResults(@Query("s") String query,                                         @Query("plot") String plot,                                         @Query("type") String type,                                         @Query("r") String format);@GET("/")Observable 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 searchResultsSubscriber() {    return new Subscriber() {        @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个的时候,我们就要生成这个订阅,把事件发出去

@Overridepublic 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 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> moviesForSearchSubscriber() {    return new Subscriber>() {        @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 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> moviesForMultiQuerySearchSubscriber() {    return new Subscriber>() {        @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 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. SpringBoot 2.0 中 HikariCP 数据库连接池原理解析
  2. Android(安卓)4.2 Ethernet启动流程
  3. Android(安卓)6.0 构建更高质量的应用
  4. [Android]ActivityUnitTestCase解释
  5. android wifiservice enable流程
  6. Android(安卓)使用全局变量
  7. android面试总结加强再加强版
  8. Android四种模式里隐含的哲学
  9. Android用户界面开发:TabHost

随机推荐

  1. Go 和 Android 集成实战
  2. android仿网易云音乐引导页、仿书旗小说F
  3. android windowManager
  4. 【Android 内存优化】Bitmap 内存缓存 (
  5. Android中Intent,service,broadcast应用浅
  6. Android漏洞挖掘工具收集与整理
  7. 范例解析:学习Android的IPC主板模式
  8. android 字节数据的转换与处理
  9. 是时候让 Android Tools 属性拯救你了
  10. flutter系列之flutter工程如何与android