Android RxJava使用介紹(一) Hello World
最近在做東西的時候,一直在使用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!
說了那麼多概念性的東西,可能大家會一頭霧水,下面我們就使用獲取天氣預報的例子來說明吧。
準備工作
-
獲取天氣預報,我們就使用新浪提供的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有了整體認識,最後獻上程式碼和效果圖:
相關文章
- java介紹、環境搭建與Hello,World!Java
- Android RxJava使用介紹(二) RxJava的操作符AndroidRxJava
- Android RxJava使用介紹(三) RxJava的操作符AndroidRxJava
- Android RxJava使用介紹(四) RxJava的操作符AndroidRxJava
- Android RxJava:基礎介紹與使用AndroidRxJava
- Android hello world例項Android
- 使用Android Studio建立第一個Hello World應用程式Android
- Hello, World
- Hello World!
- Hello World
- 3dMax建模筆記(一):介紹3dMax和建立第一個模型Hello world3D筆記模型
- spring boot(一)hello worldSpring Boot
- 第一個程式Hello world
- Go - Hello WorldGo
- Docker Hello WorldDocker
- 【Java】Hello worldJava
- React Hello,WorldReact
- Mockito Hello WorldMockito
- ant Hello World
- Deep "Hello world!"
- Go:Hello WorldGo
- 一篇不太一樣的RxJava介紹RxJava
- react 第一個元件 “hello world!”React元件
- spring boot(一)hello world 搭建Spring Boot
- 第一個ncurses程式: hello world !!!
- 第一個PHP程式——Hello WorldPHP
- Hello Python worldPython
- react的”Hello World !“React
- WebGL 的 Hello WorldWeb
- ABAP程式Hello World
- dotnet hello world
- RabbitMQ tutorial - "Hello world!"MQ
- 輸出hello world
- Android 淺析 RxJava (一) 使用AndroidRxJava
- Rust使用Tauri開發GUI程式——Hello WorldRustGUI
- Android 基於Netty的訊息推送方案之Hello World(一)AndroidNetty
- Flutter Web 之 Hello WorldFlutterWeb
- ROS之初見Hello WorldROS