android 多線程技術(shù)應(yīng)用
這個(gè)案例中,屏幕啟動(dòng)之后,進(jìn)入如圖所示的界面。
屏幕上有一個(gè)文本框用于顯示逝去的時(shí)間,此外還有一個(gè)“停止計(jì)時(shí)”按鈕。案例的用例圖如圖所示。

能夠在屏幕上“實(shí)時(shí)地顯示”時(shí)間的流逝,單線程程序是無法實(shí)現(xiàn)的,必須要多線程程序才可以實(shí)現(xiàn),即便有些計(jì)算機(jī)語言可以通過封裝好的類實(shí)現(xiàn)這一功能,但從本質(zhì)上講這些封裝好的類就是封裝了一個(gè)線程。
綜上所述,完成本案例用到的知識(shí)及技術(shù)如下:
1)進(jìn)程和線程的概念;
2)Java中的線程,在Java中創(chuàng)建線程的方式;
3)Android中的線程,包括:Message、Handler、Looper和HandlerThread等概念。
線程究竟是什么?在Windows操作系統(tǒng)出現(xiàn)之前,個(gè)人計(jì)算機(jī)上的操作系統(tǒng)都是單任務(wù)系統(tǒng),只有在大型計(jì)算機(jī)上才具有多任務(wù)和分時(shí)設(shè)計(jì)。Windows、Linux操作系統(tǒng)的出現(xiàn),把原本只在大型計(jì)算機(jī)才具有的優(yōu)點(diǎn),帶到了個(gè)人計(jì)算機(jī)系統(tǒng)中。
進(jìn)程概念
一般可以在同一時(shí)間內(nèi)執(zhí)行多個(gè)程序的操作系統(tǒng)都有進(jìn)程的概念。一個(gè)進(jìn)程就是一個(gè)執(zhí)行中的程序,而每一個(gè)進(jìn)程都有自己獨(dú)立的一塊內(nèi)存空間、一組系統(tǒng)資源。在進(jìn)程的概念中,每一個(gè)進(jìn)程的內(nèi)部數(shù)據(jù)和狀態(tài)都是完全獨(dú)立的。在Windows操作系統(tǒng)下我們可以通過〈Ctrl+Alt+Del〉組合鍵查看進(jìn)程,在UNIX和Linux操作系統(tǒng)下是通過PS命令查看進(jìn)程的。打開Windows當(dāng)前運(yùn)行的進(jìn)程,如圖所示。

在Windows操作系統(tǒng)中一個(gè)進(jìn)程就是一個(gè)exe或dll程序,它們相互獨(dú)立,互相也可以通信,在Android操作系統(tǒng)中進(jìn)程間的通信應(yīng)用也是很多的。
線程概念
多線程指的是在單個(gè)程序中可以同時(shí)運(yùn)行多個(gè)不同的線程,執(zhí)行不同的任務(wù)。多線程意味著一個(gè)程序的多行語句可以看上去幾乎在同一時(shí)間內(nèi)同時(shí)運(yùn)行。
線程與進(jìn)程相似,是一段完成某個(gè)特定功能的代碼,是程序中單個(gè)順序的流控制。但與進(jìn)程不同的是,同類的多個(gè)線程共享一塊內(nèi)存空間和一組系統(tǒng)資源,所以系統(tǒng)在各個(gè)線程之間切換時(shí),資源占用要比進(jìn)程小得多,正因如此,線程也被稱為輕量級(jí)進(jìn)程。一個(gè)進(jìn)程中可以包含多個(gè)線程。圖所示是計(jì)時(shí)器程序進(jìn)程和線程之間的關(guān)系,主線程負(fù)責(zé)管理子線程,即子線程的啟動(dòng)、掛起、停止等操作。

Java中的線程
Java的線程類是java.lang.Thread類。當(dāng)生成一個(gè)Thread類的對(duì)象之后,一個(gè)新的線程就產(chǎn)生了。Java中每個(gè)線程都是通過某個(gè)特定Thread對(duì)象的方法run()來完成其操作的,方法run( )稱為線程體。
下面是構(gòu)建線程類幾種常用的方法:
public Thread()
public Thread(Runnable target)
public Thread(Runnable target, String name)
public Thread(String name)
參數(shù)target是一個(gè)實(shí)現(xiàn)Runnable接口的實(shí)例,它的作用是實(shí)現(xiàn)線程體的run()方法。目標(biāo)target可為null,表示由本身實(shí)例來執(zhí)行線程。name參數(shù)指定線程名字,但沒有指定的構(gòu)造方法,線程的名字是JVM分配的,例如JVM指定為thread-1、thread-2等名字。
1、Java中的實(shí)現(xiàn)線程體方式1
在Java中有兩種方法實(shí)現(xiàn)線程體:一是繼承線程類Thread,二是實(shí)現(xiàn)接口Runnable。下面我們先看看繼承線程類Thread方式。
如果采用第1種方式,它繼承線程類Thread并重寫其中的方法 run(),在初始化這個(gè)類實(shí)例的時(shí)候,目標(biāo)target可為null,表示由本實(shí)例來執(zhí)行線程體。由于Java只支持單重繼承,用這種方法定義的類不能再繼承其他父類,例如代碼清單如圖:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class textThread extends Thread {
boolean flag = true;
int timer = 0;
@Override
public void run() {
super.run();
try {
while (flag) {
this.currentThread().sleep(1000);
timer++;
System.out.print(timer);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
textThread thread = new textThread();
thread.start();
System.out.print("啟動(dòng)計(jì)時(shí)器...");
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
try {
String line = reader.readLine();
if(line.equalsIgnoreCase("1")){
//thread.stop();
thread.flag = false;
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
在main主方法中通過new textThread()創(chuàng)建子線程,并通過thread.start()方法啟動(dòng)子線程,main主方法所在線程為主線程,主線程負(fù)責(zé)管理其他的子線程。本例進(jìn)程、主線程和子線程之間的關(guān)系如圖所示。
子線程啟動(dòng)之后就開始調(diào)用run()方法,run()是一個(gè)線程體,我們?cè)谧泳€程中處理事情就是在這里編寫代碼實(shí)現(xiàn)的。本案例中子線程要做的事情就是:休眠1s,計(jì)時(shí)器加1,再反復(fù)執(zhí)行。Thread.currentThread().sleep(1000)就是休眠1s。
為了能夠停止線程,我們?cè)谥骶€程中增加了一個(gè)標(biāo)識(shí),通過在控制臺(tái)輸入一個(gè)字符
“1”來改變?cè)摌?biāo)識(shí)t1.isRunning = false,從而結(jié)束這個(gè)線程。

注意:
事實(shí)上線程中有一個(gè)stop()方法也可以停止線程,但是由于這種方法會(huì)產(chǎn)生線程死鎖問題,所以在新版JDK中已經(jīng)廢止了,它的替代解決方法就是增加標(biāo)識(shí),就是我們?cè)诒纠胁捎玫姆桨浮?
很多人覺得線程難理解,主要有兩個(gè)問題:
線程休眠,既然線程已經(jīng)休眠了,程序的運(yùn)行速度還能提高嗎?
線程體一般都進(jìn)行死循環(huán),既然線程死循環(huán),程序就應(yīng)該死掉了,就會(huì)沒有反應(yīng)。
1.關(guān)于線程休眠問題
對(duì)線程休眠問題頭痛的讀者,其實(shí)還是在用單線程的思維模式考慮問題,多數(shù)情況下我們的PC都是單CPU的,某個(gè)時(shí)間點(diǎn)只能有一個(gè)線程運(yùn)行。所謂多線程就是多個(gè)線程交替執(zhí)行就好像同時(shí)運(yùn)行似的。因此,休眠當(dāng)前線程可以交出CPU控制權(quán),讓其他的線程有機(jī)會(huì)運(yùn)行,多個(gè)線程之間只有交替運(yùn)行效率才是最高的,這就像我們開車過十字路口,只有我等等,讓你先過,你再等等讓他先過,才能保證最高效率,否則就會(huì)造成交通系統(tǒng)崩潰,對(duì)線程情況也是一樣的。因此,多線程中線程的休眠是程序運(yùn)行的最有效方式。
2.關(guān)于線程體死循環(huán)問題
在單線程中如果是死循環(huán),程序應(yīng)就會(huì)死掉,沒有反應(yīng),但是多線程中線程體(run方法)中的死循環(huán),可以保證線程一直運(yùn)行,如果不循環(huán)線程,則運(yùn)行一次就停止了。在上面的例子中線程體運(yùn)行死循環(huán),可以保證線程一直運(yùn)行,每次運(yùn)行都休眠1s,然后喚醒,再然后把時(shí)間信息輸出到控制臺(tái)。所以,線程體死循環(huán)是保證子線程一直運(yùn)行的前提。由于是子線程它不會(huì)堵塞主線程,就不會(huì)感覺到程序死掉了。但是需要注意的是有時(shí)我們確實(shí)執(zhí)行一次線程體,就不需要循環(huán)了。
程序運(yùn)行后開始啟動(dòng)線程,線程啟動(dòng)后就計(jì)算逝去的時(shí)間,每過1s將結(jié)果輸出到控制臺(tái)。當(dāng)輸入1字符后線程停止,程序終止。如圖所示。

Java中的實(shí)現(xiàn)線程體方式2
上面介紹繼承Thread方式實(shí)現(xiàn)線程體,下面介紹另一種方式,這種方式是提供一個(gè)實(shí)現(xiàn)接口Runnable的類作為一個(gè)線程的目標(biāo)對(duì)象,構(gòu)造線程時(shí)有兩個(gè)帶有Runnable target參數(shù)的構(gòu)造方法:
Thread(Runnable target);
Thread(Runnable target, String name)。
其中的target就是線程目標(biāo)對(duì)象了,它是一個(gè)實(shí)現(xiàn)Runnable的類,在構(gòu)造Thread類時(shí)候把目標(biāo)對(duì)象(實(shí)現(xiàn)Runnable的類)傳遞給這個(gè)線程實(shí)例,由該目標(biāo)對(duì)象(實(shí)現(xiàn)Runnable的類)提供線程體run()方法。這時(shí)候?qū)崿F(xiàn)接口Runnable的類仍然可以繼承其他父類。
請(qǐng)參看代碼清單,這是一個(gè)Java AWT的窗體應(yīng)用程序。
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class textThread2 extends Frame implements Runnable, ActionListener {
public Label label;
public Button button;
public Thread ClockThread;
public boolean isRunning;
public int timer = 0;
public textThread2() {
button = new Button("停止計(jì)時(shí)器");
label = new Label("計(jì)算器啟動(dòng)");
button.addActionListener(this);
setLayout(new BorderLayout());
add(button, "North");
add(label, "Center");
setSize(320, 480);
setVisible(true);
// textThread2 textThread2 = new textThread2();
ClockThread = new Thread(this);
ClockThread.start();
isRunning = true;
}
@Override
public void actionPerformed(ActionEvent e) {
isRunning = false;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
while (isRunning) {
Thread.currentThread().sleep(1000);
timer++;
label.setText("逝去了" + timer);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
textThread2 textThread2 = new textThread2();
}
}
其中關(guān)于Java AWT知識(shí)就不在這里介紹了,有興趣的讀者可以自己看看相關(guān)書籍。在本例中構(gòu)建AWT窗體的應(yīng)用程序方式是繼承Frame類。采用第1種方式——繼承方式實(shí)現(xiàn)線程體是不可以的,因?yàn)镴ava是單繼承的,這個(gè)類不能既繼承Frame又繼承Thread。應(yīng)該采用第2種方式——實(shí)現(xiàn)Runnable接口方式。Runnable接口也有一個(gè)run()方法,它是實(shí)現(xiàn)線程體方法,其代碼處理與上一節(jié)是一樣。需要注意的是,在第2種方法中,創(chuàng)建了一個(gè)Thread成員變量clockThread,才用構(gòu)造方法new Thread(this)創(chuàng)建一個(gè)線程對(duì)象,其中創(chuàng)建線程使用的構(gòu)造方法是Thread(Runnable target),其中的this就是代表本實(shí)例,它是一個(gè)實(shí)現(xiàn)了Runnable接口的實(shí)現(xiàn)類。
程序運(yùn)行結(jié)果如圖所示,屏幕開始加載的時(shí)候線程啟動(dòng)開始計(jì)算時(shí)間,1s更新一次UI,當(dāng)單擊“結(jié)束計(jì)時(shí)”按鈕時(shí),停止計(jì)時(shí)。

Java中的實(shí)現(xiàn)線程體方式3
實(shí)現(xiàn)線程體方式3是實(shí)現(xiàn)線程體方式2的變種,本質(zhì)上還是實(shí)現(xiàn)線程體方式2,但是在Android應(yīng)用開發(fā)中經(jīng)常采用第3種方式。下面我們看第3種方式的計(jì)時(shí)器代碼清單.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class TextThread3 extends Frame implements ActionListener {
private Button button;
private Label label;
private Thread clockThread;
private int timer = 0;
private boolean isRunning = true;
public TextThread3() {
button = new Button("停止計(jì)時(shí)");
label = new Label("計(jì)時(shí)器開始。。。");
button.addActionListener(this);
setLayout(new BorderLayout());
add(button, "North");
add(label, "Center");
setSize(320, 480);
setVisible(true);
clockThread = new Thread(new Runnable() {
@Override
public void run() {
while (isRunning) {
try {
Thread.currentThread().sleep(1000);
timer ++;
label.setText("逝去了:"+timer);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
clockThread.start();
isRunning = true;
}
@Override
public void actionPerformed(ActionEvent e) {
isRunning = false;
}
/**
* @param args
*/
public static void main(String[] args) {
TextThread3 thread3 = new TextThread3();
}
}
與第2種方式比較,我們發(fā)現(xiàn)Frame類不再實(shí)現(xiàn)Runnable接口了,而是在實(shí)例化Thread類的時(shí)候,定義了一個(gè)實(shí)現(xiàn)Runnable接口的匿名內(nèi)部類:
clockThread = new Thread(new Runnable() {
@Override
public void run() {
while (isRunning) {
try {
Thread.currentThread().sleep(1000);
timer ++;
label.setText("逝去了:"+timer);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
有關(guān)Java多線程的內(nèi)容還有很多,例如線程優(yōu)先級(jí)、線程同步等,由于這些內(nèi)容與本書關(guān)系不是很緊密,所以不再介紹了,有關(guān)其他的線程知識(shí)可以參考Java方面的書籍。接下來介紹一下Android中的線程。
Android中的線程
在Android平臺(tái)中多線程應(yīng)用很廣泛,在UI更新、游戲開發(fā)和耗時(shí)處理(網(wǎng)絡(luò)通信等)等方面都需要多線程。Android線程涉及的技術(shù)有:Handler;Message;MessageQueue;Looper;HandlerThread。
Android線程應(yīng)用中的問題與分析
為了介紹這些概念,我們把計(jì)時(shí)器的案例移植到Android系統(tǒng)上,按照在Frame方式修改之后的代碼清單.
package com.example.testthread4;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.*;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
public class MainActivity extends Activity {
private Button mButton;
private TextView mTextView;
private boolean isRunning = true;
private Thread mThread;
private int timer = 0;
//private Handler handler;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button) findViewById(R.id.button1);
mTextView = (TextView) findViewById(R.id.textView1);
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
isRunning = false;
}
});
/*handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
mTextView.setText("逝去了:" + msg.obj);
}
}
};*/
mThread = new Thread(new Runnable() {
@Override
public void run() {
try {
while (isRunning) {
Thread.currentThread().sleep(1000);
timer++;
/*Message message = new Message();
message.obj = timer;
message.what = 0;
handler.sendMessage(message);*/
mTextView.setText("逝去了:"+timer);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
mThread.start();
}
}
程序打包運(yùn)行結(jié)果出現(xiàn)了異常,如圖所示。

們打開LogCat窗口,出錯(cuò)日志信息如圖所示。

系統(tǒng)拋出的異常信息是“Only the original thread that created a view hierarchy can touch its views”,在Android中更新UI處理必須由創(chuàng)建它的線程更新,而不能在其他線程中更新。上面的錯(cuò)誤原因就在于此。
現(xiàn)在分析一下上面的案例,在上面的程序中有兩個(gè)線程:一個(gè)主線程和一個(gè)子線程,它們的職責(zé)如圖所示。
由于labelTimer是一個(gè)UI控件,它是在主線程中創(chuàng)建的,但是它卻在子線程中被更新了,更新操作在clockThread線程的run()方法中實(shí)現(xiàn),代碼如下:

mThread = new Thread(new Runnable() {
@Override
public void run() {
try {
while (isRunning) {
Thread.currentThread().sleep(1000);
timer++;
/*Message message = new Message();
message.obj = timer;
message.what = 0;
handler.sendMessage(message);*/
mTextView.setText("逝去了:"+timer);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
這樣的處理違背了Android多線程編程規(guī)則,系統(tǒng)會(huì)拋出異?!癘nly the original thread that created a view hierarchy can touch its views”。
要解決這個(gè)問題,就要明確主線程和子線程的職責(zé)。主線程的職責(zé)是創(chuàng)建、顯示和更新UI控件、處理UI事件、啟動(dòng)子線程、停止子線程;子線程的職責(zé)是計(jì)算逝去的時(shí)間和向主線程發(fā)出更新UI消息,而不是直接更新UI。
主線程的職責(zé)是顯示UI控件、處理UI事件、啟動(dòng)子線程、停止子線程和更新UI,子線程的職責(zé)是計(jì)算逝去的時(shí)間和向主線程發(fā)出更新UI消息。但是新的問題又出現(xiàn)了:子線程和主線程如何發(fā)送消息、如何通信呢?
在Android中,線程有兩個(gè)對(duì)象—消息(Message)和消息隊(duì)列(MessageQueue)可以實(shí)現(xiàn)線程間的通信。下面再看看修改之后的代碼清單.
package com.example.testthread4;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.*;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
public class MainActivity extends Activity {
private Button mButton;
private TextView mTextView;
private boolean isRunning = true;
private Thread mThread;
private int timer = 0;
private Handler handler;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button) findViewById(R.id.button1);
mTextView = (TextView) findViewById(R.id.textView1);
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
isRunning = false;
}
});
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
mTextView.setText("逝去了:" + msg.obj);
}
}
};
mThread = new Thread(new Runnable() {
@Override
public void run() {
try {
while (isRunning) {
Thread.currentThread().sleep(1000);
timer++;
Message message = new Message();
message.obj = timer;
message.what = 0;
handler.sendMessage(message);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
mThread.start();
}
}
有的時(shí)候?yàn)榱藢ndroid代碼變得更加緊湊,把線程的創(chuàng)建和啟動(dòng)編寫在一條語句中,如下面
/*mThread = */new Thread(new Runnable() {
@Override
public void run() {
try {
while (isRunning) {
Thread.currentThread().sleep(1000);
timer++;
Message message = new Message();
message.obj = timer;
message.what = 0;
handler.sendMessage(message);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
運(yùn)行模擬器結(jié)果如圖8-1所示,加載屏幕后馬上開始計(jì)時(shí),也可以單擊“停止計(jì)時(shí)”按鈕來停止計(jì)時(shí)。

相關(guān)文章
Android編程開發(fā)之打開文件的Intent及使用方法
這篇文章主要介紹了Android編程開發(fā)之打開文件的Intent及使用方法,已實(shí)例形式分析了Android打開文件Intent的相關(guān)布局及功能實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10Android Studio修改Log信息顏色的實(shí)現(xiàn)
這篇文章主要介紹了Android Studio修改Log信息顏色的實(shí)現(xiàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-04-04Android 實(shí)現(xiàn)IOS選擇拍照相冊(cè)底部彈出的實(shí)例
這篇文章主要介紹了Android 實(shí)現(xiàn)IOS選擇拍照相冊(cè)底部彈出的實(shí)例的相關(guān)資料,這里提供了實(shí)現(xiàn)效果圖及實(shí)現(xiàn)代碼,需要的朋友可以參考下2017-07-07Android使用Notification實(shí)現(xiàn)普通通知欄(一)
這篇文章主要為大家詳細(xì)介紹了Android使用Notification實(shí)現(xiàn)普通通知欄,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12Android編程之殺毒的實(shí)現(xiàn)原理及具體實(shí)例
這篇文章主要介紹了Android編程之殺毒的實(shí)現(xiàn)原理及具體實(shí)例,結(jié)合實(shí)例形式分析了Android殺毒功能的原理與簡(jiǎn)單實(shí)現(xiàn)技巧,需要的朋友可以參考下2015-12-12Android中RecyclerView 滑動(dòng)時(shí)圖片加載的優(yōu)化
本篇文章主要介紹了Android中RecyclerView 滑動(dòng)時(shí)圖片加載的優(yōu)化,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04深入理解與運(yùn)行Android Jetpack組件之ViewModel
ViewModel是Android Jetpack組件之一,是一種用于管理UI相關(guān)數(shù)據(jù)的架構(gòu)組件,它能夠幫助開發(fā)者實(shí)現(xiàn)優(yōu)雅的數(shù)據(jù)驅(qū)動(dòng)和生命周期管理,本文將深入淺出地介紹ViewModel的使用和原理,帶你一步步掌握這個(gè)強(qiáng)大的組件2023-08-08Android app開發(fā)中的Fragment入門學(xué)習(xí)教程
這篇文章主要介紹了Android app開發(fā)中的Fragment入門學(xué)習(xí)教程,包括Fragment的創(chuàng)建和XML布局文件中的Fragment定義等,需要的朋友可以參考下2016-02-02Android自定義加載控件實(shí)現(xiàn)數(shù)據(jù)加載動(dòng)畫
這篇文章主要為大家詳細(xì)介紹了Android自定義加載控件實(shí)現(xiàn)數(shù)據(jù)加載動(dòng)畫的相關(guān)資料,仿美團(tuán)、京東數(shù)據(jù)加載動(dòng)畫、小人奔跑動(dòng)畫,感興趣的小伙伴們可以參考一下2016-04-04