溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android編程實現異步消息處理機制的幾種方法總結

發布時間:2020-09-17 00:21:16 來源:腳本之家 閱讀:226 作者:adayabetter 欄目:移動開發

本文實例講述了Android編程實現異步消息處理機制的幾種方法。分享給大家供大家參考,具體如下:

1、概述

Android需要更新ui的話就必須在ui線程上進行操作。否則就會拋異常。

假如有耗時操作,比如:在子線程中下載文件,通知ui線程下載進度,ui線程去更新進度等,這個時候我們就需要用到異步消息處理。

一、什么是Handler

Handler是Android提供用來異步更新UI的一套機制,也是一套消息處理機制,可以用它來發送消息,也可以用它來接收消息。

二、為什么使用Handler

Android在設計之時,就封裝了一套消息的創建、傳遞、處理機制,作為系統原生的異步消息處理機制的實現之一,我們需要遵循這樣的處理機制,該機制的另外一種實現是AsyncTask。

三、Handler用法

1、postdelayed()延時發送執行子線程(Demo)
2、sendMessage()回調handleMessage()傳遞消息
3、sendToTarget()傳遞消息

四、為什么在Android中只能通過Handler機制在主線程中更新UI?

最根本的是解決多線程并發問題。
假如在同一個Activity中,有多個線程同時更新UI,且沒有加鎖,那會導致什么問題呢?
UI更新混亂。
假如加鎖呢?
會導致性能下降。
使用Handler機制,我們不用去考慮多線程的問題,所有更新UI的操作,都是在 主線程消息隊列中輪詢去處理的。
Handler 、 Looper 、Message 這三者都與Android異步消息處理線程相關的概念。那么什么叫異步消息處理線程呢?
異步消息處理線程啟動后會進入一個無限的循環體之中,每循環一次,從其內部的消息隊列中取出一個消息,然后回調相應的消息處理函數,執行完成一個消息后則繼續循環。若消息隊列為空,線程則會阻塞等待。
—此處有圖為證。

Android編程實現異步消息處理機制的幾種方法總結

源碼解析

1、Looper

對于Looper主要是prepare()loop()兩個方法。

A. 首先看prepare()方法
public static final void prepare() {
    if (sThreadLocal.get() != null) {
      throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(true));
}

sThreadLocal是一個ThreadLocal對象,可以在一個線程中存儲變量。在第5行,將一個Looper的實例放入了ThreadLocal,并且2-4行判斷了sThreadLocal是否為null,否則拋出異常。這也就說明了Looper.prepare()方法不能被調用兩次,同時也保證了一個線程中只有一個Looper實例~

B. Looper的構造方法:
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
  }

在構造方法中,創建了一個MessageQueue(消息隊列)。

C. 然后我們看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.recycle();
    }
}

第2行:

public static Looper myLooper() {
return sThreadLocal.get();
}

方法直接返回了sThreadLocal存儲的Looper實例,如果me為null則拋出異常,也就是說loop方法必須在prepare方法之后執行。
第6行:拿到該looper實例中的mQueue(消息隊列)
13到45行:就進入了我們所說的無限循環。
14行:取出一條消息,如果沒有消息則阻塞。
27行:使用調用 msg.target.dispatchMessage(msg);把消息交給msg的target的dispatchMessage方法去處理。Msg的target是什么呢?其實就是handler對象,下面會進行分析。
44行:釋放消息占據的資源。

Looper主要作用:

1、 與當前線程綁定,保證一個線程只會有一個Looper實例,同時一個Looper實例也只有一個MessageQueue。
2、 loop()方法,不斷從MessageQueue中去取消息,交給消息的target屬性的dispatchMessage去處理。

好了,我們的異步消息處理線程已經有了消息隊列(MessageQueue),也有了在無限循環體中取出消息的哥們,現在缺的就是發送消息的對象了,于是乎:Handler登場了。

2、Handler

使用Handler之前,我們都是初始化一個實例,比如用于更新UI線程,我們會在聲明的時候直接初始化,或者在onCreate中初始化Handler實例。所以我們首先看Handler的構造方法,看其如何與MessageQueue聯系上的,它在子線程中發送的消息(一般發送消息都在非UI線程)怎么發送到MessageQueue中的。

public Handler() {
    this(null, false);
}
public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
      final Class<? extends Handler> 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;
    mAsynchronous = async;
  }

14行:通過Looper.myLooper()獲取了當前線程保存的Looper實例,然后在19行又獲取了這個Looper實例中保存的MessageQueue(消息隊列),這樣就保證了handler的實例與我們Looper實例中MessageQueue關聯上了。

A.sendMessage方法

輾轉反則最后調用了sendMessageAtTime方法。

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

enqueueMessage中首先為msg.target賦值為this,【如果大家還記得Looper的loop方法會取出每個msg然后交給msg,target.dispatchMessage(msg)去處理消息】,也就是把當前的handler作為msg的target屬性。最終會調用queue的enqueueMessage的方法,也就是說handler發出的消息,最終會保存到消息隊列中去。

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

可以看到,第10行,調用了handleMessage方法,下面我們去看這個方法:

/**
  * Subclasses must implement this to receive messages.
  */
 public void handleMessage(Message msg) {
 }

可以看到這是一個空方法,為什么呢,因為消息的最終回調是由我們控制的,我們在創建handler的時候都是復寫handleMessage方法,然后根據msg.what進行消息處理。

3、Handler post

post方法:

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

getPostMessage方法:

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

可以看到,在getPostMessage中,得到了一個Message對象,然后將我們創建的Runable對象作為callback屬性,賦值給了此message.
注:產生一個Message對象,可以new ,也可以使用Message.obtain()方法;兩者都可以,但是更建議使用obtain方法,因為Message內部維護了一個Message池用于Message的復用,避免使用new 重新分配內存。
sendMessageDelayed方法和handler.sendMessage方法最終調用的都是:

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

可以看到,這里msg的callback和target都有值,那么會執行哪個呢?
看dispatchMessage方法就能看出來。

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

第2行,如果不為null,則執行callback回調,也就是我們的Runnable對象。
mCallback 的值是如何賦值的,可以查看Handler的構造方法,默認mCallback 的值為Null

到此,這個流程已經解釋完畢,總結一下

  • 1、首先Looper.prepare()在本線程中保存一個Looper實例,然后該實例中保存一個MessageQueue對象;因為Looper.prepare()在一個線程中只能調用一次,所以MessageQueue在一個線程中只會存在一個。
  • 2、Looper.loop()會讓當前線程進入一個無限循環,不斷從MessageQueue的實例中讀取消息,然后回調msg.target.dispatchMessage(msg)方法。
  • 3、Handler的構造方法,會首先得到當前線程中保存的Looper實例,進而與Looper實例中的MessageQueue相關聯。
  • 4、Handler的sendMessage方法,會給msg的target賦值為handler自身,然后加入MessageQueue中。
  • 5、在構造Handler實例時,我們會重寫handleMessage方法,也就是msg.target.dispatchMessage(msg)最終調用的方法。

在Activity中,我們并沒有顯示的調用Looper.prepare()Looper.loop()方法,為啥Handler可以成功創建呢,這是因為在Activity的啟動代碼中,已經在當前UI線程調用了Looper.prepare()Looper.loop()方法。

4、擴展

其實Handler不僅可以更新UI,你完全可以在一個子線程中去創建一個Handler,然后使用這個handler實例在任何其他線程中發送消息,最終處理消息的代碼都會在你創建Handler實例的線程中運行。

代碼:

new Thread()
    {
      private Handler handler;
      public void run()
      {

        Looper.prepare();

        handler = new Handler()
        {
          public void handleMessage(android.os.Message msg)
          {
            Log.e("TAG",Thread.currentThread().getName());
          };
        };
         Looper.loop();
        }

四種更新UI的方法

1、Handler.post();
2、Handler.sendMessage();
3、runOnUIThread()
4、View.post()

查看runOnUIThread()的源代碼(Activity中)

Runs the specified action on the UI thread. If the current thread is the UI thread, then the action is executed immediately. If the current thread is not the UI thread, the action is posted to the event queue of the UI thread.
Parameters:
action the action to run on the UI thread
public final void  runOnUiThread(Runnable action) {
       if (Thread.currentThread() != mUiThread) {
           mHandler.post(action);
       } else {
            action.run();
       }
}

補充:

1.異步消息處理機制的另一種實現:AsyncTask:

主要方法:

  • onPreExecute(): 這個方法是在執行異步任務之前的時候執行,并且是在UI
    Thread當中執行的,通常我們在這個方法里做一些UI控件的初始化的操作,例如彈出ProgressDialog
  • doInBackground(Params… params):

    onPreExecute()方法執行完后,會馬上執行這個方法,這個方法就是來處理異步任務的方法,Android操作系統會在后臺的線程池當中開啟一個worker
    thread來執行這個方法(即在worker thread當中執行),執行完后將執行結果發送給最后一個 onPostExecute
    方法,在這個方法里,我們可以從網絡當中獲取數據等一些耗時的操作

  • onProgressUpdate(Progess… values): 這個方法也是在UI

    Thread當中執行的,在異步任務執行的時候,有時需要將執行的進度返回給UI界面,例如下載一張網絡圖片,我們需要時刻顯示其下載的進度,就可以使用這個方法來更新進度。這個方法在調用之前,我們需要在
    doInBackground 方法中調用一個 publishProgress(Progress) 的方法來將進度時時刻刻傳遞給
    onProgressUpdate 方法來更新

  • onPostExecute(Result… result): 當異步任務執行完之后,就會將結果返回給這個方法,這個方法也是在UI

    Thread當中調用的,我們可以將返回的結果顯示在UI控件上

更多關于Android相關內容感興趣的讀者可查看本站專題:《Android線程與消息機制用法總結》、《Android開發入門與進階教程》、《Android調試技巧與常見問題解決方法匯總》、《Android基本組件用法總結》、《Android視圖View技巧總結》、《Android布局layout技巧總結》及《Android控件用法總結》

希望本文所述對大家Android程序設計有所幫助。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女