天天看點

Handler了解和使用Handler的了解和使用

Handler的了解和使用

寫在最前面。最近在面試找工作,發現好多知識點會用,但是你要是詳細的問我一些内容我回答的可能就會有偏差。借這個機會整理一下這些知識點的同時,也把自己整理的内容分享出來。不能保證寫的一定正确。如有錯誤的地方希望大家批評指正。

什麼是Handler,以及Handler怎麼使用。

什麼是Handler?

讓我們先看看Android官方API是怎麼對Handler進行描述的

A Handler allows you to send and process

Message

and Runnable objects associated with a thread’s

MessageQueue

. Each Handler instance is associated with a single thread and that thread’s message queue. When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it – from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.

There are two main uses for a Handler: (1) to schedule messages and runnables to be executed at some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.

Scheduling messages is accomplished with the

post(Runnable)

,

postAtTime(Runnable, long)

,

postDelayed(Runnable, Object, long)

,

sendEmptyMessage(int)

,

sendMessage(Message)

,

sendMessageAtTime(Message, long)

, and

sendMessageDelayed(Message, long)

methods. The post versions allow you to enqueue Runnable objects to be called by the message queue when they are received; the sendMessage versions allow you to enqueue a

Message

object containing a bundle of data that will be processed by the Handler’s

handleMessage(Message)

method (requiring that you implement a subclass of Handler).

When posting or sending to a Handler, you can either allow the item to be processed as soon as the message queue is ready to do so, or specify a delay before it gets processed or absolute time for it to be processed. The latter two allow you to implement timeouts, ticks, and other timing-based behavior.

When a process is created for your application, its main thread is dedicated to running a message queue that takes care of managing the top-level application objects (activities, broadcast receivers, etc) and any windows they create. You can create your own threads, and communicate back with the main application thread through a Handler. This is done by calling the same post or sendMessage methods as before, but from your new thread. The given Runnable or Message will then be scheduled in the Handler’s message queue and processed when appropriate.

按照我個人的了解翻譯一下上面這段話,不一定準确。大家湊合着看。

一個Handler允許您發送和處理與線程的MessageQueue相關聯的Message和Runnable對象。每個Handler執行個體将單線程和該線程的消息隊列相關聯。從您建立一個新的Handler時起,它被綁定到建立它的線程和消息隊列。他将發送Messages和Runnables到消息隊列中,并且在它們從消息隊列取出來的時候執行它們。

Handler有兩個主要用途:

  1. 去安排Messages和Runnables在将來的某個時間節點被執行。
  2. 将一個活動列入到非目前線程的另一個線程去執行

    通過如下方法調用Message:

post(Runnable), postAtTime(Runnable, long), postDelayed(Runnable, Object, long), 
sendEmptyMessage(int), sendMessage(Message), sendMessageAtTime(Message, long), sendMessageDelayed(Message, long)
           

post版本允許您在接收消息隊列時排序Runnable對象。sendMessage版本允許你将Handler的handleMessage(Message) 方法處理過的帶有資料的Message對象進行排序(要求您實作Handler的子類)。

在傳遞或發送到 Handler 前, 您可以允許在消息隊列準備就緒後立即處理該項, 或者在處理之前指定延遲, 或将其處理為絕對時間。 後兩個允許您實作逾時、定時和其他基于計時的行為。

當你的應用程式建立程序的時候,主線程會運作一個專用的消息隊列,用于管理頂級的應用對象(Activites,broadcast receivers等)和它建立的其他視窗。你可以建立自己的線程,并通過Handler回調主線程。做這些操作和之前一樣通過post或sentMessage方法,但是是從你的新線程裡發送。然後Runnable和Message将被安排在Handler的消息隊列中,并在适當的時間進行處理。

從官方描述中我們大概可以知道Handler是一個可以線上程間通訊的工具,同時也可以通過Handler進行一些延時操作。結合Android的特點禁止在子線程中更新UI(禁止不是代表不可以,有一種情況下在子線程中可以更新,文章最後會說)。是以在進行Android開發時,Handler主要被用于子線程更新UI使用。

Handler怎麼用?

Handler主要是通過Looper和MessageQueue來更新UI的,我們也簡單的介紹一下:

Message

我們先看一下Message對象的字段:

參數類型 參數名稱 參數描述
int arg1 如果您僅需要傳遞一個Ingeger值,使用arg1和arg2相比使用setDate()方法會消耗更少的資源。
int arg2 同上.
Object obj 發送給接收者的一個人一的對象。
Messenger replyTo 将消息發送出去之後選擇的接收Messenger。
int sendingUid 選擇發送uid消息的字段。
int what 使用者定義的消息碼,以便接收者能夠識别此條消息的内容。

在Message中,我們常用的字段是what,obj,arg1,arg2。replyTo和sendingUid則是使用者程序間通訊使用(通過Handler+Messenger的形式,傳遞Message對象,在文章最後也說一下)。

從上面的表格不難看出每個字段存儲資訊的作用。是以選擇合适的字段也能夠幫助我們的App節省記憶體,比如在簡單的Integer值中使用arg1和arg2來替代setData(Bundle)方法。

下面我們從源碼中看一下Message的構造方法:

/** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
    */
    public Message() {
    }
           

Message隻有一個空的構造方法來建立Message對象,但是在這個方法的注釋上面,我們可以清晰的看到Android官方建議我們使用Message.obtain()方法來建立一個對象。obtain()建立的使用構造方法建立的有什麼不一樣呢?我們在看一下源碼:

/** @hide */
    public static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;

    private static final int MAX_POOL_SIZE = 50;

    private static boolean gCheckRecycle = true;

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
	/**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
           

Looper類:

public static void loop() {
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            msg.recycleUnchecked();
        }
    }
           

從上面我們可以看出obtain()方法在調用時如果sPool為null會傳回一個new Message()對象,在Looper進行循環周遊MessageQueue時,在第一個Message()對象被取出使用時,會調用Message的recycleUnchecked方法,在recycleUnchecked方法中會建立一個大小為50的Message消息池。在sPoll不為null時候調用obtain時,會從消息池中擷取。

這樣通過Message.obtain()方法擷取Message對象就能夠合理的利用資源。比直接在程式中new Message()更節省記憶體。

最後說一下handler.obtainMessage();方法。我們還是看一下源碼:

public final Message obtainMessage(){
        return Message.obtain(this);
    }f
           

該方法同樣是調用Message.obtain方法,是以說Message.obtain()和Handler.obtainMessage()方法可以說是相同的。

Looper

Looper是一個循環器,去循環周遊MessageQueue。在Activity中系統會自動幫幫使用者啟動Looper對象,但是在使用者自定義的類中,則需要使用者手動去調用Looper.prepare()方法,才能正常啟動Looper對象。

下面我們還是繼續看一下源碼:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
	final MessageQueue mQueue;
    final Thread mThread;
    
    public static void prepare() {
        prepare(true);
    }

    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));
    }
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
           

在這段代碼中我們會發現Looper的構造方法是私有的。是以我們是不能直接調用他的構造方法的,Looper提供了prepare方法給我們建立Looper使用。從prepare方法中我們可以看出在每個線程中,隻能建立一個Looper對象。且Looper實際是一個ThreadLocal。何為ThreadLocal?請參考:Java并發程式設計:深入剖析ThreadLocal。而且在執行個體化Looper時會執行個體化一個MessageQueue,是以MessageQueue就不需要我們去建立了。而且MQ的構造方法

MessageQueue(boolean quitAllowed)

是沒有被任何通路控制符修飾的。是以隻能被目前包下的類通路,我們的類是不能通路的。

繼續看源碼:

public static void loop() {
        final Looper me = myLooper();//擷取目前線程的Looper
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;//擷取目前線程的MessageQueue
        for (;;) {//開啟死循環周遊
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            try {
                msg.target.dispatchMessage(msg);//重點。開始分發消息。
            } 
            
            msg.recycleUnchecked();
        }
    }
           

調用loop方法後,Looper就開始真正的工作了。他不斷的從自己的MessageQueue中取出隊頭的消息執行。我們可以看一下msg.target.dispatchMessage(msg),msg.target其實是一個Handler對象,也就是說Message是包含Handler對象的。

Handler

Handler我們就直接寫使用,在過程中出現的問題會對應的提到。

既然上面說過有post版本和send版本,那麼我們就分别來看一下應該怎麼寫。首先我們先說一下xxxAtTime()和xxxDelayed()的差別,我們都用三個參數的方法來說:

xxxAtTime(Runnable/Message r/msg, Object token , long uptimeMillis)這個方法是指在固定的某一個時間節點執行Runnable或者Message。token是傳入的一個用于識别目前任務的參數,可以用于取消使用。uptimeMillis是執行時間點的時間戳。

xxxDelayed(Runnable/Message r/msg, Object token , long uptimeMillis)這個方法是在間隔一個固定的時間執行Runnable或者Message。token是傳入的一個用于識别目前任務的參數,可以用于取消使用。uptimeMillis是執行間隔時間的時間戳。

下面我就通過post(Runnable r)和sendMessage(Message msg)方法來寫,另外的xxxAtTime和xxxDelayed就不在寫。

post方式

先實作post方式的三種情況:子線程向主線程通訊,主線程向子線程通訊,子線程向子線程通訊。

1.子線程向主線程通訊

在主線程中建立Handler,在子線程中直接調用post方法,并重新Runnable的run()方法就可以了。

public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";
    private Handler mainHandler = new Handler();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(){
            @Override
            public void run() {
                mainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.i(TAG ,Thread.currentThread() + "");
                    }
                });
            }
        }.start();
    }
}
           

輸出結果:

Handler了解和使用Handler的了解和使用

2.主線程向子線程通訊

主線程向子線程通訊,其實就是将Handler建立給子線程,通過new Handler(Looper)的形式可以将Handler和Looper綁定,Looper所在的線程就是Handler所在的線程。

public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";
    
    Handler mHandler ;
    
    class MyThread extends Thread{
        private Looper looper;
        
        @Override
        public void run() {
            Looper.prepare();
            looper = Looper.myLooper();
            Looper.loop();
        }
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        MyThread myThread = new MyThread();
        myThread.start();
        
        mHandler = new Handler(myThread.looper);
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                Log.i(TAG ,Thread.currentThread() + "");
            }
        });
    }
}
           

運作這段代碼你會發現将會報錯,錯誤資訊:

Handler了解和使用Handler的了解和使用

報空指針的原因是因為在Handler初始化的時候,thread.looper還沒有初始化完成,是以就會報空指針,解決辦法也不難,可以通過一個循環判斷一下是否初始化完成,在執行個體化Handler。不過Android本身就給我們提供了一個方法解決這個問題,通過HandlerThread來建立一個子線程。是以我們将程式改為:

public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";

    Handler mHandler ;

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

        HandlerThread myThread = new HandlerThread("my thread");
        myThread.start();

        mHandler = new Handler(myThread.getLooper());
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                Log.i(TAG ,Thread.currentThread() + "");
            }
        });
    }
}

           

運作結果:

Handler了解和使用Handler的了解和使用

3.子線程向子線程通訊

線上程1中先調用Looper.prepare()方法,然後在将Handler綁定到目前的Looper,最後在執行Looper.loop()方法,線上程2中,通過Handler調用post()方法即可。

public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";

    Handler mHandler ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main)
        
        new Thread("接收消息線程"){
            @Override
            public void run() {
                Looper.prepare();
                mHandler = new Handler(Looper.myLooper());
                Looper.loop();
            }
        }.start();

        new Thread("發送消息線程") {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.i(TAG ,"實際執行線程是:" + Thread.currentThread() );
                    }
                });
            }
        }.start();
    }
}
           

運作結果:

Handler了解和使用Handler的了解和使用

sendMessage()方式

實作sendMessage方式也通過上面三種方式實作:子線程向主線程通訊,主線程向子線程通訊,子線程向子線程通訊。

在實作之前有必要說一下通過sendMessage方式實作時,需要内部類,當出現内部類時就有可能會導緻記憶體洩漏的發生。産生記憶體洩漏的原因是内部類會持有外部類的引用,當内部類沒有及時釋放持有的資源時,會導緻外部類也無法被釋放,這就會造成記憶體洩漏。這也就是為什麼我們通過内部類建立一個Handler時,IDE工具會有警告的黃色。解決Handler的記憶體洩漏有兩種辦法。

  1. 通過程式邏輯進行維護。

    可以再外部類被銷毀之前,調用Handler的removeCallbacksAndMessages (Object token),如果傳入參數,具有和token相同obj的Message将被移除。如果token傳入為null,則所有的callback和Message将被移除。

  2. 通過靜态内部類+弱引用的形式解決問題。

1.子線程向主線程通訊

public HandlerActivity extends Activity{
	public static final int PRINT_THREAD_NAME_LOG = 0x00;
    Handler mHandler ;

    private static class MyHandler extends Handler {
        private  final WeakReference<SecondActivity> mWeakReference;
        public MyHandler(SecondActivity activity) {
            mWeakReference=new WeakReference<>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            final SecondActivity activity=mWeakReference.get();
            switch (msg.what) {
                case PRINT_THREAD_NAME_LOG:
                    if(activity!=null) {
                        Toast.makeText(activity, Thread.currentThread() + "", Toast.LENGTH_SHORT).show();
                    }
                    break;
                default:
                    break;
            }
        }
    }

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

        final SecondActivity activity = this;
        mHandler = new MyHandler(activity);
        new Thread(new Runnable() {
            @Override
            public void run() {
                mHandler.sendEmptyMessageDelayed(PRINT_THREAD_NAME_LOG,5000);
            }
        }).start();

    }
}
           

将Handler聲明為靜态類,這樣Handler就不會持有外部類的引用,由于Handler不再持有外部類對象的引用,程式就不允許你在Handler中操作Activity的對象了,是以需要在Handler中增加一個對Activity的弱引用。

2.主線程向子線程通訊

public HandlerActivity extends Activity{
	public static final int PRINT_THREAD_NAME_LOG = 0x00;
    Handler mHandler ;

    private static class MyHandler extends Handler {
        private  final WeakReference<SecondActivity> mWeakReference;
        public MyHandler(SecondActivity activity) {
            mWeakReference=new WeakReference<>(activity);
        }
        public MyHandler(SecondActivity activity , Looper looper){
            super(looper);
            mWeakReference=new WeakReference<>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            final SecondActivity activity=mWeakReference.get();
            switch (msg.what) {
                case PRINT_THREAD_NAME_LOG:
                    if(activity!=null) {
                        Toast.makeText(activity, Thread.currentThread() + "", Toast.LENGTH_LONG).show();
                    }
                    break;
                default:
                    break;
            }
        }
    }

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

        final SecondActivity activity = this;

        HandlerThread thread = new HandlerThread("my thread");
        thread.start();

        mHandler = new MyHandler(activity, thread.getLooper());
        mHandler.sendEmptyMessageDelayed(PRINT_THREAD_NAME_LOG,1000);
    }
}
           

3.子線程和子線程通訊

public class HandlerActivity extends AppCompatActivity {

    public static final int PRINT_THREAD_NAME_LOG = 0x00;
    Handler mHandler ;

    private static class MyHandler extends Handler {
        private  final WeakReference<SecondActivity> mWeakReference;
        public MyHandler(SecondActivity activity) {
            mWeakReference=new WeakReference<>(activity);
        }
        public MyHandler(SecondActivity activity , Looper looper){
            super(looper);
            mWeakReference=new WeakReference<>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            final SecondActivity activity=mWeakReference.get();
            switch (msg.what) {
                case PRINT_THREAD_NAME_LOG:
                    if(activity!=null) {
                        Toast.makeText(activity, Thread.currentThread() + "", Toast.LENGTH_LONG).show();
                    }
                    break;
                default:
                    break;
            }
        }
    }




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

        final SecondActivity activity = this;

        new Thread("接收消息線程"){
            @Override
            public void run() {
                Looper.prepare();
                mHandler = new MyHandler(activity);
                Looper.loop();
            }
        }.start();

        new Thread("發送消息線程") {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                mHandler.sendEmptyMessageDelayed(PRINT_THREAD_NAME_LOG,1000);
            }
        }.start();
    }
}

           

需要注意在子線程建立Handler一定要先确認Looper的prepare()和loop()的調用。在主線程不需要調用的原因在官方描述已經說了,主線程會自動建立。

最後說一下上面說到會提到的内容。

1.子線程真的不能更新UI麼?

其實在特定情況下是可以的,但是不建議這麼做。在onCreate()方法裡直接建立子線程,通過子線程更新UI也是可以的,以為ViewRootImpl是在onResume方法中建立的,在調用checkThread方法之前,ViewRootImpl還沒建立,不會檢查目前線程是不是主線程,是以也不會報錯,可以更新UI。

2.Messenger的使用

大家提到程序間通訊的時候大部分人第一個想到的會是AIDL其實通過Messenger也是可以完成程序間通訊的,而且還不用寫AIDL檔案,是不是感覺很爽?

這一塊我暫時就不去寫了給大家分享一篇我看過的Messenger使用與解析,說的聽明白的。Android 進階10:程序通信之Messenger使用與解析