美文网首页程序员
Android 多线程通信总结(一)

Android 多线程通信总结(一)

作者: Android阿南 | 来源:发表于2021-01-12 15:52 被阅读0次

    HandlerThread

    1.HandlerThread特点及注意事项

    HandlerThread本质上是一个线程类,继承自Thread。在线程内部,代码是串行处理的。
    其内部拥有自己的Looper对象,也就是说它可以自己进行消息的循环。通过getLooper()方法可以将这个Looper对象传递给Handler对象,这样就可以在handleMessage()方法中执行异步任务。HandlerThread将looper对象传递给子线程进行处理,目的是为了分担MainLooper的工作量,降低了主线程的压力,使界面更加流畅。
    开启一个线程起到多个线程的作用,处理任务是串行的,按消息发送顺序进行处理,但是由于因为每个任务都会以队列的形式被执行到,如果队列中某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。
    HandlerThread拥有自己的消息队列,因此它不会阻塞UI线程。
    创建HandlerThread后需要先调用handlerThread.start()方法,Thread会先调用run()方法,创建Looper对象。

    2.应用场景

    适用于会长时间在后台执行,并且间隔时间内会调用的任务。

    3.Handler,Thread和HandlerThread的区别

    Handler会关联一个单独的线程,Looper和消息队列,默认关联是UI线程。
    HandlerThread继承自Thread,所以它本质就是一个Thread,只不过其内部有自己的Looper对象和消息队列,可以用来循环消息队列,并将消息回调到子线程中的Handler进行处理。

    4.HandlerThread使用实例

    1.创建实例对象

    HandlerThread handlerThread = new HandlerThread("majiyao");
    

    ​ 传入参数的作用主要是标记当前线程的名字,可以任意字符串。

    2.启动HandlerThread线程

    //实例创建完成之后,必须要先开启线程
    handlerThread.start();
    

    3.构建循环消息处理机制

    /**
     * 该callback运行于子线程
     */
    class ChildCallback implements Handler.Callback {
        @Override
        public boolean handleMessage(Message msg) {
            //在子线程中进行相应的网络请求
    
            //通知主线程去更新UI
            mUIHandler.sendMessage(msg1);
            return false;
        }
    }
    

    4.构建异步handler

    Handler childHandler = new Handler(handlerThread.getLooper(),new ChildCallback());
    

    ​ 创建子线程Handler,获取handlerThread的Looper对象,并将回调事件传入,这样异步handler就拥有了HandlerThread的Looper对象,由于HandlerThread本身是异步线程,因此Looper也与异步线程绑定,从而handlerMessage方法也就可以异步处理耗时任务了,这样我们的Looper+Handler+MessageQueue+Thread异步循环机制构建完成。
    5.源码分析

    public class HandlerThread extends Thread {
        int mPriority;//线程优先级
        int mTid = -1;
        Looper mLooper;//当前线程持有的Looper对象
        private @Nullable Handler mHandler;
    
        public HandlerThread(String name) {
            super(name);
            mPriority = Process.THREAD_PRIORITY_DEFAULT;
        }
        
        /**
         * Constructs a HandlerThread.
         * @param name
         * @param priority The priority to run the thread at. The value supplied must be from 
         * {@link android.os.Process} and not from java.lang.Thread.
         */
        public HandlerThread(String name, int priority) {
            super(name);
            mPriority = priority;
        }
        
        /**
         * Call back method that can be explicitly overridden if needed to execute some
         * setup before Looper loops.
         */
        protected void onLooperPrepared() {
        }
        
        
     }
    

    ​ 从源码可以看出HandlerThread继续自Thread,构造函数的传递参数有两个,一个是name指的是线程的名称,一个是priority指的是线程优先级,我们根据需要调用即可。其中成员变量mLooper就是HandlerThread自己持有的Looper对象。onLooperPrepared()该方法是一个空实现,是留给我们必要时可以去重写的,但是注意重写时机是在Looper循环启动前,再看看run方法:

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

    前面我们在HandlerThread的常规使用中分析过,在创建HandlerThread对象后必须调用其start()方法才能进行其他操作,而调用start()方法后相当于启动了线程,也就是run方法将会被调用,而我们从run源码中可以看出其执行了Looper.prepare()代码,这时Looper对象将被创建,当Looper对象被创建后将绑定在当前线程(也就是当前异步线程),这样我们才可以把Looper对象赋值给Handler对象,进而确保Handler对象中的handleMessage方法是在异步线程执行的。接着将执行代码:

    synchronized (this) {
                mLooper = Looper.myLooper();
                notifyAll();
            }
    

    这里在Looper对象创建后将其赋值给HandlerThread的内部变量mLooper,并通过notifyAll()方法去唤醒等待线程,最后执行Looper.loop();代码,开启looper循环语句。那这里为什么要唤醒等待线程呢?我们来看看,getLooper方法

     /**
         * This method returns the Looper associated with this thread. If this thread not been started
         * or for any reason isAlive() returns false, this method will return null. If this thread
         * has been started, this method will block until the looper has been initialized.  
         * @return The looper.
         */
        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;
        }
    

    ​ 事实上可以看出外部在通过getLooper方法获取looper对象时会先判断当前线程是否启动了,如果线程已经启动,那么将会进入同步语句并判断Looper是否为null,为null则代表Looper对象还没有被赋值,也就是还没被创建,此时当前调用线程进入等待阶段,直到Looper对象被创建并通过 notifyAll()方法唤醒等待线程,最后才返回Looper对象,之所以需要等待唤醒机制,是因为Looper的创建是在子线程中执行的,而调用getLooper方法则是在主线程进行的,这样我们就无法保障我们在调用getLooper方法时Looper已经被创建,到这里我们也就明白了在获取mLooper对象时会存在一个同步的问题,只有当线程创建成功并且Looper对象也创建成功之后才能获得mLooper的值,HandlerThread内部则通过等待唤醒机制解决了同步问题。

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

    从源码可以看出当我们调用quit方法时,其内部实际上是调用Looper的quit方法而最终执行的则是MessageQueue中的removeAllMessagesLocked方法(Handler消息机制知识点),该方法主要是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送)还是非延迟消息。
      当调用quitSafely方法时,其内部调用的是Looper的quitSafely方法而最终执行的是MessageQueue中的removeAllFutureMessagesLocked方法,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理完成后才停止Looper循环,quitSafely相比于quit方法安全的原因在于清空消息之前会派发所有的非延迟消息。最后需要注意的是Looper的quit方法是基于API 1,而Looper的quitSafely方法则是基于API 18的。

    作者:Android大师哥
    链接:https://juejin.cn/post/6914652574205345806

    相关文章

      网友评论

        本文标题:Android 多线程通信总结(一)

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