Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Handler消息傳遞處理機制

Handler消息傳遞處理機制

編輯:關於Android編程

Android是消息驅動的,實現消息驅動有幾個要素:

  1. 消息的表示:Message
  2. 消息隊列:MessageQueue
  3. 消息循環,用於循環取出消息進行處理:Looper
  4. 消息處理,消息循環從消息隊列中取出消息後要對消息進行處理:Handler

1.學習路線圖:

\


2.Handler類的引入:

\


3.Handler的執行流程圖:

\

流程圖解析:相關名詞

  • UI線程:就是我們的主線程,系統在創建UI線程的時候會初始化一個Looper對象,同時也會創建一個與其關聯的MessageQueue;
  • Handler:作用就是發送與處理信息,如果希望Handler正常工作,在當前線程中要有一個Looper對象
  • Message:Handler接收與處理的消息對象
  • MessageQueue:消息隊列,先進先出管理Message,在初始化Looper對象時會創建一個與之關聯的MessageQueue;
  • Looper:每個線程只能夠有一個Looper,管理MessageQueue,不斷地從中取出Message分發給對應的Handler處理!

簡單點說:

當我們的子線程想修改Activity中的UI組件時,我們可以新建一個Handler對象,通過這個對象向主線程發送信息;而我們發送的信息會先到主線程的MessageQueue進行等待,由Looper按先入先出順序取出,再根據message對象的what屬性分發給對應的Handler進行處理!

 

4.Handler的相關方法:

  • voidhandleMessage(Message msg):處理消息的方法,通常是用於被重寫!
  • sendEmptyMessage(int what):發送空消息
  • sendEmptyMessageDelayed(int what,long delayMillis):指定延時多少毫秒後發送空信息
  • sendMessage(Message msg):立即發送信息
  • sendMessageDelayed(Message msg):指定延時多少毫秒後發送信息
  • final booleanhasMessage(int what):檢查消息隊列中是否包含what屬性為指定值的消息 如果是參數為(int what,Object object):除了判斷what屬性,還需要判斷Object屬性是否為指定對象的消息

5.Handler的使用示例:

1)Handler寫在主線程中

在主線程中,因為系統已經初始化了一個Looper對象,所以我們直接創建Handler對象,就可以進行信息的發送與處理了!

代碼示例:簡單的一個定時切換圖片的程序,通過Timer定時器,定時修改ImageView顯示的內容,從而形成幀動畫

運行效果圖:

\

實現代碼:

 

  
      
 

 

MainActivity.java:

 

public class MainActivity extends Activity {  
    //定義切換的圖片的數組id  
    int imgids[] = new int[]{  
        R.drawable.s_1, R.drawable.s_2,R.drawable.s_3,  
        R.drawable.s_4,R.drawable.s_5,R.drawable.s_6,  
        R.drawable.s_7,R.drawable.s_8  
    };  
    int imgstart = 0;   
	final Handler myHandler = new Handler()  
    {  
      @Override  
      //重寫handleMessage方法,根據msg中what的值判斷是否執行後續操作  
      public void handleMessage(Message msg) {  
        if(msg.what == 0x123)  
           {  
            imgchange.setImageResource(imgids[imgstart++ % 8]);  
           }  
        }  
    };    
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        final ImageView imgchange = (ImageView) findViewById(R.id.imgchange);  
        //使用定時器,每隔200毫秒讓handler發送一個空信息  
        new Timer().schedule(new TimerTask() {            
            @Override  
            public void run() {  
                myHandler.sendEmptyMessage(0x123);        
            }  
        }, 0,200);  
    }  
} 

 


2)Handler寫在子線程中

如果是Handler寫在了子線程中的話,我們就需要自己創建一個Looper對象了!創建的流程如下:

1 )直接調用Looper.prepare()方法即可為當前線程創建Looper對象,而它的構造器會創建配套的MessageQueue;2 )創建Handler對象,重寫handleMessage( )方法就可以處理來自於其他線程的信息了!3 )調用Looper.loop()方法啟動Looper

使用示例: 輸入一個數,計算後通過Toast輸出在這個范圍內的所有質數

實現代碼:main.xml:

 

  
    

 

MainActivity.java:

 

public class CalPrime extends Activity  
{  
    static final String UPPER_NUM = "upper";  
    EditText etNum;  
    CalThread calThread;  
    // 定義一個線程類  
    class CalThread extends Thread  
    {  
        public Handler mHandler;  
  
        public void run()  
        {  
            Looper.prepare();  
            mHandler = new Handler()  
            {  
                // 定義處理消息的方法  
                @Override  
                public void handleMessage(Message msg)  
                {  
                    if(msg.what == 0x123)  
                    {  
                        int upper = msg.getData().getInt(UPPER_NUM);  
                        List nums = new ArrayList();  
                        // 計算從2開始、到upper的所有質數  
                        outer:  
                        for (int i = 2 ; i <= upper ; i++)  
                        {  
                            // 用i處於從2開始、到i的平方根的所有數  
                            for (int j = 2 ; j <= Math.sqrt(i) ; j++)  
                            {  
                                // 如果可以整除,表明這個數不是質數  
                                if(i != 2 && i % j == 0)  
                                {  
                                    continue outer;  
                                }  
                            }  
                            nums.add(i);  
                        }  
                        // 使用Toast顯示統計出來的所有質數  
                        Toast.makeText(CalPrime.this , nums.toString()  
                            , Toast.LENGTH_LONG).show();  
                    }  
                }  
            };  
            Looper.loop();  
        }  
    }  
    @Override  
    public void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
        etNum = (EditText)findViewById(R.id.etNum);  
        calThread = new CalThread();  
        // 啟動新線程  
        calThread.start();  
    }  
    // 為按鈕的點擊事件提供事件處理函數  
    public void cal(View source)  
    {  
        // 創建消息  
        Message msg = new Message();  
        msg.what = 0x123;  
        Bundle bundle = new Bundle();  
        bundle.putInt(UPPER_NUM ,  
            Integer.parseInt(etNum.getText().toString()));  
        msg.setData(bundle);  
        // 向新線程中的Handler發送消息  
        calThread.mHandler.sendMessage(msg);  
    }  
} 

 

6.Handler的工作流程

在上面我們簡單的說明了Handler是如何使用的。那麼現在我們就來看一下這個Handler是如何工作的。在Android的消息機制中主要是由Handler,Looper,MessageQueue,Message等組成。而Handler得運行依賴後三者。那麼我們就來看一下它們是如何聯系在一起的。

Looper

  在一個Android應用啟動的時候,會創建一個主線程,也就是UI線程。而這個主線程也就是ActivityThread。在ActivityThread中有一個靜態的main方法。這個main方法也就是我們應用程序的入口點。我們來看一下這個main方法。

 

public static void main(String[] args) {

    ......

    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    ......

    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

 

在上面代碼中通過prepareMainLooper方法為主線程創建一個Looper,而loop則是開啟消息循環。從上面代碼我們可以猜想到在loop方法中應該存在一個死循環,否則給我們拋出RuntimeException。也就是說主線程的消息循環是不允許被退出的。下面我們就來看一下這個Looper類。

  首先我們看一下Looper的構造方法。

 

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

 

在這個構造方法中創建了一個消息隊列。並且保存當前線程的對象。其中quitAllowed參數表示是否允許退出消息循環。但是我們注意到這個構造方法是private,也就是說我們自己不能手動new一個Looper對象。那麼我們就來看一下如何創建一個Looper對象。之後在Looper類中我們找到下面這個方法。

 

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

 

 

在這裡新建了一個Looper對象,然後將這個對象保存在ThreadLocal中,當我們下次需要用到Looper的之後直接從這個sThreadLocal中取出即可。在這裡簡單說明一下ThreadLocal這個類,ThreadLocal它實現了本地變量存儲,我們將當前線程的數據存放在ThreadLocal中,若是有多個變量共用一個ThreadLocal對象,這時候在當前線程只能獲取該線程所存儲的變量,而無法獲取其他線程的數據。在Looper這個類中為我們提供了myLooper來獲取當前線程的Looper對象。從上面的方法還能夠看出,一個線程只能創建一次Looper對象。然後我們在看一下這個prepare在哪裡被使用的。

 

 

public static void prepare() {
    prepare(true);
}

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

 

prepareMainLooper方法:這個方法在上面的ActivityThread中的main方法中我們就已經見到過了。它是為主線程創建一個Looper,在主線程創建Looper對象中,就設置了不允許退出消息循環。並且將主線程的Looper保存在sMainLooper中,我們可以通過getMainLooper方法來獲取主線程的Looper。
  在ActivityThread中的main方法中除了創建一個Looper對象外,還做了另外一件事,那就是通過loop方法開啟消息循環。那麼我們就來看一下這個loop方法做了什麼事情。

 

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        // This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        msg.target.dispatchMessage(msg);

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }

        msg.recycleUnchecked();
    }
}

 

和我們剛才猜想的一樣,在loop中確實存在一個死循環,而唯一退出該循環的方式就是消息隊列返回的消息為空。然後我們通過消息隊列的next()方法獲得消息。msg.target是發送消息的Handler,通過Handler中的dispatchMessage方法又將消息交由Handler處理。消息處理完成之後便對消息進行回收處理。在這裡我們也能夠通過quit和quitSafely退出消息循環。

 

public void quit() {
    mQueue.quit(false);
}

public void quitSafely() {
    mQueue.quit(true);
}

 

我們可以看出對於消息循環的退出,實際上就是調用消息隊列的quit方法。這時候從MessageQueue的next方法中取出的消息也就是null了。下面我們來看一下這個MessageQueue。

MessageQueue

  MessageQueue翻譯為消息隊裡,在這個消息隊列中是采用單鏈表的方式實現的,提高插入刪除的效率。

Handler

  在這裡我們首先看一下Handler的構造方法。

 

public Handler(Callback callback, boolean async) {

    ......

    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

 

從這個構造方法中我們可以看出在一個沒有創建Looper的線程中是無法創建一個Handler對象的。所以說我們在子線程中創建一個Handler時首先需要創建Looper,並且開啟消息循環才能夠使用這個Handler。

總結

  在這裡我們重新整理一下我們的思路,看一下這個Handler的整個工作流程。在主線程創建的時候為主線程創建一個Looper,創建Looper的同時在Looper內部創建一個消息隊列。而在創鍵Handler的時候取出當前線程的Looper,並通過該Looper對象獲得消息隊列,然後Handler在子線程中發送消息也就是在該消息隊列中添加一條Message。最後通過Looper中的消息循環取得這條Message並且交由Handler處理。

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