"一篇就夠"系列: Handler擴(kuò)展篇

前言

Handler系列文章共兩篇:

第一篇:"一篇就夠"系列: Handler消息機(jī)制完全解析

第二篇: "一篇就夠"系列: Handler擴(kuò)展篇

上一篇中,我們對Handler的主體部分進(jìn)行了講解,今天,我們就來學(xué)習(xí)一下Handler相關(guān)的一些擴(kuò)展知識,講完這些擴(kuò)展知識后,在來回答之前列出來的一系列問題

同步屏障

通過上一篇的學(xué)習(xí),我們知道: Handler發(fā)送的Message會放入到MessageQueue中,MessageQueue中維護(hù)了一個優(yōu)先級隊列,優(yōu)先級隊列的意思就是將存儲數(shù)據(jù)的單鏈表按照時間升序進(jìn)行排序形成的,Looper則按照順序,每次從這個優(yōu)先級隊列中取出一個Message進(jìn)行分發(fā),一個處理完就處理下一個。

那么問題來了:我能不能讓我的一個Message被優(yōu)先處理?

可以,使用同步屏障

這里,我心里又會有個疑問,什么是同步屏障?怎么使用同步屏障?同步屏障有啥作用?帶著這些疑問???,我們來分析下源碼

先看下MessageQueue的next方法,在上一篇中,我們省略了一部分代碼,其中有一部分是這樣子的,僅貼出關(guān)鍵代碼

Message next() {
    //...
    for (;;) {
        synchronized (this) {
            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());
            }
    }
}

上述代碼:

1、判斷當(dāng)前msg不為空并且msg.target為空,則進(jìn)入條件體里面

2、條件體里面有一行源碼注釋,翻譯過來就是: 被一個屏障給阻礙。在隊列中查找下一個異步消息

3、接下來就是一個循環(huán),遍歷找出一條異步消息,循環(huán)體里面就是鏈表相關(guān)的操作

這里大家是不是會有個疑問?msg.target怎么可能會為空呢?之前發(fā)送消息的一系列方法不是都會給msg.target對象賦值嗎?

沒錯,我們在回顧一下Handler的enqueueMessage

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
     //將當(dāng)前Handler賦值給msg.target
     msg.target = this;
     msg.workSourceUid = ThreadLocalWorkSource.getUid();
     if (mAsynchronous) {
         msg.setAsynchronous(true);
     }
     //調(diào)用MessageQueue的enqueueMessage方法
     return queue.enqueueMessage(msg, uptimeMillis);
}

我們知道Handler的postsend系列方法發(fā)送的消息,最終都會走到這個方法,msg.target都會被賦值,因此不可能為空。那msg.target啥時候會為空呢?我們推斷肯定是其他發(fā)送消息的方法使得msg.target為空,那我們就找一下,會發(fā)現(xiàn)MessageQueue的postSyncBarrier的方法中沒有給msg.target對象賦值:

public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
     // Enqueue a new sync barrier token.
     // We don't need to wake the queue because the purpose of a barrier is to stall it.
     synchronized (this) {
         final int token = mNextBarrierToken++;
         final Message msg = Message.obtain();
         msg.markInUse();
         msg.when = when;
         msg.arg1 = token;

         Message prev = null;
         Message p = mMessages;
         if (when != 0) {
             while (p != null && p.when <= when) {
                 prev = p;
                 p = p.next;
             }
         }
         if (prev != null) { // invariant: p == prev.next
             msg.next = p;
             prev.next = msg;
         } else {
             msg.next = p;
             mMessages = msg;
         }
         return token;
     }
 }

上述代碼就是往消息隊列中合適的位置插入target屬性為null的Message

因此我們是不是可以知道,Message的target屬性為空和非空是很不一樣的,這里就不賣關(guān)子了,直接給結(jié)論: target屬性為空的Message就是同步屏障,他是一種特殊的消息,并不會被消費,僅僅是作為一個標(biāo)識處于 MessageQueue 中,當(dāng)MessageQueue的next方法遇到同步屏障的時候,就會循環(huán)遍歷整個鏈表找到標(biāo)記為異步消息的Message,其他的消息會直接忽視,那么這樣異步消息就會提前被執(zhí)行了

現(xiàn)在我們現(xiàn)在就可以回答上面的問題了:target屬性為空的Message就是同步屏障,同步屏障可以使得異步消息優(yōu)先被處理,通過MessageQueue的postSyncBarrier可以添加一個同步屏障

注意: 在異步消息處理完之后,同步屏障并不會被移除,需要我們手動移除,從上面的源碼我們也可以看出,如果不移除同步屏障,那么他會一直在那里,這樣同步消息就永遠(yuǎn)無法被執(zhí)行了。

因此我們在使用完同步屏障后,需要手動移除,代碼如下:

public void removeSyncBarrier(int token) {
     // Remove a sync barrier token from the queue.
     // If the queue is no longer stalled by a barrier then wake it.
     synchronized (this) {
         Message prev = null;
         Message p = mMessages;
         while (p != null && (p.target != null || p.arg1 != token)) {
             prev = p;
             p = p.next;
         }
         if (p == null) {
             throw new IllegalStateException("The specified message queue synchronization "
                     + " barrier token has not been posted or has already been removed.");
         }
         final boolean needWake;
         if (prev != null) {
             prev.next = p.next;
             needWake = false;
         } else {
             mMessages = p.next;
             needWake = mMessages == null || mMessages.target != null;
         }
         p.recycleUnchecked();

         // If the loop is quitting then it is already awake.
         // We can assume mPtr != 0 when mQuitting is false.
         if (needWake && !mQuitting) {
             nativeWake(mPtr);
         }
     }
 }

到這里我心里又有一個疑問了?怎么把一個消息變成異步消息呢?還是回到Handler的enqueueMessage方法:

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
     msg.target = this;
     msg.workSourceUid = ThreadLocalWorkSource.getUid();
     //如果mAsynchronous,則將該消息設(shè)置為異步消息
     if (mAsynchronous) {
         msg.setAsynchronous(true);
     }
     return queue.enqueueMessage(msg, uptimeMillis);
}

從上述代碼我是可以看到,通過msg.setAsynchronous方法設(shè)置為true,可以把一個消息變成異步消息,但是前提得滿足mAsynchronous屬性為true,mAsynchronous是Handler中的一個屬性,他會在這兩個構(gòu)造方法中被賦值:

@UnsupportedAppUsage
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
    //...
    mAsynchronous = async;
}

public Handler(@Nullable Callback callback, boolean async) {
    //...
    mAsynchronous = async;
}

因此我們是不是可以得出結(jié)論,把一個消息設(shè)置為異步消息,有兩種方式:

1、在Handler的構(gòu)造方法中,傳入async為true,那么這個時候發(fā)送的Message就都是異步的的消息

2、給Message通過setAsynchronous 方法標(biāo)志為異步

但是,上面兩個構(gòu)造方法對外是不可見的,我們調(diào)用不到,而且設(shè)置同步屏障的方法對外也是不可見的,說明谷歌不想要我們?nèi)ナ褂盟?。所以這里同步屏障也是作為一個了解,一般只有系統(tǒng)會去使用它,例如:在進(jìn)行UI繪制的時候,以下是ViewRootImpl中執(zhí)行UI繪制的方法使用到了同步屏障:

@UnsupportedAppUsage
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

void unscheduleTraversals() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        mChoreographer.removeCallbacks(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}

上述代碼在把繪制消息放入隊列之前,先放入了一個同步屏障,然后在發(fā)送異步繪制消息,從而使得界面繪制的消息會比其他消息優(yōu)先執(zhí)行,避免了因為 MessageQueue 中消息太多導(dǎo)致繪制消息被阻塞導(dǎo)致畫面卡頓,當(dāng)繪制完成后,就會將同步屏障移除。

IdleHandler

見名知意,idle是空閑的意思,那么IdleHandler就是空閑的Handler,有點這個意思,實際上它是MessageQueue中有一個靜態(tài)接口

public static interface IdleHandler {
    boolean queueIdle();
}

可以看到它是一個單方法的接口,也可稱為函數(shù)型接口,它的作用是:在UI線程處理完所有View事務(wù)后,回調(diào)一些額外的操作,且不會堵塞主進(jìn)程;我們來實際操作一下

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new Handler().getLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
                Log.d("print", "queueIdle: 空閑時做一些輕量級別");
                return false;
            }
        });
    }
}

//上面代碼會打印如下結(jié)果
queueIdle: 空閑時做一些輕量級別

接著進(jìn)行源碼分析,我們在看下addIdleHandler這個方法:

public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) {
        throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}

可以看到,被添加進(jìn)來的handler放到了mIdleHandlers,跟過去看下mIdleHandlers,會發(fā)現(xiàn)MessageQueue中定義了IdleHandler的集合和數(shù)組,并且有一些操作方法,如下:

private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
private IdleHandler[] mPendingIdleHandlers;

public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) {
        throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}

public void removeIdleHandler(@NonNull IdleHandler handler) {
    synchronized (this) {
        mIdleHandlers.remove(handler);
    }
}

最后在看下MessageQueue中的Next方法,僅貼出關(guān)鍵代碼:

Message next() {
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    for (;;) {
        //...
        synchronized (this) {
             //...
             //當(dāng)前無消息,或還需要等待一段時間消息才能分發(fā),獲得IdleHandler的數(shù)量
             if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
                 pendingIdleHandlerCount = mIdleHandlers.size();
             }
          
             if (pendingIdleHandlerCount <= 0) {
                 // No idle handlers to run.  Loop and wait some more.
                 //如果沒有idle handler需要執(zhí)行,阻塞線程進(jìn)入下次循環(huán)
                 mBlocked = true;
                 continue;
             }
         //初始化mPendingIdleHandlers
             if (mPendingIdleHandlers == null) {
                 mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
             }
             //把List轉(zhuǎn)化成數(shù)組類型
             mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
         }
      
        //循環(huán)遍歷所有的IdleHandler
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler

            boolean keep = false;
            try {
                //獲得idler.queueIdle的返回值
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            //keep即idler.queueIdle的返回值,如果為false表明只要執(zhí)行一次,并移除,否則不移除
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
         }
         // Reset the idle handler count to 0 so we do not run them again.
         //將pendingIdleHandlerCount置為0避免下次再次執(zhí)行
         pendingIdleHandlerCount = 0;
      
         // 當(dāng)在執(zhí)行IdleHandler的時候,可能有新的消息已經(jīng)進(jìn)來了
         // 所以這個時候不能阻塞,要回去循環(huán)一次看一下
         nextPollTimeoutMillis = 0;
    }
}

上述代碼解析:

1、當(dāng)調(diào)用next方法的時候,會將pendingIdleHandlerCount賦值為-1

2、判斷pendingIdleHandlerCount是否小于0并且MessageQueue 是否為空或者有延遲消息需要執(zhí)行,如果是則把存儲IdleHandler的list的長度賦值給pendingIdleHandlerCount

3、判斷如果沒有IdleHandler需要執(zhí)行,阻塞線程進(jìn)入下次循環(huán),如果有,則初始化mPendingIdleHandlers,把list中的所有IdleHandler放到數(shù)組中。這一步是為了不讓在執(zhí)行IdleHandler的時候List被插入新的IdleHandler,造成邏輯混亂

4、循環(huán)遍歷所有的IdleHandler并執(zhí)行,查看idler.queueIdle方法的返回值,為false表明這個IdleHandler只需要執(zhí)行一次,并移除,為true,則不移除

5、將pendingIdleHandlerCount置為0避免下次再次執(zhí)行, 當(dāng)在執(zhí)行IdleHandler的時候,可能有新的消息已經(jīng)進(jìn)來了,所以這個時候不能阻塞,要回去循環(huán)一次看一下

到這里同步屏障和IdleHandler都講完了,建議讀者配合完整的源碼在去仔細(xì)閱讀一次。

實際應(yīng)用: 可以在IdleHandler里面獲取View的寬高

主線程消息循環(huán)

在上一篇中我們講到,ActivityThread就是主線程,也可以說是UI線程,在主線程的main方法中創(chuàng)建了Looper,并開啟了消息循環(huán):

public static void main(String[] args) {
  //...
  //創(chuàng)建Looper
  Looper.prepareMainLooper();
  
  ActivityThread thread = new ActivityThread();
  thread.attach(false);

  if (sMainThreadHandler == null) {
    sMainThreadHandler = thread.getHandler();
  }
  //...
  //開啟循環(huán)讀取消息
  Looper.loop();
  //Looper如果因異常原因停止循環(huán)則拋異常
  throw new RuntimeException("Main thread loop unexpectedly exited");
}

主線程的消息循環(huán)開始了以后,ActivityThread還需要有一個Handler來和消息隊列進(jìn)行交互,這個Handler就是ActivityThread.H,它內(nèi)部定義了很多的消息類型,例如四大組件的啟動,Application的啟動等等

class H extends Handler {
        public static final int BIND_APPLICATION        = 110;
        @UnsupportedAppUsage
        public static final int EXIT_APPLICATION        = 111;
        @UnsupportedAppUsage
        public static final int RECEIVER                = 113;
        @UnsupportedAppUsage
        public static final int CREATE_SERVICE          = 114;
        @UnsupportedAppUsage
        public static final int SERVICE_ARGS            = 115;
        @UnsupportedAppUsage
        public static final int STOP_SERVICE            = 116;

        public static final int CONFIGURATION_CHANGED   = 118;
        public static final int CLEAN_UP_CONTEXT        = 119;
        @UnsupportedAppUsage
        public static final int GC_WHEN_IDLE            = 120;
        @UnsupportedAppUsage
        public static final int BIND_SERVICE            = 121;
        @UnsupportedAppUsage
        public static final int UNBIND_SERVICE          = 122;
        public static final int DUMP_SERVICE            = 123;
        public static final int LOW_MEMORY              = 124;
        public static final int PROFILER_CONTROL        = 127;
        public static final int CREATE_BACKUP_AGENT     = 128;
        public static final int DESTROY_BACKUP_AGENT    = 129;
        public static final int SUICIDE                 = 130;
            //...
        public void handleMessage(Message msg) {
            //...
        }
}

關(guān)于ActivityThread.H的實際應(yīng)用,我們在看Activity的啟動流程可能會有比較深入的理解,ActivityThread通過ApplicationThread和AMS進(jìn)行進(jìn)程間通信的方式完成ActivityThread的請求后,會回調(diào)ApplicationThread中的Binder方法,然后ApplicationThread會向H發(fā)送消息,H收到消息后會將ApplicationThread中的邏輯切換到ActivityThread中去執(zhí)行,即切換到主線程去執(zhí)行,這個過程就是主線程的消息循環(huán)模型

妙用 Looper 機(jī)制

1、我們可以通過LoopergetMainLooper方法獲取主線程Looper,從而可以判斷當(dāng)前線程是否是主線程

2、將 Runnable post 到主線程執(zhí)行

public final class MainThread {

    private MainThread() {
    }

    private static final Handler HANDLER = new Handler(Looper.getMainLooper());

    public static void run(@NonNull Runnable runnable) {
        if (isMainThread()) {
            runnable.run();
        }else{
            HANDLER.post(runnable);
        }
    }

    public static boolean isMainThread() {
        return Looper.myLooper() == Looper.getMainLooper();
    }
}

子線程使用Handler及相關(guān)注意事項

我們通常使用Handler都是從子線程發(fā)送消息到主線程去處理,那么這里我們嘗試一下從主線程發(fā)送消息到子線程來處理,上代碼:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //創(chuàng)建線程實例并開啟
        MyThread myThread = new MyThread();
        myThread.start();
        //打開這段注釋就不會crash,且看下面分析
//      try {
//          Thread.sleep(500);
//      } catch (InterruptedException e) {
//          e.printStackTrace();
//      }
        //獲取Handler發(fā)送消息
        myThread.getHandler().sendEmptyMessage(0x001);
    }

    public static class MyThread extends Thread {
        private Handler mHandler;

        public void run() {
            Looper.prepare();
            mHandler = new Handler() {
                public void handleMessage(@NonNull Message msg) {
                    if(msg.what == 0x001){
                        Log.d("print", "handleMessage: ");
                    }
                }
            };
            Looper.loop();
        }

        public Handler getHandler(){
            return mHandler;
        }
    }
}

運行一下上述代碼,發(fā)現(xiàn)會Crash,如下圖:

image-20210223112129732

報了一個空指針異常,原因就是多線程并發(fā),當(dāng)主線程執(zhí)行到sendEnptyMessage時,子線程的Handler還沒有創(chuàng)建。因此我們可以在獲取Handler的時候讓主線程休眠一下在執(zhí)行,應(yīng)用就不會Crash了,打開上面代碼的注釋即可

值得注意的是:我們自己創(chuàng)建的Looper在使用完畢后應(yīng)該調(diào)用quit方法來終止消息循環(huán),如果不退出的話,那么該線程的Looper處理完所有的消息后,就會處于一個阻塞狀態(tài),要知道線程是比較重量級的,如果一直存在,肯定會對應(yīng)用性能造成一定的影響。而如果退出Looper,這個線程就會立刻終止,因此建議不需要的時候終止Looper。

因此在子線程使用Handler,我們需要注意一下兩點:

1、必須調(diào)用Looper.prepare()創(chuàng)建當(dāng)前線程的 Looper,并調(diào)用Looper.loop()開啟消息循環(huán)

2、必須在使用結(jié)束后調(diào)用Looper的quit方法退出當(dāng)前線程

HandlerThread

上面講到主線程發(fā)送消息到子線程來處理,其實Android已經(jīng)給我們提供了一個這樣輕量級的異步類,那就是HandlerThread

HandlerThread的實現(xiàn)原理也比較簡單:繼承Thread并對Looper進(jìn)行了封裝

具體源碼就不過多分析了,大家有興趣的可以去看一下,也就100多行代碼,這里主要講解一下使用:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //1,創(chuàng)建Handler實例
        HandlerThread mHandlerThread = new HandlerThread("HandlerThread");
        //2,啟動線程
        mHandlerThread.start();
        //3,使用傳入Looper為參數(shù)的構(gòu)造方法創(chuàng)建Handler實例
        Handler mHandler = new Handler(mHandlerThread.getLooper()){
            @Override
            public void handleMessage(@NonNull Message msg) {
                Log.d("print", "當(dāng)前線程: " + Thread.currentThread().getName() + " handleMessage");
            }
        };
        //4,使用Handler發(fā)送消息
        mHandler.sendEmptyMessage(0x001);
        //5,在合適的時機(jī)調(diào)用HandlerThread的quit方法,退出消息循環(huán)
    }
}

//上述代碼打印結(jié)果:
當(dāng)前線程: HandlerThread handleMessage

Handler HandlerThread Thread三者區(qū)別

Handler:在Android中負(fù)責(zé)發(fā)送和處理消息

HandlerThread:繼承自Thread,對Looper進(jìn)行了封裝,也就是說它在子線程維護(hù)了一個Looper,方便我們在子線程中去處理消息

Thread: cpu執(zhí)行的最小單位,即線程,它在執(zhí)行完后就立馬結(jié)束了,并不能去處理消息。如果要處理,需要配合Looper,Handler一起使用

子線程彈Toast

//1
new Thread(){
    @Override
    public void run() {
        Toast.makeText(MainActivity.this, "子線程彈Toast", Toast.LENGTH_SHORT).show();
    }
}.start();

//2
new Thread(){
    @Override
    public void run() {
        Looper.prepare();
        Toast.makeText(MainActivity.this, "子線程彈Toast", Toast.LENGTH_SHORT).show();
        Looper.loop();
    }
}.start();

上述1代碼運行會奔潰,會報這么一個異常提示:"Can't toast on a thread that has not called Looper.prepare()"

原因就是Toast的實現(xiàn)也是依賴Handler,而我們知道在子線程中創(chuàng)建Handler,需先創(chuàng)建Looper并開啟消息循環(huán),這點在Toast中的源碼也有體現(xiàn),如下圖:

image-20210223131023498

因此我們在子線程創(chuàng)建Toast就需要使用上述2代碼的方式

子線程彈Dialog

new Thread(){
    @Override
    public void run() {
            Looper.prepare();
            new AlertDialog.Builder(MainActivity.this)
                  .setTitle("標(biāo)題")
                  .setMessage("子線程彈Dialog")
                  .setNegativeButton("取消",null)
                  .setPositiveButton("確定",null)
                  .show();
            Looper.loop();     
    }    
}.start();

和上面Toast差不多,這里貼出正確的代碼示例,它的實現(xiàn)也是依賴Handler,我們在它的源碼中可以看到:

private final Handler mHandler = new Handler();

他直接就new了一個Handler實例,我們知道,創(chuàng)建Handler,需要先創(chuàng)建Looper并開啟消息循環(huán),主線程中已經(jīng)給我們創(chuàng)建并開啟消息循環(huán),而子線程中并沒有,如果不創(chuàng)建那就會報這句經(jīng)典的異常提示:"Can't create handler inside thread that has not called Looper.prepare() ",因此在子線程中,需要我們手動去創(chuàng)建并開啟消息循環(huán)

到這里,Handler相關(guān)的擴(kuò)展知識就全部講完了,我們會發(fā)現(xiàn)也有著很多使用的小技巧,比如 IdleHandler,判斷是否是主線程等等

由于 Handler 的特性,它在 Android 里的應(yīng)用非常廣泛,比如: AsyncTask、HandlerThread、Messenger、IdleHandler 和 IntentService 等等,下面我們來回答上一篇中列出來的一系列問題

問題

1、Handler有哪些作用?

答:

1、Handler能夠進(jìn)行線程之間的切換

2、Handler能夠按照順序處理消息,避免并發(fā)

3、Handler能夠阻塞線程

4、Handler能夠發(fā)送并處理延遲消息

解析:

1、Handler能夠進(jìn)行線程之間的切換,是因為使用了不同線程的Looper處理消息

2、Handler能夠按照順序處理消息,避免并發(fā),是因為消息在入隊的時候會按照時間升序?qū)Ξ?dāng)前鏈表進(jìn)行排序,Looper讀取的時候,MessageQueue的next方法會循環(huán)加鎖,同時配合阻塞喚醒機(jī)制

3、Handler能夠阻塞線程主要是基于Linux的epoll機(jī)制實現(xiàn)的

4、Handler能夠處理延遲消息,是因為MessageQueue的next方法中會拿當(dāng)前消息時間和當(dāng)前時間做比較,如果是延遲消息,那么就會阻塞當(dāng)前線程,等阻塞時間到,在執(zhí)行該消息

2、為什么我們能在主線程直接使用Handler,而不需要創(chuàng)建Looper?

答:主線程已經(jīng)創(chuàng)建了Looper,并開啟了消息循環(huán)

3、如果想要在子線程創(chuàng)建Handler,需要做什么準(zhǔn)備?

答:需要先創(chuàng)建Looper,并開啟消息循環(huán)

4、一個線程有幾個Handler?

答:可以有任意多個

5、一個線程有幾個Looper?如何保證?

答:一個線程只有一個Looper,通過ThreadLocal來保證

6、Handler發(fā)送消息的時候,時間為啥要取SystemClock.uptimeMillis() + delayMillis,可以把SystemClock.uptimeMillis() 換成System.currentTimeMillis()嗎?

答:不可以

SystemClock.uptimeMillis() 這個方法獲取的時間,是自系統(tǒng)開機(jī)到現(xiàn)在的一個毫秒數(shù),這個時間是個相對的

System.currentTimeMillis() 這個方法獲取的是自1970-01-01 00:00:00 到現(xiàn)在的一個毫秒數(shù),這是一個和系統(tǒng)強(qiáng)關(guān)聯(lián)的時間,而且這個值可以做修改

1、使用System.currentTimeMillis()可能會導(dǎo)致延遲消息失效

2、最終這個時間會被設(shè)置到Message的when屬性,而Message的when屬性只是需要一個時間差來表示消息的先后順序,使用一個相對時間就行了,沒必要使用一個絕對時間

7、為什么Looper死循環(huán),卻不會導(dǎo)致應(yīng)用卡死?

答:因為當(dāng)Looper處理完所有消息的時候,會調(diào)用Linux的epoll機(jī)制進(jìn)入到阻塞狀態(tài),當(dāng)有新的Message進(jìn)來的時候會打破阻塞繼續(xù)執(zhí)行。

應(yīng)用卡死即ANR: 全稱Applicationn Not Responding,中文意思是應(yīng)用無響應(yīng),當(dāng)我發(fā)送一個消息到主線程,Handler經(jīng)過一定時間沒有執(zhí)行完這條消息,那么這個時候就會拋出ANR異常

Looper死循環(huán): 循環(huán)執(zhí)行各種事務(wù),Looper死循環(huán)說明線程還活著,如果沒有Looper死循環(huán),線程結(jié)束,應(yīng)用就退出了,當(dāng)Looper處理完所有消息的時候會調(diào)用Linux的epoll機(jī)制進(jìn)入到阻塞狀態(tài),當(dāng)有新的Message進(jìn)來的時候會打破阻塞繼續(xù)執(zhí)行

8、Handler內(nèi)存泄露原因? 如何解決?

內(nèi)存泄漏的本質(zhì)是長生命周期的對象持有短生命周期對象的引用,導(dǎo)致短生命周期的對象無法被回收,從而導(dǎo)致了內(nèi)存泄漏

下面我們就看個導(dǎo)致內(nèi)存泄漏的例子

public class MainActivity extends AppCompatActivity {

    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
           //do something
        }
    };
  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //發(fā)送一個延遲消息,10分鐘后在執(zhí)行
        mHandler.sendEmptyMessageDelayed(0x001,10*60*1000);
    }
}

上述代碼:

1、我們通過匿名內(nèi)部類的方式創(chuàng)建了一個Handler的實例

2、在onCreate方法里面通過Handler實例發(fā)送了一個延遲10分鐘執(zhí)行的消息

我們發(fā)送的這個延遲10分鐘執(zhí)行的消息它是持有Handler的引用的,根據(jù)Java特性我們又知道,非靜態(tài)內(nèi)部類會持有外部類的引用,因此當(dāng)前Handler又持有Activity的引用,而Message又存在MessageQueue中,MessageQueue又在當(dāng)前線程中,因此會存在一個引用鏈關(guān)系:

當(dāng)前線程->MessageQueue->Message->Handler->Activity

因此當(dāng)我們退出Activity的時候,由于消息需要在10分鐘后在執(zhí)行,因此會一直持有Activity,從而導(dǎo)致了Activity的內(nèi)存泄漏

通過上面分析我們知道了內(nèi)存泄漏的原因就是持有了Activity的引用,那我們是不是會想,切斷這條引用,那么如果我們需要用到Activity相關(guān)的屬性和方法采用弱引用的方式不就可以了么?我們實際操作一下,把Handler寫成一個靜態(tài)內(nèi)部類

public class MainActivity extends AppCompatActivity {

    private final SafeHandler mSafeHandler = new SafeHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //發(fā)送一個延遲消息,10分鐘后在執(zhí)行
        mSafeHandler.sendEmptyMessageDelayed(0x001,10*60*1000);
    }

    //靜態(tài)內(nèi)部類并持有Activity的弱引用
    private static class SafeHandler extends Handler{
      
        private final WeakReference<MainActivity> mWeakReference;
      
        public SafeHandler(MainActivity activity){
            mWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            MainActivity mMainActivity = mWeakReference.get();
            if(mMainActivity != null){
                //do something
            }
        }
    }
}

上述代碼

1、把Handler定義成了一個靜態(tài)內(nèi)部類,并持有當(dāng)前Activity的弱引用,弱引用會在Java虛擬機(jī)發(fā)生gc的時候把對象給回收掉

經(jīng)過上述改造,我們解決了Activity的內(nèi)存泄漏,此時的引用鏈關(guān)系為:

當(dāng)前線程->MessageQueue->Message->Handler

我們會發(fā)現(xiàn)Message還是會持有Handler的引用,從而導(dǎo)致Handler也會內(nèi)存泄漏,所以我們應(yīng)該在Activity銷毀的時候,在他的生命周期方法里,把MessageQueue中的Message都給移除掉,因此最終就變成了這樣:

public class MainActivity extends AppCompatActivity {

    private final SafeHandler mSafeHandler = new SafeHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //發(fā)送一個延遲消息,10分鐘后在執(zhí)行
        mSafeHandler.sendEmptyMessageDelayed(0x001,10*60*1000);
    }
  
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSafeHandler.removeCallbacksAndMessages(null);
    }

    //靜態(tài)內(nèi)部類并持有Activity的弱引用
    private static class SafeHandler extends Handler{
      
        private final WeakReference<MainActivity> mWeakReference;
      
        public SafeHandler(MainActivity activity){
            mWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            MainActivity mMainActivity = mWeakReference.get();
            if(mMainActivity != null){
                //do something
            }
        }
    }
}

因此當(dāng)Activity銷毀后,引用鏈關(guān)系為:

當(dāng)前線程->MessageQueue

而當(dāng)前線程和MessageQueue的生命周期和應(yīng)用生命周期是一樣長的,因此也就不存在內(nèi)存泄漏了,完美。

所以解決Handler內(nèi)存泄漏最好的方式就是:將Handler定義成靜態(tài)內(nèi)部類,內(nèi)部持有Activity的弱引用,并在Activity銷毀的時候移除所有消息

9、線程維護(hù)的Looper,在消息隊列無消息時的處理方案是什么?有什么用?

答:當(dāng)消息隊列無消息時,Looper會阻塞當(dāng)前線程,釋放cpu資源,提高App性能

我們知道Looper的loop方法中有個死循環(huán)一直在讀取MessageQueue中的消息,其實是調(diào)用了MessageQueue中的next方法,這個方法會在無消息時,調(diào)用Linux的epoll機(jī)制,使得線程進(jìn)入阻塞狀態(tài),當(dāng)有新消息到來時,就會將它喚醒,next方法里會判斷當(dāng)前消息是否是延遲消息,如果是則阻塞線程,如果不是,則會返回這條消息并將其從優(yōu)先級隊列中給移除

10、MessageQueue什么情況下會被喚醒?

答:需要分情況

1、發(fā)送消息過來,此時MessageQueue中無消息或者當(dāng)前發(fā)送過來的消息攜帶的when為0或者有延遲執(zhí)行的消息,那么需要喚醒

2、當(dāng)遇到同步屏障且當(dāng)前發(fā)送過來的消息為異步消息,判斷該異步消息是否插入在所有異步消息的隊首,如果是則需要喚醒,如果不是,則不喚醒

11、線程什么情況下會被阻塞?

答:分情況

1、當(dāng)MessageQueue中沒有消息的時候,這個時候會無限阻塞,

2、當(dāng)前MessageQueue中全部是延遲消息,阻塞時間為(當(dāng)前延遲消息時間 - 當(dāng)前時間),如果這個阻塞時間超過來Integer類型的最大值,則取Integer類型的最大值

12、我們可以使用多個Handler往消息隊列中添加數(shù)據(jù),那么可能存在發(fā)消息的Handler存在不同的線程,那么Handler是如何保證MessageQueue并發(fā)訪問安全的呢?

答:循環(huán)加鎖,配合阻塞喚醒機(jī)制

我們可以發(fā)現(xiàn)MessageQueue其實是“生產(chǎn)者-消費者”模型,Handler不斷地放入消息,Looper不斷地取出,這就涉及到死鎖問題。如果Looper拿到鎖,但是隊列中沒有消息,就會一直等待,而Handler需要把消息放進(jìn)去,鎖卻被Looper拿著無法入隊,這就造成了死鎖。Handler機(jī)制的解決方法是循環(huán)加鎖。在MessageQueue的next方法中:

Message next() {
   ...
    for (;;) {
  ...
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            ...
        }
    }
}

我們可以看到他的等待是在鎖外的,當(dāng)隊列中沒有消息的時候,他會先釋放鎖,再進(jìn)行等待,直到被喚醒。這樣就不會造成死鎖問題了。

那在入隊的時候會不會因為隊列已經(jīng)滿了然后一邊在等待消息處理一邊拿著鎖呢?這一點不同的是MessageQueue的消息沒有上限,或者說他的上限就是JVM給程序分配的內(nèi)存,如果超出內(nèi)存會拋出異常,但一般情況下是不會的。

13、Handler是如何進(jìn)行線程切換的呢?

答:使用不同線程的Looper處理消息

我們通常處理消息是在Handler的handleMessage方法中,那么這個方法是在哪里回調(diào)的呢?看下面這段代碼

public static void loop() {
    //開啟死循環(huán)讀取消息
    for (;;) {
         // 調(diào)用Message對應(yīng)的Handler處理消息
         msg.target.dispatchMessage(msg);
    }
}

上述代碼中msg.target其實就是我們發(fā)送消息的Handler,因此他會回調(diào)Handler的dispatchMessage方法,而dispatchMessage這個方法我們在上一篇中重點分析過,其中有一部分邏輯就是會回調(diào)到Handler的handleMessage方法,我們還可以發(fā)現(xiàn),Handler的handleMessage方法所在的線程是由Looper的loop方法決定的。平時我們使用的時候,是從異步線程發(fā)送消息到 Handler,而這個 Handler 的 handleMessage() 方法是在主線程調(diào)用的,因為Looper是在主線程創(chuàng)建的,所以消息就從異步線程切換到了主線程。

14、我們在使用Message的時候,應(yīng)該如何去創(chuàng)建它?

答:Android 給 Message 設(shè)計了回收機(jī)制,官方建議是通過Message.obtain方法來獲取,而不是直接new一個新的對象,所以我們在使用的時候應(yīng)盡量復(fù)用 Message ,減少內(nèi)存消耗,方式有二:

1、調(diào)用 Message 的一系列靜態(tài)重載方法 Message.obtain 獲取

2、通過 Handler 的公有方法 handler.obtainMessage,實際上handler.obtainMessage內(nèi)部調(diào)用的也是Message.obtain的重載方法

15、Handler里面藏著的CallBack能做什么?

答: 利用此CallBack攔截Handler的消息處理

在上一篇中我們分析到,dispatchMessage方法的處理步驟:

1、首先,檢查Message的callback是否為null,不為null就通過handleCallBack來處理消息,Message的callback是一個Runnable對象,實際上就是Handler的post系列方法所傳遞的Runnable參數(shù)

2、其次,檢查Handler里面藏著的CallBack是否為null,不為null就調(diào)用mCallback的handleMessage方法來處理消息,并判斷其返回值:為true,那么 Handler 的 handleMessage(msg) 方法就不會被調(diào)用了;為false,那么就意味著一個消息可以同時被 Callback 以及 Handler 處理。

3、最后,調(diào)用Handler的handleMessage方法來處理消息

通過上面分析我們知道Handler處理消息的順序是:Message的Callback > Handler的Callback > Handler的handleMessage方法

使用場景: Hook ActivityThread.mH , 在 ActivityThread 中有個成員變量 mH ,它是個 Handler,又是個極其重要的類,幾乎所有的插件化框架都使用了這個方法。

16、Handler阻塞喚醒機(jī)制是怎么一回事?

答: Handler的阻塞喚醒機(jī)制是基于Linux的阻塞喚醒機(jī)制。

這個機(jī)制也是類似于handler機(jī)制的模式。在本地創(chuàng)建一個文件描述符,然后需要等待的一方則監(jiān)聽這個文件描述符,喚醒的一方只需要修改這個文件,那么等待的一方就會收到文件從而打破喚醒。和Looper監(jiān)聽MessageQueue,Handler添加message是比較類似的。具體的Linux層知識讀者可通過這篇文章詳細(xì)了解(傳送門

17、什么是Handler的同步屏障?

答: 同步屏障是一種使得異步消息可以被更快處理的機(jī)制

18、能不能讓一個Message被加急處理?

答:可以,添加加同步屏障,并發(fā)送異步消息

19、什么是IdleHandler?

答: IdleHandler是MessageQueue中一個靜態(tài)函數(shù)型接口,它在主線程執(zhí)行完所有的View事務(wù)后,回調(diào)一些額外的操作,且不會阻塞主線程

總結(jié)

Handler消息機(jī)制在Android系統(tǒng)源碼中進(jìn)行了大量的使用,可以說是涉及了Android的方方面面,比如我們四大組件的啟動,Application的創(chuàng)建等等,學(xué)好Handler相關(guān)的知識,可以幫助我們更好的去閱讀Android源碼,而且Handler在我們?nèi)粘i_發(fā)中直接或間接的會被用到。同時通過對Handler源碼的學(xué)習(xí),讓我感受到了代碼設(shè)計的背后,蘊藏著工程師大量的智慧,心里直呼666,哈哈。

到了這里,關(guān)于Handler相關(guān)的知識就都講完了,如果你還有什么問題,評論區(qū)告訴我吧。

參考和推薦

Android全面解析之Handler機(jī)制(終篇):常見問題匯總

Handler 都沒搞懂,拿什么去跳槽?。?/a>

換個姿勢,帶著問題看Handler

全文到此,原創(chuàng)不易,歡迎點贊,收藏,評論和轉(zhuǎn)發(fā),你的認(rèn)可是我創(chuàng)作的動力

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容