Android 必懂系列 —— handler机制【由浅及深到源码分析】(含ThreadLocal原理)

【1】handler在Android 开发中到底有什么用?

首先我们要先搞懂handler到底被设计出来有什么用。——一种东西被设计出来肯定就有它存在的意义,而Handler的意义就是切换线程。(线程间通信) 常用的场景就是:网络交互后切换到主线程进行UI更新。【注:网友指出:(在Handler源码的注释上写的很清楚,还可以用于将来某个时间点上执行方法】

(1) 为什么不直接在子线程更新UI?

Android的UI是线程不安全的,肯定不能同时多个线程操作UI线程。如果加锁又会降低UI的效率,所以通常不能在子线程更新UI。


【2】handler的简单使用如下:

public class MainActivity extends AppCompatActivity {
			//传递的数据
			Bundle bundle = new Bundle();
			bundle.putString("msg", "传递我这个消息");
			
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
         
	//数据的接收
	final Handler handler = new Handler() {
	        @Override
	        public void handleMessage(Message msg) {
	            super.handleMessage(msg);
	            if (msg.what == 0x11) {
	            	//这里模拟就获取到闯过来的数据就行了
	                Bundle bundle = msg.obj;
	                String date = bundle.getString("msg");
	            }
	        }
	};

        new Thread(new Runnable() {
            @Override
            public void run() {
               //发送数据
				Message message = Message.obtain();
				message.obj=bundle;  
				message.what = 0x11;
				handler.sendMessage(message);
            }
        }).start();
    }

在主线程中创建handler对象,并且重写handleMessage方法,在子线程中发送message对象。

  • 注意:不建议直接new Message,Message内部保存了一个缓存的消息池,我们可以用obtain从缓存池获得一个消息,Message使用完后系统会调用recycle回收,如果自己new很多Message,每次使用完后系统放入缓存池,会占用很多内存的。

【3】handler 原理分析:

使用Handler方式进行异步消息处理主要由Message,Handler,MessageQueue,Looper四部分组成:

  • (1)Message,线程之间传递的消息,用于不同线程之间的数据交互。Message中的what字段用来标记区分多个消息,arg1、arg2 字段用来传递int类型的数据,obj可以传递任意类型的字段。

  • (2)Handler,用于发送和处理消息。其中的sendMessage()用来发送消息,handleMessage()用于消息处理,进行相应的UI操作。

  • (3)MessageQueue,消息队列(先进先出),用于存放Handler发送的消息,一个线程只有一个消息队列。

  • (4)Looper,可以理解为消息队列的管理者,当发现MessageQueue中存在消息,Looper就会将消息传递到handleMessage()方法中,同样,一个线程只有一个Looper。

Handler实现原理如下图:
在这里插入图片描述
实现逻辑顺序:
在这里插入图片描述

  • 这里网友指出可能出现误导的情况:looper.loop的执行是一直都再进行的,并不是sendMessage后再loop。

过程模拟:
在这里插入图片描述
messagequeue是一个由单链表实现的优先级队列。由when决定,根据执行时刻决定优先顺序。


【4】源码分析

  • (1)handler 源码分析:

在这里插入图片描述
发现很多sendpost开头函数,都是发送消息的,最终都会调用到sendMessageAtTime这个函数,而这个函数又会调用到handler里面的enqueueMessage函数,这个函数又会调用到MessageQueue里面的enqueueMessage函数。然后我们来分析MessageQueue。

  • (2)MessageQueue 源码分析:

在这里插入图片描述

MessageQueue主要作用就两个:1.存放消息进入MessageQueue。2.从这个消息队列中取出一个Message。
所以来到存放函数:enqueueMessage

	boolean enqueueMessage(Message msg, long when) {
...
...
		if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }


....
....

}

直接看到else里面的for死循环,大概意思就是如果要存放的这个Message的when要比当前的这个Message的when要小的话就直接将要存放的Message放到当前这个Message的前一个,如果不是那么久依次向下找。

说完入队,我们来看看Message如何取出:next函数

Message next() {
		...
		...
		...
	if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
           }
           ...
           ...
           ...
   }

就从当前的MessageQueue的头部取出就可以了。

  • (3)Looper源码分析:

首先我们要知道每个线程都只有一个Looper实例,这个是怎么实现的呢?
要想清楚这个问题,我们就必须知道ThreadLocal
下面就具体说说ThreadLocal运行机制。


①ThreadLocal
//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) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

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类中的getset方法可以大致看出来,有一个ThreadLocalMap变量,这个变量存储着键值对形式的数据。

-keythis,也就是当前ThreadLocal变量。

  • valueT,也就是要存储的值。

然后继续看看ThreadLocalMap哪来的,也就是getMap方法:

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

//Thread.java
ThreadLocal.ThreadLocalMap threadLocals = null;

原来这个ThreadLocalMap变量是存储在线程类Thread中的。

所以ThreadLocal的基本机制就搞清楚了。

在每个线程中都有一个threadLocals变量,这个变量存储着ThreadLocal对应的需要保存的对象

这样带来的好处就是,在不同的线程,访问同一个ThreadLocal对象,但是能获取到的值却不一样。

挺神奇的是不是,其实就是其内部获取到的Map不同,MapThread绑定,所以虽然访问的是同一个ThreadLocal对象,但是访问的Map却不是同一个,所以取得值也不一样。

ThreadLocal原理见下图(个人理解,如果有误,欢迎指正):

在这里插入图片描述

这样,由于Looper里面的ThreadLocal是final,且只有一个的,所以每个线程来访问时,只会有同一个ThreadLocal对象,并且在prepare函数中创建Looper时,如果已经存在就抛出异常,由此就保证了每个线程都只有一个Looper对象。


②prepare函数
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

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

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

③loop函数
public static void loop() {
....
....
....
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);
            }
            // Make sure the observer won't change while processing a transaction.
            final Observer observer = sObserver;

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            Object token = null;
            if (observer != null) {
                token = observer.messageDispatchStarting();
            }
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            try {
            //注意这里
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
...
...
...
...
}

进去有个for死循环,保证一直检索MessageQueue中的任务。
注意这句话:
msg.target.dispatchMessage(msg);
我们追到Handler中看到target是Handler对象
在这里插入图片描述
那么在Looper的loop中我们又回到了Handler中的dispatchMessage
ok,那我接下来我们进入Handler的dispatchMessage中:

/**
     * Handle system messages here.
     */
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

看到这一句话:

if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }

mCallback是什么呢?你还记得我们使用Handler的时候在Main函数中传入的那个参数吗?
在这里插入图片描述
这一堆就是mCallback;而mCallback.handleMessage(msg)不就回调到了上图中吗,执行我们需要执行的代码。很神奇!!是吧


  • (4)Message源码分析:

  1. 这一部分比较简单了,主要记住以下变量的含义就行了:
变量名作用
what用于表示这一个任务
arg1传值int型
arg2传值int型
obj传大量数据
when延迟时间,延迟多久执行这个任务
  1. 比较重要的一点就是Message中维护了一个任务池(链表)初始值是0,最大值是50。
    所以我么通常使用Message.obtain获取一个Message对象,如果我们一直new出对象的话,太消耗系统资源了。

在这里插入图片描述

  1. 最后就是回收Message,应该是Loop循环结束后调用:
    在这里插入图片描述
    然后来到MessagerecycleUnchecked()方法中
    在这里插入图片描述
  • 10
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 32
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 32
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值