Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android線程篇

Android線程篇

編輯:關於Android編程

在Android中,UI主線程並非線程安全的,所有UI相關的操作均需在UI主線程中完成。在默認情況下,開發者創建的Service、Activity、Broadcast均運行在UI主線程中,但將一些耗時操作,如網絡下載、大文件讀寫、加解密計算、數據庫操作等,也放在UI線程中執行,往往會阻塞UI線程,造成ANR異常,因此,在Android應用開發中,應特別注意線程的使用。

在Android中,實現多線程的方式有多種,開發者可以通過原生Java線程或Android對Java線程的封裝即AsyncTask來實現多線程的效果。

為了開發出性能更好的應用,開發者必須對Android的多線程實現由清楚的了解,了解每種實現方式的優缺點和線程安全方面的問題,這樣才能最大程度地發揮出Android的潛力。

1.Java線程實現

Android線程的實現本質上仍是Java線程,只是為了方便開發者進行實現,針對特定場景做了封裝。本節將著重介紹基於Thread、Runnable的java線程的實現,並介紹Android的線程優先級。

(1)基於Thread的實現

基於Thread實現新線程和在Runnable構造器中實現新線程是傳統Java實現線程的兩種方式,基於Thread的實現非常簡單。示例如下:

static class AThread extends Thread{

ActivityManagerService mService;

boolean mRead=false;

public AThread(){

super("ActivityManager");

}

public void run(){

Looper.prepare(); //初始化事件循環之後調用loop()

android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_FOREGROUND):

android.os.Precess.setCanSelfBackground(false);

ActivityManagerService m=mew ActivityManagerService();

synchronized(this){

mSerice=m;

notifyAll();

}

synchronized(this){

while(!mReady){

try{

wait();

catch(InterruptedException e){

}

}

}

Looper.loop();

}

}

在Thread聲明周期中,存在NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED等多個狀態。線程的生命周期和狀態遷移過程如下圖:

\

下面是Thread中常用方法的說明,在實現Java線程時,務必掌握這些方法的用途。

getState() //獲取線程的當前狀態 interrupt() //中斷當前線程

interrupted() //判斷當前線程是否已中斷 isAlive //判斷線程是否仍在運行

isInterrupted() //判斷是否處於“中斷”狀態 sleep() //線程睡眠指定的時間

join() //線程同步處理 run() //線程暫停一段時間

start() //開啟線程 yield() //暫停當前線程,同優先級的其它線程獲得時間片

(2)基於Runable的實現

對於APP開發來說,由於應用層本身的特點,Runnable使用更加廣泛,下面是一個Runnable實現的示例:

private Runnable mForceCheckBoxRunnable =new Runnable(){

public void run(){

if(mCheckBoxPreference!=null){

mCheckBoxPreference.setChecked(!mCheckBoxPreference.isChecked());

}

mHandle.postDelayed(this, 1000);

}

};

另外,通過Handle的removeCallbacks()方法可以移除待運行的線程,通過postAtTime方法可以在特定時間將線程放入消息隊列。

(3)線程優先級

Android線程優先級是以及Linux設置的,其優先級是用數字表示的,范圍是-20~19,其中-20為最高優先級,19為最低優先級。優先級的設置通常用於處理並發線程產生的阻塞,防止重要性較低的線程占用大量的CPU資源。設置優先級的方法如下:

Process.setThreadPriority(priority);

根據適用場景的不同,Android提供了以下幾種常用場景的線程優先級:

THREAD_PRIORITY_AUDIO //值為-16,用於音樂播放場景

THREAD_PRIORITY_BACKGROUND //值為10,用於普通後台程序

THREAD_PRIORITY_DEFAULT //值為0,默認優先級

THREAD_PRIORITY_DISPLAY //值為-4,用於普通顯示線程

THREAD_PRIORITY_FOREGROUND //值為-2,用於普通前台程序

THREAD_PRIORITY_LESS_FAVORABLE //值為1,低於默認優先級

THREAD_PRIORITY_LOWEST //值為19,最低優先級

THREAD_PRIORITY_MORE_FAVORABLE //值為-1,最高默認優先級

THREAD_PRIORITY_URGENT_AUDIO //值為-19,重要的音頻線程

THREAD_PRIORITY_UNGENT_DISPLAY //值為-8,重要的顯示線程

2.Android線程封裝

在Android中,為了簡化開發者的工作,對Java線程進行了一定程度的封裝,最主要的工作包括AsyncTask封裝和接入UI線程。

(1)AsyncTask封裝

AsyncTask通常用於後台線程和UI線程的交互。雖然AsyncTask本質上還是Thread加Handler的一個封裝,但是由於采用了Java 1.5中的並發庫FutureTask,故其在處理異步事務時表現卓越。使用AsyncTask的示例如下:

private class ReceicePushTask extends AsyncTask{

protected Void doInBackground(Intent... instentd){

Intent intent-intents[0];

}

public void onReceive(Context context, Intent intent){}

}

AsyncTask中最基本的兩個方法為doInBackground和onReceive,前者執行真正的後台計算,後者向UI主線程反饋計算結果。為了使用AsyncTask,開發者必須對AsyncTask進行繼承。

AsyncTask的其他重要方法如下:

public final AsyncTaskexecute(Params... params) //執行AsyncTask

public void onPostExecute(Result result) //在doInBackGround方法執行後執行,該方法在UI線程中執行

protected void onPreExecute() //在執行doInBackground方法前執行

protected final void publishProgress(Progress... values) //向UI線程反饋計算進度

public final boolean cancel(boolean mayInterruptIfRunning) //中斷線程執行

public final boolean isCancelled() //判斷AsyncTask是否已經被中斷

在Android3.0中,Google又引入了execute(Runnable runnable)等多個方法。AsyncTask可以支持多個輸入參數,甚至可變參數,其參數在execute方法中傳入。

在中斷開始執行時,系統將會調用AsyncTask的onCancelled方法,開發者可以在該方法中進行一些收尾工作,但是要注意,必須顯式判斷線程是否中斷。假設在某個目錄下尋找和關鍵字匹配的文件和文件夾,後台計算的工作被放置在AsyncTask中,顯式判斷線程是否中斷的工作在dosth中進行,示例如下:

SearchTask mSearchTask=new SearchTask();

mSearchTask.execute(path) //執行線程

public void dosth(String value){

for(...){

if(mSearchTask.isCancelled()){

return;

}

}

}

下面是SearchTask的實現,其中包含了後台計算的入口、正常結束的入口、線程中斷的入口等。

class SearchTask extends AsyncTask{

public void doInBackground(String... value){

dosth(value[0]);

}

pretected void onCanceled(){

//執行中斷的善後工作

}

protected void onPostExecute(Result result){ //該方法在UI線程執行

//執行正常的結束工作

}

}

(2)接入UI線程

在多線程編程中,執行後台計算的線程通常需要和UI線程進行交互,將計算的結果反饋給UI線程,呈現給用戶,而傳統的方法一般需要借組Handle。在Android中,充分考慮了開發者者方面的需要,從Activity、View和UI主線程3個層次提供了4種方式來接入UI線程。

1)Activity的runOnUiThread(Runnable)方法

Activity提供的接入UI線程的方法實際上是通過runOnUiThread(Runnable)方法進行的,從本質上講,runOnUiThread方法時基於Handler的post(Runnable r)方法實現的。runOnUiThread(Runnable)的示例如下:

mActivity.runOnUiThread(new Runnable(){

public void run(){

mToast.setGravity(Gravity.BOTTOM, 0, 0);

mToast.show();

}

});

2)View的post(Runnable)方法

View的post(Runnable)方法從控件層面對多線程提供了支持,是開發更加靈活,其實現原理與基於Activity的runOnUiThread(Runnable)方法類似,均是借助Handle進行的。關於View的post(Runnable)方法的示例如下:

public void onClick(View v){

new Thread(new Runnable(){

public void run(){

finnal Bitmap bitmap=loadImageFromNetwork(http://example.com/image.png);

mImageView.post(new Runnable(){

public void run(){

mImageView.setImageBitmap(bitmap);

}

});

}

}).start

}

(3)View的postDelayed(Runnable, long)方法

View的postDelayed(Runnable, long)方法提供了延時處理的能力,示例如下:

final static int PAUSE_DELAY=100;

Runnable mRunnable=new Runnable(){

public void run(){

mView.postDelayed(mRunnable, PAUSE_DELAY);

}

};

(4)Handler方法

利用Message消息向UI主線程發送後台計算的消息,及時將計算結果反饋給實際用戶,這一切均需要借助Handle進行消息處理。當UI線程接收到Message消息後,會調用其Handler進行消息處理。Handler處理消息的過程如下:

Message message=mHandle.obtainMessage(ORIENTATION_CHANGED);

mHandler.sendMessage(message);

Handler mHandler=new Handler(){

public void handleMessage(Message msg){

switch(msg.what){

case ORIENTATION_CHANGED;

break;

}

}

};

3.線程間的消息通信

相比Thread加Handler的封裝,Async更為可靠,更易於維護。AsyncTask的缺點是,一旦線程開啟即dobackground方法執行後無法向線程發送消息,僅能通過預先設置好的標記來控制,當然可以通過掛起線程並等待標志位的改變來進行通信。

對於某些應用,原生的Thread加Handler以及Looper可能更靈活。下圖揭示了三者之間的關系。

\

(1)消息隊列

消息隊列的維護是在線程中進行的,但默認除UI主線程外,其他線程並不擁有消息隊列。為了維護一個消息隊列,需要在線程中調用Looper的prepare方法進行初始化。為了使線程可以接收發送過來的消息,通常需要借組Handler來接收消息。一個維護消息隊列的示例如下:

private class Queryrunner extends Thread{

public Handler mHandle

public void run(){

Looper.prepare(); //loop()方法應隨後調用

mHandler=new Handler(){

public void handleMessage(Message msg){//處理收到的消息}

};

Looper.loop();

};

}

通過Looper還可以判斷當前線程是否為UI主線程,方法如下:

private void assertUiThread(){

if(!Looper.getMainLooper().equals(Looper.myLooper())){

throw new RuntimeException("Not on the UI thread!");

}

}

通過查看Message的實現即可發現,Message繼承於Parcelable,在消息的傳遞過程中,實際上傳遞的是序列化數據。Message的實現如下:

public final class message implements Parcelable{

public int what; //消息表示符

public int arg1; //傳遞參數

public int arg2; //傳遞對象

public Object obj; //傳遞的對象

public Messager replyTo;

long when;

Bundle data;

Handle target; //目標Handler

Runnable callback;

Message next;

private static final int MAX_POOL_SIZE=10;

...

}

通過分析Message的源代碼實現可以發現,一個線程會自動維護一個消息池,該消息池的大小為10.在需要生成消息時,首先從消息池中獲取消息,只有當消息池中的消息均被使用時,才會重新創建新消息,當消息使用結束時,消息池會回收消息。

從obtain方法的實現中可以看出,在發送消息時,通過obtain方法獲得Message比創建Message的效率更高。發送消息的示例如下:

public void progress(boolean progress){

android.os.Message msg=android.os.Message.obtain();

msg.what=MSG_PROGRESS;

msg.arg1=progress ? 1: 0;

sendMessage(msg);

}

(2)消息分發

消息的分發和接收均與Handler密切相關,對於消息的分發,Android目前提供了兩種消息類型,一種是post消息,一種是send消息。其中post消息會在為了某一時間加入消息隊列,而send消息則會立即加入到消息隊列。

1)send消息

分發一個簡單的send消息的方法如下:

Messagemsg=mHandler.obtainMessage(KILL_APPLICATION_MSG);

msg.arg1=uid;

msg.arg2=0;

msg.obj=pkg;

mHandler.sendMessage(msg);

如果沒有參數需要傳遞,那麼可以發送僅攜帶消息表示符的空消息,方法如下:

mHandler.sendEnptyMessage(CANCEL_HEAVY_NOTIFICATION_MSG);

2)post消息

通過post消息可以實現類似循環計算的功能,方法如下:

mTicker = new Runnable(){

public void run(){

if(mTickerStopped) return;

mCalender.setTimeInMillis(System.currentTimeMillis());

setText(DateFormat(mFormat, mCalendar));

invalidate();

long now=SystemClock.uptimerMillis();

long next=now + (1000-now%1000);

mHandler.postAtTime(mTicker, next);

}

};

mTicker.run();

(3)消息接收

消息的接收相對簡單,但是如果存在重入問題,應注意數據的同步。關於數據安全的內容。在Handler中消息接收的過程如下:

public void dispatchMessage(Message msg){

if(msg.callback !=null){

handleCallback(msg); //通過Message自身回調進行處理

}else{

if(mCallback.handleMessage(msg)){

return;

}

}

handleMessage(msg); //handle自己處理

}

對於消息隊列而言,如何監聽消息的來臨呢,筆者的經驗是,在Looper的loop方法中執行對消息的監聽。loop方法的實現如下:

public static final void loop(){

Looper me=myLooper();

MessageQueue queue=me.mQueue;

Binder.clearCallingIdentity();

finnal long ident=Binder.clearCallingIdentity();

if(msg!=null){

if(msg.target==null){

return;

}

msg.target.dispatchMessage(msg); //調用Handler進行消息處理

msg.recycle(); //消息回收

}

}

4.線程安全處理

線程安全多是由多線程對共享資源的訪問引起的,在線程安全層面,Android更多的是采用Java的實現,除了Java的join()、wait()、sleep()、notify()等安全方法和synchronized關鍵字外,還有Java的並發庫。

除了synchronized關鍵字外,基於Java的多線程安全均比較復雜,對於不是十分復雜的場景,優先進行多線程處理。

(1)synchronized同步

在Android應用層,線程的並發很多是靠synchronized關鍵字來實現的,這樣的實現非常簡單。通過synchronized關鍵字,可以實現方法和語句塊的同步。同步的本質是對特定的對象加鎖,他們可以是來自調用方法的對象,也可以是開發者設計的對象。

另外,synchronized關鍵字並不能繼承,對於父類的同步方法,在子類中必須再次顯式聲明才能成為同步方法。

synchronized關鍵字的局限在於在試圖獲得鎖時無法設定超時和中斷,每個鎖只有一個條件,在某些場景下可能不夠用。

另外,同步會帶來較大的系統開銷,甚至造成死鎖,因此在進行開發時應避免無謂的同步。synchronize關鍵字可以實現方法同步和語句塊同步。

1)方法同步

方法同步分一般方法同步和靜態方法同步兩種。一般方法同步的本質在於將synchronized關鍵字作用於對象引用(object referece),作用域僅限類的單個對象,下面是AbsSeekBar中的一個實現:

public synchronized void setMax(int max){

super.setMax(max);

}

以上實現等同於如下實現:

public void setMax(int max){

synchronized(this){

super.setMax(max);

}

}

靜態方法同步的本質是將類本身作為所,其作用域是該類的所有對象。下面是BitmapManaget中利用synchronized實現單子模式的過程。

private static BitmapManager sManager=null;

public static synchronized BitmapManaget instance(){

if(sManager==null){

sManager=new BitmapManager();

}

}

2)語句塊的同步

方法同步的作用域較大,但事實上需要同步的范圍可能並沒那麼大。當需要同步的范圍不是很大時,可采用語句塊同步。下面是AccelerometerListener中的一個示例:

private void setOrientation(int orientation){

synchronized(this){

}

}

將對象引用本身作為鎖,顯然影響並發效率,更靈巧的設計是自定義鎖。自定義鎖可以是任何類型的對象,但通常將其類型設計為Object。AbstractPreferences中的一個示例如下:

public abstract class AbstractPreferences extends Preferences

{

protected final Object lock;

protected AbstractPreferences getChild(String name) throws

BackingStoreException{

synchronized(lock){

}

}

}

采用自定義鎖可以帶來更高的並發效率,當然具體采用什麼樣的鎖,需要根據實際場景決定。

(2)RPC通信

在基於AIDL的通信中,由於允許多個客戶端的存在,其實現必須是線程安全的,開發者要注意,對於IPC調用方法,如果是一種耗時的操作,應避免在UI主線程中調用,以免阻塞UI主線程。

(3)SQLite調用

對於SQLite的調用,可能會存在多處執行讀寫操作的場景,這種場景也需要考慮線程的安全性。為了方便開發者操作,Android提供了類似於AsyncTask的AsyncQueryHandler方法來解決這一問題,將耗時的查詢等操作放置在非UI主線程中,操作結束後,通過Handler調用相應的UI主線程的方法處理操作執行的結果,示例如下:

AsyncQueryHandler mAsyncQueryHandler=new AsyncQueryHandler(getContectResolver()){

protected void onQueryComplete(int token, Object cookie, Cursor cursor){

if(cursor!=null && cursor.moveToFirst()){

}

}

}

mAsyncQueryHandler.startQuery(0, null, mUri, new String[] {

MediaStore.Audio.Media.TITLE,MediaStore.Audio.Media.ARTIST

},MediaStore.Audio.Media,DATA+”=?“, new string[]{path}, null());

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved