当前位置: 首页 > article >正文

Android的消息机制

Android的消息机制-从入门到精通

  • 前言
  • Android消息机制概述
  • Android 的消息机制分析
    • ThreadLocal 的工作原理
    • 消息队列的工作原理
    • Looper的工作原理
    • Handler的工作原理
  • 主线程的消息循环

前言

作为开发者,提及Android的消息机制,必然绕不开Handler,Handler是Android消息机制的上层接口,很多人认为其主要作用就是更新UI,这点也没错,但这仅仅是Handler的一个特殊使用场景:有时候需要在子线程中进行耗时的I/O操作,当完成该操作后需要在UI上进行一些改变,由于Android开发规范的限制,我们并不能直接在子线程中对UI控件进行操作,这个时候便可以通过使用Handler更新UI

Android的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue(消息队列)和Looper(循环)的支撑。

  • MessageQueue,内部存储一组消息,以队列形式对外提供插入和删除的工作,内部存储结构是单链表的数据结构
  • Looper:消息循环,由MessageQueue负责消息的存储单元,Looper负责去处理消息,它会以无限循环的形式去查找是否有新消息,否则就一直等待(Looper中还有一个概念:ThreadLocal)
  • ThreadLocal:在每个线程中存储数据,可以在不同线程中互不干扰的存储并提供数据

Handler创建时会采用当前线程的Looper来构建消息循环系统,其内部使用ThreadLocal来获取到当前线程的Looper,如果需要使用Handler就必须为线程创建Looper

Android消息机制概述

Android的消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程 (PS:之所以提供Handler,是为了解决在子线程中无法访问UI的矛盾),下面主要讲解下在Android中Handler的主要执行过程及功能:

//非主线程操作UI,提示报错
void checkThread() {
	if (mThread != Thread.currentThread() ) {
		throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its view")
	}
}

简单描述下Handler的工作原理:

  • Handler创建时会采用当前线程的Looper来构建内部消息的循环系统,若当前线程没有Looper会报如下错误
    在这里插入图片描述
  • Handler创建完毕后,内部的Looper及MessageQueue可以与Handler一起协同工作,通过Handler的post方法将一个Runnable投递到Handler内部的Looper中处理(也可通过Handler的send方法发送消息到Looper中处理)
  • send方法工作过程:调用send后,它会调用MessageQueue的enqueueMessage方法,将消息放进消息队列中,后Looper进行消息处理操作,最终调用到消息中的Runnable或者Handler的handleMessage方法。
    在这里插入图片描述

Android 的消息机制分析

ThreadLocal 的工作原理

ThreadLocal是线程内部的数据存储类,可以在指定线程中存储数据。(使用场景:当某些数据是以线程为作用域并且不同线程具有不同数据副本的时候,可以考虑使用ThreadLocal,如Handler的使用),下面将举例演示

//定义一个ThreadLocal对象
private ThreadLocal<Boolean> mBooleanTheadLocal = new ThreadLocal<Boolean>();

//分别在主线程及子线程1、子线程2中设置和访问该值
mBooleanTheadLocal.set(true);
Log.d(TAG,"[ThreadLocal#main] mBooleanThreadLocal = " + mBooleanThreadLocal.get());
new Thread("Thread#1") {
	@override
	public void run() {
		mBooleanThreadLocal.set(false);
		Log.d(TAG,"[ThreadLocal#Thread1] mBooleanThreadLocal = " + mBooleanThreadLocal.get());
	};
}.start();

new Thread("Thread#2") {
	@override
	public void run() {
		Log.d(TAG,"[ThreadLocal#Thread2] mBooleanThreadLocal = " + mBooleanThreadLocal.get());
	};
}.start();

该代码,在主线程中设置mBooleanTheadLocal为true,子线程1中为false,子线程2中不设置,后分别在三个线程中通过get方式获取mBooleanThreadLocal的值,日志如下:

D/TestActivity(8676): [Thread#main] mBooleanThreadLocal=true
D/TestActivity(8676): [Thread#1] mBooleanThreadLocal=false
D/TestActivity(8676): [Thread#2] mBooleanThreadLocal=null

下面分析ThreadLocal的内部实现,ThreadLocal是一个泛型类,重点介绍其get与set方法

//ThreadLocal set 方法
public void set(T value) {
	Thread currentThread = Thread.currentThread();
	Values values = values(currentThread);
	if (values == null) {
		values = initializaValues(currentThread);
	}
	values.put(this, values);
}

在Thread类的内部有一个成员变量专门存储线程的ThreadLocal的数据:ThreadLocal.Values localValues,在localValues内部有一个数组:private Object[] table,ThreadLocal的值就存在这个table数组中

void put (ThreadLocal<?> key, Object value) {
	cleanUp();
	//keep track of first tombstone. That's where we want to go back
	//and add an entry if necessary
	int firstTomstone = -1;
	for(int index = key.hash & mask;; index = next(index)) {
		Object k = table[index];
		if(k == key.reference) {
			//Replace existing entry
			table[index + 1] = value;
			return;
		}
		if(k == null) {
			if(firstTombstone == -1) {
				//Fill in null slot
				table[index] = key.reference;
				table[index + 1] = value;
				size++;
				return;
			}
			//Go back and replace first tombstone
			table[firstTombstone] = key.reference;
			table[firstTombstone + 1] = value;
			tombstone--;
			size++;
			return; 
		}
		//Remember first tombstone
		if(firstTombstone == -1 && k == TOMBSTONE) {
			firstTombstone = index;
		}
	}
}

由此得出一个存储规则:ThreadLocal的值在table数组中的存储位置总是为ThreadLocal的reference字段所标识的对象的下一个位置(比如ThreadLocal的reference对象在table数组中的索引为index,那么ThreadLocal的值在table数组中的索引就是index + 1)

//ThreadLocal get 方法
public T get() {
	//Optimized for the first path.
	Thread currentThread = Thread.currentThread();
	Values values = values(currentThread);
	if (values != null) {
		Object[] table = values.table;
		int index = hash & values.table;
		if(this,reference == table[index]) {
			return (T) table[index + 1];
		}
	}else {
		values = initializeValues(currentThread);
	}
	return (T) values.getAfterMiss(this);
}

ThreadLocal的get方法:取出当前线程的local-value对象,如果这个对象为null那么就返回初始值,初始值由ThreadLocal的initialValue方法来描述,默认情况下为null
如果localValues对象不为null,那就读取它的table数组并找出ThreadLocal的reference对象在table数组中的位置,然后table数组中的下一个位置所存储的数据就是ThreadLocal的值

从ThreadLocal的set 和 get 方法中可以发现,它们所操作的对象都是当前线程的localValues对象的table数组,因此在不同线程中访问同一个ThreadLocal的set 和 get 方法,它们对ThreadLocal所做的读/写操作仅限于各自线程的内部,这也是为什么ThreadLocal可以在多线程中互不干扰地存储和修改数据的原因!

消息队列的工作原理

消息队列MessageQueue主要包括两个操作:插入和读取。读取操作会涉及到删除操作。插入和读取对应的方法分别是equeueMessage 和 next ,其中enqueueMessage的作用是往消息队列中插入一条消息,而next的作用是从消息队列中取出一条信息并将其从消息队列中删除,MessageQueue的内部实现是一个单链表,本身在插入和删除上具有优势

//enqueueMessage 方式实现 主要操作就是单链表的插入
boolean enqueueMessage (Message msg, long when) {
	...
	synchronized (this) {
		...
		msg.markInUse();
		msg.when = when;
		Message p = mMessage;
		boolean needWake;
		if (p == null || when == 0 || when < p.when) {
			//New head, wake up the event queue if blocked.
			msg.next = p;
			mMessage = 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 = mBlock && 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;
		}
		//We can assume mPtr  != 0 because mQuitting is false
		if (needWake) {
			nativeWake(mPtr);
		}
	}
	return true;
}
// next 方法实现
Message next() {
	...
	int pendingIdleHandlerCount = -1;// -1 only during first iteration
	int nextPollTimeoutMills = 0;
	for(;;) {
		if (nextPollTimeoutMills != 0) {
			Binder.flushPendingCommands();
		}
		nativePollOnce(ptr, nextPollTimeoutMills);
		synchronized(this) {
			//Try to retrieve the next message. Return if found.
			final long now = SystemClock.uptimeMills();
			Message prevMsg = null;
			Message msg = mMessages;
			if(msg != null && msg.target == null) {
				//Stalled by a barrier.Find the next asynchronous message in the queue
				do {
					prevMsg = msg;
					msg = msg.next;
				} while (msg != null && !msg.isAsynchronous());
			}
			if (msg != null) {
				if (now < msg.when) {
					//Next message is not ready. Set a timeout to wake up when it is ready.
					nextPollTimeoutMills = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
				} else {
					//Got a message.
					mBlocked = false;
					if (prevMsg != null) {
						prevMsg,next = msg.next;
					} else {
						mMessage = msg.next;
					}
					msg.next = null;
					if (false) Log.v("MessageQueue","Returning message:" + msg);
					return msg;
				} else {
					//No more message.
					nextPollTimeoutMills = -1;
				}
				...
			}
			...
		}
	}
}

源码分析next 方法的实现,可以知道该方法是一个无限循环方法,当消息队列中没有消息时,next方法就会一直阻塞在这里,当有新消息来时,next方法会返回这条消息并将其从单链表中移除。

Looper的工作原理

Looper在Android消息机制中扮演着消息循环的角色,具体将它会不停从MessageQueue中查看是否有新消息,如果有新消息就立刻处理,否则就阻塞在那里。

//Looper 的构造方法:创建一个MessageQueue,并将当前线程的对象保存起来
private Looper (boolean quitAllowed) {
	mQueue = new MessageQueue(quitAllowed);
	mThread = Thread.currentThread();
}

//为当前线程创建一个Looper
new Thread("Thread#2") {
	@override
	public void run () {
		Looper.prepare();
		Handler handler = new Handler();
		Looper.loop();//开启消息循环
	};
}.start();
Looper.getMainLooper()// 获取到主线程的Looper
Looper.quit()//退出一个Looper
Looper.quitSafely//设定一个退出标记

以下分析Looper.loop方法的具体实现:

/**
	* Run the message queue in this thread. Be sure to call
	* {@link #quit()} to end the 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 qutting.
			return;
		}
		//This must be in a local variable, in case a UI event sets the logger
		Printer logging = me.mLogging;
		if(logging != null) {
			logging.println(">>>>>Dispatching to+ msg.target "+ " " + msg.callback + ": " + msg.what) 
		}
		//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();
	}
}

分析:loop方法是一个死循环,唯一跳出循环的条件是MessageQueue的next方法返回的是null。当Looper的quit方法被调用时,MessageQueue的quit或者quitSafely会被调用来通知消息队列退出,它的next方法会返回null,即Looper必须退出。否则loop方法就会无限循环下去。
loop方法调用MessageQueue的next方法来获取新消息,next是一个阻塞操作,如果返回新消息,Looper就会去处理:
msg.target是发送消息的Handler对象,Handler发送消息最终又交给它的dispatchMessage方法来处理,Handler的dispatchMessage方法是创建Handler时所使用的Looper中执行的,这样就成功将代码逻辑切换到指定线程中去了

Handler的工作原理

Handler的工作主要是信息的发送和接收,消息的发送可以通过post的一系列方法以及send的一系列方法来实现,以下举例演示:

public final boolean sendMessage(Message msg) {
	return sendMessageDelayed(msg, 0);
}

public final boolean sendMessageDelayed(Message msg, long delayMills) {
	if (delayMillis < 0) {
		delayMillis = 0;
	}
	return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
	MessageQueue queue = mQueue;
	if (queue == null) {
		RuntimeExcption e = new RuntimeException(
								this + "sendMessageAtTime() called with no mQueue");
		Log.w("Looper", e.getMessage(), e);
		return false;
	}
	return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMills) {
	msg.target = this;
	if (mAsynchronous) {
		msg.setAsynchronous(true);
	}
	return queue.equeueMessage(msg, uptimeMillis);
}

不难发现,Handler向消息队列中插入一条信息,MessageQueue的next方法就会返回这条信息给Looper处理,最终消息由Looper交由Handler处理,即Handler的dispatchMessage方法被调用,此时Handler进入处理消息阶段

//dispatchMessage具体实现
public void dispatchMessage(Message msg) {
	if(msg.callback != null) {
		handleCallback(msg);
	} else {
		if(mCallback != null) {
			if(mCallback.handlerMessage(msg)) {
				return;
			}
		}
		handleCallback(msg);
	}
}

Handler处理消息过程如下:

  • 首先,检查Message的callback是否为null,不为null就通过handleCallback来处理消息。Message的callback是一个Runnable对象,实际上就是Handler的post方法所传递的Runnable参数。
//handleCallback 具体实现
private static void handleCallback(Message message) {
	message.callback.run();
}
  • 其次,检查mCallback是否为null,不为null就调用mCallback的handleMessage方法来处理消息。
//Callback接口
/**
	* Callback interface you can use when instantiating a Handler to avoid
	* having to implement your own subclass of Handler.
	*
	* @param msg A {@link android.os.Message Message} object
	* @return True if no further handling is desired
	*/
public interface Callback {
	public boolean handleMessage(Message msg);
}
  • 最后,调用Handler的handleMessage方法来处理消息。Handler处理信息流程图如下:
    在这里插入图片描述
    PS:Handler还有一个特殊的构造方法:及通过一个特定的Looper来构造Handler
public Handler(Looper looper) {
	this(looper, null, false);
}

//示例:若当前线程没有Looper,会抛异常
public Handler(Callback callback, boolean async) {
	...
	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;
}

主线程的消息循环

在Android主线程中的ActivityThread的main方法中:系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,通过Looper.loop()开启消息循环:

public static void main(String[] args) {
	...
	Process.setArgV0(""<pre-initialized>");
	Loopr.prepareMainLooper();
	ActivityThread thread = new ActivityThread();
	thread.attach(false);
	if (sMainThreadHandler == null) {
		sMainThreadHandler = thread.getHandler();
	} 
	AsyncTask.init();
	if (false) {
		Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));
		Looper.loop();
		throw new RuntimeException("Main thread loop unexpectedly exited");
	}
}

打开主线程的消息循环后,ActivityThread还需要一个Handler来和消息队列进行交互,ActivityThread.H来完成这个工作(内部定义一组消息类型,主要包括四大组件的启动和停止)

private class H extends Handler {
	public static final int LAUNCH_ACTIVITY = 100;
	public static final int PAUSE_ACTIVITY = 101;
	public static final int PAUSE_ACTIVITY_FINISHING = 102;
	public static final int STOP_ACTIVITY_SHOW = 103;
	public static final int STOP_ACTIVITY_HIDE = 104;
	public static final int SHOW_WINDOW = 105;
	public static final int HIDE_WINDOW = 106;
	public static final int RESUME_ACTIVITY = 107;
	public static final int SEND_RESULT = 108;
	public static final int DESTROY_ACTIVITY = 109;
	public static final int BIND_APPLICATION = 110;
	public static final int EXIT_APPLICATION = 111;
	public static final int NEW_INTENT = 112;
	public static final int RECEIVER = 113;
	public static final int CREATE_SERVICE = 114;
	public static final int SERVICE_ARGS = 115;
	public static final int STOP_SERVICE = 116;
	...
}

ActivityThread通过ApplicationThread和AMS进行进程间通信,AMS以进程间通信的方式完成ActivityThread的请求后会回调ApplicationThread中的Binder方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到ActivityThread(即主线程)中去执行,这个过程就是主线程的消息循环模型。


http://www.kler.cn/a/590185.html

相关文章:

  • 黑马商城完成随笔
  • SpringBoot 第一课(Ⅲ) 配置类注解
  • 网页制作18-Javascipt图像特效の图片闪烁
  • Python + Qt Designer构建多界面GUI应用程序:Python如何调用多个界面文件
  • Qt常用控件之网格布局QGridLayout
  • 使用 GetX 实现状态管理:同一页面和不同页面之间的数据变化
  • 【Unity6打包Android】游戏启动的隐私政策弹窗(报错处理)
  • VSTO(C#)Excel开发12:多线程的诡异
  • 数据库核心技术面试题深度剖析:主从同步、二级索引与Change Buffer
  • 前端技巧第六期JavaScript对象
  • 【最新版】智慧小区物业管理小程序源码+uniapp全开源
  • 关于deepseek R1模型分布式推理效率分析
  • Java学习——数据库查询操作
  • 解决MySQL 8.x初始化后设置忽略表和字段大小写敏感的问题
  • 使用computed计算属性实现购物车勾选
  • Go vs Rust vs C++ vs Python vs Java:谁主后端沉浮
  • 【面试手撕】非常规算法,多线程常见手撕题目
  • Windows11 新机开荒(二)电脑优化设置
  • 企业向媒体发出邀约,有哪些注意点?
  • redis终章