Android/java 多執行緒(二)-Thread的好兄弟Handl

25minutes發表於2021-09-09

簡介

Handler機制在安卓中應用非常廣泛,像我們常見的用於在子執行緒中更新UI:

public class MainActivity extends AppCompatActivity {    @SuppressLint("HandlerLeak")    private Handler mHandler = new Handler(){        @Override
        public void handleMessage(Message msg) {            //更新UI
        }
    };    
    @Override
    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyThread thread = new MyThread();
        thread.start();
    }    class MyThread extends Thread {        @Override
        public void run() {             //處理子執行緒邏輯
            //....
            //傳送訊息更新UI
            Message message = mHandler.obtainMessage(1);
            mHandler.sendMessage(message);
        }
    }
}

由此引出一些問題,為什麼sendMessage之後handleMessage方法會被執行?.為什麼handleMessage是在主執行緒中?.它們的訊息是如何傳遞的?.這些會在下面講解

且看還有一種使用情況,在子執行緒中使用Handler:

public class MainActivity extends AppCompatActivity {    @Override
    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyThread thread = new MyThread();
        thread.start();
    }    class MyThread extends Thread {        @Override
        public void run() {
            Looper.prepare();            @SuppressLint("HandlerLeak")
            Handler mHandler = new Handler(){                @Override
                public void handleMessage(Message msg) {                     //處理邏輯
                    l(Thread.currentThread().getName());
                }
            };
            Message message = mHandler.obtainMessage(1);
            mHandler.sendMessage(message);
            Looper.loop();
        }
    }    public void l(String s) {
        Log.i("HJ", s);
    }
}

列印結果如下:

2018-12-20 10:48:21.682 1808-1824/? I/HJ: Thread-2

如果我們不使用Looper.prepare()Looper.loop()方法,直接執行,那麼會丟擲以下異常:

2018-12-20 11:00:49.654 2029-2045/com.zj.example.customview.funnel E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.zj.example.customview.funnel, PID: 2029
    java.lang.RuntimeException: Only one Looper may be created per thread
        at android.os.Looper.prepare(Looper.java:95)
        at android.os.Looper.prepare(Looper.java:90)
        at com.zj.example.customview.funnel.MainActivity$MyThread.run(MainActivity.java:48)

這是為什麼呢?,為什麼子執行緒不能直接建立Handler呢?使用了Looper.prepare()Looper.loop()為什麼又可以了呢?,這兩個方法又是幹什麼用的呢?請看以下原始碼分析。

原理分析

Handler機制的核心類主要有三個,Handler ,Message,Looper,而與Looper相關聯的類還有ThreadLocalMessageQueue,下面來一一介紹下它們的作用:

Message

訊息的載體,內部主要存放一些訊息型別,引數主要有:
(1)public int what:變數,用於定義此Message屬於何種操作
(2)public Object obj:變數,用於定義此Message傳遞的資訊資料,透過它傳遞資訊
(3)public int arg1:變數,傳遞一些整型資料時使用
(4)public int arg2:變數,傳遞一些整型資料時使用
(5)public Handler getTarget():普通方法,取得操作此訊息的Handler物件。
在該類的使用上,儘量使用obtain()方法來獲取,而非構造方法,這樣可以節省記憶體。而在資料傳遞方面,如果是int型別的,建議使用arg1arg2,其次再考慮使用Bundle

Looper

訊息通道,訊息機制的主要邏輯實現類,內部有使用ThreadLocal用來儲存當前執行緒的Looper,使用MessageQueue用來維護Message池,而我們使用Looper.prepare()方法其實也就是將此執行緒的Looper物件加入到ThreadLocal中去:

    public static void prepare() {
        prepare(true);
    }    private static void prepare(boolean quitAllowed) {        if (sThreadLocal.get() != null) {         //一個執行緒只能建立一個Looper
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

看原始碼就知道,當前執行緒中有且只有一個Looper,當我們再次建立,就會丟擲異常,所以上面拋異常的原因就是在這裡。

而我們的loop()方法就是訊息機制的核心,原理是實現了一個無限迴圈,用於從MessageQueue中取出訊息分發到各個Handler中去,以下是精簡原始碼:

public static void loop() {        //從ThreadLocal中取出Looper
        final Looper me = myLooper();        if (me == null) {            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }       //從Looper中獲取到MessageQueue 
        final MessageQueue queue = me.mQueue;

        ........        //建立迴圈
        for (;;) {            //從MessageQueue中一個一個的取出Message
            Message msg = queue.next(); // might block
            if (msg == null) {               //沒訊息取了就退出
                return;
            }
           ...........            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();            final long end;            try {               //dispatchMessage方法的呼叫在這
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            .........           //回收訊息,同時將Message置為空訊息物件,儲存在訊息池中,用於obtain()方法再次獲取
            msg.recycleUnchecked();
        }
    }

綜合要點就是使用loop()來開啟訊息分發,並且使用prepare來建立Looper。並且一個執行緒只能有一個Looper

可以看到,dispatchMessage就是在這裡回撥的,那這個target是什麼東西呢,其實就是我們的Handler物件。那這個Handler物件是在哪裡賦值的呢,請看Handler原始碼

Handler

首先看我們的構造方法最終會呼叫到這裡:

public Handler(Callback callback, boolean async) {        if (FIND_POTENTIAL_LEAKS) {            final Class<? extends Handler> klass = getClass();            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }         //這裡就將Looper建立好了,主要這裡是在主執行緒中
        mLooper = Looper.myLooper();        if (mLooper == null) {            throw new RuntimeException(                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
}

這裡主要起到從ThreadLocal中獲取Looper物件的作用。看Looper.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();
    }

隨後我們會呼叫sendMessage()型別的方法來傳送訊息,最終都會呼叫這個方法:

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this; //這裡將Handler賦值了
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }        return queue.enqueueMessage(msg, uptimeMillis);
    }

注意看這裡msg.target = this,這裡實現了將當前Handler賦值到了Message中。隨後呼叫了enqueueMessage方法就將此Message投入到了訊息池MessageQueue中.到這裡,一系列的初始化與呼叫都完成了。

所以,經過以上分析,我們之前的問題就迎刃而解了,但是還是有一個問題,我們在第一個例項中並沒有使用Looperprepareloop方法,那我們的訊息機制為什麼會生效呢,其實在我們的app建立的時候系統已經預設為我們開啟了,並且是在主程式的最後呼叫的,並沒有在onCreate()方法中,而其他的生命週期方法,是透過AMS進行回撥。這就保證了我們的迴圈不會造成主執行緒卡頓,具體的原始碼呼叫是在ActivityThread類中的main函式中:

public static void main(String[] args) {
    SamplingProfilerIntegration.start();    // 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();    // Set the reporter for event logging in libcore
    EventLogger.setReporter(new EventLoggingReporter());

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

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

    Looper.prepareMainLooper();  // //為當前執行緒(主執行緒)建立一個Looper物件

    ActivityThread thread = new ActivityThread();
    thread.attach(false);    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();  //為當前執行緒設定Handler
    }

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

    Looper.loop();  // 執行從訊息佇列中獲取Message,並呼叫Handler進行處理的無限迴圈;所有和主執行緒相關的訊息處理都在該方法中執行

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

分析到這,Handler流程其實也差不多了,最後做下總結:

1.在主執行緒中初始化了一個Handler,此時Looper.loop()已經開啟,透過Handler構造方法建立了一個在主執行緒中的Looper,並儲存到了ThreadLocal中,此時主執行緒不允許再建立第二個Looper.

2.在子執行緒中使用sendMessage方法,此時會呼叫enqueueMessage()方法並建立一個Message(或從池中取出空Message物件),然後我們將需要傳送的訊息儲存在Message中,並將此Handler儲存到Message的target物件中,然後壓入到MessageQueue訊息池中

3.Looper中會迴圈從MessageQueue中取出Message物件,並回撥到target物件上的Handler的dispatchMessage()方法中去,由於Looper.loop()方法是在主執行緒中呼叫,所以dispatchMessage()方法也是執行在主執行緒中。Looper.loop()方法執行所在的執行緒決定dispatchMessage()方法回撥的執行緒,並且需要與Looper.prepare()方法配套使用



作者:我是黃教主啊
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4328/viewspace-2821566/,如需轉載,請註明出處,否則將追究法律責任。

相關文章