Android日记之消息机制(2)
前言
上一篇Android日记之消息机制(1)分析了Handler的用法和实现原理,这一篇主要讲讲ThreadLocal和HandlerThread,这两个也是在面试中经常被问到的东西,我们就先从ThreadLocal开始讲起。
ThreadLocal(基于jdk1.8)
ThreadLocal基本使用
它是一个线程内部的数据存储类,就是线程局部变量,通过它就可以在指定的线程中去存储数据了,当数据存储完毕后,也只有在指定的线程中才可以获取到存储的数据,而其他的线程是无法获取到这些数据的。在一般的日常开发中用的比较少,但是在某些场景下,也可以使用ThreadLocal实现一些看起来很复杂的功能。一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用 ThreadLocal。
老样子,我们看下基本的使用方法:
//ThreadLocal使用方法
package com.ju.threadlocaldemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
private ThreadLocal<Boolean> threadLocal = new ThreadLocal<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
threadLocal.set(true);
Log.d("ThreadLocal","[main]="+ threadLocal.get());
new Thread("Thread1"){
@Override
public void run() {
threadLocal.set(false);
Log.d("ThreadLocal","[Thread1]="+ threadLocal.get());
}
}.start();
new Thread("Thread2"){
@Override
public void run() {
Log.d("ThreadLocal","[Thread2]="+ threadLocal.get());
}
}.start();
}
}
首先实例化这个ThreadLocal,然后传入你要传递的值的泛型,这里为了测试我就传入Boolen类型,接下来,你就可以通过set()
和get()
去获得对应的值。这里我们做了一个测试,分别创建两个子线程,然后包括主线程在内每一个ThreadLocal都set()
和get()
对应的值看看,结果会是怎么样。
从Logcat日记中我们可以看出,虽然不同线程访问的都是同一个ThreadLocal对象,但是它们获取到的值却是不一样的,这就是ThreadLocal的特别之处了,为什么会这样呢?接下来我们主要分析
set()
和get()
的源码来更深刻的去理解。set()源码分析
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的map
ThreadLocalMap map = getMap(t);
//这里会判断是否之前有没有调用过set和get方法,如果没有,就为当前线程创建一个ThreadLocalMap,
if (map != null)
map.set(this, value);
else
//key为当前ThreadLocalMap对象
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
这里我们看到了ThreadLocalMap这个类,它是ThreadLocal的里的一个内部类,是用基于线性探测法的散列表实现的。set()
方法很简单,看源码就可以熟悉,。
get()源码分析
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
// 这里判断是否调用过set或get方法时,对象值已经设置过,就返回上一次的值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//没有的话,就自动设置为初始值
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
//通常可以自己去重写,来设置初始值
protected T initialValue() {
return null;
}
代码也很简单,首先判断是否存在这个值,如果有直接返回就好了,如果没有的话,就通过setInitialValue()
这个方法来穿件一个初始值进行返回,也可以通过initialValue()
这个来设置初始值。
HandlerThread
它继承了一个Thread,是一种可以使用Handler的Thread,是属于Android多线程Android应用开发的很经常使用的类,在IntentService的源码也有用到HandlerThread,它的实现也很简单,就是在run()
方法里通过Looper.prepare()
来创建消息队列,并通过Looper.loop()
来开启消息循环,这样就可以运行可以在HandlerThread里创建Handler了。它的本质其实就是Handler+Thread。官方介绍是这样的:HandlerThread是Android API提供的一个方便、便捷的类,使用它我们可以快速的创建一个带有Looper的线程。Looper可以用来创建Handler实例。
HandlerThread的基本使用
package com.ju.handlerthreaddemo;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private HandlerThread handlerThread = new HandlerThread("HandlerThread");
private Handler handler;
private Handler mainHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
//这里更新UI
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//开启线程
handlerThread.start();
//创建Handler与handlerThread绑定
handler = new Handler(handlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
//这里进行耗时任务的处理,处理完毕可以发送给主Handler进行相应的操作。
//也可以通过继承HandlerThread来处理
mainHandler.sendMessage(msg);
}
};
}
//销毁
@Override
protected void onDestroy() {
super.onDestroy();
handlerThread.quit();
}
}
首先先创建HandlerThread的实例,然后创建一个Handler与HandlerThread进行绑定通过getLooper()
来获取Looper,然后一定要使用start()
方法开启线程,这里注意的是handler是运行在子线程的,所以是不能在这里面进行更新UI的操作,这时候你就可以在这个handler里面进行耗时的操作,然后吧结果可以通过sendMessage()
方法发送给处于UI线程的Handler进行更新UI的操作就可以了。最后要记得要销毁这个HandlerThread才行。
HandlerThread的run()源码解析
因为HandlerThread本质上其实就是继承了Thread,所以我们就就是看Thread最重要的run()
方法实现。
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
从实现来看,一般的Thread的run()
只执行一个耗时的任务,HandlerThread在内部创建了一个消息队列,外界需要通过Handler的消息方式通知HandlerThread要执行哪一个具体的任务,这里注意一下,当不需要使用HandlerThread时,就要通过quit()
或者quitSafely
方法来终止线程的执行。
参考
- [任玉刚]Android开发艺术探索
- HandlerThread(详细例子)