主頁 > 移動端開發 > Handler原始碼決議(高工視角)

Handler原始碼決議(高工視角)

2021-08-22 07:49:41 移動端開發

轉載請注明出處:https://blog.csdn.net/m0_37840695/article/details/119801922

原始碼版本:Sources for android 30,(31的版本太新,android studio還沒法下載)

還是由易到難,先講HandlerThread,再講Handler用于切換到主執行緒,

一.HandlerThread

1.HandlerThread用法

public class TestActivity extends AppCompatActivity {
    private static final int MSG_GET_DATA_VIA_NETWORK = 101;
    private HandlerThread handlerThread;
    private Handler renderHandler;
    private SurfaceView surfaceView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        surfaceView = findViewById(R.id.surface_view);

        handlerThread = new HandlerThread("surfaceView render thread");
        handlerThread.start();
        renderHandler = new Handler(handlerThread.getLooper(), new Handler.Callback() {
            @Override
            public boolean handleMessage(@NonNull Message msg) {

                System.out.println(Thread.currentThread().getName());/********列印:"surfaceView render thread"********/

                if (msg.what == MSG_GET_DATA_VIA_NETWORK) {
                    //render surfaceView on work thread
                    surfaceView.draw(null);
                }
                return true;
            }
        });
    }

    public void refresh(View view){
        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO: 2021/8/19  get data via network

                renderHandler.sendMessage(renderHandler.obtainMessage(MSG_GET_DATA_VIA_NETWORK, "data json"));
            }
        }).start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (handlerThread != null) {
            handlerThread.quit();
        }
    }
}

代碼解釋:surfaceview支持在作業執行緒繪制渲染,這里我們用HandlerThread執行緒繪制、渲染surfaceView,surfaceview要渲染的資料來自網路,我們的專案中的網路請求模塊往往會有自己的執行緒執行網路請求;網路請求得到的資料需要帶回到surfaceview的渲染執行緒中,

2.原始碼決議

原始碼中漢字注釋是我補充的

2.1 handlerThread = new HandlerThread("surfaceView render thread");

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    private @Nullable Handler mHandler;

    public HandlerThread(String threadName) {
        super(threadName);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

2.2 handlerThread.start()觸發HandlerThread的run方法;

    @Override
    public void run() {
        mTid = Process.myTid();//********獲取執行緒id
        Looper.prepare();//**********創建looper并保存到當前執行緒的threadLocal中
        synchronized (this) {
            mLooper = Looper.myLooper();//******從當前執行緒的threadLocal獲取looper
            notifyAll();
        }
        Process.setThreadPriority(mPriority);//********設定當前執行緒的優先級
        onLooperPrepared();//********空方法,用于子類去實作的
        Looper.loop();//**************讓死回圈跑起來:回圈從mLooper的messageQueue中獲取訊息,并dispatch給sendMessage的handler->handlerMessage
        mTid = -1;
    }

2.3 Looper.prepare();

    /*Looper.java類中*/
    /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    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));
        //******將looper保存在了當前執行緒(就是我們的surfaceView的繪制渲染執行緒)的threadLocal中
    }

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);//**********Looper實體化時會創建自己的訊息佇列
        mThread = Thread.currentThread();
    }

2.4 Looper.loop();

    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        //***********************從surfaceview的渲染執行緒的threadLocal中獲取剛剛創建的looper
        final Looper me = sThreadLocal.get();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        if (me.mInLoop) {
            Slog.w(TAG, "Loop again would have the queued messages be executed"
                    + " before this one completed.");
        }

        me.mInLoop = true;
        //looper的訊息佇列
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;

        for (;;) {
            Message msg = queue.next(); // might block//********************當佇列為空時,這里會阻塞住,當handler發送一個訊息添加到隊尾時,此處便喚醒了,是通過linux的epoll實作的
            if (msg == null) {
                // No message indicates that the message queue is quitting.
//**********************只有Looper退出、銷毀的時候,queue才會在沒有訊息的情況下被喚醒
                return;
            }
            /*****************下方處理訊息的邏輯我們放后面研究***************/
            ...

到目前為止,我們的Looper、MessageQueue已經創建好了,Looper在surfaceview的渲染執行緒中創建并呼叫的loop()方法,所以所有的message的處理(handleMessage())都會發生在surfaceview的渲染執行緒,

2.5 Handler renderHandler = new Handler(handlerThread.getLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
System.out.println(Thread.currentThread().getName());

if (msg.what == MSG_GET_DATA_VIA_NETWORK) {
//render surfaceView on work thread
surfaceView.draw(null);
}
return true;
}
});

傳入looper和Handler.CallBack實體用于構造Handler:

    /**Handler.java中**/
    @UnsupportedAppUsage
    final Looper mLooper;
    final MessageQueue mQueue;
    @UnsupportedAppUsage
    final Callback mCallback;
    final boolean mAsynchronous;
    @UnsupportedAppUsage
    IMessenger mMessenger;
    /**
     * Use the provided {@link Looper} instead of the default one and take a callback
     * interface in which to handle messages.  Also set whether the handler
     * should be asynchronous.
     *
     * Handlers are synchronous by default unless this constructor is used to make
     * one that is strictly asynchronous.
     *
     * Asynchronous messages represent interrupts or events that do not require global ordering
     * with respect to synchronous messages.  Asynchronous messages are not subject to
     * the synchronization barriers introduced by conditions such as display vsync.
     *
     * @param looper The looper, must not be null.
     * @param callback The callback interface in which to handle messages, or null.
     * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
     * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
     *
     * @hide
     */
    @UnsupportedAppUsage
    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;//默認false
    }

當你看到Handler的成員變數,是否覺得它是執行緒不安全的?不急,我們等下會分析(事實上它通過在相關方法上加鎖的方式實作了執行緒安全),

2.6 renderHandler.sendMessage(renderHandler.obtainMessage(MSG_GET_DATA_VIA_NETWORK, "data json"));//這行代碼是跑在執行網路請求的執行緒中的

    /**Handler.java**/
    /**
     * 
     * Same as {@link #obtainMessage()}, except that it also sets the what and obj members 
     * of the returned Message.
     * 
     * @param what Value to assign to the returned Message.what field.
     * @param obj Value to assign to the returned Message.obj field.
     * @return A Message from the global message pool.
     */
    @NonNull
    public final Message obtainMessage(int what, @Nullable Object obj) {
        return Message.obtain(this, what, obj);
    }


    /**Message.java**/
    /**
     * Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>, and <em>obj</em>
     * members.
     * @param h  The <em>target</em> value to set.
     * @param what  The <em>what</em> value to set.
     * @param obj  The <em>object</em> method to set.
     * @return  A Message object from the global pool.
     */
    public static Message obtain(Handler h, int what, Object obj) {
        /***********Message是單向鏈表的一個節點,其中的target變數就是發送訊息的handler實體,what和obj大家很熟悉,還有一個變數next--指向單向鏈表中的下一個Message節點************/
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.obj = obj;

        return m;
    }
    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     * 
     */
/*************它將使用過的Message池化了(該Message池是也是一個佇列),因為sendMessage可能是高頻操作,程序中產生的臨時變數不池化會引起記憶體抖動;我們都應該通過obtain方法獲取message*********/
    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();
    }
    /**Handler.java**/
    /**************這里是跑在執行網路請求的執行緒中的**************/
    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }
    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    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);/********所以發送訊息其實就是將訊息添加到訊息佇列********/
    }




    /**MessageQueue.java**/
    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }

            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 (;;) {/**************通過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) {/**************這里為true表示原先訊息佇列為空,執行在surfaceview渲染執行緒的Looper.loop死回圈阻塞在了queue.next()方法中,現在網路請求執行緒中添加了一個message,通過下方的本地方法(使用的是linux的epoll機制)跨執行緒定點喚醒queue.next()方法 **************/
                nativeWake(mPtr);
            }
        }
        return true;
    }
    /**Looper.java**/
    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     * 
     */
/******************這里依然是跑在surfaceview的繪制渲染執行緒中*****************?
    public static void loop() {
/*****************從surfaceview的渲染執行緒的threadLocal中獲取剛剛創建的looper**************/
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        if (me.mInLoop) {
            Slog.w(TAG, "Loop again would have the queued messages be executed"
                    + " before this one completed.");
        }

        me.mInLoop = true;
        /*****************looper的訊息佇列**************/
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;

        for (;;) {
            Message msg = queue.next(); /***********此時這里被喚醒,獲取到了新添加的Message***********/
            if (msg == null) {
                // No message indicates that the message queue is quitting.
/*********只有Looper退出、銷毀的時候,queue才會在沒有訊息的情況下被喚醒*********/
                return;
            }
            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            // Make sure the observer won't change while processing a transaction.
            final Observer observer = sObserver;

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            Object token = null;
            if (observer != null) {
                token = observer.messageDispatchStarting();
            }
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            try {
                msg.target.dispatchMessage(msg);
                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);
                }
            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            if (logSlowDispatch) {
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }
/**********將訊息的變數恢復初始值,并添加到訊息池等待復用**********/
            msg.recycleUnchecked();
        }
    }

2.7 msg.target.dispatchMessage(msg);

我們前面分析了msg.target就是發送msg的handler,我們看handler是如何分發訊息的:

    /**Handler.java**/
    /**
     * Handle system messages here.
     */
    public void dispatchMessage(@NonNull Message msg) {

/************這里依然是跑在surfaceview的繪制渲染執行緒中**************/

        if (msg.callback != null) {/******** msg的callback優先級最高********/
            handleCallback(msg);
        } else {
            if (mCallback != null) {/********handler的callback優先級其次********/
                if (mCallback.handleMessage(msg)) {
/********這個mCallBack就是我們傳入給Handler的(在構建Handler的時候)********/
                    return;
                }
            }
            handleMessage(msg);/********Handler的子類重寫的handlerMessage方法優先級最低********/
        }
    }



public class TestActivity extends AppCompatActivity {
    private static final int MSG_GET_DATA_VIA_NETWORK = 101;
    private HandlerThread handlerThread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        SurfaceView surfaceView = findViewById(R.id.surface_view);

        handlerThread = new HandlerThread("surfaceView render thread");
        handlerThread.start();
        Handler renderHandler = new Handler(handlerThread.getLooper(), new Handler.Callback() {

/**************跑在surfaceview的繪制渲染執行緒(HandlerThread)中**************/
            @Override
            public boolean handleMessage(@NonNull Message msg) {

                System.out.println(Thread.currentThread().getName());/**列印"surfaceView render thread"**/

                if (msg.what == MSG_GET_DATA_VIA_NETWORK) {
                    //render surfaceView on work thread
                    surfaceView.draw(null);
                }
                return true;
            }
        });

3.問題串列

3.1我們構造Handler時傳入的匿名內部類實體--Handler.Callback是否可能造成記憶體泄露?

答案:是的,

雖然我們在activity.onDestroy時呼叫了handlerThread.quit()方法

/**HandlerThread.java**/
    /**
     * Quits the handler thread's looper.
     * <p>
     * Causes the handler thread's looper to terminate without processing any
     * more messages in the message queue.
     * </p><p>
     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
     * For example, the {@link Handler#sendMessage(Message)} method will return false.
     * </p><p class="note">
     * Using this method may be unsafe because some messages may not be delivered
     * before the looper terminates.  Consider using {@link #quitSafely} instead to ensure
     * that all pending work is completed in an orderly manner.
     * </p>
     *
     * @return True if the looper looper has been asked to quit or false if the
     * thread had not yet started running.
     *
     * @see #quitSafely
     */
    public boolean quit() {/********丟棄訊息佇列中的訊息,退出loop死回圈********/
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }

    /**
     * Quits the handler thread's looper safely.
     * <p>
     * Causes the handler thread's looper to terminate as soon as all remaining messages
     * in the message queue that are already due to be delivered have been handled.
     * Pending delayed messages with due times in the future will not be delivered.
     * </p><p>
     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
     * For example, the {@link Handler#sendMessage(Message)} method will return false.
     * </p><p>
     * If the thread has not been started or has finished (that is if
     * {@link #getLooper} returns null), then false is returned.
     * Otherwise the looper is asked to quit and true is returned.
     * </p>
     *
     * @return True if the looper looper has been asked to quit or false if the
     * thread had not yet started running.
     */
    public boolean quitSafely() {/********處理完訊息佇列中的訊息,再退出loop死回圈********/
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }


/**Looper.java**/
    public void quit() {
        mQueue.quit(false);
    }
    public void quitSafely() {
        mQueue.quit(true);
    }



/**MessageQueue.java**/
    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();/********移除延時執行的訊息********/
            } else {
                removeAllMessagesLocked();/********清空佇列********/
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);/********以上代碼都是跑在主執行緒中的,這里我們假設跑在surfaceView的繪制渲染執行緒中的Looper.loop回圈中正在處理一個message,沒有因為queue.nex()獲取不到訊息而阻塞,即handleMessage方法還在跑,意味著不需要通過nativeWake來喚醒********/
        }
    }

我們呼叫handlerThread.quit()方法只是清空訊息佇列,如果有一條message的處理很耗時,極端情況是死回圈或阻塞了,則匿名內部類Handler.CallBack實體將始終參考我們的activity導致記憶體泄露,如果非極端情況,比如handleMessage方法只會耗時10s,意味著當前界面關閉10s后才能回收,假設我們當前界面在這10s內進出10次,記憶體中將存在10個activity實體,所以我們得改進:

public class TestActivity extends AppCompatActivity {
    private static final int MSG_GET_DATA_VIA_NETWORK = 101;
    private HandlerThread handlerThread;
    private Handler renderHandler;
    private SurfaceView surfaceView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        surfaceView = findViewById(R.id.surface_view);

        handlerThread = new HandlerThread("surfaceView render thread");
        handlerThread.start();
        renderHandler = new Handler(handlerThread.getLooper(), new TestHandlerCallback(new WeakReference<>(surfaceView)));
    }

    private static class TestHandlerCallback implements Handler.Callback {
        private WeakReference<SurfaceView> surfaceViewWeakReference;

        public TestHandlerCallback(WeakReference<SurfaceView> surfaceViewWeakReference) {
            this.surfaceViewWeakReference = surfaceViewWeakReference;
        }

        @Override
        public boolean handleMessage(@NonNull Message msg) {
            SurfaceView surfaceView = surfaceViewWeakReference.get();
            if (surfaceView != null) {

                System.out.println(Thread.currentThread().getName());/********列印:"surfaceView render thread"********/

                if (msg.what == MSG_GET_DATA_VIA_NETWORK) {
                    //render surfaceView on work thread
                    surfaceView.draw(null);
                }
            }
            return true;
        }
    }

3.2 Handler是如何保證執行緒安全的?

顯然我們會在多執行緒中使用同一個handler物件,即在多執行緒中呼叫這行代碼:

renderHandler.sendMessage(renderHandler.obtainMessage(MSG_GET_DATA_VIA_NETWORK, "data json"));

先看renderHandler.obtainMessage方法:

/**Handler.java**/
    @NonNull
    public final Message obtainMessage(int what, @Nullable Object obj) {
        return Message.obtain(this, what, obj);
    }



/**Message.java**/
    public static Message obtain(Handler h, int what, Object obj) {
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.obj = obj;

        return m;
    }
    public static Message obtain() {
        synchronized (sPoolSync) {/********加了sPoolSync物件鎖,保證從message池獲取message的執行緒安全********/
            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();
    }

再看renderHandler.sendMessage方法:

/**Handler.java**/
    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }
    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    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);
    }


/**MessageQueue.java**/

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {/********以this(當前訊息佇列)為鎖,保證this在不同執行緒enqueueMessage的執行緒安全********/
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }

            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;
    }

另外,我們在surfaceView的繪制渲染執行緒從messageQueue取出message,同時在別的執行緒往messageQueue添加message,是如何保證執行緒安全的?

/**MessageQueue.java**/
/********在surfaceView的繪制渲染執行緒從messageQueue取出message********/
    @UnsupportedAppUsage
    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);

            synchronized (this) {/**********這里也是加this(messageQueue物件鎖),對messageQueue存取加同一把鎖,則多執行緒存與取方法中2個synchronized代碼塊將是串行的**********/
                // 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;
        }
    }

另外,Looper中的messageQueue并沒有volatile修飾,一個執行緒往其中添加了一個message,有一個執行緒怎么讀到最新的messageQueue呢?

答案:還是存與取2個方法加了鎖實作的,根據JMM(java記憶體模型):

現在我們知道存與取中相應的2個synchronized代碼塊是串行的,一個添加message的執行緒獲到this鎖時(對應圖中Lock位置),它會從主記憶體中Read最新的messageQueue,添加完message釋放鎖(對應Unlock位置)前,它會將添加了message的messageQueue寫(對應Write位置)到主記憶體中,此時另外一個執行緒獲取到this鎖了,來到了圖中Lock位置,它會讀取最新的messageQueue,

看到這兒,其實咱已經掌握了Handler的原理,Handler就是用于將“代碼執行和資料”切換到其Looper所在的執行緒的,而使用handler切換到主執行緒,只不過是該handler的looper是在主執行緒創建而已,

二.Handler用于切換到主執行緒

1.用法

public class TestMainHandlerActivity extends AppCompatActivity {
    private static final int MSG_HELLO_HANDLER = 101;
    private Handler handler = new MainHandler(new WeakReference<>(TestMainHandlerActivity.this));
    private TextView textView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.text_view);
    }

    public void refresh(View view) {
        new Thread(new Runnable() {
            @Override
            public void run() {

                // TODO: 2021/8/20  get data via network

                handler.sendMessage(handler.obtainMessage(MSG_HELLO_HANDLER, "data json"));
            }
        }).start();
    }

    private static class MainHandler extends Handler {
        private WeakReference<TestMainHandlerActivity> weakReference;

        public MainHandler(WeakReference<TestMainHandlerActivity> weakReference) {
            super(Looper.getMainLooper());/********這兒是與第一節唯一不同之處,app行程啟動后創建了主執行緒的同時,也創建好了主執行緒自己的looper,而我們構造的handler,就是使用的主執行緒的looper********/
            this.weakReference = weakReference;
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            TestMainHandlerActivity activity = weakReference.get();
            if (activity != null) {
                if (msg.what == MSG_HELLO_HANDLER) {
                    activity.textView.setText((String) msg.obj);
                }
            }
        }
    }
}

2.原始碼決議

/**ActivityThread.java**/

    public static void main(String[] args) {/********我們app程式的入口,跑在主執行緒中**********/
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // Install selective syscall interception
        AndroidOs.install();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        // Call per-process mainline module initialization.
        initializeMainlineModules();

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();/********主執行緒創建了自己的looper**********/

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

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

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();/********主執行緒開始了自己的looper.loop死回圈;接下來就是如果有界面繪制、渲染message或用戶與界面互動相關message添加到looper的messageQueue,此loop死回圈就跑起來,如果messageQueue為空,就會阻塞在queue.next(),等待有message添加時被喚醒,而我們創建的handler,也是使用的主執行緒的looper,意味著我們在作業執行緒中handler.sendMessage(handler.obtainMessage(MSG_HELLO_HANDLER, "data json")),此message也是進入主執行緒looper的messageQueue中**********/

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




/**Looper.java**/

    @Deprecated
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = sThreadLocal.get();
        }
    }
    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));
    }

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/295438.html

標籤:其他

上一篇:【不求人】手機端內應用或網頁根據apk/ipa內的scheme/包名喚起別的應用

下一篇:十年大廠Android開發經驗,委身小廠工資卻只有三分之一

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more