Android Handler源碼分析

前言

在Android中砰逻,經(jīng)常會遇到線程間通信的場景九府,下面就說說Android中最重要的異步消息機制Handler

異步消息機制Handler

Handler是Android中最重要的異步消息機制证膨,總共由四部分組成:Handler,Message,MessageQueue,Looper

1民逼、主線程創(chuàng)建 Handler 對象(如果在子線程創(chuàng)建,必須保證調(diào)用了Looper.prepare())涮帘,并重寫 handleMessage() 方法拼苍。

2、子線程創(chuàng)建 Message 對象调缨,通過第一步創(chuàng)建的Handler 發(fā)送消息疮鲫,handler.sendMessage(message),handler將消息發(fā)送到MessageQueue中苟鸯。

3、Looper 通過loop()循環(huán)從 MessageQueue 中取出待處理消息棚点。

4早处、looper將取出的message分發(fā)回 Handler 的 handleMessage() 方法中處理。

Looper

創(chuàng)建 Looper 的方法是調(diào)用 Looper.prepare() 方法瘫析。注意:在 ActivtyThread 中的 main 方法為我們 prepare 了

public static void main(String[] args) {
  Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,     "ActivityThreadMain");
  //...
  Looper.prepareMainLooper(); //初始化Looper以及MessageQueue
  ActivityThread thread = new ActivityThread();
  thread.attach(false);
  if (sMainThreadHandler == null) {
      sMainThreadHandler = thread.getHandler();
  }
  if (false) {
      Looper.myLooper().setMessageLogging(new
      LogPrinter(Log.DEBUG, "ActivityThread"));
  }
  // End of event ActivityThreadMain.
  Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
  Looper.loop(); //開始輪循操作
  throw new RuntimeException("Main thread loop unexpectedly exited");
}

Looper.prepareMainLopper()

public static void prepareMainLooper() {
prepare(false);//調(diào)用prepare(), 消息隊列不可以quit
synchronized (Looper.class) {
    if (sMainLooper != null) {
        throw new IllegalStateException("The main Looper has already been
        prepared.");
    }
    sMainLooper = myLooper();
  }
}

Looper.prepare(boolean quitAllowed)

public static void prepare() { 
    prepare(true);//消息隊列可以quit
}
// 私有的構(gòu)造函數(shù)
  private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {//不為空表示當(dāng)前線程已經(jīng)創(chuàng)建了Looper
            throw new RuntimeException("Only one Looper may be created per thread");
            //每個線程只能創(chuàng)建一個Looper
        }
        // ThreadLocal 多線程中重要的知識點砌梆,線程上下文的存儲變量
        sThreadLocal.set(new Looper(quitAllowed));//創(chuàng)建Looper并設(shè)置給sThreadLocal,這樣get的時候就不會為null了
}

注意:一個線程只要一個Looper贬循。如何保證只有一個咸包?
這里用到了ThreadLocal。一句話理解ThreadLocal杖虾,threadlocl是作為當(dāng)前線程中屬性ThreadLocalMap集合中的某一個Entry的key值Entry(threadlocl,value)烂瘫,雖然不同的線程之間threadlocal這個key值是一樣,但是不同的線程所擁有的ThreadLocalMap是獨一無二的奇适,也就是不同的線程間同一個ThreadLocal(key)對應(yīng)存儲的值(value)不一樣坟比,從而到達了線程間變量隔離的目的,但是在同一個線程中這個value變量地址是一樣的嚷往。所以利用ThreadLocal可以保證一個線程只有一個Looper葛账。
為了保證 ThreadLocalMap.set(value) 時,value 不會被覆蓋(即Looper不會改變)皮仁,會先進行上面代碼的 if 操作籍琳,if (sThreadLocal.get() != null) , 不為空表示當(dāng)前線程已經(jīng)創(chuàng)建了Looper,然后直接拋異常結(jié)束prepare贷祈。如果 if 不成立趋急,則 new Looper()。

MessageQueue

MessageQueue的創(chuàng)建是在Looper構(gòu)造函數(shù)中

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);//創(chuàng)建了MessageQueue
    mThread = Thread.currentThread(); //當(dāng)前線程的綁定
}

MessageQueue(boolean quitAllowed) {
    //mQuitAllowed決定隊列是否可以銷毀 主線程的隊列不可以被銷毀需要傳入false, 在MessageQueue的 quit()方法
    //省略...
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

Looper.loop()

在 ActivtyThread的main 方法中 Looper.prepareMainLooper() 后 Looper.loop() 開始輪詢

public static void loop() {
    final Looper me = myLooper();//里面調(diào)用了sThreadLocal.get()獲得剛才創(chuàng)建的Looper對象
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }//如果Looper為空則會拋出異常
    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 (;;) {
        //這是一個死循環(huán)势誊,從消息隊列不斷的取消息
        Message msg = queue.next(); // might block
        if (msg == null) {
            //由于剛創(chuàng)建MessageQueue就開始輪詢呜达,隊列里是沒有消息的,等到Handler sendMessage enqueueMessage后
            //隊列里才有消息
            // 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);//msg.target就是綁定的Handler,詳見后面Message的部分键科,Handler開始
    //后面代碼省略.....
    msg.recycleUnchecked();
    }
}

loop方法開啟后闻丑,不斷地從MessageQueue中獲取Message,對 Message 進行 Delivery 和 Dispatch勋颖,最終發(fā)給對應(yīng)的 Handler 去處理(通過msg.target找到對應(yīng)的handler)嗦嗡。
說明:MessageQueue隊列中是 Message,在沒有 Message 的時候饭玲,MessageQueue借助Linux的ePoll機制侥祭,阻塞休眠等待,直到有Message進入隊列將其喚醒。

Handler

handler通過發(fā)送和處理Message和Runnable對象來關(guān)聯(lián)相對應(yīng)線程的MessageQueue矮冬。

最常見的創(chuàng)建 handler 的方式:

//第一種方式
Handler handler=new Handler(){
    @Override
    public void handleMessage(Message msg) {
    super.handleMessage(msg);
    }
};
//第二種方式(looper作為參數(shù)傳入)
 Handler handler=new Handler(Looper looper)

第一種方式在內(nèi)部調(diào)用 this(null, false);

public Handler(Callback callback, boolean async) {
    //前面省略
    mLooper = Looper.myLooper();//獲取Looper谈宛,**注意不是創(chuàng)建Looper**!
    if (mLooper == null) {
        throw new RuntimeException(
       "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;//創(chuàng)建消息隊列MessageQueue
    mCallback = callback; //初始化了回調(diào)接口
    mAsynchronous = async;
}

Looper.myLooper() 胎署;

//這是Handler中定義的ThreadLocal ThreadLocal主要解多線程并發(fā)的問題
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

sThreadLocal.get() will return null unless you’ve called prepare(). 這句話告訴我們 get 可能返回 null 除非先調(diào)用 prepare()方法創(chuàng)建 Looper 吆录。在前面已經(jīng)介紹了

Message

message的創(chuàng)建可以直接 new Message() ,但是有更好的方式 Message.obtain()琼牧。因為可以檢測是否有可以復(fù)用的 Message恢筝,復(fù)用避免過多的創(chuàng)建、銷毀 Message 對象巨坊,達到優(yōu)化內(nèi)存和性能的目的撬槽。

public static Message obtain(Handler h) {
    Message m = obtain();//調(diào)用重載的obtain方法
    m.target = h;//并綁定的創(chuàng)建Message對象的handler
    return m;
}
public static Message obtain() {
    synchronized (sPoolSync) {//sPoolSync是一個Object對象,用來同步保證線程安全
    if (sPool != null) {
        //sPool是就是handler dispatchMessage 后 通過recycleUnchecked回收用以復(fù)用的Message
        Message m = sPool;
        sPool = m.next;
        m.next = null;
        m.flags = 0; // clear in-use flag
        sPoolSize--;
        return m;
        }
    }
    return new Message();
}

創(chuàng)建 Message 的時候通過 Message.obtain(Handler h) 這個構(gòu)造方法將message和handler綁定趾撵。當(dāng)然也可以在 Handler 中的 enqueueMessage() 綁定侄柔。

Handler 發(fā)送消息(消息入隊)

Handler 發(fā)送消息的重載方法很多,我們用得比較多的方法就是post占调、sendMessage暂题、sendMessageDelay方法,先挨個看看

post方法

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

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

post接受一個Runnable類型的參數(shù)妈候,并將其封裝為一個Message對象敢靡,并且將Runnable參數(shù)賦值給msg的callback字段,這里要記住苦银,后面有用——Runnable什么時候執(zhí)行的呢?
最后調(diào)用的就是sendMessageDelayed

sendMessage方法

public final boolean sendMessage(@NonNull Message msg) {
     return sendMessageDelayed(msg, 0);
}

最后調(diào)用的也是sendMessageDelay赶站,第二個參數(shù)是0幔虏。

sendMessageDelay方法
可見無論是post還是sendMessage方法,最后都走到了sendMessageDelayed

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
     if (delayMillis < 0) {
         delayMillis = 0;
     }
     return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// 這里注意第二個參數(shù) @param updateMillis 是一個具體的時間點贝椿。
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
     MessageQueue queue = mQueue;
     //……
     return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) {
       // 【關(guān)鍵點6】這里要注意target指向了當(dāng)前Handler
         msg.target = this;
     if (mAsynchronous) {
         msg.setAsynchronous(true);
     }
     // 【關(guān)鍵點7】調(diào)用到了queue#enqueueMessage方法
     return queue.enqueueMessage(msg, uptimeMillis);
}

最終會走到handler中的enqueueMessage方法想括,然后走到quene.enqueneMessage(msg, uptimeMillis),將message放入了消息隊列烙博,那么我們來看看瑟蜈,是如何放入隊列的吧。

boolean enqueueMessage(Message msg, long when) {
     //……
     // 【關(guān)鍵點8】對queue對象上鎖
     synchronized (this) {
         //……
         msg.markInUse();
         msg.when = when; // msg的when時刻賦值
         Message p = mMessages;
         boolean needWake;
         if (p == null || when == 0 || when < p.when) {
             // New head, wake up the event queue if blocked.
             // 翻譯:新的頭結(jié)點渣窜,如果queue阻塞铺根,則wakeup喚醒
             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 (;;) { // for循環(huán)位迂,break結(jié)束時when < p.when,說明按照when進行排序插入,或者尾節(jié)點
                 prev = p;
                 p = p.next;
                 if (p == null || when < p.when) {
                     break;   // 【關(guān)鍵點9】 找到插入位置掂林,條件尾部或者when從小到大的位置
                 }
                 if (needWake && p.isAsynchronous()) {
                     needWake = false;
                 }
             }
           // 執(zhí)行插入
             msg.next = p; // invariant: p == prev.next
             prev.next = msg;
         }

         // We can assume mPtr != 0 because mQuitting is false.
         if (needWake) {
             nativeWake(mPtr); // native方法臣缀,喚醒
         }
     }
     return true;
}

這里面將msg放到消息隊列中,可以看到這個隊列是一個簡單的單鏈表結(jié)構(gòu)泻帮,按照msg的when進行的排序精置,并且進行了synchronized加鎖,確保添加數(shù)據(jù)的線程安全锣杂。之所以采用鏈表的數(shù)據(jù)結(jié)構(gòu)脂倦,原因是鏈表方便插入。
初看源碼的時候蹲堂,應(yīng)該忽略掉wakeup這些處理狼讨,關(guān)注msg是如何加入隊列即可。
到這里柒竞,我們了解了message是如何加入消息隊列MesssageQueue政供。但是消息什么時候執(zhí)行,以及post(Runnable)中的Runnable什么時候才執(zhí)行朽基。

消息出隊執(zhí)行

前面提到的Looper.loop()中布隔,主要調(diào)用me.mQueue.next()獲取一個消息msg,注意這里可能阻塞稼虎。這里調(diào)用了msg.target.dispatchMessage(msg)衅檀,這里msg.target可以回頭看看前面Message創(chuàng)建賦值的地方。所以這里就將消息分發(fā)給了對應(yīng)的Handler去處理了霎俩。待會兒再看Handler.dispatchMessage哀军,我們接著看next方法是怎么取消息的。

 Message next() {
     // Return here if the message loop has already quit and been disposed.
     // This can happen if the application tries to restart a looper after quit
     // which is not supported.
     final long ptr = mPtr;
     if (ptr == 0) {
         return null;
     }

     //……
     int nextPollTimeoutMillis = 0;
     // 【關(guān)鍵點12】繼續(xù)死循環(huán)
     for (;;) { 
         //……
         nativePollOnce(ptr, nextPollTimeoutMillis);

       // 加鎖
         synchronized (this) {
             // Try to retrieve the next message.  Return if found.
             final long now = SystemClock.uptimeMillis(); // 當(dāng)前時間
             Message prevMsg = null;
             Message msg = mMessages; // msg 指向鏈表頭結(jié)點
             if (msg != null && msg.target == null) { // 這個if可以先忽略
                 // 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) {
               // 【關(guān)鍵點13】如果當(dāng)前時間小于頭結(jié)點的when打却,更新nextPollTimeoutMillis杉适,并在對應(yīng)時間就緒后poll通知
                 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; // 更新頭結(jié)點 
                     }
                     msg.next = null; // 斷鏈處理,等待返回
                     //……
                     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; 
             }
             //……
         }
         //……
         // 下面是IdleHandler的處理柳击,還不是很了解
     }
 }

next方法中也是一個死循環(huán)猿推,不斷的嘗試獲取當(dāng)前消息隊列已經(jīng)到時間的消息,如果沒有滿足的消息捌肴,就會一直循環(huán)蹬叭,這就是為什么會next會阻塞的原因。

看完了next方法状知,獲取到了msg秽五,回到剛才的msg.target.dispatchMessage(msg),接著看Handler是如何處理消息的试幽。

 public void dispatchMessage(@NonNull Message msg) {
     if (msg.callback != null) {
       // 如果消息有CallBack則直接筝蚕,優(yōu)先調(diào)用callback
         handleCallback(msg);
     } else {
         // 如果Handler存在mCallback卦碾,優(yōu)先處理Handler的Callback
         if (mCallback != null) {
             if (mCallback.handleMessage(msg)) {
                 return;
             }
         }
         // 此方法會被重寫,用戶用于處理具體的消息
         handleMessage(msg);
     }
 }

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

dispatchMessage方法中優(yōu)先處理Message的callback起宽,再回頭看看Handler.post方法應(yīng)該知道callback是個啥了吧洲胖。

如果Handler設(shè)置了mCallback, 則優(yōu)先判斷mCallback.handleMessage的返回值,這個機制可以讓我們做一些勾子坯沪,監(jiān)聽Handler上的一些消息绿映。

handleMessage(msg)這個方法一般在創(chuàng)建Handler時被重寫,用于接收消息腐晾。

總結(jié)

1叉弦、一個線程只能有一個Looper,如何確保藻糖?
答案:ThreadLocal

2淹冰、Handler.post(Runnable)會將Runnable封裝到一個Message中。

3巨柒、MessageQueue采用單鏈表的實現(xiàn)方式樱拴,并且在存取消息時都會進行加鎖。

4洋满、Looper.loop采用死循環(huán)的方式晶乔,會阻塞線程。那么為什么主線程不會被阻塞牺勾?
因為Android是事件驅(qū)動的正罢,很多的系統(tǒng)事件(點擊事件、屏幕刷新等)都是通過Handler處理的驻民,因此主線程的消息隊列翻具,會一直有消息的。

5回还、Handler是如何實現(xiàn)線程切換的直秆?
Looper和MessageQueue和線程綁定的事富,也就是說這個消息隊列中的所有消息箩做,最后分給對應(yīng)的Handler都是在創(chuàng)建Looper的線程或杠。所以無論Handler在什么線程發(fā)送消息瘫怜,最后都回到創(chuàng)建Looper的線程中執(zhí)行谎柄。

6膏斤、Thread和Looper彻舰、MessageQueue是一對一的關(guān)系糙捺,Looper诫咱、MessageQueue對于Handler是一對多的關(guān)系。這里要注意洪灯,一個具體的Handler實例坎缭,肯定只關(guān)聯(lián)一個Looper和queue的喲。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市掏呼,隨后出現(xiàn)的幾起案子坏快,更是在濱河造成了極大的恐慌,老刑警劉巖憎夷,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件莽鸿,死亡現(xiàn)場離奇詭異,居然都是意外死亡拾给,警方通過查閱死者的電腦和手機祥得,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蒋得,“玉大人级及,你說我怎么就攤上這事《钛茫” “怎么了饮焦?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長入偷。 經(jīng)常有香客問我追驴,道長,這世上最難降的妖魔是什么疏之? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任殿雪,我火速辦了婚禮,結(jié)果婚禮上锋爪,老公的妹妹穿的比我還像新娘丙曙。我一直安慰自己,他們只是感情好其骄,可當(dāng)我...
    茶點故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布亏镰。 她就那樣靜靜地躺著,像睡著了一般拯爽。 火紅的嫁衣襯著肌膚如雪索抓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天毯炮,我揣著相機與錄音逼肯,去河邊找鬼。 笑死桃煎,一個胖子當(dāng)著我的面吹牛篮幢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播为迈,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼三椿,長吁一口氣:“原來是場噩夢啊……” “哼缺菌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起搜锰,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤伴郁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后纽乱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蛾绎,經(jīng)...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年鸦列,在試婚紗的時候發(fā)現(xiàn)自己被綠了租冠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡薯嗤,死狀恐怖顽爹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情骆姐,我是刑警寧澤镜粤,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站玻褪,受9級特大地震影響肉渴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜带射,卻給世界環(huán)境...
    茶點故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一同规、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧窟社,春花似錦券勺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至匣吊,卻和暖如春儒拂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背色鸳。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工侣灶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人缕碎。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像池户,于是被迫代替她去往敵國和親咏雌。 傳聞我的和親對象是個殘疾皇子凡怎,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,860評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 什么是Handler? Handler主要用于異步消息的處理:當(dāng)發(fā)出一個消息之后,首先進入一個消息隊列,發(fā)送消息的...
    劉小廚閱讀 427評論 0 4
  • 前言 文章是記錄自己研究源碼的過程,方便日后的回顧赊抖,文章可能篇幅過長统倒。 Handler 基本使用 源碼分析 Han...
    是劉航啊閱讀 446評論 0 2
  • 1. 概述 代碼路徑 1.1 架構(gòu)介紹: 消息機制主要包含: Message:消息分為硬件產(chǎn)生的消息(如按鈕、觸摸...
    lilykeke閱讀 163評論 0 0
  • 一.什么是Handelr Android SDK中用來處理異步消息的核心類 子現(xiàn)程可以通過Handler來通知主線...
    __素顏__閱讀 653評論 3 12
  • 開頭三連 Handler 是什么??? Handler 能做什么?? Handler 怎么做到的?? 1.Han...
    沒有了遇見閱讀 452評論 0 2