麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 系統(tǒng) > Android > 正文

AsyncTask陷阱之:Handler,Looper與MessageQueue的詳解

2020-04-11 12:23:27
字體:
供稿:網(wǎng)友
AsyncTask的隱蔽陷阱
先來看一個(gè)實(shí)例
這個(gè)例子很簡單,展示了AsyncTask的一種極端用法,挺怪的。
復(fù)制代碼 代碼如下:

public class AsyncTaskTrapActivity extends Activity {
    private SimpleAsyncTask asynctask;
    private Looper myLooper;
    private TextView status;

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        asynctask = null;
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                myLooper = Looper.myLooper();
                status = new TextView(getApplication());
                asynctask = new SimpleAsyncTask(status);
                Looper.loop();
            }
        }).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
        setContentView((TextView) status, params);
        asynctask.execute();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        myLooper.quit();
    }

    private class SimpleAsyncTask extends AsyncTask<Void, Integer, Void> {
        private TextView mStatusPanel;

        public SimpleAsyncTask(TextView text) {
            mStatusPanel = text;
        }

        @Override
        protected Void doInBackground(Void... params) {
            int prog = 1;
            while (prog < 101) {
                SystemClock.sleep(1000);
                publishProgress(prog);
                prog++;
            }
            return null;
        }

        // Not Okay, will crash, said it cannot touch TextView
        @Override
        protected void onPostExecute(Void result) {
            mStatusPanel.setText("Welcome back.");
        }

        // Okay, because it is called in #execute() which is called in Main thread, so it runs in Main Thread.
        @Override
        protected void onPreExecute() {
            mStatusPanel.setText("Before we go, let me tell you something buried in my heart for years...");
        }

        // Not okay, will crash, said it cannot touch TextView
        @Override
        protected void onProgressUpdate(Integer... values) {
            mStatusPanel.setText("On our way..." + values[0].toString());
        }
    }
}

這個(gè)例子在Android2.3中無法正常運(yùn)行,在執(zhí)行onProgressUpdate()和onPostExecute()時(shí)會(huì)報(bào)出異常



復(fù)制代碼 代碼如下:

11-03 09:13:10.501: E/AndroidRuntime(762): FATAL EXCEPTION: Thread-10
11-03 09:13:10.501: E/AndroidRuntime(762): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
11-03 09:13:10.501: E/AndroidRuntime(762):  at android.view.ViewRoot.checkThread(ViewRoot.java:2990)
11-03 09:13:10.501: E/AndroidRuntime(762):  at android.view.ViewRoot.requestLayout(ViewRoot.java:670)
11-03 09:13:10.501: E/AndroidRuntime(762):  at android.view.View.requestLayout(View.java:8316)
11-03 09:13:10.501: E/AndroidRuntime(762):  at android.view.View.requestLayout(View.java:8316)
11-03 09:13:10.501: E/AndroidRuntime(762):  at android.view.View.requestLayout(View.java:8316)
11-03 09:13:10.501: E/AndroidRuntime(762):  at android.view.View.requestLayout(View.java:8316)
11-03 09:13:10.501: E/AndroidRuntime(762):  at android.widget.TextView.checkForRelayout(TextView.java:6477)
11-03 09:13:10.501: E/AndroidRuntime(762):  at android.widget.TextView.setText(TextView.java:3220)
11-03 09:13:10.501: E/AndroidRuntime(762):  at android.widget.TextView.setText(TextView.java:3085)
11-03 09:13:10.501: E/AndroidRuntime(762):  at android.widget.TextView.setText(TextView.java:3060)
11-03 09:13:10.501: E/AndroidRuntime(762):  at com.hilton.effectiveandroid.os.AsyncTaskTrapActivity$SimpleAsyncTask.onProgressUpdate(AsyncTaskTrapActivity.java:110)
11-03 09:13:10.501: E/AndroidRuntime(762):  at com.hilton.effectiveandroid.os.AsyncTaskTrapActivity$SimpleAsyncTask.onProgressUpdate(AsyncTaskTrapActivity.java:1)
11-03 09:13:10.501: E/AndroidRuntime(762):  at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:466)
11-03 09:13:10.501: E/AndroidRuntime(762):  at android.os.Handler.dispatchMessage(Handler.java:130)
11-03 09:13:10.501: E/AndroidRuntime(762):  at android.os.Looper.loop(Looper.java:351)
11-03 09:13:10.501: E/AndroidRuntime(762):  at com.hilton.effectiveandroid.os.AsyncTaskTrapActivity$1.run(AsyncTaskTrapActivity.java:56)
11-03 09:13:10.501: E/AndroidRuntime(762):  at java.lang.Thread.run(Thread.java:1050)
11-03 09:13:32.823: E/dalvikvm(762): [DVM] mmap return base = 4585e000

但在Android4.0及以上的版本中運(yùn)行就正常(3.0版本未測試)。



從2.3運(yùn)行時(shí)的Stacktrace來看原因是在非UI線程中操作了UI組件。不對呀,神奇啊,AsyncTask#onProgressUpdate()和AsyncTask#onPostExecute()的文檔明明寫著這二個(gè)回調(diào)是在UI線程里面的嘛,怎么還會(huì)報(bào)出這樣的異常呢!
原因分析
AsyncTask設(shè)計(jì)出來執(zhí)行異步任務(wù)卻又能與主線程通訊,它的內(nèi)部有一個(gè)InternalHandler是繼承自Handler的靜態(tài)成員sHandler,這個(gè)sHandler就是用來與主線程通訊的。看下這個(gè)對象的聲明:private static final InternalHandler sHandler = new InternalHandler();而InternalHandler又是繼承自Handler的。所以本質(zhì)上講sHandler就是一個(gè)Handler對象。Handler是用來與線程通訊用的,它必須與Looper和線程綁定一起使用,創(chuàng)建Handler時(shí)必須指定Looper,如果不指定Looper對象則使用調(diào)用棧所在的線程,如果調(diào)用棧線程沒有Looper會(huì)報(bào)出異常??磥磉@個(gè)sHandler是與調(diào)用new InternalHandler()的線程所綁定,它又是靜態(tài)私有的,也就是與第一次創(chuàng)建AsyncTask對象的線程綁定。所以,如果是在主線程中創(chuàng)建的AsyncTask對象,那么其sHandler就與主線程綁定,這是正常的情況。在此例子中AsyncTask是在衍生線程里創(chuàng)建的,所以其sHandler就與衍生線程綁定,因此,它自然不能操作UI元素,會(huì)在onProgressUpdate()和onPostExecute()中拋出異常。

以上例子有異常的原因就是在衍生線程中創(chuàng)建了SimpleAsyncTask對象。至于為什么在4.0版本上沒有問題,是因?yàn)?.0中在ActivityThread.main()方法中,會(huì)進(jìn)行BindApplication的動(dòng)作,這時(shí)會(huì)用AsyncTask對象,也會(huì)創(chuàng)建sHandler對象,這是主線程所以sHandler是與主線程綁定的。后面再創(chuàng)建AsyncTask對象時(shí),因?yàn)閟Handler已經(jīng)初始化完了,不會(huì)再次初始化。至于什么是BindApplication,為什么會(huì)進(jìn)行BindApplication的動(dòng)作不影響這個(gè)問題的討論。
AsyncTask的缺陷及修改方法
這其實(shí)是AsyncTask的隱藏的Bug,它不應(yīng)該這么依賴開發(fā)者,應(yīng)該強(qiáng)加條件限制,以保證第一次AsyncTask對象是在主線程中創(chuàng)建:
1. 在InternalHandler的構(gòu)造中檢查當(dāng)前線程是否為主線程,然后拋出異常,顯然這并不是最佳實(shí)踐。
復(fù)制代碼 代碼如下:

new InternalHandler() {

復(fù)制代碼 代碼如下:

     if (Looper.myLooper() != Looper.getMainLooper()) {
  throw new RuntimeException("AsyncTask must be initialized in main thread");
     }

復(fù)制代碼 代碼如下:

11-03 08:56:07.055: E/AndroidRuntime(890): FATAL EXCEPTION: Thread-10
11-03 08:56:07.055: E/AndroidRuntime(890): java.lang.ExceptionInInitializerError
11-03 08:56:07.055: E/AndroidRuntime(890):  at com.hilton.effectiveandroid.os.AsyncTaskTrapActivity$1.run(AsyncTaskTrapActivity.java:55)
11-03 08:56:07.055: E/AndroidRuntime(890):  at java.lang.Thread.run(Thread.java:1050)
11-03 08:56:07.055: E/AndroidRuntime(890): Caused by: java.lang.RuntimeException: AsyncTask must be initialized in main thread
11-03 08:56:07.055: E/AndroidRuntime(890):  at android.os.AsyncTask$InternalHandler.<init>(AsyncTask.java:455)
11-03 08:56:07.055: E/AndroidRuntime(890):  at android.os.AsyncTask.<clinit>(AsyncTask.java:183)
11-03 08:56:07.055: E/AndroidRuntime(890):  ... 2 more

2. 更好的做法是在InternalHandler構(gòu)造時(shí)把主線程的MainLooper傳給
復(fù)制代碼 代碼如下:

 new IntentHandler() {
     super(Looper.getMainLooper());
 }

會(huì)有人這樣寫嗎,你會(huì)問?通常情況是不會(huì)的,沒有人會(huì)故意在衍生線程中創(chuàng)建AsyncTask。但是假如有一個(gè)叫Worker的類,用來完成異步任務(wù)從網(wǎng)絡(luò)上下載圖片,然后顯示,還有一個(gè)WorkerScheduler來分配任務(wù),WorkerScheduler也是運(yùn)行在單獨(dú)線程中,Worker用AsyncTask來實(shí)現(xiàn),WorkScheduler會(huì)在接收到請求時(shí)創(chuàng)建Worker去完成請求,這時(shí)就會(huì)出現(xiàn)在WorkerScheduler線程中---衍生線程---創(chuàng)建AsyncTask對象。這種Bug極其隱蔽,很難發(fā)現(xiàn)。
如何限制調(diào)用者的線程
正常情況下一個(gè)Java應(yīng)用一個(gè)進(jìn)程,且有一個(gè)線程,入口即是main方法。安卓應(yīng)用程序本質(zhì)上也是Java應(yīng)用程序,它的主入口在ActivityThread.main(),在main()方法中會(huì)調(diào)用Looper.prepareMainLooper(),這就初始化了主線程的Looper,且Looper中保存有主線程的Looper對象mMainLooper,它也提供了方法來獲取主線程的Looper,getMainLooper()。所以如果需要?jiǎng)?chuàng)建一個(gè)與主線程綁定的Handler,就可以用new Handler(Looper.getMainLooper())來保證它確實(shí)與主線程綁定。
如果想要保證某些方法僅能在主線程中調(diào)用就可以檢查調(diào)用者的Looper對象:
復(fù)制代碼 代碼如下:

 if (Looper.myLooper() != Looper.getMainLooper()) {
    throw new RuntimeException("This method can only be called in main thread");
 }

Handler,Looper,MessageQueue機(jī)制
線程與線程間的交互協(xié)作
線程與線程之間雖然共享內(nèi)存空間,也即可以訪問進(jìn)程的堆空間,但是線程有自己的棧,運(yùn)行在一個(gè)線程中的方法調(diào)用全部都是在線程自己的調(diào)用棧中。通俗來講西線程就是一個(gè)run()方法及其內(nèi)部所調(diào)用的方法。這里面的所有方法調(diào)用都是獨(dú)立于其他線程的,由于方法調(diào)用的關(guān)系,一個(gè)方法調(diào)用另外的方法,那么另外的方法也發(fā)生在調(diào)用者的線程里。所以,線程是時(shí)序上的概念,本質(zhì)上是一列方法調(diào)用。
那么線程之間要想?yún)f(xié)作,或者想改變某個(gè)方法所在的線程(為了不阻塞自己線程),就只能是向另外一個(gè)線程發(fā)送一個(gè)消息,然后return;另外線程收到消息后就去執(zhí)行某些操作。如果是簡單的操作可以用一個(gè)變量來標(biāo)識,比如A線程主需要B線程做某些事時(shí),可以把某個(gè)對象obj設(shè)置值,B則當(dāng)看到obj != null時(shí)就去做事,這種線程交互協(xié)作在《Java編程思想》中有大量示例。
Android中的ITC-Inter Thread Communication
注意:當(dāng)然Handler也可以用做一個(gè)線程內(nèi)部的消息循環(huán),不必非與另外的線程通信,但這里重點(diǎn)討論的是線程與線程之間的事情。

Android當(dāng)中做了一個(gè)特別的限制就是非主線程不能操作UI元素,而一個(gè)應(yīng)用程序是不可能不創(chuàng)衍生線程的,這樣一來主線程與衍生線程之間就必須進(jìn)行通信。由于這種通信很頻繁,所以不可能全用變量來標(biāo)識,程序?qū)⒆兊檬只靵y。這個(gè)時(shí)候消息隊(duì)列就變得有十分有必要,也就是在每個(gè)線程中建立一個(gè)消息隊(duì)列。當(dāng)A需要B時(shí),A向B發(fā)一個(gè)消息,此過程實(shí)質(zhì)為把消息加入到B的消息隊(duì)列中,A就此return,B并不專門等待某個(gè)消息,而是循環(huán)的查看其消息隊(duì)列,看到有消息后就去執(zhí)行。

整套ITC的基本思想是:定義一個(gè)消息對象,把需要的數(shù)據(jù)放入其中,把消息的處理的方法也定義好作為回調(diào)放到消息中,然后把這個(gè)消息發(fā)送另一個(gè)線程上;另外的線程在循環(huán)處理其隊(duì)列里的消息,看到消息時(shí)就對消息調(diào)用附在其上的回調(diào)來處理消息。這樣一來可以看出,這僅僅是改變了處理消息的執(zhí)行時(shí)序:正常是當(dāng)場處理,這種則是封裝成一個(gè)消息丟給另外的線程,在某個(gè)不確定的時(shí)間被執(zhí)行;另外的線程也僅提供CPU時(shí)序,對于消息是什么和消息如何處理它完全不干預(yù)。簡言之就是把一個(gè)方法放到另外一個(gè)線程里去調(diào)用,進(jìn)而這個(gè)方法的調(diào)用者的調(diào)用棧(call stack)結(jié)束,這個(gè)方法的調(diào)用棧轉(zhuǎn)移到了另外的線程中。
那么這個(gè)機(jī)制改變的到底是什么呢?從上面看它僅是讓一個(gè)方法(消息的處理)安排到了另外一個(gè)線程里去做(異步處理),不是立刻馬上同步的做,它改變的是CPU的執(zhí)行時(shí)序(execution sequence)。
那么消息隊(duì)列存放在哪里呢?不能放在堆空間里(直接new MessageQueue()),這樣的話對象的引用容易丟失,針對線程來講也不易維護(hù)。Java支持線程的本地存儲(chǔ)ThreadLocal,通過ThreadLocal對象可以把對象放到線程的空間上,每個(gè)線程都有了屬于自己的對象。因此,可以為每個(gè)需要通信的線程創(chuàng)建一個(gè)消息隊(duì)列并放到其本地存儲(chǔ)中。
基于這個(gè)模型還可以擴(kuò)展,比如給消息定義優(yōu)先級等。



MessageQueue
以隊(duì)列的方式來存儲(chǔ)消息,主要是二個(gè)操作一個(gè)是入列enqueueMessage,一個(gè)是出列next(),需要保證的是線程安全,因?yàn)槿肓型ǔJ橇硗獾木€程在調(diào)用。
MessageQueue是一個(gè)十分接近底層的機(jī)制,所以不方便開發(fā)者直接使用,要想使用此MessageQueue必須做二個(gè)方面工作,一個(gè)是目標(biāo)線程端:創(chuàng)建,與線程關(guān)聯(lián),運(yùn)轉(zhuǎn)起來;另一個(gè)就是隊(duì)列線程的客戶端:創(chuàng)建消息,定義回調(diào)處理,發(fā)送消息到隊(duì)列。Looper和Handler就是對MessageQueue的封裝:Looper是給目標(biāo)線程用的:用途是創(chuàng)建MessageQueue,將MessageQueue與線程關(guān)聯(lián)起來,并讓MessageQueue運(yùn)轉(zhuǎn)起來,且Looper有保護(hù)機(jī)制,讓一個(gè)線程僅能創(chuàng)建一個(gè)MessageQueue對象;而Handler則是給隊(duì)列客戶端用的:用來創(chuàng)建消息,定義回調(diào)和發(fā)送消息。
因?yàn)長ooper對象封裝了目標(biāo)隊(duì)列線程及其隊(duì)列,所以對隊(duì)列線程的客戶端來講,Looper對象就代表著一個(gè)擁有MessageQueue的線程,和這個(gè)線程的MessageQueue。也即當(dāng)你構(gòu)建Handler對象時(shí)用的是Looper對象,而當(dāng)你檢驗(yàn)?zāi)硞€(gè)線程是否是預(yù)期線程時(shí)也用Looper對象。
Looper內(nèi)幕
Looper的任務(wù)是創(chuàng)建消息隊(duì)列MessageQueue,放到線程的ThreadLocal中(與線程關(guān)聯(lián)),并且讓MessageQueue運(yùn)轉(zhuǎn)起來,處于Ready的狀態(tài),并要提供供接口以停止消息循環(huán)。它主要有四個(gè)接口:
public static void Looper.prepare()
這個(gè)方法是為線程創(chuàng)建一個(gè)Looper對象和MessageQueue對象,并把Looper對象通過ThreadLocal放到線程空間里去。需要注意的是這個(gè)方法每個(gè)線程只能調(diào)用一次,通常的做法是在線程run()方法的第一句,但只要保證在loop()前面即可。
•public static void Looper.loop()
這個(gè)方法要在prepare()這后調(diào)用,是讓線程的MessageQueue運(yùn)轉(zhuǎn)起來,一旦調(diào)用此方法,線程便會(huì)無限循環(huán)下去(while (true){...}),無Message時(shí)休眠,有Message入隊(duì)時(shí)喚醒處理,直到quit()調(diào)用為止。它的簡化實(shí)現(xiàn)就是:
復(fù)制代碼 代碼如下:

loop() {
   while (true) {
      Message msg = mQueue.next();
      if msg is a quit message, then
         return;
      msg.processMessage(msg)
   }
}

public void Looper.quit()
讓線程結(jié)束MessageQueue的循環(huán),終止循環(huán),run()方法會(huì)結(jié)束,線程也會(huì)停止,因此它是對象的方法,意即終止某個(gè)Looper對象。一定要記得在不需要線程的時(shí)候調(diào)用此方法,否則線程是不會(huì)終止退出的,進(jìn)程也就會(huì)一直運(yùn)行,占用著資源。如果有大量的線程未退出,進(jìn)程最終會(huì)崩掉。
public static Looper Looper.myLooper()
這個(gè)是獲得調(diào)用者所在線程所擁有的Looper對象的方法。
還有二個(gè)接口是與主線程有關(guān)的:
一個(gè)是專門為主線程準(zhǔn)備的
public static void Looper.prepareMainLooper();
這個(gè)方法只給主線程初始化Looper用的,它僅在ActivityThread.main()方法中調(diào)用,其他地方或其他線程不可以調(diào)用,如果在主線程中調(diào)用會(huì)有異常拋出,因?yàn)橐粋€(gè)線程只能創(chuàng)建一個(gè)Looper對象。但是如在其他線程中調(diào)用此方法,會(huì)改變mainLooper,接下來的getMainLooper就會(huì)返回它而非真正的主線程的Looper對象,這不會(huì)有異常拋出,也不會(huì)有明顯的錯(cuò)誤,但是程序?qū)⒉荒苷9ぷ鳎驗(yàn)樵驹O(shè)計(jì)在主線程中運(yùn)行的方法將轉(zhuǎn)到這個(gè)線程里面,會(huì)產(chǎn)生很詭異的Bug。這里L(fēng)ooper.prepareMainThread()的方法中應(yīng)該加上判斷:
復(fù)制代碼 代碼如下:

public void prepareMainLooper() {
    if (getMainLooper() != null) {
         throw new RuntimeException("Looper.prepareMainthread() can ONLY be called by Frameworks");
     }
     //...
}

以防止其他線程非法調(diào)用,光靠文檔約束力遠(yuǎn)不夠。
•另外一個(gè)就是獲取主線程Looper的接口:
public static Looper Looper.getMainLooper()
這個(gè)主要用在檢查線程合法性,也即保證某些方法只能在主線程里面調(diào)用。但這并不保險(xiǎn),如上面所說,如果一個(gè)衍生線程調(diào)用了prepareMainLooper()就會(huì)把真正的mMainLooper改變,此衍生線程就可以通過上述檢測,導(dǎo)致getMainLooper() != myLooper()的檢測變得不靠譜了。所以ViewRoot的方法是用Thread來檢測:mThread != Thread.currentThread();其mThread是在系統(tǒng)創(chuàng)建ViewRoot時(shí)通過Thread.currentThread()獲得的,這樣的方法來檢測是否是主線程更加靠譜一些,因?yàn)樗鼪]有依賴外部而是相信自己保存的Thread的引用。
Message對象
消息Message是僅是一個(gè)數(shù)據(jù)結(jié)構(gòu),是信息的載體,它與隊(duì)列機(jī)制是無關(guān)的,封裝著要執(zhí)行的動(dòng)作和執(zhí)行動(dòng)作的必要信息,what, arg1, arg2, obj可以用來傳送數(shù)據(jù);而Message的回調(diào)則必須通過Handler來定義,為什么呢?因?yàn)镸essage僅是一個(gè)載體,它不能自己跑到目標(biāo)MessageQueue上面去,它必須由Handler來操作,把Message放到目標(biāo)隊(duì)列上去,既然它需要Handler來統(tǒng)一的放到MessageQueue上,也可以讓Handler來統(tǒng)一定義處理消息的回調(diào)。需要注意的是同一個(gè)Message對象只能使用一次,因?yàn)樵谔幚硗晗⒑髸?huì)把消息回收掉,所以Message對象僅能使用一次,嘗試再次使用時(shí)MessageQueue會(huì)拋出異常。
Handler對象
它被設(shè)計(jì)出來目的就是方便隊(duì)列線程客戶端的操作,隱藏直接操作MessageQueue的復(fù)雜性。Handler最主要的作用是把消息發(fā)送到與此Handler綁定的線程的MessageQueue上,因此在構(gòu)建Handler的時(shí)候必須指定一個(gè)Looper對象,如果不指定則通過Looper獲取調(diào)用者線程的Looper對象。它有很多重載的send*Message和post方法,可以以多種方式來向目標(biāo)隊(duì)列發(fā)送消息,廷時(shí)發(fā)送,或者放到隊(duì)列的頭部等等;
它還有二個(gè)作用,一個(gè)是創(chuàng)建Message對象通過obtain*系統(tǒng)方法,另一個(gè)就是定義處理Message的回調(diào)mCallback和handleMessage,由于一個(gè)Handler可能不止發(fā)送一個(gè)消息,而這些消息通常共享此Handler的回調(diào)方法,所以在handleMessage或者mCallback中就要區(qū)分這些不同的消息,通常是以Message.what來區(qū)分,當(dāng)然也可以用其他字段,只要能區(qū)別出不同的Message即可。需要指明的是,消息隊(duì)列中的消息本身是獨(dú)立的,互不相干的,消息的命名空間是在Handler對象之中的,因?yàn)镸essage是由Handler發(fā)送和處理的,所以只有同一個(gè)Handler對象需要區(qū)別不同的Message對象。廣義上講,如果一個(gè)消息自己定義有處理方法,那么所有的消息都是互不相干的,當(dāng)從隊(duì)列取出消息時(shí)就調(diào)用其上的回調(diào)方法,不會(huì)有命名上的沖突,但由Handler發(fā)出的消息的回調(diào)處理方法都是Handler.handleMessage或Handler.mCallback,所以就會(huì)有影響了,但影響的范圍也令局限在同一個(gè)Handler對象。

因?yàn)镠andler的作用是向目標(biāo)隊(duì)列發(fā)送消息和定義處理消息的回調(diào)(處理消息),它僅是依賴于線程的MessageQueue,所以Handler可以有任意多個(gè),都綁定到某個(gè)MessageQueue上,它并沒有個(gè)數(shù)限制。而MessageQueue是有個(gè)數(shù)限制的,每個(gè)線程只能有一個(gè),MessageQueue通過Looper創(chuàng)建,Looper存儲(chǔ)在線程的ThreadLocal中,Looper里作了限制,每個(gè)線程只能創(chuàng)建一個(gè)。但是Handler無此限制,Handler的創(chuàng)建通過其構(gòu)造函數(shù),只需要提供一個(gè)Looper對象即可,所以它沒有個(gè)數(shù)限制。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 一级一级一级一级毛片 | www.17c亚洲蜜桃 | 在线播放av片 | 伊人99在线 | 日本黄色免费观看视频 | 毛片在线看免费 | 久久综合给合久久狠狠狠97色69 | 国产成人在线一区二区 | 精品久久久久久久久久久久久久久久久久久 | 久久亚洲春色中文字幕久久 | 国产九九热视频 | 久久久久久久亚洲精品 | 女18一级大黄毛片免费女人 | 做羞羞视频 | 国产精品久久久久久久久久10秀 | 成人羞羞视频在线观看 | 男男羞羞视频网站国产 | 第一区免费在线观看 | 九九热视频这里只有精品 | 亚洲国产超高清a毛毛片 | 蜜桃网站在线 | 久久999精品久久久 国产噜噜噜噜久久久久久久久 | 日本高清在线免费 | 最新一级毛片 | hdhdhd69ⅹxxx黑人| 欧美成人午夜一区二区三区 | 九九福利视频 | 久久爽精品区穿丝袜 | 成人男男视频拍拍拍在线观看 | 亚洲第一页在线观看 | 天天夜碰日日摸日日澡性色av | 欧美第1页| 久久精品性视频 | 亚洲成人福利在线 | 亚洲视频成人 | 国产免费福利视频 | 国产精品中文在线 | 国内精品伊人久久久久网站 | 精品一区二区亚洲 | 亚洲午夜视频 | 看毛片电影 |