執行緒Thread、HandlerThread的Android範例


如果有用過java差不多都有學過Thread-多執行緒
這裡先做Thread小介紹:
多執行緒的機制可以同時執行多個程式區塊,使程式執行的效率變高
如果要某類別啟動執行緒則必須繼承自Thread類別,而執行緒的處理必須撰寫在run()方法內,但執行時是呼叫start()
class 類別名稱 extends Thread
{
    類別裡的成員資料;
    類別裡的方法;
    修飾子 run()    //改寫Thread類別裡的run()方法
    {
        以執行緒處理的程序;
    }
}

在連續呼叫兩次start()時會同時執行 (建議程序內使用迴圈+延遲來看出是否有同時執行)


如本身以繼承某父類別只能使用Runnable介面

class 類別名稱 implements Runnable
{
    類別裡的成員資料;
    類別裡的方法;
    修飾子 run()
    {
        以執行緒處理的程序;
    }
}

執行時一樣使用start()


以上為Thread的小介紹,那跟Android有什麼關係呢?
原因是因為在Android中,如果做超過5秒就會被系統強制關閉(會收到Application Not Responsed簡稱ANR)
onCreate()如果做超過10秒就會跳ANR,所以繁重的事情不能放在onCreate()裡,那麼解決辦法當然就是使用多執行緒

Thread為執行緒(臨時工)

Runnable為申請的執行內容

一般的Thread就如同臨時工都是指定執行內容在 publice run() 裡面用執行start()做完就可以閃人了

有一種Thread性質不一樣會先執行 start() 等待工作透過Runnable申請到來
這種Thread執行緒如特約工人(上班族)一樣就算沒事也要執行著等待工作,而且需要有經紀人做規劃工作:

HandlerThread 為特約工人(上班族)

Handler 為經紀人

一般Android執行時會有個UI Thread或叫main Thread,這是程式本身原有的執行緒,用於管理畫面呈現....等等事物也是程式的進入點

而main Thread本身也是HandlerThread(特約工人),可以給予他一個Handler(經紀人)做接收訊息用途
有時候需要做一些比較花時間的運算,為了避免UI Thread停頓阻塞住我們必須使用另一個HandlerThread(特約工人)來幫我們做花時間的工作
如果在onCreate()內使用UI Thread需等到執行完畢才會去執行畫面呈現等等事物,所以為了避免UI工人太過於繁忙,背景動作都派遣給另一個工人,而畫面呈現時在使用UI Thread

以下為使用Thread與HandlerThread範例:

package com.example.thread_demo;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
    HandlerThread ht=new HandlerThread("name");    //宣告在背景工作的特約工人,"name"為工人名子
    Handler h,uih;                                //宣告2個經紀人,uih為UI Thread的經紀人
    Thread t;                                    //普通的執行緒工人
    TextView t01;
    Button b01,cancel;
    int ht_i=1,t_i=10;            //用來計數HandlerThread與Thread兩位工人各別顯示的行數
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();                                //連結控制項
        t01.setText("Test Start:\n");
        uih=new Handler();                    //uih為UI Thread的經紀人可以直接使用=new Handler()獲取UI Thread,之後將能透過經紀人指派UI工人做事
        ht.start();                            //先讓特約工人開始執行(如果沒有執行不能交給經紀人)
        h=new Handler(ht.getLooper());        //把特約工人交給經紀人,找到特約工人的經紀人才能開始派遣工作
        //作為HandlerThread添加工作的按鈕
        b01.setOnClickListener(new OnClickListener() {    
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                h.post(r0);                    //經紀人指派r0工作給工人
            }
        });
        //作為註銷HandlerThread執行緒指定任務的按鈕
        cancel.setOnClickListener(new OnClickListener() {    
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                if(h!=null)                    //用來取消排在任務列內的所有r0任務(如已經開始執行該任務,則無法取消)
                    h.removeCallbacks(r0);
                t01.append("註銷HandlerThread內指定的任務\n");
            }
        });
        //讓臨時工執行緒派遣工作
        t=new Thread(new Runnable(){        
            @Override
            public void run() {
                // TODO Auto-generated method stub
                try {
                    //顯示10次後就能閃人
                    while(t_i>0){            //判斷是否使用Thread顯示字串
                        runOnUiThread(new Runnable() {        //可以使用此方法臨時交給UI做顯示
                            @Override
                            public void run() {
                                // TODO Auto-generated method stub
                                t01.append("Thread臨時工倒數第"+t_i+"次顯示\n");
                                t_i--;
                            }
                        });
                        Thread.sleep(5000);                //每隔5秒顯示一次
                    }
                    runOnUiThread(new Runnable() {        //可以使用此方法臨時交給UI做顯示
                        public void run(){     
                            t01.append("Thread結束工作閃人\n");
                        }     
                    });
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });
        t.start();                    //Thread開始執行
    }
    //連結控制項
    public void init(){            
        t01=(TextView) this.findViewById(R.id.t01);
        b01=(Button) this.findViewById(R.id.b01);
        cancel=(Button) this.findViewById(R.id.cancel);
    }
    //設置r0工作,每秒在t01添加一行(5次)
    private Runnable r0=new Runnable(){        
        //工作內容寫進run(),繁雜的工作可以給另一個特約工人做
        public void run(){                    
            try{
                runOnUiThread(new Runnable() {    //可以臨時交給UI做顯示 
                    public void run(){     
                        t01.append("特約工人接到工作開始執行:\n");
                    }     
                }); 
                Thread.sleep(1000);            //每隔1秒顯示一次
                for(int i=0;i<5;i++){
                    uih.post(ui0);            //給UI Thread經紀人指派ui0工作,才不會崩潰(因android工具須給UI Thread工人做)
                    Thread.sleep(1000);        //每隔1秒顯示一次
                }
            } catch(Exception e){
                e.printStackTrace();
            }
        }
    };
    //設置ui0工作,替特約工人顯示TextView
    private Runnable ui0=new Runnable(){    
        @Override
        public void run() {
            // TODO Auto-generated method stub
            t01.append("特約工人執行第"+ht_i+"次\n");
            ht_i++;
        }
    };
    @Override
    protected void onDestroy(){
        super.onDestroy();
        if(ht!=null)                //關閉該執行緒(需等待目前的任務執行完畢才會關閉)
            ht.quit();
    }
}


當你的執行緒需要不斷大量的更新 UI 的時後,還是需要使用UI Thread,所以提供非常方便的API
Activity.runOnUiThread( Runnable action ) 
顧名思義,引數所帶入的action,將會保證在UI Thread被執行
已經在剛剛的範例中示範過了
.....
runOnUiThread(new Runnable() {     
    public void run(){     
        //這裡為想讓他執行的工作
    }     
}); 
.....
Android 4.0 之後,有明文規定所有的網路行為都不能在主執行緒(Main Thread)執行,主執行緒又稱UI執行緒(UI Thread),如果要在主執行緒做網路的事,就需要使用
Thread-執行緒或是AsyncTask-異步任務
任何有關UI的東西都在主執行緒中執行,若是你的程式佔據主執行緒很久,使用者體驗會非常的差
(當你什麼事都丟給UI Thread做時,會導致該Thread阻塞,自然無法去解決使用者的操作而導致LAG)
//使用該LOG可以知道當下是使用哪個執行緒,1為主執行緒
Log.d("text","thread id="+String.valueOf(Thread.currentThread().getId()).toString);


Thread可以理解為做完一件事馬上閃人的打工族

HandlerThread則是在未使用quit關閉前能不斷丟給他不同工作去執行的上班族

以上是個人在執行緒方面的一些理解,有更多資訊會在上來補充





arrow
arrow

    SIN 發表在 痞客邦 留言(0) 人氣()