Android Handler的使用和原理解析

Handler使用與原理解析

使用

原理

總結

使用

1.在主線程中使用:

public static final int UPDATE_NAME=2;
	private MyHandler mHandler;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mHandler=new MyHandler(new WeakReference<MainActivity>(this));
	}
	
	@Override
	protected void onResume() {
		super.onResume();
		new Thread(new Runnable() {
			@Override
			public void run() {
				Log.d("ZX", "發送消息"+Thread.currentThread().getName());
				mHandler.sendEmptyMessage(UPDATE_NAME);
			}
		},"thread-2").start();
	}
	
	static class MyHandler extends Handler{
		
		public MyHandler(WeakReference<MainActivity> activity) {
			reference=activity;
		}
		
		WeakReference<MainActivity> reference;
		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case UPDATE_NAME:
				Log.d("ZX", "處理消息"+Thread.currentThread().getName());
				break;
			default:
				break;
			}
		}
	}

說明:

  1. 新建一個靜態內部類MyHandler,使用弱引用引用外部實例,主要是爲了防止內存泄漏。
  2. 在點擊的時候創建了一個子線程,在子線程中用handler發送了一個消息。
  3. MyHander接收到消息時打印出線程名稱

執行結果:

在這裏插入圖片描述

進程號100695是主線程,名字叫做main,這個是在Activity創建的時候系統設置的名稱,那又是另外一個故事了,這裏暫時先不管。進程號10707是我們創建的子線程,名字叫做thread-2,這個是我們自己取的。因爲在linux看來沒有進程和線程的區別都是進程,唯一的區別就是進程會有獨立的內存等資源,而線程沒有。

2.在子線程中使用。

	public static final int UPDATE_NAME=2;
	private TextView mTvName;
	private Handler mHandler;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mTvName=(TextView) findViewById(R.id.inset_tv);
		findViewById(R.id.boast).setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				new Thread(new Runnable() {
					
					@Override
					public void run() {
						Log.d("ZX", "發送消息"+Thread.currentThread().getName());
						mHandler.sendEmptyMessage(UPDATE_NAME);
					}
				},"thread--2").start();
			}
		});
		new Thread(new Runnable() {
			@Override
			public void run() {
				Looper.prepare();
				mHandler=new Handler(){
					@Override
					public void dispatchMessage(Message msg) {
						Log.d("ZX", "處理消息"+Thread.currentThread().getName());
					}
				};
				Looper.loop();
				
			}
		},"thread--1").start();
	}

說明:

  1. 新建一個線程取名thread-1,在其內部創建mHhandler。
  2. 在點擊的時候創建了一個子線程thread-2,在子線程中用mHhandler發送了一個消息。
  3. mHhandler接收到消息時執行打印。

執行結果:

在這裏插入圖片描述

這裏就實現了線程間通信,要知道他們是怎麼做到的,就需要進入源碼分析了。

原理

我們還是以主線程中的使用來分析吧,畢竟用的多。這裏我們要知道Activity的創建是從ActivityThread.java的main函數開始的。本文源碼來自android10。

 public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        //無本文無關的代碼暫時去掉

        Looper.prepareMainLooper();//步驟 step 1

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
       
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

       
        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();//step 2

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

//step 1

 public static void prepareMainLooper() {
        prepare(false);//step 1.1
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();//step 1.2
        }
    }

按順序先看//step 1.1

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)//step 1.1.1);//step 1.1.2
    }

第一進來肯定是null,

//step 1.1.1

 private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);//step 1.1.1.1
        mThread = Thread.currentThread();//step 1.1.1.2
    }

這裏就是創建了一個MessageQueue對象,獲取當前線程。

MessageQueue我們稍後再看,繼續看Looper類。

//step 1.1.1 執行完了,我們回到//step 1.1.2

   public void set(T value) {
        Thread t = Thread.currentThread();//step 1.1.2.1
        ThreadLocalMap map = getMap(t);//step 1.1.2.2
        if (map != null)
            map.set(this, value);1.1.2.3
        else
            createMap(t, value);1.1.2.4
    }

進入//step 1.1.2.1

 /**
     * Returns a reference to the currently executing thread object.
     *
     * @return  the currently executing thread.
     */
    @FastNative
    public static native Thread currentThread();

這個是一個JNI方法,實現在C++,本文我們不去追蹤,這裏從註釋就能看出是返回一個當前執行線程對象的引用。

我們繼續看//step 1.1.2.2

   /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

獲取當前線程中的 threadlocalMap。看名字就知道是個map集合。是個容器用來存東西的。我們點進去稍微看下。它是ThreadLocal的一個靜態內部類。

 static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

        /**
         * The number of entries in the table.
         */
        private int size = 0;

        /**
         * The next size value at which to resize.
         */
        private int threshold; // Default to 0

        /**
         * Set the resize threshold to maintain at worst a 2/3 load factor.
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * Increment i modulo len.
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * Decrement i modulo len.
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

看到這些字段應該很熟悉了吧,它和hashmap非常的類似。

定義了一個數組Entry[] table,存儲是元素是Entry。繼承至弱引用。k-v的形式存儲內容。key是ThreadLocal<?>。
初始容量是16,每次增加必須是2的指數倍,當滿了2/3時擴容。

由於第一次進來獲取到的map肯定是空,所以執行//step 1.1.2.4

/**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);//step 1.1.2.4.1
    }

這個就是創建一個ThreadLocalMap 然後設置到當前的線程中。

//step 1.1.2.4.1

  /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

這個就是創建一個ThreadlocalMap。並存入第一個元素Entry。這個的firstValue就是前面創建的Looper。fristKey就是在Looper裏面創建的靜態常量 sThreadLocal。我們計算一下,第一個entry的下標是存在哪。

int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);計算這個I。

前面
int threadLocalHashCode = nextHashCode();
int INITIAL_CAPACITY = 16;

所以後面的是16-1=15。主要是看前面的數。

private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
 private static AtomicInteger nextHashCode =
        new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647; 

AtomicInteger是隨着jdk5.0出來的,它位於java.util.concurrent.atomic包下,AtomicInteger,一個提供原子操作的Integer的類。也就是說在Java語言中,++i和i++操作並不是線程安全的,在使用的時候,不可避免的會用到synchronized關鍵字。而AtomicInteger則通過一種線程安全的加減操作接口,也就是說當有多個線程操作同一個變量時,使用AtomicInteger不會導致變量出現問題,而且比使用 synchronized效率高,

AtomicInteger的常用方法如下:

  • AtomicInteger(int initialValue):創建一個AtomicInteger實例,初始值由參數指定。不帶參的構造方法初始值爲0。

  • int addAndGet(int delta):以原子方式將輸入的數值與實例中的值(AtomicInteger裏的value)相加,並返回結果,與getAndAdd(int delta)相區分,從字面意思即可區分,前者返回相加後結果,後者先返回再相加。

  • boolean compareAndSet(int expect, int update) :如果當前值等於預期值,則以原子方式將該值設置爲輸入的值。

  • int getAndIncrement():以原子方式將當前值加1,注意:這裏返回的是自增前的值。

  • void lazySet(int newValue):最終會設置成newValue,使用lazySet設置值後,可能導致其他線程在之後的一小段時間內還是可以讀到舊的值。

  • int getAndSet(int newValue):以原子方式設置爲newValue的值,並返回舊值。

   /**
     * Atomically adds the given value to the current value.
     *
     * @param delta the value to add
     * @return the previous value
     */
    public final int getAndAdd(int delta) {
        return U.getAndAddInt(this, VALUE, delta);
    }

這裏返回的上一個值。上一個值由於我們沒設置初始值所以這裏返回的就是0.

那麼0&15還是0 。所以第一個元素的下標是0。

到這裏整個1.1 prepare()步驟就都走完了。

總結:就是創建了Looper對象,MessageQueue對象,再把創建的Looper存到了當前線程的ThreadLocalMap中。下標爲0,Key是Looper中的靜態常量sThreadLocal,value是前面創建的Looper對象。

繼續看//step1.2 myLooper()方法。

  /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();//step 1.2.1
    }

看註釋就知道是獲取前面我們存到ThreadLocalMap的looper。不過我還是要點進去看看。


/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//step 1.2.1.1
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//step 1.2.1.2
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

//step 1.2.1.1

    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

就是取出當前線程的ThreadLocalMap。就是之前在//step 1.1.2.4.1 存進去的。

//step 1.2.1.2

  /**
         * Get the entry associated with key.  This method
         * itself handles only the fast path: a direct hit of existing
         * key. It otherwise relays to getEntryAfterMiss.  This is
         * designed to maximize performance for direct hits, in part
         * by making this method readily inlinable.
         *
         * @param  key the thread local object
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);//step 1.2.1.2.1
        }

這裏就是取出最終的Entry,就是前面我們存進去的第一個元素。我們計算一下這個I

首先(table.length - 1)=15 沒什麼疑問。
關鍵是看key.threadLocalHashCode。這個這裏的Key就是sThreadLocal。

/**
     * The difference between successively generated hash codes - turns
     * implicit sequential thread-local IDs into near-optimally spread
     * multiplicative hash values for power-of-two-sized tables.
     */
    private static final int HASH_INCREMENT = 0x61c88647;

    /**
     * Returns the next hash code.
     */
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

這裏和我們前面存的流程是一樣的。之前我們返回的是0.但是這一次會返回。HASH_INCREMENT=0x61c88647。

實際就是0x61c88647&15=7 但是我們之前存的是0.所以走 //step 1.2.1.2.1

/**
         * Version of getEntry method for use when key is not found in
         * its direct hash slot.
         *
         * @param  key the thread local object
         * @param  i the table index for key's hash code
         * @param  e the entry at table[i]
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

這裏會通過循環一個一個的去找entry直到找到。expungeStaleEntry是擦除過期的元素。估計因爲和弱引用有關。這裏有點不是很明白。問題不大,繼續回到//step 1.2.1.2 往下走

就是取出value值返回。直接回到//step 1.2 也沒什麼東西,繼續回到 //step 1。 Looper.prepareMainLooper();方法執行完了。接着會去創建目標Activity。這個我們不管。看//step 2

    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;

        for (;;) {
            Message msg = queue.next(); // might block //step 2.1
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            try {
                msg.target.dispatchMessage(msg);//step 2.2
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
           
        }
    }

這個方法比較簡單了。先進入死循環,//step 2.1就是從消息隊列中MessageQueue取出消息。如果返回消息是null則return 就意味着looper所在的線程 代碼都執行完了,這個線程結束。主線程肯定是不會出現這種情況的。

//step 2.2 中的dispatchMessage方法應該很熟悉了吧。msg.target 就是Handler。

總結一下Looper類:

1.創建了當前所在線程的Looper唯一對象,並通過ThreadLocalMap保存起來。
2.創建了MessageEqueue消息隊列
3.無限循環從消息隊列中取出消息,並回調dispatchMessage方法。這一步是一直運行在創建Looper對象的線程中的,這個是跨線程的關鍵。

接下來是Handler機制中最重要的類。MesssageQueue。
我們從之前跳過的//step 1.1.1.1 創建開始。
//step 1.1.1.1

    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit(); //step 1
    }

參數從意思看就是 是否允許退出,主線程是傳入false是不允許主動退出的。
這裏關鍵看 mPtr = nativeInit()

這個其實是個C++的指針地址

private long mPtr; // used by native code

//step 1

這裏是JNI調用,我們通過包名+類名+方法名的規律可以知道 其實現在 android_os_MessageQueue.cpp類中。JNI類一般位於framework\base\core\jni 包中,但是也有一些是例外。那就只能去源碼網站上去搜索了。

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();//step 1.1
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }

    nativeMessageQueue->incStrong(env);//step 1.2
    return reinterpret_cast<jlong>(nativeMessageQueue);//step 1.3
}

這裏需要解釋下幾個類。

JNIEnv* env:代表的JNI環境,虛擬機會傳參數進來

jclass clazz:代表Java的對象Object,也是虛擬機傳入進來

//step 1.1

NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);//step 1.1.1
        Looper::setForThread(mLooper);
    }
}

//step 1.1.1 創建native Looper

Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
        mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);//step 1.1.1.1
    LOG_ALWAYS_FATAL_IF(mWakeEventFd < 0, "Could not make wake event fd: %s",
                        strerror(errno));

    AutoMutex _l(mLock);
    rebuildEpollLocked();//step 1.1.1.2
}

//step 1.1.1.1 構造喚醒事件,這個已經是內核中的方法了。

加鎖,再執行 //step 1.1.1.2 重建eqooll事件

void Looper::rebuildEpollLocked() {
    // Close old epoll instance if we have one.
    關閉舊的 epoll實例
    if (mEpollFd >= 0) {
        close(mEpollFd);
    }

    // Allocate the new epoll instance and register the wake pipe.
    //創建新的epoll實例,註冊喚醒管道
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));

    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeEventFd;
    //添加喚醒事件的監聽
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
  
    for (size_t i = 0; i < mRequests.size(); i++) {
        const Request& request = mRequests.valueAt(i);
        struct epoll_event eventItem;
        request.initEventItem(&eventItem);
        //添加請求隊列事件的監聽
        int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, request.fd, & eventItem);
        if (epollResult < 0) {
            ALOGE("Error adding epoll events for fd %d while rebuilding epoll set: %s",
                  request.fd, strerror(errno));
        }
    }
}

這個方法主要是 使用epoll多路IO複用的機制,這套機制是Linux提供的。簡單的說就是 能監聽文件描述符 並且註冊要監聽的事件,當有事件產生時就會觸發 往後面執行,如果沒有事件則會阻塞。具體可以自行了解Eqoll機制。這個機制是非常重要的。很多地方都有用到。

Looper對象中的mWakeEventFd添加到epoll監控,以及mRequests也添加到epoll的監控範圍內。這樣我們可以用2種方式喚醒。

//step 1.2

nativeMessageQueue->incStrong(env);//step 1.2

這句意思是增加引用計數。incStrong方法在RefBase類中。
是C++中的智能指針。
想使用智能指針處理一個對象,這個對象必須要繼承RefBase類, 以便智能指針能操作類提供引用計數操作!

這裏主要是爲了回收機制。

Android C++層的內存收回主要是通過三個類來實現,分別是RefBase,sp,wp;

SP和WP是兩個智能指針模板類,sp是strong pointer,wp則是weak pointer,亦我們常說的強引用和弱引用;實例化sp和wp這兩個模板類的類型必須是派生自RefBase的類。

//step 1.3

return reinterpret_cast<jlong>(nativeMessageQueue)

將創建的nativeMessageQueue 指針地址通過reinterpret_cast強制轉化符 轉化爲long類型返回到java層。到這裏就知道了。java層中的mPtr就是一個指針地址。

接着看Lopper從MessageQueue中取消息的操作。

final MessageQueue queue = me.mQueue;

     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 pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);//step 2.1

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                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());
                }
                if (msg != null) {
                    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;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        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;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            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 {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

這個方法比較關鍵,我們一步一步看。

首先判斷初始化創建的NativeMessageQueue是否成功。

定義了2個int 變量。其中 nextPollTimeoutMillis 是指阻塞時間。

然後開啓一個無限循環。這個循環退出只有2個返回,1是返回一個Messaged,2是返回null 前面分析Looper就知道如果獲取的Message是null Looper循環就會退出 結束了。

//step 2.1

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);//step 2.1.1
}

這裏傳入了2個參數,1個是之前創建的NativeMessageQueue指針,1個是timeoutMillis 延遲退出時間。

//step 2.1.1

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    mPollEnv = env;
    mPollObj = pollObj;
    mLooper->pollOnce(timeoutMillis);//step 2.1.1.1
    mPollObj = NULL;
    mPollEnv = NULL;

    if (mExceptionObj) {
        env->Throw(mExceptionObj);
        env->DeleteLocalRef(mExceptionObj);
        mExceptionObj = NULL;
    }
}

這個就是賦值 然後就調用pollOnce(timeout) 輪詢一次。

//step 2.1.1.1

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    
        result = pollInner(timeoutMillis);//step 2.1.1.1.1
    }
}

調用內部輪詢

//step 2.1.1.1.1

int Looper::pollInner(int timeoutMillis) {

    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    // No longer idling.

這個方法很長,概括就是開始等待事件發生,timeoutMillis=0 則立即往下執行,timeoutMillis=-1則會一直阻塞 直到監聽的事件到來,我們在前面添加了2個事件,1是喚醒,1是請求隊列。

回到step 2.1進行往下走。就到了處理消息的時候了。

   Message prevMsg = null;
                Message msg = mMessages;
                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());
                }

消息Message是單鏈表結構存儲。異常查找 直到找到消息。

if (msg != null) {
                    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;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

如果沒有消息則把超時設置爲-1 。就是一直等待。

如果找到了Message則判斷其when是否需要立刻值,還是要延時執行。

   // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

如果要退出,則返回NUll 。接着處理空閒Handler。的回調問題。如果都沒有則需要阻塞。

接下來。看Handler類。這個是我們用的最多的。首先看構造函數。
有很多構造函數。最常用的是 無參和1個Looper參數的。

public Handler(@Nullable Callback callback, boolean async) {
        
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

獲取當前線程創建保存的Looper。再取出MessageQueue。這2個對象一個線程中只有一個。

在看最常用的。Handler的sendMessage方法和post方法。

public boolean sendMessageAtTime(@NonNull 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);
    }

他們最終都會調用到這個方法。

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

關鍵是queue.enqueueMessage(msg, uptimeMillis);看它怎麼把消息插入隊列中。

 boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                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 (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

如果頭消息爲null,當前消息的時間延長爲0,當前消息的延遲時間 小於頭消息

則把當前消息設置爲頭消息。

否則把消息 插入到 鏈表的合適位置通過when。

when=systemClock.uptimeMillis() + delayMillis

這個延時是通過設置的時間加系統開機後的時間。

最後再看看分發方法。

   /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(@NonNull Message msg) {
    }
    
    /**
     * Handle system messages here.
     */
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    
    private static void handleCallback(Message message) {
        message.callback.run();
    }
    

如果消息的callback不爲空則直接回調run方法。這裏的callback其實就是runnable接口,它和線程每關係,只是作爲一個接口使用。

否則就執行handleMessage方法。子類Handler必須重寫這個方法來接收Message。

到此整個Handler機制就分析完了,設計到的知識點還是很多的。

首先畫一個UML圖:

在這裏插入圖片描述

再畫一個簡單的流程圖。
在這裏插入圖片描述

藍色和紅色代表2個不同的線程。

總結

Handler是消息處理機制也是線程間的通信機制,它是無法跨進程使用,利用的是進程中各線程能共享內存。是一種生產消費者模型。主要由 Looper,MessageQueue,Handler,Message 和ThreadLocal 組合而成。

調用Looper.prepare()時,會創建一個Looper對象 保存到當前線程的ThreadLocalMap中,每個線程只會創建一個Looper。Looper持有一個MessageQueue的引用。

調用Looper.loop()時,會開啓無線循環調用消息隊列的Next()方法去獲取消息,當取到消息時就會回調Handler的dispatchMessage()分發消息方法,然後繼續循環。

MessageQueue持有消息列表的頭結點消息,初始化時會調用nativeInit()方法。會調用到C++中的android_os_MessageQueue.cpp和Looper.cpp中的方法。主要是利用Linux聽的epoll機制。使用epoll_create()創建一個文件描述符。
再用epoll_ctl()往描述符中添加要監聽的事件。再用
epoll_wait()開始監聽。最後一個參數是timeout,
指定epoll的超時時間,單位是毫秒。當timeout爲-1是,epoll_wait調用將永遠阻塞,直到某個時間發生。當timeout爲0時,epoll_wait調用將立即返回。

消息隊列的next()方法中,開發無限循環然後調用nativePollOnce()輪詢一次,第一次的參數爲0會立即往下執行。由於目前沒消息,所以會把timeout參數設置爲-1。進入阻塞狀態 直到被喚醒。當有消息時則會判斷當前消息的when參數是否需要立即執行,是則返回 否則設置timeout值爲等待時間。

當我們new Handler對象時,會獲取當前線程中的Looper對象,所以主線的Hander一定要在主線程中初始化。

當調用sendMessage或者post時最終會調用到 消息隊列中的enqueueMessage(),把消息插入到消息單鏈表的合適位置,並根據需要喚醒之前阻塞的next()進行執行。

next()方法運行在之前創建looper的線程中。而enqueueMessage()方法是運行在被調用的各個線程中。這裏就實現了跨線程。

我覺得整個機制中最關鍵的是。linux的epoll多路IO複用機制。它保證了無限循環時不會出現CPU佔有率很高,有消息時喚醒處理,沒有消息時則阻塞。在android的很多其他地方都有用到。比如Zygota進程,binder機制。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章