国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

Android消息機制Handler的工作過程詳解

2019-12-12 04:01:20
字體:
來源:轉載
供稿:網友

綜述

  在Android系統中,出于對性能優化的考慮,對于Android的UI操作并不是線程安全的。也就是說若是有多個線程來操作UI組件,就會有可能導致線程安全問題。所以在Android中規定只能在UI線程中對UI進行操作。這個UI線程是在應用第一次啟動時開啟的,也稱之為主線程(Main Thread),該線程專門用來操作UI組件,在這個UI線程中我們不能進行耗時操作,否則就會出現ANR(Application Not Responding)現象。如果我們在子線程中去操作UI,那么程序就回給我們拋出異常。這是因為在ViewRootImpl中對操作UI的線程進行檢查。如果操作UI的線程不是主線程則拋出異常(對于在檢查線程之前在非UI線程已經操作UI組件的情況除外)。所以這時候我們若是在子線程中更新UI的話可以通過Handler來完成這一操作。

Handler用法簡介

  在開發中,我們對Handler的使用也基本上算是家常便飯了。在這里我們就簡單的說一下Handler的幾種用法示例,就不在具體給出Demo進行演示。在這里我們只針對后面這一種情形來看一下Handler的使用:在子線程完成任務后通過Handler發送消息,然后在主線程中去操作UI。
  一般來說我們會在主線程中創建一個Handler的匿名內部類,然后重寫它的handleMessage方法來處理我們的UI操作。代碼如下所示。

private Handler mHandler = new Handler(){  @Override  public void handleMessage(Message msg) {    switch (msg.what){      //根據msg.what的值來處理不同的UI操作      case WHAT:        break;      default:        super.handleMessage(msg);        break;    }  }};

  我們還可以不去創建一個Handler的子類對象,直接去實現Handler里的CallBack接口,Handler通過回調CallBack接口里的handleMessage方法從而實現對UI的操作。

private Handler mHandler = new Handler(new Handler.Callback() {  @Override  public boolean handleMessage(Message msg) {    return false;  }});

  然后我們就可以在子線程中發送消息了。

new Thread(new Runnable() {  @Override  public void run() {    //子線程任務    ...    //發送方式一 直接發送一個空的Message    mHandler.sendEmptyMessage(WHAT);    //發送方式二 通過sendToTarget發送    mHandler.obtainMessage(WHAT,arg1,arg2,obj).sendToTarget();    //發送方式三 創建一個Message 通過sendMessage發送    Message message = mHandler.obtainMessage();    message.what = WHAT;    mHandler.sendMessage(message);  }}).start();

  在上面我們給出了三種不同的發送方式,當然對于我們還可以通過sendMessageDelayed進行延時發送等等。如果我們的Handler只需要處理一條消息的時候,我們可以通過post一系列方法進行處理。

private Handler mHandler = new Handler();new Thread(new Runnable() {  @Override  public void run() {    mHandler.post(new Runnable() {      @Override      public void run() {        //UI操作        ...      }    });  }}).start();

  在Handler中處理UI操作時,上面的Handler對象必須是在主線程創建的。如果我們想在子線程中去new一個Handler對象的話,就需要為Handler指定Looper。

private Handler mHandler;new Thread(new Runnable() {  @Override  public void run() {    mHandler = new Handler(Looper.getMainLooper()){      @Override      public void handleMessage(Message msg) {        super.handleMessage(msg);        //UI操作        ...      }    };  }}).start();

  對于這個Looper是什么,下面我們會詳細介紹。對于Handler的使用依然存在一個問題,由于我們創建的Handler是一個匿名內部類,他會隱式的持有外部類的一個對象(當然內部類也是一樣的),而往往在子線程中是一個耗時的操作,而這個線程也持有Handler的引用,所以這個子線程間接的持有這個外部類的對象。我們假設這個外部類是一個Activity,而有一種情況就是我們的Activity已經銷毀,而子線程仍在運行。由于這個線程持有Activity的對象,所以,在Handler中消息處理完之前,這個Activity就一直得不到回收,從而導致了內存泄露。如果內存泄露過多,則會導致OOM(OutOfMemory),也就是內存溢出。那么有沒有什么好的解決辦法呢?
  我們可以通過兩種方案來解決,第一種方法我們在Activity銷毀的同時也殺死這個子線程,并且將相對應的Message從消息隊列中移除;第二種方案則是我們創建一個繼承自Handler的靜態內部類。因為靜態內部類不會持有外部類的對象??墒沁@時候我們無法去訪問外部類的非靜態的成員變量,也就無法對UI進行操作。這時候我們就需要在這個靜態內部類中使用弱引用的方式去指向這個Activity對象。下面我們看一下示例代碼。

static class MyHandler extends Handler{  private final WeakReference<MyActivity> mActivity;  public MyHandler(MyActivity activity){    super();    mActivity = new WeakReference<MyActivity>(activity);  }  @Override  public void handleMessage(Message msg) {    MyActivity myActivity = mActivity.get();    if (myActivity!=null){      myActivity.textView.setText("123456789");    }  }}

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

    prepare方法:這個是用于在子線程中創建一個Looper對象,在子線程中是可以退出消息循環的。
    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();  }}

  第2~6行:獲取當前線程中的Looper,并從Looper中獲得消息隊列。
  第10~11行:確保當前線程屬于當前進程,并且記錄真實的token。clearCallingIdentity的實現是在native層,對于具體是如何實現的就不在進行分析。
  第14~18行:從消息隊列中取出消息,并且只有當取出的消息為空的時候才會跳出循環。
  第27行:將消息重新交由Handler處理。
  第35~42行:確保調用過程中線程沒有被銷毀。
  第44行:對消息進行回收處理。
  和我們剛才猜想的一樣,在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翻譯為消息隊里,在這個消息隊列中是采用單鏈表的方式實現的,提高插入刪除的效率。對于MessageQueue在這里我們也只看一下它的入隊和出隊操作。
  MessageQueue入隊方法。

boolean enqueueMessage(Message msg, long when) {  ......  synchronized (this) {    ......    msg.markInUse();    msg.when = when;    Message p = mMessages;    boolean needWake;    if (p == null || when == 0 || when < p.when) {      // New head, wake up the event queue if blocked.      msg.next = p;      mMessages = msg;      needWake = mBlocked;    } else {      // Inserted within the middle of the queue. Usually we don't have to wake      // up the event queue unless there is a barrier at the head of the queue      // and the message is the earliest asynchronous message in the queue.      needWake = mBlocked && p.target == null && msg.isAsynchronous();      Message prev;      for (;;) {        prev = p;        p = p.next;        if (p == null || when < p.when) {          break;        }        if (needWake && p.isAsynchronous()) {          needWake = false;        }      }      msg.next = p; // invariant: p == prev.next      prev.next = msg;    }    // We can assume mPtr != 0 because mQuitting is false.    if (needWake) {      nativeWake(mPtr);    }  }  return true;}

  在這里我們簡單說一下這個入隊的方法。消息的插入過程是在第13~36行完成了。在這里首先判斷首先判斷消息隊列里有沒有消息,沒有的話則將當前插入的消息作為隊頭,并且這時消息隊列如果處于等待狀態的話則將其喚醒。若是在中間插入,則根據Message創建的時間進行插入。
  MessageQueue出隊方法。

Message next() {  ......  int nextPollTimeoutMillis = 0;  for (;;) {    if (nextPollTimeoutMillis != 0) {      Binder.flushPendingCommands();    }    nativePollOnce(ptr, nextPollTimeoutMillis);    synchronized (this) {      // Try to retrieve the next message. Return if found.      final long now = SystemClock.uptimeMillis();      Message prevMsg = null;      Message msg = mMessages;      if (msg != null && msg.target == null) {        // Stalled by a barrier. Find the next asynchronous message in the queue.        do {          prevMsg = msg;          msg = msg.next;        } while (msg != null && !msg.isAsynchronous());      }      if (msg != null) {        if (now < msg.when) {          // Next message is not ready. Set a timeout to wake up when it is ready.          nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);        } else {          // Got a message.          mBlocked = false;          if (prevMsg != null) {            prevMsg.next = msg.next;          } else {            mMessages = msg.next;          }          msg.next = null;          if (DEBUG) Log.v(TAG, "Returning message: " + msg);          msg.markInUse();          return msg;        }      } else {        // No more messages.        nextPollTimeoutMillis = -1;      }      // Process the quit message now that all pending messages have been handled.      if (mQuitting) {        dispose();        return null;      }      ......    }    .....  }}

  第11行:nativePollOnce方法在native層,若是nextPollTimeoutMillis為-1,這時候消息隊列處于等待狀態。
  第25~42行:按照我們設置的時間取出消息。
  第43~45行:這時候消息隊列中沒有消息,將nextPollTimeoutMillis設為-1,下次循環消息隊列則處于等待狀態。
  第48~52行:退出消息隊列,返回null,這時候Looper中的消息循環也會終止。
  最后我們在看一下退出消息隊列的方法:

void quit(boolean safe) {  if (!mQuitAllowed) {    throw new IllegalStateException("Main thread not allowed to quit.");  }  synchronized (this) {    if (mQuitting) {      return;    }    mQuitting = true;    if (safe) {      removeAllFutureMessagesLocked();    } else {      removeAllMessagesLocked();    }    // We can assume mPtr != 0 because mQuitting was previously false.    nativeWake(mPtr);  }}

  從上面我們可以看到主線程的消息隊列是不允許被退出的。并且在這里通過將mQuitting設為true從而退出消息隊列。也使得消息循環被退出。到這里我們介紹了Looper和MessageQueue,就來看一下二者在Handler中的作用。

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。但是在上面的例子中我們確實在子線程中new了一個Handler對象。我們再來看一下上面那個例子的構造方法。

public Handler(Looper looper, Callback callback, boolean async) {  mLooper = looper;  mQueue = looper.mQueue;  mCallback = callback;  mAsynchronous = async;}

  在這個構造方法中我們為Handler指定了一個Looper對象。也就說在上面的例子中我們在子線程創建的Handler中為其指定了主線程的Looper,也就等價于在主線程中創建Handler對象。下面我們就來看一下Handler是如何發送消息的。
  對于Handler的發送方式可以分為post和send兩種方式。我們先來看一下這個post的發送方式。

public final boolean post(Runnable r){  return sendMessageDelayed(getPostMessage(r), 0);}

  在這里很明顯可以看出來,將post參數中的Runnable轉換成了Message對象,然后還是通過send方式發出消息。我們就來看一下這個getPostMessage方法。

private static Message getPostMessage(Runnable r) {  Message m = Message.obtain();  m.callback = r;  return m;}

  在這里也是將我們實現的Runnable交給了Message對象的callback屬性。并返回該Message對象。
  既然post發送也是由send發送方式進行的,那么我們一路找下去,最終消息的發送交由sendMessageAtTime方法進行處理。我們就來看一下這個sendMessageAtTime方法。

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {  MessageQueue queue = mQueue;  if (queue == null) {    RuntimeException e = new RuntimeException(        this + " sendMessageAtTime() called with no mQueue");    Log.w("Looper", e.getMessage(), e);    return false;  }  return enqueueMessage(queue, msg, uptimeMillis);}

  然后再來看一下enqueueMessage方法。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {  msg.target = this;  if (mAsynchronous) {    msg.setAsynchronous(true);  }  return queue.enqueueMessage(msg, uptimeMillis);}

  到這里我們可以看出來了所謂通過Handler發送消息只不過是在Looper創建的消息隊列中插入一條消息而已。而在Looper中只不過通過loop取出消息,然后交由Handler中的dispatchMessage方發進行消息分發處理。下面我們來看一下dispatchMessage方法。

public void dispatchMessage(Message msg) {  if (msg.callback != null) {    handleCallback(msg);  } else {    if (mCallback != null) {      if (mCallback.handleMessage(msg)) {        return;      }    }    handleMessage(msg);  }}

  這里面的邏輯也是非常的簡單,msg.callback就是我們通過post里的Runnable對象。而handleCallback也就是去執行Runnable中的run方法。

private static void handleCallback(Message message) {  message.callback.run();}

  mCallback就是我們所實現的回調接口。最后才是對我們繼承Handler類中重寫的handleMessage進行執行。可見其中的優先級順序為post>CallBack>send;
  到這里我們對整個Handler的工作過程也就分析完了?,F在我們想要通過主線程發送消息給子線程,然后由子線程接收消息并進行處理。這樣一種操作也就很容易實現了。我們來看一下怎么實現。

package com.example.ljd.myapplication;import android.os.Bundle;import android.os.Handler;import android.os.Looper;import android.os.Message;import android.util.Log;import android.support.v7.app.AppCompatActivity;import android.view.View;import android.widget.Button;public class MyActivity extends AppCompatActivity {  private final String TAG = "MyActivity";  public Handler mHandler;  public Button button;  @Override  protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    button = (Button) findViewById(R.id.send_btn);    button.setOnClickListener(new View.OnClickListener() {      @Override      public void onClick(View v) {        if (mHandler != null){          mHandler.obtainMessage(0,"你好,我是從主線程過來的").sendToTarget();        }      }    });    new Thread(new Runnable() {      @Override      public void run() {        //在子線程中創建一個Looper對象        Looper.prepare();        mHandler = new Handler(){          @Override          public void handleMessage(Message msg) {            if (msg.what == 0){              Log.d(TAG,(String)msg.obj);            }          }        };        //開啟消息循環        Looper.loop();      }    }).start();  }}

  點擊按鈕我們看一下運行結果。


總結

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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 资兴市| 科技| 洛隆县| 巍山| 华亭县| 三明市| 武乡县| 霍州市| 定边县| 织金县| 泽普县| 上虞市| 湖州市| 南宁市| 大田县| 黑河市| 河西区| 阳泉市| 乐都县| 花垣县| 涟水县| 长子县| 长宁县| 德钦县| 万全县| 太仆寺旗| 灌阳县| 中江县| 太和县| 湾仔区| 太原市| 安达市| 车致| 屯门区| 兖州市| 龙岩市| 隆林| 清远市| 白朗县| 砀山县| 郓城县|