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方法来终止线程的执行。

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容