小言_互联网的博客

Android 必懂系列 —— handler机制【由浅及深到源码分析】

236人阅读  评论(0)

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

首先我们要先搞懂handler到底被设计出来有什么用。——一种东西被设计出来肯定就有它存在的意义,而Handler的意义就是切换线程。(线程间通信) 常用的场景就是:网络交互后切换到主线程进行UI更新。

(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实现原理如下图:

实现逻辑顺序:

过程模拟:

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.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对象。

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

转载:https://blog.csdn.net/qq_45866344/article/details/116453332
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场