Quantcast
Viewing all articles
Browse latest Browse all 767

Android RxJava使用介绍(一) Hello World

Android RxJava使用介绍(一) Hello World

分类: Android 860人阅读 评论(4) 收藏 举报

最近在做东西的时候,一直在使用RxJava框架,越是深入了解RxJava,就越觉得这个框架威力实在是太大了。好东西不能一个人独自享受,后面几篇文章我会由浅入深来介绍一下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”变量,如图:
    Image may be NSFW.
    Clik here to view.
    这里写图片描述
  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=%s&password=DJOYnieT8234jlsK&day=0";

然后通过开HttpURLConnection连接获取天气预报,如下:

/**
     * 获取指定城市的天气情况
     * @param city
     * @return
     * @throws
     */
    private String getWeather(String city) throws Exception{
        BufferedReader reader = null;
        HttpURLConnection connection=null;
        try {
            String urlString = String.format(WEATHRE_API_URL, URLEncoder.encode(city, "GBK"));
            URL url = new URL(urlString);
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setReadTimeout(5000);
            //连接
            connection.connect();

            //处理返回结果
            reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"));
            StringBuffer buffer = new StringBuffer();
            String line="";
            while(!TextUtils.isEmpty(line = reader.readLine()))
                buffer.append(line);
            return buffer.toString();
        } finally {
            if(connection != null){
                connection.disconnect();
            }
            if(reader != null){
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

代码也比较简单,就是通过打开HttpURLConnection连接,根据城市名通过GET方式获取查询结果,由于使用了网络连接,别忘了在AndroidManfest.xml中申请使用网络连接权限:

    <!--申请网络访问权限-->
    <uses-permission android:name="android.permission.INTERNET"/>

通过网络连接请求返回的结果是xml文件,需要对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()),没错,奥妙就在于此:
    3.1 subscribeOn(Schedulers.newThread())表示开一个新线程处理Observable.create()方法里的逻辑,也就是处理网络请求和解析xml工作
    3.2 observeOn(AndroidSchedulers.mainThread())表示subscriber所运行的线程是在UI线程上,也就是更新控件的操作是在UI线程上
    3.3 如果这里只有subscribeOn()方法而没有observeOn()方法,那么Observable.create()和subscriber()都是运行在subscribeOn()所指定的线程中;
    3.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有了整体认识,最后献上代码和效果图:

Image may be NSFW.
Clik here to view.
这里写图片描述

源代码下载


Image may be NSFW.
Clik here to view.
Image may be NSFW.
Clik here to view.

Viewing all articles
Browse latest Browse all 767

Trending Articles