Handler機(jī)制從入門到放棄(一)

閑來無事,準(zhǔn)備好好梳理一下Handler機(jī)制,之前分析過沒有寫成博客,結(jié)果就是慢慢的淡忘了,這次趁著剛分析完,趕緊寫下來。

在開始分析之前先打打基礎(chǔ),理解理解什么是線程以及什么是Handler,這里大部分內(nèi)容引用一篇來自伯樂在線的文章,因?yàn)榭磥砜慈リP(guān)于基礎(chǔ)的部分這個(gè)人已經(jīng)說得很好了,我就負(fù)責(zé)把主要的部分抽取出來。

原文地址:Android線程和Handler基礎(chǔ)入門


現(xiàn)在大多數(shù)的移動(dòng)設(shè)備已經(jīng)變得越來越快,但是它們其實(shí)也不算是非??臁H绻阆胱屇愕腁PP既可以承受一些繁雜的工作而又不影響用戶體驗(yàn)的話,那么必須把任務(wù)并行執(zhí)行。在Android上,我們使用線程。

什么是線程?

線程或者線程執(zhí)行本質(zhì)上就是一串命令(也是程序代碼),然后我們把它發(fā)送給操作系統(tǒng)執(zhí)行。

線程

一般來說,我們的CPU在任何時(shí)候一個(gè)核只能處理一個(gè)線程。多核處理器(目前大多數(shù)Android設(shè)備已經(jīng)都是多核)顧名思義,就是可以同時(shí)處理多線程(通俗地講就是可以同時(shí)處理多件事)。

多核處理與單核多任務(wù)處理的實(shí)質(zhì)

上面我說的是一般情況,并不是所有的描述都是一定正確的。因?yàn)閱魏艘部梢杂枚嗳蝿?wù)模擬出多線程。

每個(gè)運(yùn)行在線程中的任務(wù)都可以分解成多條指令,而且這些指令不用同時(shí)執(zhí)行。所以,單核設(shè)備可以首先切換到線程1去執(zhí)行指令1A,然后切換到線程2去執(zhí)行指令2A,接著返回到線程1再去執(zhí)行1B、1C、1D,然后繼續(xù)切換到線程2,執(zhí)行2B、2C等等,以此類推。

這個(gè)線程之間的切換十分迅速,以至于在單核的設(shè)備中也會發(fā)生。幾乎所有的線程都在相同的時(shí)間內(nèi)進(jìn)行任務(wù)處理。其實(shí),這都是因?yàn)樗俣忍煸斐傻募傧螅拖耠娪啊逗诳偷蹏防锏奶毓rown一樣,可以變幻出很多的頭和手。

Java核心里的線程

在Java中,如果要想做平行任務(wù)處理的話,會在Runnable里面執(zhí)行你的代碼??梢岳^承Thread類,或者實(shí)現(xiàn)Runnable接口:

// 1
public class IAmAThread extends Thread {
    public IAmAThread() {
        super("IAmAThread");
    }

    @Override
    public void run() {

// your code (sequence of instructions)
    }
}
// to execute this sequence of instructions in a separate thread.
new IAmAThread().start();

// 2
public class IAmARunnable implements Runnable {
    @Override
    public void run() {

// your code (sequence of instructions)
    }
}
// to execute this sequence of instructions in a separate thread.
IAmARunnable myRunnable = new IAmARunnable();
new Thread(myRunnable).start();

這兩個(gè)方法基本上是一樣的。第一個(gè)版本是創(chuàng)建一個(gè)Thread類,第二個(gè)版本是需要?jiǎng)?chuàng)建一個(gè)Runnable對象,然后也需要一個(gè)Thread類來調(diào)用它。

Android上的線程

無論何時(shí)啟動(dòng)APP,所有的組件都會運(yùn)行在一個(gè)單獨(dú)的線程中(默認(rèn)的)——叫做主線程。這個(gè)線程主要用于處理UI的操作并為視圖組件和小部件分發(fā)事件等,因此主線程也被稱作UI線程(Main Thread)。除了Main Thread之外的線程都可稱為Worker Thread。Main Thread主要負(fù)責(zé)控制UI頁面的顯示、更新、交互等。 因此所有在UI線程中的操作要求越短越好,只有這樣用戶才會覺得操作比較流暢。一個(gè)比較好的做法是把一些比較耗時(shí)的操作,例如網(wǎng)絡(luò)請求、數(shù)據(jù)庫操作、 復(fù)雜計(jì)算等邏輯都封裝到單獨(dú)的線程,這樣就可以避免阻塞主線程,這樣就需要用到了Android的Handler機(jī)制。

這里劃重點(diǎn):Handler負(fù)責(zé)與子線程進(jìn)行通訊,從而讓子線程與主線程之間建立起協(xié)作的橋梁,使Android的UI更新的問題得到完美的解決

怎么創(chuàng)建Handler

既然Handler有這樣的好處,那么看Handler怎么用,官方給出了兩種方式創(chuàng)建一個(gè)Handler:

1、使用默認(rèn)的構(gòu)造方法:new Handler()。
2、使用帶參的構(gòu)造方法,參數(shù)是一個(gè)Runnable對象或者回調(diào)對象。

//第一種方法
private Handler handler = new Handler();  
   private Runnable myRunnable= new Runnable() {    
        public void run() {  
             //一些耗時(shí)操作
        }  
    }; 
 //其他地方調(diào)用
 handler.post(xxx);
 這里就寫一個(gè)post方法,實(shí)際上還有很多,諸如postDelayed、postAtTime
//第二種方法
Handler myHandler = new Handler() {  
          public void handleMessage(Message msg) {   
               switch (msg.what) {   
                   //根據(jù)參數(shù)進(jìn)行操作
                         break;   
               }   
               super.handleMessage(msg);   
          }   
     };  
  //其他地方調(diào)用
myHandler.sendMessage(xxx);

如何使用Handler

這里使用一個(gè)簡單的Demo來演示Handler的用法,界面偏簡單就不貼了,直接貼代碼,模擬的是點(diǎn)擊Button執(zhí)行下載,下載完成后更新UI。

public class MainActivity extends Activity implements Button.OnClickListener {

    private TextView statusTextView = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        statusTextView = (TextView)findViewById(R.id.statusTextView);
        Button btnDownload = (Button)findViewById(R.id.btnDownload);
        btnDownload.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        DownloadThread downloadThread = new DownloadThread();
        downloadThread.start();
    }

    class DownloadThread extends Thread{
        @Override
        public void run() {
            try{
                System.out.println("開始下載文件");
                //此處讓線程DownloadThread休眠5秒中,模擬文件的耗時(shí)過程
                Thread.sleep(5000);
                System.out.println("文件下載完成");
                //文件下載完成后更新UI
                MainActivity.this.statusTextView.setText("文件下載完成");
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

按照以前寫Java的思路的話可能會這么寫,但是運(yùn)行程序時(shí)候會發(fā)現(xiàn)控制臺報(bào)錯(cuò):

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. 

錯(cuò)誤的意思是只有創(chuàng)建View的原始線程才能更新View。出現(xiàn)這樣錯(cuò)誤的原因是Android中的View不是線程安全的,下面給出合理的解釋:

因?yàn)閁I訪問是沒有加鎖的,在多個(gè)線程中訪問UI是不安全的,如果有多個(gè)子線程都去更新UI,會導(dǎo)致界面不斷改變而混亂不堪。所以最好的解決辦法就是只有一個(gè)線程有更新UI的權(quán)限,所以這個(gè)時(shí)候就只能有一個(gè)線程振臂高呼:放開那女孩,讓我來!那么最合適的人選只能是主線程。

來自---Android中線程那些事

那么為了規(guī)避Android的這種機(jī)制,我們這里分別采用Handler的兩種方式來實(shí)現(xiàn)上面的代碼:

A、使用post方式

public class MainActivity extends Activity implements Button.OnClickListener {

    private TextView statusTextView = null;

    //uiHandler在主線程中創(chuàng)建,所以自動(dòng)綁定主線程
    private Handler uiHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        statusTextView = (TextView)findViewById(R.id.statusTextView);
        Button btnDownload = (Button)findViewById(R.id.btnDownload);
        btnDownload.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        DownloadThread downloadThread = new DownloadThread();
        downloadThread.start();
    }

    class DownloadThread extends Thread{
        @Override
        public void run() {
            try{
                System.out.println("開始下載文件");
                //此處讓線程DownloadThread休眠5秒中,模擬文件的耗時(shí)過程
                Thread.sleep(5000);
                System.out.println("文件下載完成");
                //文件下載完成后更新UI
                Runnable runnable = new Runnable() {
                    @Override
                    public void run() {
                        MainActivity.this.statusTextView.setText("文件下載完成");
                    }
                };
                uiHandler.post(runnable);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}


B、使用sendMessage方式實(shí)現(xiàn)

public class MainActivity extends Activity implements Button.OnClickListener {

    private TextView statusTextView = null;

    //uiHandler在主線程中創(chuàng)建,所以自動(dòng)綁定主線程
    private Handler uiHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 1:
                    System.out.println("msg.arg1:" + msg.arg1);
                    System.out.println("msg.arg2:" + msg.arg2);
                    MainActivity.this.statusTextView.setText("文件下載完成");
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        statusTextView = (TextView)findViewById(R.id.statusTextView);
        Button btnDownload = (Button)findViewById(R.id.btnDownload);
        btnDownload.setOnClickListener(this);
        System.out.println("Main thread id " + Thread.currentThread().getId());
    }

    @Override
    public void onClick(View v) {
        DownloadThread downloadThread = new DownloadThread();
        downloadThread.start();
    }

    class DownloadThread extends Thread{
        @Override
        public void run() {
            try{
                System.out.println("開始下載文件");
                //此處讓線程DownloadThread休眠5秒中,模擬文件的耗時(shí)過程
                Thread.sleep(5000);
                System.out.println("文件下載完成");
                //文件下載完成后更新UI
                Message msg = new Message();
                //雖然Message的構(gòu)造函數(shù)式public的,我們也可以通過以下兩種方式通過循環(huán)對象獲取Message
                //msg = Message.obtain(uiHandler);
                //msg = uiHandler.obtainMessage();

                //what是我們自定義的一個(gè)Message的識別碼,以便于在Handler的handleMessage方法中根據(jù)what識別
                //出不同的Message,以便我們做出不同的處理操作
                msg.what = 1;

                //我們可以通過arg1和arg2給Message傳入簡單的數(shù)據(jù)
                msg.arg1 = 123;
                msg.arg2 = 321;
                //我們也可以通過給obj賦值Object類型傳遞向Message傳入任意數(shù)據(jù)
                //msg.obj = null;
                //我們還可以通過setData方法和getData方法向Message中寫入和讀取Bundle類型的數(shù)據(jù)
                //msg.setData(null);
                //Bundle data = msg.getData();
                //將該Message發(fā)送給對應(yīng)的Handler
                uiHandler.sendMessage(msg);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

以上代碼來自博客:Android中Handler的使用

上面這兩種形式都能達(dá)到我們的要求,在此不一一測驗(yàn),注釋寫的很詳細(xì)了,看到這里應(yīng)該已經(jīng)大致知道了如何使用Handler,但是我想我們應(yīng)該遠(yuǎn)遠(yuǎn)不滿足于此,下一篇博客將帶著大家從源碼一起看看Handler機(jī)制到底是怎么實(shí)現(xiàn)的。
Handler機(jī)制從入門到放棄(二)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容