android 多線程技術應用

來源:互聯網
上載者:User

多線程案例——計時器
這個案例中,螢幕啟動之後,進入的介面。
螢幕上有一個文字框用於顯示逝去的時間,此外還有一個“停止計時”按鈕。案例的使用案例圖。

能夠在螢幕上“即時地顯示”時間的流逝,單線程程式是無法實現的,必須要多線程程式才可以實現,即便有些電腦語言可以通過封裝好的類實現這一功能,但從本質上講這些封裝好的類就是封裝了一個線程。
綜上所述,完成本案例用到的知識及技術如下:
  1)進程和線程的概念;
  2)Java中的線程,在Java中建立線程的方式;
  3)Android中的線程,包括:Message、Handler、Looper和HandlerThread等概念。
線程究竟是什麼?在Windows作業系統出現之前,個人電腦上的作業系統都是單任務系統,只有在大型電腦上才具有多任務和分時設計。Windows、Linux作業系統的出現,把原本只在大型電腦才具有的優點,帶到了個人電腦系統中。
進程概念
  一般可以在同一時間內執行多個程式的作業系統都有進程的概念。一個進程就是一個執行中的程式,而每一個進程都有自己獨立的一塊記憶體空間、一組系統資源。在進程的概念中,每一個進程的內部資料和狀態都是完全獨立的。在Windows作業系統下我們可以通過〈Ctrl+Alt+Del〉按鍵組合查看進程,在UNIX和Linux作業系統下是通過PS命令查看進程的。開啟Windows當前啟動並執行進程,。

在Windows作業系統中一個進程就是一個exe或dll程式,它們相互獨立,互相也可以通訊,在Android作業系統中進程間的通訊應用也是很多的。
線程概念
  多線程指的是在單個程式中可以同時運行多個不同的線程,執行不同的任務。多線程意味著一個程式的多行語句可以看上去幾乎在同一時間內同時運行。
  線程與進程相似,是一段完成某個特定功能的代碼,是程式中單個順序的流量控制。但與進程不同的是,同類的多個線程共用一塊記憶體空間和一組系統資源,所以系統在各個線程之間切換時,資源佔用要比進程小得多,正因如此,線程也被稱為輕量級進程。一個進程中可以包含多個線程。圖所示是計時器程式進程和線程之間的關係,主線程負責管理子線程,即子線程的啟動、掛起、停止等操作。

Java中的線程
  Java的線程類是java.lang.Thread類。當產生一個Thread類的對象之後,一個新的線程就產生了。Java中每個線程都是通過某個特定Thread對象的方法run()來完成其操作的,方法run( )稱為線程體。
  下面是構建線程類幾種常用的方法:
  public Thread()
  public Thread(Runnable target)
  public Thread(Runnable target, String name)
  public Thread(String name)
  參數target是一個實現Runnable介面的執行個體,它的作用是實現線程體的run()方法。目標target可為null,表示由本身執行個體來執行線程。name參數指定線程名字,但沒有指定的構造方法,線程的名字是JVM分配的,例如JVM指定為thread-1、thread-2等名字。
1、Java中的實現線程體方式1
  在Java中有兩種方法實現線程體:一是繼承線程類Thread,二是實現介面Runnable。下面我們先看看繼承線程類Thread方式。
如果採用第1種方式,它繼承線程類Thread並重寫其中的方法 run(),在初始化這個類執行個體的時候,目標target可為null,表示由本執行個體來執行線程體。由於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("啟動計時器...");
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()建立子線程,並通過thread.start()方法啟動子線程,main主方法所線上程為主線程,主線程負責管理其他的子線程。本例進程、主線程和子線程之間的關係。
  子線程啟動之後就開始調用run()方法,run()是一個線程體,我們在子線程中處理事情就是在這裡編寫代碼實現的。本案例中子線程要做的事情就是:休眠1s,計時器加1,再反覆執行。Thread.currentThread().sleep(1000)就是休眠1s。
  為了能夠停止線程,我們在主線程中增加了一個標識,通過在控制台輸入一個字元
  “1”來改變該標識t1.isRunning = false,從而結束這個線程。

注意
  事實上線程中有一個stop()方法也可以停止線程,但是由於這種方法會產生線程死結問題,所以在新版JDK中已經廢止了,它的替代解決方案就是增加標識,就是我們在本例中採用的方案。
  很多人覺得線程難理解,主要有兩個問題
  線程休眠,既然線程已經休眠了,程式的運行速度還能提高嗎?
  線程體一般都進行死迴圈,既然線程死迴圈,程式就應該死掉了,就會沒有反應。
  1.關於線程休眠問題
  對線程休眠問題頭痛的讀者,其實還是在用單線程的思維模式考慮問題,多數情況下我們的PC都是單CPU的,某個時間點只能有一個線程運行。所謂多線程就是多個線程交替執行就好像同時運行似的。因此,休眠當前線程可以交出CPU控制權,讓其他的線程有機會運行,多個線程之間只有交替運行效率才是最高的,這就像我們開車過十字路口,只有我等等,讓你先過,你再等等讓他先過,才能保證最高效率,否則就會造成交通系統崩潰,對線程情況也是一樣的。因此,多線程中線程的休眠是程式啟動並執行最有效方式。
  2.關於線程體死迴圈問題
  在單線程中如果是死迴圈,程式應就會死掉,沒有反應,但是多線程中線程體(run方法)中的死迴圈,可以保證線程一直運行,如果不迴圈線程,則運行一次就停止了。在上面的例子中線程體運行死迴圈,可以保證線程一直運行,每次運行都休眠1s,然後喚醒,再然後把時間資訊輸出到控制台。所以,線程體死迴圈是保證子線程一直啟動並執行前提。由於是子線程它不會堵塞主線程,就不會感覺到程式死掉了。但是需要注意的是有時我們確實執行一次線程體,就不需要迴圈了。
 程式運行後開始啟動線程,線程啟動後就計算逝去的時間,每過1s將結果輸出到控制台。當輸入1字元後線程停止,程式終止。。

Java中的實現線程體方式2
  上面介紹繼承Thread方式實現線程體,下面介紹另一種方式,這種方式是提供一個實現介面Runnable的類作為一個線程的目標對象,構造線程時有兩個帶有Runnable target參數的構造方法:
  Thread(Runnable target);
  Thread(Runnable target, String name)。
  其中的target就是線程目標對象了,它是一個實現Runnable的類,在構造Thread類時候把目標對象(實現Runnable的類)傳遞給這個線程執行個體,由該目標對象(實現Runnable的類)提供線程體run()方法。這時候實現介面Runnable的類仍然可以繼承其他父類。
請參看代碼清單,這是一個Java AWT的表單應用程式。 複製代碼 代碼如下: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("停止計時器");
label = new Label("計算機啟動");
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();
}
}

其中關於Java AWT知識就不在這裡介紹了,有興趣的讀者可以自己看看相關書籍。在本例中構建AWT表單的應用程式方式是繼承Frame類。採用第1種方式——繼承方式實現線程體是不可以的,因為Java是單繼承的,這個類不能既繼承Frame又繼承Thread。應該採用第2種方式——實現Runnable介面方式。Runnable介面也有一個run()方法,它是實現線程體方法,其代碼處理與上一節是一樣。需要注意的是,在第2種方法中,建立了一個Thread成員變數clockThread,才用構造方法new Thread(this)建立一個線程對象,其中建立線程使用的構造方法是Thread(Runnable target),其中的this就是代表本執行個體,它是一個實現了Runnable介面的實作類別。
程式運行結果,螢幕開始載入的時候線程啟動開始計算時間,1s更新一次UI,當單擊“結束計時”按鈕時,停止計時。

Java中的實現線程體方式3
  實現線程體方式3是實現線程體方式2的變種,本質上還是實現線程體方式2,但是在Android應用開發中經常採用第3種方式。下面我們看第3種方式的計時器代碼清單. 複製代碼 代碼如下: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("停止計時");
label = new Label("計時器開始。。。");
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種方式比較,我們發現Frame類不再實現Runnable介面了,而是在執行個體化Thread類的時候,定義了一個實現Runnable介面的匿名內部類: 複製代碼 代碼如下: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();
}
}
}
});

有關Java多線程的內容還有很多,例如線程優先順序、線程同步等,由於這些內容與本書關係不是很緊密,所以不再介紹了,有關其他的線程知識可以參考Java方面的書籍。接下來介紹一下Android中的線程。
Android中的線程
  在Android平台中多線程應用很廣泛,在UI更新、遊戲開發和耗時處理(網路通訊等)等方面都需要多線程。Android線程涉及的技術有:Handler;Message;MessageQueue;Looper;HandlerThread。
  Android線程應用中的問題與分析
  為了介紹這些概念,我們把計時器的案例移植到Android系統上,按照在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();
}
}

程式打包運行結果出現了異常,。

們開啟LogCat視窗,出錯日誌資訊。

系統拋出的異常資訊是“Only the original thread that created a view hierarchy can touch its views”,在Android中更新UI處理必須由建立它的線程更新,而不能在其他線程中更新。上面的錯誤原因就在於此。
  現在分析一下上面的案例,在上面的程式中有兩個線程:一個主線程和一個子線程,它們的職責。
  由於labelTimer是一個UI控制項,它是在主線程中建立的,但是它卻在子線程中被更新了,更新操作在clockThread線程的run()方法中實現,代碼如下:
複製代碼 代碼如下: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多線程編程規則,系統會拋出異常“Only the original thread that created a view hierarchy can touch its views”。
  要解決這個問題,就要明確主線程和子線程的職責。主線程的職責是建立、顯示和更新UI控制項、處理UI事件、啟動子線程、停止子線程;子線程的職責是計算逝去的時間和向主線程發出更新UI訊息,而不是直接更新UI。
主線程的職責是顯示UI控制項、處理UI事件、啟動子線程、停止子線程和更新UI,子線程的職責是計算逝去的時間和向主線程發出更新UI訊息。但是新的問題又出現了:子線程和主線程如何發送訊息、如何通訊呢?
在Android中,線程有兩個對象—訊息(Message)和訊息佇列(MessageQueue)可以實現線程間的通訊。下面再看看修改之後的代碼清單. 複製代碼 代碼如下: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();
}
}

有的時候為了將Android代碼變得更加緊湊,把線程的建立和啟動編寫在一條語句中,如下面 複製代碼 代碼如下:/*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();

運行模擬器結果8-1所示,載入螢幕後馬上開始計時,也可以單擊“停止計時”按鈕來停止計時。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.