作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
克里斯托弗·阿里奥拉的头像

Christopher Arriola

Christopher是一名移动工程师,拥有7年以上创建原生Android和iOS应用程序的经验.

Previously At

Blockchain.com
Share

如果你是Android开发者,你可能听说过 RxJava. 它是在Android开发中启用响应式编程的讨论最多的库之一. 它被吹捧为简化移动编程中固有的并发/异步任务的首选框架.

但是……什么是RxJava ?它是如何“简化”事情的?

Android的函数式响应式编程:RxJava简介

用RxJava将你的Android从太多的Java线程中解脱出来.

虽然网上已经有很多资源可以解释什么是RxJava, 在本文中,我的目标是向您介绍RxJava的基本知识,特别是它如何适用于Android开发. 我还将给出一些具体的示例和建议,说明如何将其集成到新的或现有的项目中.

Why Consider RxJava?

在其核心,RxJava简化了开发,因为它 提高抽象层次 around threading. That is, 作为开发人员,您不必过于担心如何执行应该在不同线程上执行的操作的细节. 这是特别有吸引力的,因为线程是具有挑战性的正确和, 如果执行不正确, 会导致一些最难调试和修复的bug吗.

Granted, this doesn’t mean RxJava is bulletproof when it comes to threading and it is still important to understand what’s happening behind the scenes; however, RxJava绝对可以使您的生活更轻松.

Let’s look at an example.

网络调用- RxJava vs AsyncTask

假设我们希望通过网络获取数据,并因此更新UI. 实现这一点的一种方法是(1)创建一个内部 AsyncTask subclass in our Activity/Fragment, (2)在后台执行网络操作, (3)获取该操作的结果并在主线程中更新UI.

public class NetworkRequestTask extends AsyncTask {

    private final int userId;

    public NetworkRequestTask(int userId) {
        this.userId = userId;
    }

    @覆盖受保护的用户doInBackground(无效... params) {
        return networkService.getUser(userId);
    }

    onPostExecute(User User) {
        nameTextView.setText(user.getName());
        // ...set other views
    }
}
   
private void onButtonClicked(按钮按钮){
   新NetworkRequestTask (123).execute()
}

虽然这看起来无害,但这种方法有一些问题和局限性. 也就是说,内存/上下文泄漏很容易产生 NetworkRequestTask 是不是一个内部类,因此持有对外部类的隐式引用. 此外,如果我们想在网络调用之后链接另一个长操作该怎么办? We’d have to nest two AsyncTaskS,这会显著降低可读性.

相比之下,RxJava执行网络调用的方法可能看起来像这样:

私人认购;

private void onButtonClicked(按钮按钮){
   订阅=网络服务.getObservableUser(123)
                      .subscribeOn(Schedulers.io())
                      .observeOn (AndroidSchedulers.mainThread())
                      .subscribe(new Action1() {
                          @覆盖公共无效调用(用户用户){
                              nameTextView.setText(user.getName());
                              // ... set other views
                          }
                      });
}

onDestroy() {
   if (subscription != null && !subscription.isUnsubscribed()) {
       subscription.unsubscribe();
   }
   super.onDestroy();
}

Using this approach, 通过保留对返回对象的引用,我们解决了这个问题(由持有外部上下文引用的运行线程引起的潜在内存泄漏) Subscription object. This Subscription 对象绑定到 Activity/Fragment object’s #onDestroy() 方法来保证 Action1#call 操作不会执行 Activity/Fragment needs to be destroyed.

的返回类型 #getObservableUser(...) (i.e. an Observable)与对它的进一步调用联系在一起. 通过这个流畅的API,我们可以解决第二个问题 AsyncTask 这是它允许进一步的网络调用/长操作链. Pretty neat, huh?

让我们更深入地了解一些RxJava概念.

可观察对象、观察者和操作符——RxJava核心的3个0

在RxJava世界中,一切都可以建模为流. 随着时间的推移,流会释放出物品,并且每次释放都可以被消耗/观察到.

If you think about it, 流并不是一个新概念:点击事件可以是一个流, 位置更新可以是一个流, 推送通知可以是一个流, and so on.

在RxJava世界中,一切都可以建模为流.

The stream abstraction is implemented through 3 core constructs which I like to call “the 3 O’s”; namely: the Observable, Observer, and the Operator. The Observable emits items (the stream); and the Observer consumes those items. 可观察对象的发射可以通过链接进一步修改、转换和操纵 Operator calls.

Observable

Observable是RxJava中的流抽象. It is similar to an Iterator 在这种情况下,给定一个序列,它以有序的方式遍历并生成这些项. 然后,消费者可以通过相同的接口消费这些项目, 不管底层的顺序如何.

假设我们想要发射数字1 2 3,按这个顺序. To do so, we can use the Observable#create(OnSubscribe) method.

Observable observable = Observable.create(new Observable.OnSubscribe() {
   @Override public void call(Subscriber subscriber) {
       subscriber.onNext(1);
       subscriber.onNext(2);
       subscriber.onNext(3);
       subscriber.onCompleted();
   }
});

Invoking subscriber.onNext(Integer) 在流中发出一个项目,当流完成发出时, subscriber.onCompleted() is then invoked.

这种创建Observable的方法相当冗长. For this reason, 有一些创建可观察对象实例的方便方法,在几乎所有情况下都是首选.

创建Observable最简单的方法是使用 Observable#just(...). 正如方法名所暗示的那样,它只是发出作为方法参数传递给它的项.

Observable.just(1, 2, 3); // 1, 2, 3 will be emitted, respectively

Observer

Observable流的下一个组件是订阅它的一个(或多个)观察者. 每当流中发生“有趣”的事情时,观察者就会得到通知. 观察者通过以下事件得到通知:

  • Observer#onNext(T) -当从流中发出项时调用
  • 可见# onError (Throwable) -当流中发生错误时调用
  • Observable#onCompleted() -在流完成发送项时调用.

要订阅流,只需调用 Observable#subscribe(...) 并传入一个Observer实例.

Observable observable = Observable.just(1, 2, 3);
observable.subscribe(new Observer() {
   @Override public void onCompleted() {
       Log.d("Test", "In onCompleted()");
   }

   @Override public void onError(Throwable e) {
       Log.d("Test", "In onError()");
   }

   @覆盖公共无效onNext(整数整数){
       Log.d("Test", "In onNext():" + integer);
   }
});

上面的代码将在Logcat中发出以下内容:

In onNext(): 1
In onNext(): 2
In onNext(): 3
In onNext(): 4
In onCompleted()

也可能在某些情况下,我们不再对可观察到的发射感兴趣. 这在Android系统中尤其重要,例如 Activity/Fragment 需要在内存中回收.

要停止观察项,我们只需要调用 订阅#退订() 返回的订阅对象.

订阅订阅= someInfiniteObservable.subscribe(new Observer() {
   @Override public void onCompleted() {
       // ...
   }

   @Override public void onError(Throwable e) {
       // ...
   }

   @覆盖公共无效onNext(整数整数){
       // ...
   }
});

//在适当的时候取消订阅
subscription.unsubscribe();

如上面的代码片段所示, 在订阅一个可观察对象时, 我们保留对返回的Subscription对象的引用,并在稍后调用 订阅#退订() when necessary. 在Android中,这最好在内部调用 Activity#onDestroy() or Fragment#onDestroy().

Operator

Observable发出的项可以被转换, modified, 并在通知订阅的Observer对象之前通过operator进行筛选. 函数式编程中最常见的一些操作(如map、filter、reduce等).)也可以应用于Observable流. 让我们以map为例:

Observable.just(1, 2, 3, 4, 5).map(new Func1() {
   @覆盖公共整数调用(整数整数){
       return integer * 3;
   }
}).subscribe(new Observer() {
   @Override public void onCompleted() {
       // ...
   }

   @Override public void onError(Throwable e) {
       // ...
   }

   @覆盖公共无效onNext(整数整数){
       // ...
   }
});

上面的代码片段将从Observable中获取每个发射,并将每个发射乘以3, producing the stream 3, 6, 9, 12, 15, respectively. 应用操作符通常会返回另一个Observable作为结果, 这是方便的,因为这允许我们链接多个操作来获得期望的结果.

对于上面的流,假设我们只想接收偶数. 这可以通过链接a来实现 filter operation.

Observable.just(1, 2, 3, 4, 5).map(new Func1() {
   @覆盖公共整数调用(整数整数){
       return integer * 3;
   }
}).filter(new Func1() {
   @覆盖公共布尔调用(整数整数){
       return integer % 2 == 0;
   }
}).subscribe(new Observer() {
   @Override public void onCompleted() {
       // ...
   }

   @Override public void onError(Throwable e) {
       // ...
   }

   @覆盖公共无效onNext(整数整数){
       // ...
   }
});

There are RxJava内置了许多操作符 toolset that modify the Observable stream; if you can think of a way to modify the stream, chances are, 这里有一个运算符. 与大多数技术文档不同, 阅读RxJava/ReactiveX文档是相当简单和切题的. 文档中的每个操作符都附带了一个可视化的操作符如何影响流. 这些可视化被称为“大理石图”.”

下面是一个叫做flip的假设算子是如何通过大理石图来建模的:

一个叫做flip的假设算子如何通过弹珠图建模的例子.

使用RxJava实现多线程

控制可观察对象链中发生操作的线程是通过指定 Scheduler 在其中应该出现一个操作符. Essentially, 您可以将Scheduler看作一个线程池, when specified, 操作符将使用并运行. By default, 如果没有提供这样的调度器, Observable链将在同一个线程上运行 Observable#subscribe(...) is called. 否则,可以通过 可见# subscribeOn(调度) and/or 可见# observeOn(调度) 其中计划的操作将发生在调度程序选择的线程上.

这两种方法的关键区别在于 可见# subscribeOn(调度) 指示源Observable应该在哪个Scheduler上运行. 中指定的调度器的线程上继续运行该链 可见# subscribeOn(调度) until a call to 可见# observeOn(调度) 是用不同的调度程序制作的吗. 当发出这样的调用时,从那里开始的所有观察者(例如.e.(链上的后续操作)将在一个线程中接收通知 observeOn Scheduler.

下面是一个大理石图,展示了这些方法如何影响操作的运行位置:

显示这些方法如何影响操作运行位置的大理石图表.

在Android的背景下, 如果UI操作需要作为长时间操作的结果而发生, 我们希望这个操作发生在UI线程上. 为此,我们可以使用 AndroidScheduler # mainThread ()中提供的调度器之一 RxAndroid library.

RxJava on Android

现在我们已经掌握了一些基础知识, 您可能想知道—将RxJava集成到web中的最佳方法是什么 Android application? As you might imagine, RxJava有很多用例,但是, in this example, 让我们来看一个具体的例子:使用Observable对象作为网络栈的一部分.

在这个例子中,我们将看看 Retrofit, 一个由Square开源的HTTP客户端,它内置了与RxJava的绑定,可以与GitHub的API进行交互. Specifically, 我们将创建一个简单的应用程序,为给定GitHub用户名的用户显示所有带星号的存储库. 如果您想跳过,源代码是可用的 here.

创建一个新的Android项目

  • 首先创建一个新的Android项目并命名它 GitHubRxJava.

屏幕截图:创建一个新的Android项目

  • In the Target Android Devices screen, keep Phone and Tablet 选择并设置最小SDK级别为17. 您可以随意将其设置为较低/较高的API级别,但对于本例,API级别17就足够了.

屏幕截图:目标Android设备屏幕

  • Select Empty Activity in the next prompt.

屏幕截图:添加一个活动到移动屏幕

  • 在最后一步中,保持Activity Name为 MainActivity 并生成一个布局文件 activity_main.

屏幕截图:自定义活动屏幕

Project Set-Up

Include RxJava, RxAndroid, and the Retrofit library in app/build.gradle. 注意,包括RxAndroid也隐式地包括RxJava. It is best practice, however, 总是显式地包含这两个库,因为RxAndroid并不总是包含最新版本的RxJava. 显式地包含最新版本的RxJava保证使用最新版本.

dependencies {
    compile 'com.squareup.retrofit2: adapter-rxjava: 2.1.0'
    compile 'com.squareup.retrofit2: converter-gson: 2.1.0'
    compile 'com.squareup.retrofit2:retrofit:2.1.0'
    compile 'io.reactivex:rxandroid:1.2.0'
    compile 'io.reactivex:rxjava:1.1.8'
    // ...other dependencies
}

Create Data Object

Create the GitHubRepo data object class. 这个类封装了GitHub中的存储库(网络响应包含更多数据,但我们只对其中的一个子集感兴趣).

公共类GitHubRepo {

    public final int id;
    public final String name;
    public final String htmlUrl;
    public final String description;
    public final String语言;
    public final int stargazersCount;

    public GitHubRepo(int id, String name, String htmlUrl, String description, String language, int stargazersCount) {
        this.id = id;
        this.name = name;
        this.htmlUrl = htmlUrl;
        this.Description =描述;
        this.language = language;
        this.stargazersCount = stargazersCount;
    }
}

Set-Up Retrofit

  • Create the GitHubService interface. 我们将把这个接口传递给Retrofit, Retrofit将创建一个实现 GitHubService.
    GitHubService公共接口
        @GET("users/{user}/starred") Observable> getStarredRepositories(@Path("user") String userName);
    }
  • Create the GitHubClient class. 这将是我们将与之交互的对象,以便从UI级别进行网络调用.

    • 的实现时 GitHubService 通过Retrofit,我们需要通过一个 RxJavaCallAdapterFactory 作为调用适配器,以便网络调用可以返回Observable对象(任何返回结果的网络调用都需要传递一个调用适配器,而不是一个 Call).

    • 我们还需要传入a GsonConverterFactory so that we can use Gson 作为将JSON对象封送到Java对象的一种方式.

    公共类GitHubClient {

        GITHUB_BASE_URL = "http://api . net ".github.com/";

        私有静态GitHubClient实例;
        private GitHubService;

        private GitHubClient() {
            final Gson gson =
                new GsonBuilder().setFieldNamingPolicy (FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
            final Retrofit = new Retrofit.Builder().baseUrl(GITHUB_BASE_URL)
                                                            .addCallAdapterFactory (RxJavaCallAdapterFactory.create())
                                                            .addConverterFactory (GsonConverterFactory.create(gson))
                                                            .build();
            gitHubService = retrofit.create(GitHubService.class);
        }

        GitHubClient getInstance() {
            if (instance == null) {
                instance = new GitHubClient();
            }
            return instance;
        }

        public Observable> getStarredRepos(@NonNull String userName) {
            return gitHubService.getStarredRepositories(用户名);
        }
    }

Set-Up Layouts

接下来,创建一个简单的UI,在给定输入GitHub用户名的情况下显示检索到的repos. Create activity_home.xml -我们活动的布局-像下面这样:




    

    

        

        

Create item_github_repo.xml - the ListView GitHub存储库对象的项目布局-类似于以下内容:




    

    

    

    


Glue Everything Together

Create a ListAdapter 它负责绑定 GitHubRepo objects into ListView items. 这个过程本质上涉及通货膨胀 item_github_repo.xml into a View if no recycled View is provided; otherwise, a recycled View 重复使用,以防止过度膨胀太多 View objects.

公共类GitHubRepoAdapter扩展BaseAdapter

    private List gitHubRepos = new ArrayList<>();

    @Override public int getCount() {
        return gitHubRepos.size();
    }

    @Override public GitHubRepo getItem(int position) {
        if (position < 0 || position >= gitHubRepos.size()) {
            return null;
        } else {
            return gitHubRepos.get(position);
        }
    }

    @Override public long getItemId(int position) {
        return position;
    }

    @Override public View getView(int position, View convertView, ViewGroup parent) {
        View = (convertView != null ? convertView: createView(parent));
        final GitHubRepoViewHolder viewHolder = (GitHubRepoViewHolder)视图.getTag();
        viewHolder.setGitHubRepo (getItem(位置));
        return view;
    }

    public void setGitHubRepos(@Nullable List repos) {
        if (repos == null) {
            return;
        }
        gitHubRepos.clear();
        gitHubRepos.addAll(repos);
        notifyDataSetChanged();
    }

    private View createView(ViewGroup父视图){
        final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        final View = inflater.inflate(R.layout.Item_github_repo, parent, false);
        final GitHubRepoViewHolder viewHolder = new GitHubRepoViewHolder(view);
        view.setTag(viewHolder);
        return view;
    }

    私有静态类GitHubRepoViewHolder {

        私有TextView texttreponame;
        private TextView texttrepodescription;
        私有TextView textLanguage;
        private TextView textStars;

        公共GitHubRepoViewHolder(视图视图){
            TextView = (TextView)视图.findViewById(R.id.text_repo_name);
            texttrepodescription = (TextView)视图.findViewById(R.id.text_repo_description);
            textLanguage = (TextView)视图.findViewById(R.id.text_language);
            textStars = (TextView)视图.findViewById(R.id.text_stars);
        }

        public void setGitHubRepo(GitHubRepo) {
            textRepoName.setText(gitHubRepo.name);
            textRepoDescription.setText(gitHubRepo.description);
            textLanguage.setText(“语言:”+ gitHubRepo.language);
            textStars.setText(“Stars:”+ gitHubRepo.stargazersCount);
        }
    }
}

把所有东西粘在一起 MainActivity. This is essentially the Activity 这在我们第一次启动应用时显示. In here, 我们要求用户输入他们的GitHub用户名, and finally, 按该用户名显示所有带星号的存储库.

MainActivity扩展appcompactivity

    TAG = MainActivity.class.getSimpleName();
    private GitHubRepoAdapter adapter = new GitHubRepoAdapter();
    私人认购;

    onCreate(Bundle savedInstanceState) {
        super.onCreate (savedInstanceState);
        setContentView(R.layout.activity_main);

        ListView = (ListView) findViewById(R.id.list_view_repos);
        listView.setAdapter(adapter);

        最后EditText editTextUsername = (EditText) findViewById(R.id.edit_text_username);
        最终按钮buttonSearch =(按钮)findViewById(R.id.button_search);
        buttonSearch.setOnClickListener(新视图.OnClickListener() {
            @覆盖公共无效onClick(视图v) {
                用户名= editTextUsername.getText().toString();
                if (!TextUtils.isEmpty(username)) {
                    getStarredRepos(用户名);
                }
            }
        });
    }

    onDestroy() {
        if (subscription != null && !subscription.isUnsubscribed()) {
            subscription.unsubscribe();
        }
        super.onDestroy();
    }

    private void getStarredRepos(String username) {
        订阅= GitHubClient.getInstance()
                                   .getStarredRepos(用户名)
                                   .subscribeOn(Schedulers.io())
                                   .observeOn (AndroidSchedulers.mainThread())
                                   .subscribe(new Observer>() {
                                       @Override public void onCompleted() {
                                           Log.d(TAG, "In onCompleted()");
                                       }

                                       @Override public void onError(Throwable e) {
                                           e.printStackTrace();
                                           Log.d(TAG, "In onError()");
                                       }

                                       @Override public void onNext(List gitHubRepos) {
                                           Log.d(TAG, "In onNext()");
                                           adapter.setGitHubRepos (gitHubRepos);
                                       }
                                   });
    }
}

Run the App

运行应用程序应该会出现一个屏幕,上面有一个输入框,可以输入GitHub用户名. 然后搜索应该显示所有带星号的仓库列表.

该应用程序的屏幕截图,显示了所有带星号的仓库列表.

Conclusion

我希望这篇文章能够成为对RxJava的有用介绍和对其基本功能的概述. 在RxJava中有大量强大的概念,我强烈建议您通过更深入地研究文档丰富的概念来探索它们 RxJava wiki.

欢迎在下面的评论框中留下任何问题或评论. 你也可以在Twitter上关注我 @arriolachris 我在twitter上发了很多关于RxJava和Android的东西.

如果您想了解有关RxJava的全面学习资源,可以查看 ebook 我和Angus Huang在Leanpub上合作过.

聘请Toptal这方面的专家.
Hire Now
克里斯托弗·阿里奥拉的头像
Christopher Arriola

Located in Berkeley, United States

Member since May 4, 2016

About the author

Christopher是一名移动工程师,拥有7年以上创建原生Android和iOS应用程序的经验.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Previously At

Blockchain.com

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

Toptal Developers

Join the Toptal® community.