【Android】Handler之線程間通信

Android多線程之間的消息傳遞是通過Handler來實現的。不同的線程之間又分爲兩類:即Main Thread(主線程)和Worker Thread(”苦力“線程) 。


Main Thread又稱爲UI Thread,意思是主線程負責Android App的界面管理與顯示和與用戶之間的交互,是最重要的也是最核心的Thread,所以我們要對它細心呵護,如果處理不當使UI Thread阻塞,跳出了不友善的窗口,這樣是非常影響用戶體驗的,也是無法容忍的。


而在Andrioid多線程開發中有兩點是我們必須要注意的:

1.原則上Worker線程不允許修改UI線程(即主線程)的內容或控件的,但是某些特殊的控件允許被修改,如ProgressBar等。


2.在一個應用程序中,主線程通常用於接收用戶的輸入,以及將運算的結果反饋給用戶,所以說對於一些可能會產生阻塞的操作,如連接遠程服務器下載數據信息、複雜的   數據計算等必須放置在Worker Thread中。


Main Thread還是比較好理解的,那麼這個Worker Thread又該如何理解呢?相信大家也看到了我上面對它的翻譯,即“苦力”線程。我們可以把一個Android App比作一個公司的Project,Thread比作一個單獨的Worker,Main Thread就是一個Leader。和用戶接洽這種拋頭露面的事(UI)和與上級的溝通由Leader來做,苦差事都是由Worker來做,比如跑腿,搬東西,訂外賣,取快遞,同時不能反對Leader的意思,然後Worker把工作的結果彙報給LeaderLeader進行彙總處理。這樣來類比着是不是更好理解呢。


接下來介紹一下我們今天的主角Handler,handle的字面意思是處理、解決,加上er後綴意思大概就是處理事務的東西。

作爲一個Android開發菜鳥,時常聽到前輩大牛們對Handler是讚不絕口,讚賞Handle設計的精妙,簡潔,強大。所以我們有必要了解一下Handler工作機理,和它一起搭配幹活的還有和Looper(循環器)以及MessageQueue(消息隊列)。俗話說的好,沒圖你說個J8,哈哈。我已經準備了高清五碼給大家呦,走你。


上圖就能夠很清晰明瞭的向我們描述Handler的工作流程。橢圓形代表着Handler,長方形代表MessageQueue,而正六邊形就是Looper。Handler可以通過obtainMessage()方法創建許多個消息Message,然後把Message發送到MessageQueue中,(在這裏提一句,MessageQueue是一個隊列,有先進先出(FIFO)的特性,相信學過數據結構的小夥伴都不會陌生,如果對它還不熟的話趕快去惡補數據結構吧),然後按照先進先出順序由Looper調用Handler的HandleMessage()方法處理(如果此刻沒有消息傳遞過來那麼Looper就會阻塞掉),注意:HandleMessage()方法是你自己需要重寫的,具體實現的功能由你的實現代碼決定。


以上就是今天的預備知識,理解了以上內容之後,讓我們通過一個例子來看看Android是如何通過Handler來實現多線程之間通信的。


首先打開我們的Android Studio,創建一個名爲TestMultiThreadsCommunicateDemo(臥槽好長)的Blank Project,一路next下去就好了。


然後在activity_Main.xml中創建如下佈局:

我們的主要業務邏輯就是點擊一下按鈕,然後從遠程服務器傳過來的消息顯示在TextView中,替換掉原來的內容(Message)


佈局文件代碼如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <TextView
        android:id="@+id/textview"
        android:text="Message"
        android:background="#A6D4F2"
        android:textSize="20sp"
        android:textColor="#000000"
        android:paddingLeft="20dp"
        android:gravity="center_vertical"
        android:layout_width="match_parent"
        android:layout_height="50dp" /><pre name="code" class="html">    <Button
        android:text="點擊"
        android:id="@+id/button"
        android:layout_below="@id/textview"
        android:layout_marginTop="20dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout></span>

然後打開MainActivity.java,定義各控件的對象,獲取TextView和Button控件,給button設置監聽器(這裏我們採用我比較習慣的匿名內部類的方式),很簡單我就不解釋了,代碼在下面

public class MainActivity extends ActionBarActivity {
    private Button mButton;
    private TextView mTextView;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton = (Button)findViewById(R.id.button);
        mTextView = (TextView)findViewById(R.id.textview);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               
            }
        });
       
    }


接下來我們自己定義一個叫做NetworkThread的Worker Thread,顧名思義,就是模擬Worker線程訪問網絡下載數據,代碼如下:

public class NetWorkThread extends Thread{
        @Override
        public void run() {
            //模擬訪問網絡,所以當線程運行時,會先休眠1秒鐘,處理數據。
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //變量s模擬從網絡中獲取的數據。
            //textview.setText(s);  這樣的做法是錯誤的,因爲在Android系統當中只有Main Thread才能操作UI。
            String s = "Message from the server!!!";

           
        }
    }

NetWorkThread繼承了Thread類,那麼就必須要重寫run()方法,我相信聰明的小夥伴們都是很清楚的。主要功能就是模擬在服務器端下載數據,其實就是字符串s而已,中間的sleep休眠1000毫秒代表取數據消耗的時間,需要注意的是,在sleep()方法調用的時候要try catch一下,防止拋出InterruptedException。還有就是千萬不要像註釋裏的

  //變量s模擬從網絡中獲取的數據。
            //textview.setText(s);  這樣的做法是錯誤的,因爲在Android系統當中只有Main Thread才能操作UI。
一樣,這樣你就違背了我上面提到的第一點:

1.原則上Worker線程不允許修改UI線程(即主線程)的內容或控件的,但是某些特殊的控件允許被修改,如ProgressBar等。


接下來我們來定義一個我們自己的Handler類MyHandler

public class MyHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {   //子類必須覆蓋這個方法來接收消息
            String string = (String)msg.obj;
            mTextView.setText(string);
        }
    }

這裏我們重寫了handleMessage()方法,還記得handleMessage方法的作用麼?對了就是Handler提供給Looper來調用處理消息的方法。msg是傳遞過來的消息對象,注意這裏的obj,它是消息msg的一個屬性,代表你傳遞的信息,可以使任意類型的數據,最後再調用TextView的setText()方法改變TextView顯示的文本。


之後我們就要實現MyHandler類了:

 private Handler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton = (Button)findViewById(R.id.button);
        mTextView = (TextView)findViewById(R.id.textview);handler = new MyHandler(); mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Thread thread = new NetWorkThread();
                thread.start();
            }
        });
        
    }


定義一個Handler對象handler,new一個MyHandler出來,然後在監聽器中定義我們的“苦力線程”,調用start()方法,開啓Worker線程。


最重要的來了,我們到底是怎樣實現不同線程間的通信的呢?

先看代碼:

public class NetWorkThread extends Thread{
        @Override
        public void run() {
            //模擬訪問網絡,所以當線程運行時,會先休眠1秒鐘,處理數據。
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //變量s模擬從網絡中獲取的數據。
            //textview.setText(s);  這樣的做法是錯誤的,因爲在Android系統當中只有Main Thread才能操作UI。    String s = "Message from the server!!!";            Message msg = handler.obtainMessage();
            //handler.sendMessage(msg);
            msg.obj = s;
            //sendMessage()方法,無論是在主線程當中發送,還是在Worker Thread當中發送都是可以的
            handler.sendMessage(msg);
        }
    }



我們定義了一個Message消息對象msg,然後把下載過來的數據,附加在msg的obj屬性上,然後通過handler的sendMessage()方法,發送給MessageQueue,然後Looper就會自動調用Handler的handleMessage()方法:
public void handleMessage(Message msg) {   //子類必須覆蓋這個方法來接收消息
            String string = (String)msg.obj;
            mTextView.setText(string);
        }

然後你點擊按鈕的時候,神奇的事情就發生了。



好了,最後把activity的代碼彙總一下給大家,感謝。


package com.example.chad.testworkerthreadcommunicate;

        import android.os.Handler;
        import android.os.Message;
        import android.support.v7.app.ActionBarActivity;
        import android.os.Bundle;
        import android.view.Menu;
        import android.view.MenuItem;
        import android.view.View;
        import android.widget.Button;
        import android.widget.TextView;


public class MainActivity extends ActionBarActivity {
    private Button mButton;
    private TextView mTextView;
    private Handler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton = (Button)findViewById(R.id.button);
        mTextView = (TextView)findViewById(R.id.textview);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Thread thread = new NetWorkThread();
                thread.start();
            }
        });
        handler = new MyHandler();
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    public class MyHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {   //子類必須覆蓋這個方法來接收消息
            String string = (String)msg.obj;
            mTextView.setText(string);
        }
    }

    public class NetWorkThread extends Thread{
        @Override
        public void run() {
            //模擬訪問網絡,所以當線程運行時,會先休眠1秒鐘,處理數據。
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //變量s模擬從網絡中獲取的數據。
            //textview.setText(s);  這樣的做法是錯誤的,因爲在Android系統當中只有Main Thread才能操作UI。
            String s = "Message from the server!!!";

            Message msg = handler.obtainMessage();
            //handler.sendMessage(msg);
            msg.obj = s;
            //sendMessage()方法,無論是在主線程當中發送,還是在Worker Thread當中發送都是可以的
            handler.sendMessage(msg);
        }
    }
}






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章