HandlerThread 置信大家都比拟相熟了,从名字上看是一个带有Handler音讯循环机制的一个线程,比个别的线程多了音讯循环的机制,能够说是Handler+Thread的联合,从源码上看也是如此的设计,个别状况下如果须要子线程和主线程之间互相交互,能够用HandlerThread来设计,这比单纯的Thread要不便,而且更容易治理,因为大家都晓得Thread的生命周期在一些状况下是不可管制的,比方间接new Thread().start(),这种形式在我的项目中是不举荐应用的,实际上Android的源码中也有很多中央用到了HandlerThread,上面我将剖析一下HandlerThread以及波及到的一些其余相干的问题。

HandlerThread的源码简略剖析

    int mPriority;    int mTid = -1;    Looper mLooper;    private @Nullable Handler mHandler;    public HandlerThread(String name) {        super(name);        mPriority = Process.THREAD_PRIORITY_DEFAULT;    }

首先字段有优先级,能够看到HandlerThread外面的优先级是默认的,当然了,你也能够批改,调用有二个参数的构造函数就能够了,在构造函数外面有个String类型,代表HandlerThread名字的,这个个别状况下能够取线程的名称。

 /**     * Call back method that can be explicitly overridden if needed to execute some     * setup before Looper loops.     */    protected void onLooperPrepared() {    }    @Override    public void run() {        mTid = Process.myTid();        Looper.prepare();        synchronized (this) {            mLooper = Looper.myLooper();            notifyAll();        }        Process.setThreadPriority(mPriority);        onLooperPrepared();        Looper.loop();        mTid = -1;    }

这外面有一个办法onLooperPrepared(),在理论中,咱们能够重写这个办法做一些初始化的操作,这个run()是重点,能够看到Looper进行了初始化并且开始循环接管音讯了,并且唤醒了以后所有期待的线程,因为run办法是在start之后才会启动的,因而在用HandlertThread的时候,在初始化了实例之后就必须调用start办法开启音讯循环了。

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

这个办法是获取以后的looper,能够看到如果没有获取的时候就始终期待直到获取,而后面也提到了获取到了就唤醒了所有的线程,看来这是线程的期待-唤醒机制利用。

public Handler getThreadHandler() {        if (mHandler == null) {            mHandler = new Handler(getLooper());        }        return mHandler;    }

这个是获取HandlerThread绑定的Looper线程的Handler

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

能够看到这两个办法去退出线程的Looper循环,那么这两个办法有什么区别呢,实际上都是调用了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办法的时候,实际上执行了MessageQueue中的removeAllMessagesLocked办法,该办法的作用是把MessageQueue音讯池中所有的音讯全副清空,无论是提早音讯(提早音讯是指通过sendMessageDelayed或通过postDelayed等办法发送的须要提早执行的音讯,只有不是立刻执行的音讯都是提早音讯)还是非提早音讯。

而quitSafely办法时,实际上执行了MessageQueue中的removeAllFutureMessagesLocked办法,通过名字就能够看出,该办法只会清空MessageQueue音讯池中所有的提早音讯,并将音讯池中所有的非提早音讯派发进来让Handler去解决,quitSafely相比于quit办法平安之处在于清空音讯之前会派发所有的非提早音讯,一句话,就是革除将来须要执行的音讯。

这两个办法有一个独特的特点就是:Looper不再接管新的音讯了,音讯循环就此结束,此时通过Handler发送的音讯也不会在放入音讯杜队列了,因为音讯队列曾经退出了。 利用这2个办法的时候须要留神的是:quit办法从API 1就开始存在了,比拟早,而quitSafely直到API 18才增加进来.

到此源码剖析完结,上面看一个理论的简略的例子:

  private static class UserHandlerThread extends HandlerThread implements Handler.Callback {        private Handler mHandler;        public UserHandlerThread(String name) {            super(name);            start();            mHandler = new Handler(getLooper(), this);        }        @Override        protected void onLooperPrepared() {            super.onLooperPrepared();        }        @Override        public boolean handleMessage(Message msg) {            if (msg.what==0x123){                Log.d("[app]","收到音讯了");            }            return false;        }        public Handler getHandler() {            return mHandler;        }    }            private UserHandlerThread handlerThread;    handlerThread = new UserHandlerThread("handlerThread");    handlerThread.getHandler().sendEmptyMessage(0x123);    

OK,大家都晓得后果了吧,个别状况下,在OnDestroy办法中须要退出循环,比方上面代码:

@Override    protected void onDestroy() {        super.onDestroy();        if (handlerThread != null) {            handlerThread.getHandler().removeCallbacksAndMessages(null);            handlerThread.quit();            handlerThread = null;        }    }    //Looper.getMainLooper().quit();

大家先疏忽正文的那句代码, 好了,上面剖析3个波及到相干的问题:

● 既然子线程能够退出音讯循环队列,那么UI线程,也就是主线程是否也一样能够在onDestroy办法退出音讯循环呢,就是勾销方才代码的正文.

先说答案:不能退出主线程的音讯队列,不然会抛出Main thread not allowed to quit.谬误,是不是很相熟,没错,下面的代码中曾经贴出来了,为什么呢,MessageQueue有一个字段:mQuitAllowed

 // True if the message queue can be quit.    private final boolean mQuitAllowed;

这个字段代表:是否能够退出音讯队列的意思,那么主线程的这个字段是什么呢,是false,就是不能退出以后音讯的音讯队列的意思,来看主线程的音讯初始化代码:Looper.prepareMainLooper();

public static void prepareMainLooper() {        prepare(false);        synchronized (Looper.class) {            if (sMainLooper != null) {                throw new IllegalStateException("The main Looper has already been prepared.");            }            sMainLooper = myLooper();        }    }    private static void prepare(boolean quitAllowed) {        if (sThreadLocal.get() != null) {            throw new RuntimeException("Only one Looper may be created per thread");        }        sThreadLocal.set(new Looper(quitAllowed));    }         private Looper(boolean quitAllowed) {        mQueue = new MessageQueue(quitAllowed);        mThread = Thread.currentThread();    }

能够看到prepare参数中,主线程传入的是false,而在Looper的构造函数外面被赋值了,也就是主线程的是false,所以这就决定了主线程是不能退出音讯队列,那为什么子线程就能够退出音讯队列呢,因为子线程传入的是true,代码如下:

Looper.prepare(); public static void prepare() {        prepare(true);    }

看到没,子线程传入的是true,这阐明子线程是能够退出音讯队列的, 实际上真正退出主线程的音讯队列是FrameWork层做的事件,上面的第三个问题会提到。

● Handler外面收回去的音讯,到底是在哪个线程执行的呢?

本文并不打算长篇剖析Handler的源码,只简略探讨跟Handler线程相干的问题,实际上,对于Handler,如果能了解上面的图,而后再本人去剖析源码,我想了解会更深

咱们晓得,handler的构造函数中有一个参数,就是设置以后Looper的,代码如下:

 /**     * Use the provided {@link Looper} instead of the default one.     *     * @param looper The looper, must not be null.     */    public Handler(Looper looper) {        this(looper, null, false);    }     public Handler(Looper looper, Callback callback, boolean async) {        mLooper = looper;        mQueue = looper.mQueue;        mCallback = callback;        mAsynchronous = async;    }    mLooper = Looper.myLooper();        if (mLooper == null) {            throw new RuntimeException(                "Can't create handler inside thread that has not called Looper.prepare()");        }    mQueue = mLooper.mQueue;

能够看到传入的Looper最终会被赋值到mLooper字段,如果没有调用这个构造函数,那么mLooper由以后线程获取,而音讯队列MessageQueue是由Looper失去的,换句话说:Handler的音讯在哪个Looper循环,那意味着所有的音讯将会被哪个Looper的音讯队列所存储和解决,而一个线程只能绑定一个Looper,因而一个Handler只能绑定一个Looper,也就意味着,Handler绑定了哪个线程的Looper,那么所有的音讯都会在哪个线程执行,这跟下面的图是统一的,当然了有同学说我在Handler外面开线程就另外说了,但Handler操作的自身肯定是在对应的Looper所在的线程的,就是如果Handler绑定了主线程的Looper,那么所有的音讯都会在主线程解决,如果绑定的是子线程,那么所有的音讯都会在子线程解决。

●在Android一个应用程序"退出"的真正含意是什么呢?

对于这个问题,有同学可能说我杀掉过程,应用程序挂了,也就相当于退出了。实际上这个说法是不正确的,因为这里的杀掉过程,不仅仅是程序自身,而且连程序的内存空间也一并被解决,咱们后面说了,子线程能够退出音讯队列,意味着子线程就再也无奈接管到任何音讯了,这就是子线程的退出含意,实际上,主线程也是一样的,只是这个过程对咱们开发者不可见而已,在主线程中,如果退出了音讯队列,那么意味着主线程也无奈接管到任何音讯,上面是代码,在ActivityThread.java外面:

 public final void scheduleExit() {            sendMessage(H.EXIT_APPLICATION, null);        } case EXIT_APPLICATION:                    if (mInitialApplication != null) {                        mInitialApplication.onTerminate();                    }                    Looper.myLooper().quit();                    break;

看到这里,我想相熟IPC过程的同学应该都晓得了,没错,scheduleExit()不是本地过程调用的,而是由服务端过程ActivityAManagerService服务进行调用的,这也是我为什么说退出主线程是由FrameWork调用的起因,在AMS外面有2处中央调用了退出的代码,别离是绑定本地过程和内存回收工作的时候调用的,上面是代码(在ActivityManagerService.Java外面):

在APP本地过程绑定到AMS过程的时候调用 private final boolean attachApplicationLocked(IApplicationThread thread,            int pid) {        // Find the application record that is being attached...  either via        // the pid if we are running in multiple processes, or just pull the        // next app record if we are emulating process with anonymous threads.        ProcessRecord app;        if (pid != MY_PID && pid >= 0) {            synchronized (mPidsSelfLocked) {                app = mPidsSelfLocked.get(pid);            }        } else {            app = null;        }        if (app == null) {            Slog.w(TAG, "No pending application record for pid " + pid                    + " (IApplicationThread " + thread + "); dropping process");            EventLog.writeEvent(EventLogTags.AM_DROP_PROCESS, pid);            if (pid > 0 && pid != MY_PID) {                Process.killProcessQuiet(pid);                //TODO: killProcessGroup(app.info.uid, pid);            } else {                try {                //这里也调用了                    thread.scheduleExit();                } catch (Exception e) {                    // Ignore exceptions.                }            }            return false;        }        //省略一些非必要代码    }//i清理内存的时候调用final void trimApplications() {        synchronized (this) {            int i;            // First remove any unused application processes whose package            // has been removed.            for (i=mRemovedProcesses.size()-1; i>=0; i--) {                final ProcessRecord app = mRemovedProcesses.get(i);                if (app.activities.size() == 0                        && app.curReceiver == null && app.services.size() == 0) {                    Slog.i(                        TAG, "Exiting empty application process "                        + app.toShortString() + " ("                        + (app.thread != null ? app.thread.asBinder() : null)                        + ")\n");                    if (app.pid > 0 && app.pid != MY_PID) {                        //杀过程                        app.kill("empty", false);                    } else {                        try {                        //这里调用了退出主线程音讯队列代码                            app.thread.scheduleExit();                        } catch (Exception e) {                            // Ignore exceptions.                        }                    }                    cleanUpApplicationRecordLocked(app, false, true, -1, false /*replacingPid*/);                    mRemovedProcesses.remove(i);                    if (app.persistent) {                        addAppLocked(app.info, false, null /* ABI override */);                    }                }            }            // Now update the oom adj for all processes.            updateOomAdjLocked();        }    }

看到了吧,在首次App绑定过程的时候,如果产生app==null这个谬误的时候就调用了退出主线程音讯队列,另一个就是在清理内存的时候调用,这两个过程对咱们开发者不可见,咱们理解就好,在源码背后,并没有什么是神奇的中央。

感激大家浏览,如果有什么谬误的中央,请欢送指出,谢谢。

相干视频举荐:
【安卓面试合集】5-FrameWork源码之handler源码解析(一)-01
【安卓面试合集】6-FrameWork源码之handler源码解析(一)-02

本文转自 https://juejin.cn/post/6844903526468943886,如有侵权,请分割删除。