Android RxJava使用介绍(一) Hello World
来源:互联网 发布:太平洋国立大学知乎 编辑:程序博客网 时间:2024/04/28 09:20
最近在做东西的时候,一直在使用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)的对比:
与迭代器模式不同的地方在于,迭代器模式在事件处理上采用的是“同步/拉式”的方式,而Observable采用的是“异步/推式”的方式,对于Subscriber(观察者)而言,这种方式会更加灵活。
开始准备 Hello World!
说了那么多概念性的东西,可能大家会一头雾水,下面我们就使用获取天气预报的例子来说明吧。
准备工作
获取天气预报,我们就使用新浪提供的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为了简化代码,使用Retrolamda框架(有时间后面会专门写文章介绍),需要安装JDK8,并且环境变量中需要增加“JAVA8_HOME”变量,如图:
- 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由于使用了多个回调,一开始理解起来可能有点难度,其实多看几遍也就明白了,它的招式套路都是一样的:
- 首先就是创建Observable,创建Observable有很多种方式,这里使用了Observable.create的方式;Observable.create()需要传入一个参数,这个参数其实是一个回调接口,在这个接口方法里我们处理开网络请求和解析xml的工作,并在最后通过onNext()、onCompleted()和onError()通知Subscriber(订阅者);
- 然后就是调用Observable.subscribe()方法对Observable进行订阅。这里要注意,如果不调用Observable.subscribe()方法,刚才在Observable.create()处理的网络请求和解析xml的代码是不会执行的,这也就解释了本文开头所说的“如果没有观察者(即Subscribers),Observables是不会发出任何事件的”
- 说了那么多,好像也没有开线程处理网络请求啊,这样不会报错吗?别急,认真看上面的代码,我还写了两个方法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有了整体认识,最后献上代码和效果图:
源代码下载
- Android RxJava使用介绍(一) Hello World
- Android RxJava使用介绍(一) Hello World
- Android RxJava使用介绍(一) Hello World
- Android开发历程(一) hello world
- RxJava Hello World
- RxJava 之 Hello World
- RabbitMQ 学习笔记(一):简单介绍及"Hello World"
- Android RxJava使用介绍(二) RxJava的操作符
- Android RxJava使用介绍(三) RxJava的操作符
- Android RxJava使用介绍(四) RxJava的操作符
- Android RxJava使用介绍(二) RxJava的操作符
- Android RxJava使用介绍(三) RxJava的操作符
- Android RxJava使用介绍(四) RxJava的操作符
- Android RxJava使用介绍(二) RxJava的操作符
- Android RxJava使用介绍(二) RxJava的操作符
- Android RxJava使用介绍(三) RxJava的操作符
- Android RxJava使用介绍(四) RxJava的操作符
- Android RxJava使用介绍(三) RxJava的操作符
- FusionCharts 用法心得
- [Anim]安卓页面交互淡入淡出Animation
- Educational Codeforces Round 11-D. Number of Parallelograms
- 关于 h5页调用本地app
- telnet指令
- Android RxJava使用介绍(一) Hello World
- 编写高质量的代码——从命名入手
- RSA"公钥加密算法"详解
- 相机手动对焦(带动画效果)
- Java设置session超时(失效)的三种方式
- 欢迎使用CSDN-markdown编辑器
- Django项目部署 部署一个博客
- Linxu下Android Studio不能输入中文的解决办法
- C#——进程、应用程序域与上下文之间的关系