9.1 服務是什麼android
服務(Service)是 Android 中實現程序後臺運行的解決方案,它很是適合用於去執行那 些不須要和用戶交互並且還要求長期運行的任務。服務的運行不依賴於任何用戶界面,即便 當程序被切換到後臺,或者用戶打開了另一個應用程序,服務仍然可以保持正常運行。編程
不過須要注意的是,服務並非運行在一個獨立的進程當中的,而是依賴於建立服務 時所在的應用程序進程。當某個應用程序進程被殺掉時,全部依賴於該進程的服務也會停 止運行。安全
另外,也不要被服務的後臺概念所迷惑,實際上服務並不會自動開啓線程,全部的代碼 都是默認運行在主線程當中的。也就是說,咱們須要在服務的內部手動建立子線程,並在這 裏執行具體的任務,不然就有可能出現主線程被阻塞住的狀況。那麼本章的第一堂課,咱們 就先來學習一下關於 Android 多線程編程的知識。服務器
9.2 Android 多線程編程網絡
熟悉 Java 的你,對多線程編程必定不會陌生吧。當咱們須要執行一些耗時操做,好比說發起一條網絡請求時,考慮到網速等其餘緣由,服務器未必會馬上響應咱們的請求,若是多線程
不將這類操做放在子線程裏去運行,就會致使主線程被阻塞住,從而影響用戶對軟件的正常 使用。那麼就讓咱們從線程的基本用法開始學習吧。異步
9.2.1 線程的基本用法ide
Android 多線程編程其實並不比 Java 多線程編程特珠,基本都是使用相同的語法。好比 說,定義一個線程只須要新建一個類繼承自 Thread,而後重寫父類的 run()方法,並在裏面 編寫耗時邏輯便可,以下所示:函數
class MyThread extends Thread {oop
@Override
public void run() {
// 處理具體的邏輯
}
}
那麼該如何啓動這個線程呢?其實也很簡單,只須要 new 出 MyThread 的實例,而後調 用它的 start()方法,這樣 run()方法中的代碼就會在子線程當中運行了,以下所示:
new MyThread().start();
固然,使用繼承的方式耦合性有點高,更多的時候咱們都會選擇使用實現 Runnable 接 口的方式來定義一個線程,以下所示:
class MyThread implements Runnable {
@Override
public void run() {
// 處理具體的邏輯
}
}
若是使用了這種寫法,啓動線程的方法也須要進行相應的改變,以下所示:
MyThread myThread = new MyThread();
new Thread(myThread).start();
能夠看到,Thread 的構造函數接收一個 Runnable 參數,而咱們 new 出的 MyThread 正是 一個實現了 Runnable 接口的對象,因此能夠直接將它傳入到 Thread 的構造函數裏。接着調用 Thread 的 start()方法,run()方法中的代碼就會在子線程當中運行了。固然,若是你不想專門再定義一個類去實現 Runnable 接口,也可使用匿名類的方式, 這種寫法更爲常見,以下所示:
new Thread(new Runnable() {
@Override
public void run() {
// 處理具體的邏輯
}
}).start();
以上幾種線程的使用方式相信你都不會感到陌生,由於在 Java 中建立和啓動線程也是 使用一樣的方式。瞭解了線程的基本用法後,下面咱們來看一下 Android 多線程編程與 Java 多線程編程不一樣的地方。
9.2.2 在子線程中更新 UI
和許多其餘的 GUI 庫同樣,Android 的 UI 也是線程不安全的。也就是說,若是想要更 新應用程序裏的 UI 元素,則必須在主線程中進行,不然就會出現異常。眼見爲實,讓咱們經過一個具體的例子來驗證一下吧。新建一個 AndroidThreadTest 項 目,而後修改 activity_main.xml 中的代碼,以下所示:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" >
<Button android:id="@+id/change_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Change Text" />
<TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="Hello world" android:textSize="20sp" />
</RelativeLayout>
佈局文件中定義了兩個控件,TextView 用於在屏幕的正中央顯示一個 Hello world 字符 串,Button 用於改變 TextView 中顯示的內容,咱們但願在點擊 Button 後能夠把 TextView 中 顯示的字符串改爲 Nice to meet you。
接下來修改 MainActivity 中的代碼,以下所示:
public class MainActivity extends Activity implements OnClickListener {
private TextView text;
private Button changeText;
@Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.text);
changeText = (Button) findViewById(R.id.change_text);
changeText.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.change_text:
new Thread(new Runnable() {
@Override
public void run() {
text.setText("Nice to meet you");
}
}).start();
break;
default:
break;
}
}
}
能夠看到,咱們在 Change Text 按鈕的點擊事件裏面開啓了一個子線程,而後在子線程中調用 TextView 的 setText()方法將顯示的字符串改爲 Nice to meet you。代碼的邏輯很是簡 單,只不過咱們是在子線程中更新 UI 的。如今運行一下程序,並點擊 Change Text 按鈕,你 會發現程序果真崩潰了,如圖 9.1 所示。
圖 9.1
而後觀察 LogCat 中的錯誤日誌,能夠看出是因爲在子線程中更新 UI 所致使的,如圖 9.2所示。
圖 9.2
由此證明了 Android 確實是不容許在子線程中進行 UI 操做的。可是有些時候,咱們必 須在子線程裏去執行一些耗時任務,而後根據任務的執行結果來更新相應的 UI 控件,這該 如何是好呢?
對於這種狀況,Android 提供了一套異步消息處理機制,完美地解決了在子線程中進行 UI 操做的問題。本小節中咱們先來學習一下異步消息處理的使用方法,下一小節中再去分 析它的原理。
修改 MainActivity 中的代碼,以下所示:
public class MainActivity extends Activity implements OnClickListener {
public static final int UPDATE_TEXT = 1;
private TextView text;
private Button changeText;
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_TEXT:
// 在這裏能夠進行UI操做
text.setText("Nice to meet you");
break;
default:
break;
}
}
};
……
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.change_text:
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message); // 將Message對象發送出去
}
}).start();
break;
default:
break;
}
}
}
這裏咱們先是定義了一個整型常量 UPDATE_TEXT,用於表示更新 TextView 這個動做。 而後新增一個 Handler 對象,並重寫父類的 handleMessage 方法,在這裏對具體的 Message 進行處理。若是發現 Message 的 what 字段的值等於 UPDATE_TEXT,就將 TextView 顯示的 內容改爲 Nice to meet you。
下面再來看一下 Change Text 按鈕的點擊事件中的代碼。能夠看到,此次咱們並無在 子線程裏直接進行 UI 操做,而是建立了一個 Message(android.os.Message)對象,並將它 的 what 字段的值指定爲 UPDATE_TEXT ,而後調用 Handler 的 sendMessage() 方法將這條 Message 發送出去。很快,Handler 就會收到這條 Message,並在 handleMessage()方法中對它 進行處理。注意此時 handleMessage()方法中的代碼就是在主線程當中運行的了,因此咱們可 以放心地在這裏進行 UI 操做。接下來對 Message 攜帶的 what 字段的值進行判斷,若是等於 UPDATE_TEXT,就將 TextView 顯示的內容改爲 Nice to meet you。
如今從新運行程序,能夠看到屏幕的正中央顯示着 Hello world。而後點擊一下 ChangeText 按鈕,顯示的內容着就被替換成 Nice to meet you,如圖 9.3 所示。
圖 9.3
這樣你就已經掌握了 Android 異步消息處理的基本用法,使用這種機制就能夠出色地解決掉在子線程中更新 UI 的問題。不過恐怕你對它的工做原理還不是很清楚,下面咱們就來 分析一下 Android 異步消息處理機制究竟是如何工做的。
9.2.3 解析異步消息處理機制
Android 中的異步消息處理主要由四個部分組成,Message、Handler、MessageQueue 和 Looper。其中 Message 和 Handler 在上一小節中咱們已經接觸過了,而 MessageQueue 和 Looper 對於你來講仍是全新的概念,下面我就對這四個部分進行一下簡要的介紹。
1. Message
Message 是在線程之間傳遞的消息,它能夠在內部攜帶少許的信息,用於在不一樣線 程之間交換數據。上一小節中咱們使用到了 Message 的 what 字段,除此以外還可使 用 arg1 和 arg2 字段來攜帶一些整型數據,使用 obj 字段攜帶一個 Object 對象。
2. Handler
Handler 顧名思義也就是處理者的意思,它主要是用於發送和處理消息的。發送消 息通常是使用 Handler 的 sendMessage()方法,而發出的消息通過一系列地展轉處理後, 最終會傳遞到 Handler 的 handleMessage()方法中。
3. MessageQueue
MessageQueue 是消息隊列的意思,它主要用於存放全部經過 Handler 發送的消息。 這部分消息會一直存在於消息隊列中,等待被處理。每一個線程中只會有一個 MessageQueue 對象。
4. Looper
Looper 是每一個線程中的 MessageQueue 的管家,調用 Looper 的 loop()方法後,就會 進入到一個無限循環當中,而後每當發現 MessageQueue 中存在一條消息,就會將它取 出,並傳遞到 Handler 的 handleMessage()方法中。每一個線程中也只會有一個 Looper 對象。 瞭解了 Message、Handler、MessageQueue 以及 Looper 的基本概念後,咱們再來對異步
消息處理的整個流程梳理一遍。首先須要在主線程當中建立一個 Handler 對象,並重寫 handleMessage()方法。而後當子線程中須要進行 UI 操做時,就建立一個 Message 對象,並 經過 Handler 將這條消息發送出去。以後這條消息會被添加到 MessageQueue 的隊列中等待 被處理,而 Looper 則會一直嘗試從 MessageQueue 中取出待處理消息,最後分發回 Handler 的 handleMessage()方法中。因爲 Handler 是在主線程中建立的,因此此時 handleMessage()方 法中的代碼也會在主線程中運行,因而咱們在這裏就能夠安心地進行 UI 操做了。整個異步 消息處理機制的流程示意圖如圖 9.4 所示。
圖 9.4
一條 Message 通過這樣一個流程的展轉調用後,也就從子線程進入到了主線程,從不能 更新 UI 變成了能夠更新 UI,整個異步消息處理的核心思想也就是如此。