Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Handler消息機制深入淺出

Android Handler消息機制深入淺出

編輯:關於Android編程

作為Android開發人員,Handler這個類應該是再熟悉不過了,因為幾乎任何App的開發,都會使用到Handler這個類,有些同學可能就要說了,我完全可以使用AsyncTask代替它,這個確實是可以的,但是其實AsyncTask也是通過Handler實現的,具體的大家可以去看看源碼就行了,Handler的主要功能就是實現子線程和主線程的通信,例如在子線程中執行一些耗時操作,操作完成之後通知主線程跟新UI(因為Android是不允許在子線程中跟新UI的)。

下面就使用一個簡單的例子開始這篇文章吧

public class MainActivity extends Activity
{
  public static final int MSG_DOWNLOAD_FINISH=0X001;
  //創建一個Handler的匿名內部類
  private Handler handler=new Handler()
  {

    @Override
    public void handleMessage(Message msg)
    {
      switch(msg.what)
      {
        case MSG_DOWNLOAD_FINISH:
          Log.v("yzy", "handler所在的線程id是-->"+Thread.currentThread().getName());
          break;
      }
    }
    
  };

  @Override
  protected void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
  }
  
  //啟動一個下載線程
  public void download(View view)
  {
    new Thread()
    {
      public void run() {
        try
        {
          Log.v("yzy", "下載子線程的Name是--->"+Thread.currentThread().getName());
          //在子線程運行,模擬一個下載任務
          Thread.sleep(2000);
          //下載完成後,發送下載完成消息
          handler.sendEmptyMessage(MSG_DOWNLOAD_FINISH);
        } catch (InterruptedException e)
        {
          e.printStackTrace();
        }
      };
    }.start();
  }

運行結果:
08-03 16:31:46.418: V/yzy(30486): 下載子線程的Name是--->Thread-22588
08-03 16:31:48.421: V/yzy(30486): handler所在的線程Name是-->main


上面例子就是模擬了一個下載任務,當下載完成後,通過Handler對象發送一個消息,最後被handlerMessage方法接收並處理,通過運行結果可以發現,download方法是在子線程中完成的,而handlerMessage是在UI線程中被調用的。到了這裡一個簡單的Handler使用案例就結束了,如果你已經明白了上面的代碼,那麼說明你已經明白了Handler的最基本使用。
下面再來一個簡單的例子:

public void postRun(View view)
  {
    new Thread(){
      public void run() {
        try
        {
          Log.v("yzy", " 下載子線程的Name是--->"+Thread.currentThread().getName());
          Thread.sleep(2000);
          handler.post(new Runnable()
          {
            
            @Override
            public void run()
            {
              Log.v("yzy", "handler post run -->"+Thread.currentThread().getName());
            }
          });
        } catch (InterruptedException e)
        {
          e.printStackTrace();
        }
       
      };
      
    }.start();
   
  }

運行結果:
08-03 16:31:52.528: V/yzy(30486): 下載子線程的Name是--->Thread-22589
08-03 16:31:54.531: V/yzy(30486): handler post run -->main

在Handler中除了可以發送Message對象還可以發送Runnable對象,從運行結果來看發送的Runnable也是在main線程中執行的
那麼是不是通過handler發出的Message和Runnable都是在UI線程中執行的呢,這個可不一定,現在我在onCreate方法中創建一個handler2

HandlerThread thread=new HandlerThread("yzy");
    thread.start();
    
    handler2=new Handler(thread.getLooper())
    {
      public void handleMessage(Message msg) {
        switch(msg.what)
        {
          case MSG_DOWNLOAD_FINISH:
            Log.v("yzy", "handler所在的線程Name是-->"+Thread.currentThread().getName());
            break;
        }
        
      };
    };

然後將上面代碼中使用handler的地方換為handler2,然後運行代碼:代碼中創建了一個名稱為"yzy"的線程,然後再yzy線程中初始化了handler2
結果如下:
08-03 17:07:10.571: V/yzy(8224): 下載子線程的Name是--->Thread-23005
08-03 17:07:12.574: V/yzy(8224): handler所在的線程Name是-->yzy

我們發現通過handler2發出的Message和Runnable都是在yzy線程中執行的。
如果對於上面的代碼和運行結果你都知道原因,那麼說明你已經對Handler的使用非常熟悉了,如果不清楚上面的運行結果,那麼請往下繼續吧

開始分析原因之前我想提出以下幾個問題:
1、Handler發出的Message和Runnable是如何傳遞到UI線程的?
2、在什麼情況下通過Handler發出的Message和Runnable不會傳遞到UI線程?
3、為什麼我是在HandlerThread初始化一個Handler?

下面我們就逐一解決上述問題吧,在解決問題之前我們需要分析一個Hanlder這個類的源碼。


首先看看Handler的構造函數,它有好幾個構造函數,我們一個個的看吧

//無參構造函數
  public Handler() {
		//檢查Handler是否是static的,如果不是的,那麼有可能導致內存洩露,具體可以百度
        if (FIND_POTENTIAL_LEAKS) {
            final Class klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
		//很重要的一個屬性,暫時就將Looper理解成一個消息隊列的管理者吧,用來從消息隊列中取消息的,稍後會分析這個類
        mLooper = Looper.myLooper();
        if (mLooper == null) {
			//這個異常非常常見哦,Handler一定要在有Looper的線程上執行,這個也就是為什麼我在HandlerThread中初始化Handler
			//而沒有在Thread裡面初始化,如果在Thread裡面初始化需要先調用Looper.prepare方法
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
		//將mLooper裡面的消息隊列復制到自身的mQueue,這也就意味著Handler和Looper是公用一個消息隊列
        mQueue = mLooper.mQueue;
		//回調函數默認是Null
        mCallback = null;
    }

	//這個和上面一樣的,只不過傳入了一個回調函數
    public Handler(Callback callback) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        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;
    }

    /**
     * 傳入一個Looper,並和Looper公用一個消息隊列
     */
    public Handler(Looper looper) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = null;
    }

    /**
     * 和上面一個差不多,就不在贅述
     */
    public Handler(Looper looper, Callback callback) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
    }

看了Handler的構造函數,發現Hanlder裡面有一個消息隊列,這個消息隊列是從Looper裡面傳入的,並且Handler需要在有Looper的線程中調用。既然它和Looper關系這麼緊密,我們看看Looper到底是什麼東西吧,打開Looper的源碼,頂部看到這樣的一個例子:

*  class LooperThread extends Thread {
  *      public Handler mHandler;
  *      
  *      public void run() {
  *          Looper.prepare();
  *          
  *          mHandler = new Handler() {
  *              public void handleMessage(Message msg) {
  *                  // process incoming messages here
  *              }
  *          };
  *          
  *          Looper.loop();
  *      }
  * 

這個就是為了說明Handler只能在有Looper的線程中創建,UI線程是有Looper的,對於沒有Looper的線程,需要調用Loope.prepare,Looper.loop等函數,如上例。
那我們正式進入Looper的源碼看看吧

//Looper的構造函數,主要是對消息隊列等屬性初始化
     private Looper() {
        mQueue = new MessageQueue();
        mRun = true;
		//記錄所在線程
        mThread = Thread.currentThread();
    }
	
   //在上面的例子中,看到當在一個沒有Looper的線程中創建Handler,就需要執行這個函數,
   //這個函數主要是new 一個Looper,燃火放入ThreadLocal中保存,做到一個線程就創建一次。第二次調用這個方法會拋出異常的
    public static final void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }
	
	//prepare是創建並保存,這個方法就是取出Looper
    public static final Looper myLooper() {
        return (Looper)sThreadLocal.get();
    }
    
	//這個方法是給系統調用的,UI線程通過調用這個線程,從而保證UI線程裡有一個Looper
	//需要注意:如果一個線程是UI線程,那麼myLooper和getMainLooper是同一個Looper,通過這個代碼很好理解
    public static final void prepareMainLooper() {
        prepare();
        setMainLooper(myLooper());
        if (Process.supportsProcesses()) {
            myLooper().mQueue.mQuitAllowed = false;
        }
    }


    
	//獲得UI線程的Looper,通常我想Hanlder的handleMessage在UI線程執行時通常會new  Handler(getMainLooper());
    public synchronized static final Looper getMainLooper() {
        return mMainLooper;
    }

    /**
     *  上面的例子是執行了這個方法的,這個方法是一個死循環,一直從消息隊列中讀取消息,並分發出去
     */
    public static final void loop() {
		//得到本線程的Looper
        Looper me = myLooper();
		//拿到消息隊列
        MessageQueue queue = me.mQueue;
        while (true) {
			//從消息隊列中拿一個消息
            Message msg = queue.next(); // might block
			//取到了一個消息
            if (msg != null) {
				//這個一般不等於Null,通常就是發出這個Message的Handler
                if (msg.target == null) {
                    // No target is a magic identifier for the quit message.
                    return;
                }
                if (me.mLogging!= null) me.mLogging.println(
                        ">>>>> Dispatching to " + msg.target + " "
                        + msg.callback + ": " + msg.what
                        );
				//調用Handler的dispatchMessage方法
                msg.target.dispatchMessage(msg);
                if (me.mLogging!= null) me.mLogging.println(
                        "<<<<< Finished to    " + msg.target + " "
                        + msg.callback);
				//將消息放入消息池
                msg.recycle();
            }
        }
    }
    
	//返回Looper的消息隊列
    public static final MessageQueue myQueue() {
        return myLooper().mQueue;
    }

看了Looper的源碼之後總結一下:
在一個線程的run方法裡面先調用prepare方法,主要保證了該線程裡面有了一個Looper對象,如果在這個線程裡面創建一個Hanlder,那麼看看上面Handler的空的構造方法,Handler裡面的Looper就是線程裡面保存的Looper,從而可以創建的Handler和線程公用一個Looper,也就是公用一個消息隊列。說的更具體一點就是Hanlder和Looper所在的線程公用一個消息隊列。最後調用loop方法,這個線程不斷從消息隊列取消息,然後調用Hanlder的dispatchMessage方法。

最後看看Handler的其他幾個比較重要的方法吧

//在Handler中發送一個消息到消息隊列,類似的方法很多,我只選了這一個
   public boolean sendMessageAtTime(Message msg, long uptimeMillis)
    {
        boolean sent = false;
        MessageQueue queue = mQueue;
        if (queue != null) {
			//將target設置成了Handler
            msg.target = this;
            sent = queue.enqueueMessage(msg, uptimeMillis);
        }
        else {
            RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
        }
        return sent;
    }
	//這個方法是在loop方法中被調用的
	 public void dispatchMessage(Message msg) {
		//首先檢查msg中callback(是一個Runnable對象)是否為Null,如果不為null,則直接調用callback中的run方法
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
			//檢查Handler中的callback是否為空,如果不為空,則直接調用callback中的handleMessage,如果返回TRUE,則直接返回不在調用Handler中的handleMessage
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
			//最後調用Handler中的handlerMessage
            handleMessage(msg);
        }
    }

在前面的例子中,我們曾經使用過Handler中的post方法,我們看看它是怎麼實現的

 public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
	
	 private final Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

	
	public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

我想如果前面明白了的,這個就不用我說了的吧。
其實在Activity中有一個方法runOnUiThread,就是使用這個原理實現的,如下:

//讓Runnable在UI線程執行
	    public final void runOnUiThread(Runnable action) {
		//如果當前線程不是UI線程,那麼通過Handler轉發到UI線程
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

View裡面也有類似的方法:

public boolean post(Runnable action) {
        Handler handler;
        if (mAttachInfo != null) {
            handler = mAttachInfo.mHandler;
        } else {
            // Assume that post will succeed later
            ViewRoot.getRunQueue().post(action);
            return true;
        }

        return handler.post(action);
    }

原理是上面是一樣的。
根據上面的學習,可以得出結論:
1、如果Handler是在UI線程創建的,那麼Handler和UI線程共享消息隊列,所以通過Hanlder發出的消息都是在UI線程中執行的
2、如果要讓Handler發出的消息在子線程中執行,很簡單,在創建Handler的時候傳入子線程的Looper(通過Looper.prepare創建)
到這裡相信在前面我提及的三個問題都已經有答案了吧,如果有沒有明白的,歡迎留言。。。。

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