Android RxJava使用介绍(一)概念

RxJava到底是什么?使用RxJava到底有什么好处呢?其实RxJava是ReactiveX中使用Java语言实现的版本,目前ReactiveX已经实现的语言版本有:

Java: RxJava
JavaScript: RxJS
C#: Rx.NET
C#(Unity): UniRx
Scala: RxScala
Clojure: RxClojure
C++: RxCpp
Ruby: Rx.rb
Python: RxPY
Groovy: RxGroovy
JRuby:RxJRuby
Kotlin: RxKotlin

可以看出ReactiveX在开发应用中如此的火爆。那到底什么是ReactiveX呢?简单来说,ReactiveX就是”观察者模式+迭代器模式+函数式编程”,它扩展了观察者模式,通过使用可观察的对象序列流来表述一系列事件,订阅者进行占点观察并对序列流做出反应(或持久化或输出显示等等);借鉴迭代器模式,对多个对象序列进行迭代输出,订阅者可以依次处理不同的对象序列;使用函数式编程思想(functional programming),极大简化问题解决的步骤。

RxJava的基本概念

RxJava最核心的两个东西就是Observables(被观察者,也就是事件源)和Subscribers(观察者),由Observables发出一系列的事件,Subscribers进行订阅接收并进行处理,看起来就好像是设计模式中的观察者模式,但是跟观察者模式不同的地方就在于,如果没有观察者(即Subscribers),Observables是不会发出任何事件的。

由于Observables发出的事件并不仅限于一个,有可能是多个的,如何确保每一个事件都能发送到Subscribers上进行处理呢?这里就借鉴了设计模式的迭代器模式,对事件进行迭代轮询(next()、hasNext()),在迭代过程中如果出现异常则直接抛出(throws Exceptions),下表是Observable和迭代器(Iterable)的对比:

事件(event) 迭代器(Iterable) Observable
接收数据 T next() onNext(T)
发现错误 throws Exception onError(Exception)
迭代完成 !hasNext() onCompleted()
与迭代器模式不同的地方在于,迭代器模式在事件处理上采用的是“同步/拉式”的方式,而Observable采用的是“异步/推式”的方式,对于Subscriber(观察者)而言,这种方式会更加灵活。

开始准备 Hello World!

说了那么多概念性的东西,可能大家会一头雾水,下面我们就使用获取天气预报的例子来说明吧。

准备工作

  1. 获取天气预报,我们就使用新浪提供的API接口吧,地址如下:
    http://php.weather.sina.com.cn/xml.php?city=%B1%B1%BE%A9&password=DJOYnieT8234jlsK&day=0
    其中,city后的城市转码。
    Password固定
    Day为0表示当天天气,1表示第二天的天气,2表示第三天的天气,以此类推,最大为4

  2. 为了简化代码,使用Retrolamda框架(有时间后面会专门写文章介绍),需要安装JDK8,并且环境变量中需要增加“JAVA8_HOME”变量,如图:
    Android RxJava使用介绍(一)概念_第1张图片

  3. Android Studio版本就用最新的1.2版本+Gradle1.0.0吧。使用Eclipse ADT的朋友,建议赶紧换成Android Studio吧,在android开发上,Android Studio比Eclipse ADT实在是不可同日而语。

环境搭建

首先在Android Studio中新建一个项目,然后修改Project级的build.gradle如下:

buildscript { repositories { jcenter() }
    dependencies { classpath 'com.android.tools.build:gradle:1.0.0' classpath 'me.tatarka:gradle-retrolambda:3.0.1' }
}

allprojects { repositories { jcenter() }
}

module级的build.gradle修改如下:

apply plugin: 'com.android.application'
apply plugin: 'me.tatarka.retrolambda'

retrolambda {
    jdk System.getenv("JAVA8_HOME")
    oldJdk System.getenv("JAVA6_HOME")
    javaVersion JavaVersion.VERSION_1_6
}

android {
    compileSdkVersion 21
    buildToolsVersion "21.1.2"

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    defaultConfig {
        applicationId "com.example.hesc.weather"
        minSdkVersion 10
        targetSdkVersion 21
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.0.0'
    compile 'io.reactivex:rxandroid:0.24.0'
}

tasks.withType(JavaCompile){
    options.encoding="utf-8"
}

开发代码

首先新建布局文件activity_main.xml如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">

    <LinearLayout  android:layout_width="match_parent" android:layout_height="50dp" android:orientation="horizontal" android:background="#FF0000">

        <EditText android:id="@+id/city" android:layout_width="0dp" android:layout_weight="1" android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:paddingLeft="15dp" android:paddingRight="15dp" android:gravity="center_vertical" android:layout_height="match_parent" android:hint="请输入城市" android:background="@drawable/edit_bg"/>
        <TextView android:id="@+id/query" android:layout_width="80dp" android:layout_height="match_parent" android:text="查询" android:gravity="center" android:textColor="#FFFFFF" android:background="@drawable/button_bg" android:layout_gravity="center_vertical"/>
    </LinearLayout>

    <TextView android:id="@+id/weather" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="10dp"/>
</LinearLayout>

布局比较简单,就是一个输入城市的EditText+查询按钮+显示天气情况的TextView,相信朋友们都能看懂哈。

打开MainActivity,在onCreate方法中添加代码:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //获取控件实例
        cityET = (EditText) findViewById(R.id.city);
        queryTV = (TextView) findViewById(R.id.query);
        weatherTV = (TextView) findViewById(R.id.weather);
        //对查询按钮侦听点击事件
        queryTV.setOnClickListener(this);
        weatherTV.setOnTouchListener(this);

    }

代码比较简单,不做过多解析。下面进入重点:通过网络连接获取天气预报,本案例是通过使用新浪提供的API来获取的,首先声明静态变量如下:

/** * 天气预报API地址 */
private static final String WEATHRE_API_URL="http://php.weather.sina.com.cn/xml.php?city=%B1%B1%BE%A9&password=DJOYnieT8234jlsK&day=0";

然后通过网络请求获取天气预报,返回结果是xml文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!-- published at 2016-05-23 23:25:14 -->
<Profiles>
<Weather>
<city>北京</city>
<status1>阵雨</status1>
<status2>阵雨</status2>
<figure1>zhenyu</figure1>
<figure2>zhenyu</figure2>
<direction1>无持续风向</direction1>
<direction2>无持续风向</direction2>
<power1>≤3</power1>
<power2>≤3</power2>
<temperature1>24</temperature1>
<temperature2>13</temperature2>
<ssd>6</ssd>
<tgd1>22</tgd1>
<tgd2>22</tgd2>
<zwx>1</zwx>
<ktk>4</ktk>
<pollution>2</pollution>
<xcz>3</xcz>
<zho></zho>
<diy></diy>
<fas></fas>
<chy>3</chy>
<zho_shuoming>暂无</zho_shuoming>
<diy_shuoming>暂无</diy_shuoming>
<fas_shuoming>暂无</fas_shuoming>
<chy_shuoming>单层薄衫、裤薄型棉衫、长裤、针织长袖衫、长袖T恤。薄型套装、牛仔衫裤、西服套装、薄型夹克</chy_shuoming>
<pollution_l></pollution_l>
<zwx_l>最弱</zwx_l>
<ssd_l>温暖舒适</ssd_l>
<fas_l>暂无</fas_l>
<zho_l>暂无</zho_l>
<chy_l>单衣类</chy_l>
<ktk_l>不需要开启</ktk_l>
<xcz_l>比较适宜</xcz_l>
<diy_l>暂无</diy_l>
<pollution_s>有利于空气污染物扩散</pollution_s>
<zwx_s>紫外线最弱</zwx_s>
<ssd_s>天气状况良好时,多到户外活动,并可适当增加户外活动时间。</ssd_s>
<ktk_s>不需要开启空调</ktk_s>
<xcz_s>洗车后未来2天内没有降水、大风或沙尘天气,比较适宜洗车</xcz_s>
<gm>1</gm>
<gm_l>低发期</gm_l>
<gm_s>天气舒适,不易发生感冒;</gm_s>
<yd>4</yd>
<yd_l>不太适宜</yd_l>
<yd_s>时阴时雨的天气,不太适宜户外运开展运动;</yd_s>
<savedate_weather>2016-05-23</savedate_weather>
<savedate_life>2016-05-23</savedate_life>
<savedate_zhishu>2016-05-23</savedate_zhishu>
<udatetime>2016-05-23 18:20:00</udatetime>
</Weather>
</Profiles>

需要对xml进行解析,我们先创建一个描述天气情况的bean类,如下:

**
     * 天气情况类
     */
    private class Weather{
        /** * 城市 */
        String city;
        /** * 日期 */
        String date;
        /** * 温度 */
        String temperature;
        /** * 风向 */
        String direction;
        /** * 风力 */
        String power;
        /** * 天气状况 */
        String status;

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append("城市:" + city + "\r\n");
            builder.append("日期:" + date + "\r\n");
            builder.append("天气状况:" + status + "\r\n");
            builder.append("温度:" + temperature + "\r\n");
            builder.append("风向:" + direction + "\r\n");
            builder.append("风力:" + power + "\r\n");
            return builder.toString();
        }
    }

然后我们使用Pull的方式解析xml,代码如下:

/** * 解析xml获取天气情况 * @param weatherXml * @return */
    private Weather parseWeather(String weatherXml){
        //采用Pull方式解析xml
        StringReader reader = new StringReader(weatherXml);
        XmlPullParser xmlParser = Xml.newPullParser();
        Weather weather = null;
        try {
            xmlParser.setInput(reader);
            int eventType = xmlParser.getEventType();
            while(eventType != XmlPullParser.END_DOCUMENT){
                switch (eventType){
                    case XmlPullParser.START_DOCUMENT:
                        weather = new Weather();
                        break;
                    case XmlPullParser.START_TAG:
                        String nodeName = xmlParser.getName();
                        if("city".equals(nodeName)){
                            weather.city = xmlParser.nextText();
                        } else if("savedate_weather".equals(nodeName)){
                            weather.date = xmlParser.nextText();
                        } else if("temperature1".equals(nodeName)) {
                            weather.temperature = xmlParser.nextText();
                        } else if("temperature2".equals(nodeName)){
                            weather.temperature += "-" + xmlParser.nextText();
                        } else if("direction1".equals(nodeName)){
                            weather.direction = xmlParser.nextText();
                        } else if("power1".equals(nodeName)){
                            weather.power = xmlParser.nextText();
                        } else if("status1".equals(nodeName)){
                            weather.status = xmlParser.nextText();
                        }
                        break;
                }
                eventType = xmlParser.next();
            }
            return weather;
        } catch(Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            reader.close();
        }
    }

到现在为止,我们已经完成了网络连接获取天气预报的xml,并对xml进行了解析成weather类,其实已经完成了大部分的工作,接下来就是对这几部分工作进行整合,这里就有以下两个问题需要注意的:

开网络连接必须开单独的线程进行处理,否则在4.x以上版本就会报错
对返回的查询结果需要显示到控件上,必须在UI线程中进行
解决这两个问题的方式有很多种办法,最常用的就是AsyncTask或者就直接是Thread+Handler的方式,其实不管哪种方式,我觉得都没有RxJava那样写起来优雅,不信,你看:

/** * 采用普通写法创建Observable * @param city */
    private void observableAsNormal(String city){
        subscription = Observable.create(new Observable.OnSubscribe<Weather>() {
            @Override
            public void call(Subscriber<? super Weather> subscriber) {
                //1.如果已经取消订阅,则直接退出
                if(subscriber.isUnsubscribed()) return;
                try {
                    //2.开网络连接请求获取天气预报,返回结果是xml格式
                    String weatherXml = getWeather(city);
                    //3.解析xml格式,返回weather实例
                    Weather weather = parseWeather(weatherXml);
                    //4.发布事件通知订阅者
                    subscriber.onNext(weather);
                    //5.事件通知完成
                    subscriber.onCompleted();
                } catch(Exception e){
                    //6.出现异常,通知订阅者
                    subscriber.onError(e);
                }
            }
        }).subscribeOn(Schedulers.newThread())    //让Observable运行在新线程中
                .observeOn(AndroidSchedulers.mainThread())   //让subscriber运行在主线程中
                .subscribe(new Subscriber<Weather>() {
                    @Override
                    public void onCompleted() {
                        //对应上面的第5点:subscriber.onCompleted();
                        //这里写事件发布完成后的处理逻辑

                    }

                    @Override
                    public void onError(Throwable e) {
                        //对应上面的第6点:subscriber.onError(e);
                        //这里写出现异常后的处理逻辑
                        Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onNext(Weather weather) {
                        //对应上面的第4点:subscriber.onNext(weather);
                        //这里写获取到某一个事件通知后的处理逻辑
                        if(weather != null)
                            weatherTV.setText(weather.toString());
                    }
                });
    }

RxJava由于使用了多个回调,一开始理解起来可能有点难度,其实多看几遍也就明白了,它的招式套路都是一样的:

  1. 首先就是创建Observable,创建Observable有很多种方式,这里使用了Observable.create的方式;Observable.create()需要传入一个参数,这个参数其实是一个回调接口,在这个接口方法里我们处理开网络请求和解析xml的工作,并在最后通过onNext()、onCompleted()和onError()通知Subscriber(订阅者);
  2. 然后就是调用Observable.subscribe()方法对Observable进行订阅。这里要注意,如果不调用Observable.subscribe()方法,刚才在Observable.create()处理的网络请求和解析xml的代码是不会执行的,这也就解释了本文开头所说的“如果没有观察者(即Subscribers),Observables是不会发出任何事件的”.
  3. 说了那么多,好像也没有开线程处理网络请求啊,这样不会报错吗?别急,认真看上面的代码,我还写了两个方法subscribeOn(Schedulers.newThread())和observeOn(AndroidSchedulers.mainThread()),没错,奥妙就在于此:
    1. subscribeOn(Schedulers.newThread())表示开一个新线程处理Observable.create()方法里的逻辑,也就是处理网络请求和解析xml工作
    2. observeOn(AndroidSchedulers.mainThread())表示subscriber所运行的线程是在UI线程上,也就是更新控件的操作是在UI线程上
    3. 如果这里只有subscribeOn()方法而没有observeOn()方法,那么Observable.create()和subscriber()都是运行在subscribeOn()所指定的线程中;
    4. 如果这里只有observeOn()方法而没有subscribeOn()方法,那么Observable.create()运行在主线程(UI线程)中,而subscriber()是运行在observeOn()所指定的线程中(本例的observeOn()恰好是指定主线程而已)

上面的代码由于使用了多个接口回调,代码看起来并不是那么完美,采用lambda的写法,看起来会更加简洁和优雅,不信,你看:

/** * 采用lambda写法创建Observable * @param city */
    private void observableAsLambda(String city){
        subscription = Observable.create(subscriber->{
                    if(subscriber.isUnsubscribed()) return;
                    try {
                        String weatherXml = getWeather(city);
                        Weather weather = parseWeather(weatherXml);
                        subscriber.onNext(weather);
                        subscriber.onCompleted();
                    } catch(Exception e){
                        subscriber.onError(e);
                    }
                }
        ).subscribeOn(Schedulers.newThread())    //让Observable运行在新线程中
                .observeOn(AndroidSchedulers.mainThread())   //让subscriber运行在主线程中
                .subscribe(
                        weather->{
                            if(weather != null)
                                weatherTV.setText(weather.toString());
                        },
                        e->{
                            Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
                        });
    }

最后一步,就是点击查询按钮时触发上面的代码逻辑:

@Override
    public void onClick(View v) {
        if(v.getId() == R.id.query){
            weatherTV.setText("");
            String city = cityET.getText().toString();
            if(TextUtils.isEmpty(city)){
                Toast.makeText(this, "城市不能为空!", Toast.LENGTH_SHORT).show();
                return;
            }
            //采用普通写法创建Observable
            observableAsNormal(city);
            //采用lambda写法创建Observable
// observableAsLambda(city);
        }
    }

通过上面的例子,相信大家已经对RxJava有了整体认识,最后献上代码和效果图:
Android RxJava使用介绍(一)概念_第2张图片

代码:http://download.csdn.net/detail/job_hesc/8714261

转自:http://blog.csdn.net/job_hesc/article/details/45798307

注意:部分内容有改动。

你可能感兴趣的:(rxjava)