Android线程和线程池(二)HandlerThread学习记录 使用+源码

java与android在线程方面大部分相同,本篇我们要介绍的是android的专属类HandlerThread,因为HandlerThread在设置思想上挺值得我们学习。
HandlerThread本质上是一个Android已封装好的轻量级异步类,一个线程类,它继承了Thread;类似于Handler+Thread

一、作用

  1. 实现多线程在工作线程中执行任务,如 耗时任务
  2. 异步通信、消息传递实现工作线程 & 主线程(UI线程)之间的通信,即:将工作线程的执行结果传递给主线程,从而在主线程中执行相关的UI操作

从而保证线程安全

二、工作原理

内部原理 = Thread类 + Handler类机制,即:

  • 通过继承Thread类,快速地创建1个带有Looper对象的新工作线程
  • 通过封装Handler类,快速创建Handler & 与其他线程进行通信
  • HandlerThread有自己的内部Looper对象,可以进行looper循环,通过消息队列来重复使用当前线程,节省资源开销;
  • 通过获取HandlerThread的looper对象传递给Handler对象,可以在handleMessage方法中执行异步任务。
  • 创建HandlerThread后必须先调用HandlerThread.start()方法,Thread会先调用run方法,创建Looper对象。
  • 他和普通线程的区别就是他在内部维护了一个looper(也正是这个原因所以他可以创建对应的Handler),然后就会通过Handler的方式来通过Looper执行下一个任务。
  • 由于内部的Looper是一个无限循环,所以当我们不再使用HandlerThread的时候就需要调用quit方法来终止他。

三、HandlerThread的特点

它和普通的Thread有显著的不同之处。

  • 普通Thread主要用于在run方法中执行一个耗时任务
  • HandlerThread在run方法内部创建了消息队列,外界需要通过Handler的消息方式来通知HandlerThread执行一个具体的任务

优势:

  • 将loop运行在子线程中处理,减轻了主线程MainLooper的压力,使主线程、界面更流畅
  • 串行执行,按消息发送顺序进行处理,开启一个线程起到多个线程的作用。HandlerThread本质是一个线程,在线程内部,代码是串行处理的。
  • 有自己的消息队列,不会干扰UI线程
  • 方便实现异步通信,即不需使用 “任务线程(如继承Thread类) + Handler”的复杂组合

通过继承Thread类和封装Handler类的使用,使得创建新线程和与其他线程进行通信变得更加方便易用

劣势:

  • 由于每一个任务队列逐步执行,一旦队列耗时过长,消息延时
  • 执行网络IO等操作,线程会等待,不能并发,因为它只有一个线程,还得排队一个一个等着。
  • 但是由于每一个任务都将以队列的方式逐个被执行到一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。
  • HandlerThread拥有自己的消息队列,它不会干扰或阻塞UI线程。

四、使用

public class MainActivity extends AppCompatActivity {

    Handler mainHandler,workHandler;
    HandlerThread mHandlerThread;
    TextView text;
    Button button1,button2,button3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 显示文本
        text = (TextView) findViewById(R.id.text1);
        // 创建与主线程关联的Handler
        mainHandler = new Handler();
        /**
         * 步骤1:创建HandlerThread实例对象
         * 传入参数 = 线程名字,作用 = 标记该线程
         */
        mHandlerThread = new HandlerThread("handlerThread");

        /**
         * 步骤2:启动线程
         */
        mHandlerThread.start();

        /**
         * 步骤3:创建工作线程Handler & 复写handleMessage()
         * 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
         * 注:消息处理操作(HandleMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
         */
        workHandler = new Handler(mHandlerThread.getLooper()){
            @Override
            // 消息处理的操作
            public void handleMessage(Message msg)
            {
                //设置了两种消息处理操作,通过msg来进行识别
                switch(msg.what){
                    // 消息1
                    case 1:
                        try {
                            //延时操作
                            Thread.sleep(600);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        // 通过主线程Handler.post方法进行在主线程的UI更新操作
                        mainHandler.post(new Runnable() {
                            @Override
                            public void run () {
                                text.setText("我爱学习");
                            }
                        });
                        break;

                    // 消息2
                    case 2:
                        try {
                            Thread.sleep(1200);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        mainHandler.post(new Runnable() {
                            @Override
                            public void run () {
                                text.setText("我喜欢学习");
                            }
                        });
                        break;
                    default:
                        break;
                }
            }
        };

        /**
         * 步骤4:使用工作线程Handler向工作线程的消息队列发送消息
         * 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
         */
        // 点击Button1
        button1 = (Button) findViewById(R.id.button1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                // 通过sendMessage()发送
                // a. 定义要发送的消息
                Message msg = Message.obtain();
                msg.what = 1; //消息的标识
                msg.obj = "A"; // 消息的存放
                // b. 通过Handler发送消息到其绑定的消息队列
                workHandler.sendMessage(msg);
            }
        });

        // 点击Button2
        button2 = (Button) findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                // 通过sendMessage()发送
                // a. 定义要发送的消息
                Message msg = Message.obtain();
                msg.what = 2; //消息的标识
                msg.obj = "B"; // 消息的存放
                // b. 通过Handler发送消息到其绑定的消息队列
                workHandler.sendMessage(msg);
            }
        });

        // 点击Button3
        // 作用:退出消息循环
        button3 = (Button) findViewById(R.id.button3);
        button3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mHandlerThread.quit();
            }
        });

    }

}

五、源码

步骤1:创建HandlerThread的实例对象

构造函数

/**
  * 具体使用
  * 传入参数 = 线程名字,作用 = 标记该线程
  */ 
   HandlerThread mHandlerThread = new HandlerThread("handlerThread");

/**
  * 源码分析:HandlerThread类的构造方法
  */
/**
 * A {@link Thread} that has a {@link Looper}.
 * The {@link Looper} can then be used to create {@link Handler}s.
 * <p>
 * Note that just like with a regular {@link Thread}, {@link #start()} must still be called.
 */
public class HandlerThread extends Thread {
    int mPriority;// 线程优先级
    int mTid = -1;// 当前线程id
    Looper mLooper;//成员变量mLooper就是HandlerThread自己持有的Looper对象
    private @Nullable Handler mHandler;

// HandlerThread类有2个构造方法
// 区别在于:设置当前线程的优先级参数,即可自定义设置 or 使用默认优先级
    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;
    }
  • 创建HandlerThread类对象 = 创建Thread类对象 + 设置线程优先级 = 新开1个工作线程 + 设置线程优先级

步骤2:启动线程

/**
  * 具体使用
  */ 
   mHandlerThread.start();

/**
  * 源码分析:此处调用的是父类(Thread类)的start(),最终回调HandlerThread的run()
  */
    /**
     * Call back method that can be explicitly overridden if needed to execute some
     * setup before Looper loops.
     */
//onLooperPrepared()该方法是一个空实现,是留给我们必要时可以去重写的,
//但是注意重写时机是在Looper循环启动前
    protected void onLooperPrepared() {
    }
		@Override
		public void run() {
// 1. 获得当前线程的id
        mTid = Process.myTid();
// 2. 创建1个Looper对象 & MessageQueue对象
        Looper.prepare();
// 3. 通过持有锁机制来获得当前线程的Looper对象
        synchronized (this) {
            mLooper = Looper.myLooper();
// 发出通知:当前线程已经创建mLooper对象成功
// 此处主要是通知getLooper()中的wait()
            notifyAll(); //唤醒等待线程

// 此处使用持有锁机制 + notifyAll() 是为了保证后面获得Looper对象前就已创建好Looper对象

        }
// 4. 设置当前线程的优先级
        Process.setThreadPriority(mPriority);
// 该方法实现体是空的,子类可实现 / 不实现该方法
        onLooperPrepared();
// 6. 进行消息循环,即不断从MessageQueue中取消息 & 派发消息
        Looper.loop();
        mTid = -1;
   }

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

Looper对象创建后将其赋值给HandlerThread的内部变量mLooper,并通过notifyAll()方法去唤醒等待线程,最后执行Looper.loop();,开启looper循环语句。

步骤3:创建工作线程Handler & 复写handleMessage()

为什么要唤醒等待线程呢?我们来看看,getLooper方法

/**
  * 具体使用
  * 作用:将Handler关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
  * 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
  */ 
   Handler workHandler = new Handler( handlerThread.getLooper() ) {
        @Override
        public boolean handleMessage(Message msg) {
            ...//消息处理
            return true;
        }
    });

/**
  * 源码分析:handlerThread.getLooper()
  * 作用:获得当前HandlerThread线程中的Looper对象
  */ 
/**
 * 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;
    }
    // 若当前线程存活,再判断线程的成员变量mLooper是否为null
    // 直到线程创建完Looper对象后才能获得Looper对象,若Looper对象未创建成功,则阻塞
    // If the thread has been started, wait until the looper has been created.
    synchronized (this) {
        while (isAlive() && mLooper == null) {
            try {
                wait();//等待唤醒
            } catch (InterruptedException e) {
            }
        }
    }
// 上述步骤run()使用 持有锁机制 + notifyAll()  获得Looper对象后
// 则通知当前线程的wait()结束等待 & 跳出循环
// 最终getLooper()返回的是在run()中创建的mLooper对象   
	 return mLooper;
}

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

步骤4:使用工作线程Handler向工作线程的消息队列发送消息

/**
  * 具体使用
  * 作用:在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
  * 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
  */ 
  // a. 定义要发送的消息
  Message msg = Message.obtain();
  msg.what = 2; //消息的标识
  msg.obj = "B"; // 消息的存放
  // b. 通过Handler发送消息到其绑定的消息队列
  workHandler.sendMessage(msg);

/**
  * 源码分析:workHandler.sendMessage(msg)
  * 此处的源码即Handler的源码,故不作过多描述
  */

步骤5:结束线程,即停止线程的消息循环

/**
  * 具体使用
  */ 
  mHandlerThread.quit();

/**
  * 源码分析:mHandlerThread.quit()
  * 说明:
  *     a. 该方法属于HandlerThread类
  *     b. HandlerThread有2种让当前线程退出消息循环的方法:quit() 、quitSafely()
  */ 
    
  // 方式1:quit() 
  // 特点:效率高,但线程不安全
public boolean quit() {
    Looper looper = getLooper();
    if (looper != null) {
        **looper.quit();**
        return true;
    }
    return false;
}

// 方式2:quitSafely()
// 特点:效率低,但线程安全
/**
 * 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() {
    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的。

// 注:上述2个方法最终都会调用MessageQueue.quit(boolean safe)->>分析1

/**
  * 分析1:MessageQueue.quit(boolean safe)
  */ 
	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(); // 方式1(不安全)会调用该方法 ->>分析2
	        } else {
	            removeAllMessagesLocked(); // 方式2(安全)会调用该方法 ->>分析3
	        }
	        // We can assume mPtr != 0 because mQuitting was previously false.
	        nativeWake(mPtr);
	    }
	}
/**
  * 分析2:removeAllMessagesLocked()
  * 原理:遍历Message链表、移除所有信息的回调 & 重置为null
  */ 
  private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {
        Message n = p.next;
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;
}
/**
  * 分析3:removeAllFutureMessagesLocked() 
  * 原理:先判断当前消息队列是否正在处理消息
  *      a. 若不是,则类似分析2移除消息
  *      b. 若是,则等待该消息处理处理完毕再使用分析2中的方式移除消息退出循环
  * 结论:退出方法安全与否(quitSafe() 或 quit()),在于该方法移除消息、退出循环时是否在意当前队列是否正在处理消息
  */ 
  private void removeAllFutureMessagesLocked() {

    final long now = SystemClock.uptimeMillis();
    Message p = mMessages;

    if (p != null) {
        // 判断当前消息队列是否正在处理消息
        // a. 若不是,则直接移除所有回调
        if (p.when > now) {
            removeAllMessagesLocked();
        } else {
        // b. 若是正在处理,则等待该消息处理处理完毕再退出该循环
            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);
        }
    }
}

参考资料

https://blog.youkuaiyun.com/javazejian/article/details/52426353?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164942783916780264020913%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=164942783916780264020913&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-52426353.142v7pc_search_result_control_group,157v4control&utm_term=HandlerThread&spm=1018.2226.3001.4187

https://blog.youkuaiyun.com/carson_ho/article/details/79285760

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值