Handler的使用

新建handler并为其指定运行线程。

在主线程中创建Handler

Handler handler = new Handler();

Handler构造函数:

/** Handler.java */
public Handler() {
    this(null, false);
}

/** Handler.java */
public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }

    //取Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

创建Handler需要Lopper对象,而Looper对象通过Looper.myLooper()获取 :

/** Looper.java */
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

ThreadLocal用于保存线程中的数据,在Looper类中是以静态变量的形式存在的,sThreadLocal对象是在Looper类加载阶段创建的:

/** Looper.java */
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

sThreadLocal不属于任何Looper的实例,在一个app中,每个进程Process有且仅有一个sThreadLocal。

ThreadLocal内部维护一个ThreadLocalMap,用于保存Looper对象。

ThreadLocal#get(),获取当前线程中保存的Looper对象:

/** ThreadLocal.java */
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}

getMap:

/** ThreadLocal.java */
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

threadLocals是Thread类中包访问级别的非静态变量:ThreadLocal.ThreadLocalMap threadLocals = null;,ThreadLocal和Thread是在同一个包目录下面的,所以ThreadLocal中可以取得Thread类中的threadLocals变量。

通过getMap(Thread)方法取得对应线程中的ThreadLocalMap后,再用map.getEntry(this)获取Entry节点,然后返回value。这里用ThreadLocal类实例的哈希值作为取资源的键值,说明一个ThreadLocal对象在Thread中只能对应一个value值,要想要存储多个对象就要创建多个对应的ThreadLocal对象。

ThreadLocal#get() 函数有用到Thread t = Thread.currentThread(),因此,只能在对应的线程中才能获取到存入的值。

以上就是Looper.myLooper()获取当前线程Looper对象的过程。

创建Handler需要Looper,没有的话会报Can't create handler inside thread that has not called Looper.prepare()异常,在主线程中创建Handler时是没有问题的,因为app启动时已经为当前线程创建了Looper对象。

在新线程中创建Handler

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        Log.i(TAG, "mHandler thread start");
        Looper.prepare();
        mHandler = new Handler();
        Looper.loop();
        Log.i(TAG, "mHandler thread end");
    }
}, "mHandlerThread");
thread.start();

首先调用 Looper.prepare() 创建Looper对象:

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

/* Looper.java */
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));
}

新建一个Looper对象并添加到静态成员变量sThreadLocal中,如果当前线程已创建过Looper就抛异常。

ThreadLocal#set(T value) 把当前线程的Looper对象存储到当前线程的ThreadLocalMap中

/* ThreadLocal.java */
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocal#getMap(Thread t) :

/* ThreadLocal.java */
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

第一次取的时候map为null,创建并添加,ThreadLocal#createMap(Thread t, T firstValue) :

/* ThreadLocal.java */
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

这样Handler就创建完了,下面就是使用了。

Handler,Looper,MessageQueue,Message

简单地说就是MessageQueue存储Message,Looper跑循环,不断从MessageQueue里面取任务然后执行,如果MessageQueue为空,就卡在那里等,然后Handler发信息,Message携带信息。

创建完Handler后,要在线程里面调用Looper.loop();

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        // This must be in a local variable, in case a UI event sets the logger
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        final long traceTag = me.mTraceTag;
        if (traceTag != 0) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        try {
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }

        msg.recycleUnchecked();
    }
}

停止Handler

Looper只有在MessageQueue#next()为null时才会停下来

调用链:

Looper.myLooper().quitSafely() -> MessageQueue#quit(true) 清空MessageQueue -> Looper.loop()终止

Looper.myLooper().quitSafely()要在对应的线程中调用,就是说要用Handler实例发事件才行。

内存泄露:创建Handler的线程一直在执行,所以创建的Handler对象是不会被系统回收的,Handler中保存的对象引用也是不会被回收的,非静态内部类保存有外部类的引用。

解决办法:

  • 及时清理Handler,当一个Handler不用时及时在此Handler线程中调用Looper.myLooper().quitSafely()
  • 使用静态类也是一个办法
  • 不重写Handler#handleMessage(Message msg)方法,而是用Handler#post(Runnable r)发事件,因为post发送过去的Runnable是被包装在Message的Handler target里面的,在Message的target执行完成后,会清理掉Message,Message#recycleUnchecked()

Handler发送事件

mHandler.post(new Runnable() {
    @Override
    public void run() {
        Log.i(TAG, "current thread name=" + Thread.currentThread().getName();
    }
});

//or
mHandler.sendMessage(new Message());

post 也是调用的sendMessage:

public final boolean post(Runnable r)
{
    return  sendMessageDelayed(getPostMessage(r), 0);
}

最终调用 Handler#enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)把Message放入MessageQueue中

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

推荐阅读更多精彩内容