美文网首页
HandlerThread 使用&源码分析

HandlerThread 使用&源码分析

作者: 请叫我林锋 | 来源:发表于2019-10-22 22:14 被阅读0次

1、HandlerThread 使用&源码分析

是什么?

继承 Thread 的类。

有什么用?

它本质是一个带有 Looper 的线程,可以通过 HandlerThread+Handler 实现不同线程之间的消息传递。

有什么优点?

它只有一个优点:就是能够直接创建一个带有 Looper 的线程。也就是说当我们在子线程中使用绑定该线程的 Handler 时,不需要再手动创建 Looper 和开启 Looper 了。

如何使用(下面是一个例子)?
可以看我上传的 github:HandlerThread 使用示例

布局文件:activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="downloadImage"
            android:text="Download Image" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="downloadAudio"
            android:text="Download Audio" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="downloadVideo"
            android:text="Download Video" />

    </LinearLayout>

    <TextView
        android:id="@+id/tv_show"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:textSize="30dp" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:onClick="Quit"
        android:text="quit" />
</LinearLayout>

MainActivity.java

package com.wlf.test1910031;

import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.view.View;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private TextView mShowTv;
    private Handler mChildHandler;
    private Handler mUiHandler;
    private HandlerThread mHandlerThread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mShowTv = findViewById(R.id.tv_show);
        mUiHandler = new Handler();

        // 1.创建 HandlerThread 的示例对象
        mHandlerThread = new HandlerThread("testHandlerThread");

        // 2.启动 HandlerThread 子线程
        mHandlerThread.start();

        // 3.创建绑定子线程 Looper 的 Handler 对象
        mChildHandler = new Handler(mHandlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                try {
                    int what = msg.what;
                    switch (what) {
                        case 1:
                            // 模拟耗时操作
                            Thread.sleep(1000);
                            mUiHandler.post(new Runnable() {
                                @Override
                                public void run() {
                                    mShowTv.setText("Show Your Image");
                                }
                            });
                            break;
                        case 2:
                            Thread.sleep(1000);
                            mUiHandler.post(new Runnable() {
                                @Override
                                public void run() {
                                    mShowTv.setText("Show Your Audio");
                                }
                            });
                            break;
                        case 3:
                            Thread.sleep(1000);
                            mUiHandler.post(new Runnable() {
                                @Override
                                public void run() {
                                    mShowTv.setText("Show Your Video");
                                }
                            });
                        default:
                            break;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
    }

    // 4.发送消息给子线程
    public void downloadImage(View view) {
        Message firstMessage = Message.obtain();
        firstMessage.what = 1;
        firstMessage.obj = "DownLoad Image";
        mChildHandler.sendMessage(firstMessage);
    }

    public void downloadAudio(View view) {
        Message secondMessage = Message.obtain();
        secondMessage.what = 2;
        secondMessage.obj = "DownLoad Audio";
        mChildHandler.sendMessage(secondMessage);
    }

    public void downloadVideo(View view) {
        Message thirdMessage = Message.obtain();
        thirdMessage.what = 3;
        thirdMessage.obj = "DownLoad Video";
        mChildHandler.sendMessage(thirdMessage);
    }

    // 5.结束 Looper 消息循环
    public void Quit(View view) {
        mHandlerThread.quit();
    }
}

2、源码分析(基于 API 28)

首先第一步,我们调用 HandlerThread 的构造方法创建了一个 HandlerThread 的对象,这里我们传入的参数是这个 HandlerThread 的名称,用来标记这个线程,看看这个它的构造方法做了什么。

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

可以看到这里调用了父类方法的构造方法创建了一个线程,然后为当前线程设置了默认优先级。HandlerThread 一共有两个构造方法,另一个方法如下所示。

      public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }

​​这个构造方法同样也会调用父类的方法创建一个线程,然后它能够主动设置一个线程优先级,这个 priority 可以引用 Process 类的常量,优先级最高为-20,最低为19,第一个构造方法的 Process.THREAD_PRIORITY_DEFAULT 为 0,表示默认优先级。优先级高的线程在运行时会具有优先权,但这依赖于线程调度的实现。

接着第二步,我们调用了 handlerThread 的 start(),所以 java虚拟机会调用 HandlerThread 对象的 run() 方法,让我们看看 HandlerThread 中的 run() 方法做了什么。

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

在 run() 方法中,第一行代码先获取mTid,它是线程的唯一标志。然后调用了 Looper.prepare() 初始化当前线程。接着获取前面初始化过的 Looper,并唤醒其他线程。然后为当前线程设置优先级。然后会调用下面的 onLooperPrepared() 方法,如果我们需要,可以实现这个方法。接着使用 Looper.loop() 开启了消息循环,不断的从消息队列中取出消息。

我们知道 Handler 的使用离不开 Looper,系统自动帮我们在主线程创建了 Looper 并开启了消息循环,所以我们可以自由的在主线程使用 Handler。而若想使用子线程 Handler 就得手动创建 Looper 并开启消息循环,所以 HandlerThread 就帮我们省去了这一步。

然后第三步,创建了一个绑定子线程 Looper 的 Handler,看看 Looper.getLooper() 做了什么。

    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

这里首先判断了这个线程是否是存活的,如果线程已经 started 了并且还没有死亡,那么这个线程就是存活的。疑问:为什么在使用 wait() 要在层加 while 循环呢?

    public final boolean isAlive() {
        return nativePeer != 0;
    }

接着判断如果这个线程是存活的并且它的 Looper 还没有被创建,那么使用 wait() 就阻塞这个线程,直到创建了 Looper,使用 notiyAll() 来唤醒线程,然后返回 Looper对象。这也是为什么在 HandlerThread 的 run() 方法中使用了 notiyAll() 的原因。

接着在例子中我们利用这个 Looper 创建了一个绑定子线程的 Handler,也就是 mChildHandler,然后我们可以实现它的 handleMessage 方法,因为它绑定的是子线程 Looper,所以我们可以在这个方法内部使用耗时方法。

第四步就是向子线程发送消息,在 handleMessage 中处理耗时操作,然后通过主线程的 mUiHandler 向主线程发送消息更新 UI。这部分的源码可以参考 Handler 消息通信机制。

最后一步,通过调用 mHandlerThread.quit(),结束该线程的 Looper。

    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }

可以看到,这里获取了当前线程的 Looper,然后调用了 looper.quit() 去结束这个 Looper。同时它还有一个 quitSafely() 方法。

    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }

他们最后都会调用 MessageQueue 的 quit() 方法,不同之处在于这个结束操作是否安全。

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

如上代码所示,调用 quit() 最终会去执行 removeAllMessagesLocked() 方法。

    private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }

可以看到这里把消息链表都清空了。如果调用 quitSafely() 方法,最终会去执行 removeAllFutureMessagesLocked() 方法。

     private void removeAllFutureMessagesLocked() {
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;
        if (p != null) {
            if (p.when > now) {
                removeAllMessagesLocked();
            } else {
                Message n;
                for (;;) {
                    n = p.next;
                    if (n == null) {
                        return;
                    }
                    if (n.when > now) {
                        break;
                    }
                    p = n;
                }
                p.next = null;
                do {
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while (n != null);
            }
        }
    }

这个方法的大概意思就是先判断当前有没有消息在处理,如果没有的话直接移除消息队列中的所有消息。如果有的话,等这个消息处理完了再去移除消息队列中的消息。

当使用了 quit() 方法时,再使用绑定 HandlerThread 的 Handler 去发送消息时,这个消息就不会进入消息队列了。原因是在使用 quit() 后,mQuitting 就被置为 true 了。

    boolean enqueueMessage(Message msg, long when) {
        ...
        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;
            }
        }
        ...
    }

可以看到在 enqueueMessage() 方法中,会进行判断,如果这个这个消息循环已经停止了,那么就不会再有消息被加入到消息队列当中去了。

到这里我们的源码分析也结束了。


3、注意事项

最后再使用 HandlerThread 时要注意:

  1. 在创建了 HandlerThread 对象后,记得使用 HandlerThread.start() 方法去开启消息循环。
  2. 当我们不需要使用 HandlerThread 时,需要使用它的 quit() 方法退出消息循环。普通 Thread 在 run() 方法结束后,就会自动销毁线程,而 HandlerThread 内部开启了消息循环,所以我们需要手动的去关闭这个循环。
  3. HandlerThread 只是开启了一个线程,我们使用绑定 HandlerThread 的 Handler 去发送消息时,只是把消息放到了消息队列排队,然后去等待消息的处理。

结尾:HandlerThread 的源码还是比较简单的,但是其中包含了 Handler 消息机制的知识点,如果没有了解过的可以去学习一下,这个也属于必须知道的知识。在学习源码过程中,会将一个个安卓的知识点串联起来,遇到不了解的东西的时候才会去进行了解。最后这篇文章也只是个人看法,如果有不对的地方,欢迎指出。

相关文章

网友评论

      本文标题:HandlerThread 使用&源码分析

      本文链接:https://www.haomeiwen.com/subject/shicvctx.html