用24k纯java自己实现android的handler+message通信机制

事情的起因还是因为一次面试经历。

面试官:“说一下android的handler机制。”

经过几次面试之后发现几乎每个面试官都会问到这个问题,真的都快被问烦了好吧,于是用飞快的速度把之前都快说烂了答案的又说了一遍,好不容易都说完了,这时候面试官的操作来了,直接拿过来几张白纸。

“恩,说的不错,来,那你试试能不能尝试用伪代码实现一下。”

还有这种操作,当时我就蒙了,心想,要不就试试吧,反正都用了不知道多少次了。

一开始写想的思路就是启动一个线程开启一个死循环仿照looper来一直轮训消息队列,然后没有的话就阻塞线程,等消息来了再唤醒继续取出消息处理,但到了postdelay()这个方法时,延时处理就不知道怎么搞了,这把我难住了,因为需要根据时间线来取消息,比如先插入个等待2秒的后插入一个等待1秒的,那么就要时间少的最先运行,想了一会,一时没想出好的方法,然后就下一题了。。。。

回来就想,用的熟练跟自己能实现出来还是有差别呀,之前咋就没想过如果自己实现的话会是什么样子呢。

回来之后好好翻了翻android的源码,痛定思痛。花了半天,终于按照android的handler机制的思路自己实现了出来。

下面就是我的实现过程,比起android官方的肯定要简单点,但主要的线程通讯和延迟处理都实现出来了。

handler message 机制的由来

android 的 UI 操作并不是线程安全的,我们知道,在android中子线程可以有好多个,但是如果每个线程都可以对ui进行访问,我们的界面可能就会变得混乱不堪,这样多个线程操作同一资源就会造成线程安全问题,当然,需要解决线程安全问题的时候,我们第一想到的可能就是加锁,但是加锁会降低运行效率,所以android出于性能的考虑,并没有使用加锁来进行ui操作的控制。

但问题又回来了,既然想要性能,有想要解决子线程更新ui的需求,那么应该怎么办呢?

因此android规定,ui线程只能由MainThread进行访问,其他菜鸡子线程想更新ui只能先写报告(handler.sendmessage())交给MainThread代为处理。

而这个写报告并由MainThread代为更新UI的过程,就是使用handler message机制来实现的了。

因此handler message 机制也是对于UI刷新线程安全问题的一个高性能的解决方案,并且这套机制不仅仅适用于主线程和子线程通讯,它适用于任何一对多线程间的通讯。

主要构成成员

  1. Handler:消息处理,消息循环从消息队列中取出消息后要对消息进行处理。
  2. Message:消息的实体
  3. MessageQueue:消息队列
  4. Looper:消息循环

实现代码

Looper.java

package com.miqt.test;

import com.miqt.test.MessageQueue.DelayRunnable;

public class Looper {

	MessageQueue queue;

	public Looper() {
		queue = new MessageQueue();
	}

	public static void loop() {
		Looper me = myLooper();
		while (true) {
			me.queue.next().run();
		}
	}

	static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

	public static void prepare() {
		// 检查线程中是否已经有一个Looper循环了
		if (sThreadLocal.get() != null) {
			throw new RuntimeException("Only one Looper may be created per thread");
		}
		sThreadLocal.set(new Looper());
	}

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

}

Looper实现了核心的prepare方法和loop方法,loop开启轮训之后则是开启死循环一直取消息来处理,这里的queue.next(),实际上类似于一个阻塞方法,android源码的实现方式我看是放在了native层中,而java原生不是,java原生的代码它实际上是首先会调用DelayRunnable.getDelay()取得一个纳秒值,然后一直减去系统的时间,直到减到小于0返回DelayRunnable对象,看一眼这块的实现源码:

public final long awaitNanos(long nanosTimeout)
                throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            final long deadline = System.nanoTime() + nanosTimeout;
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                if (nanosTimeout <= 0L) {
                    transferAfterCancelledWait(node);
                    break;
                }
                if (nanosTimeout >= spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
                nanosTimeout = deadline - System.nanoTime();
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null)
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
            return deadline - System.nanoTime();
        }

Message.java

package com.miqt.test;

public class Message {
	public int what;
	public Object obj1;
	public Object obj2;
	public Handler target;
	public Runnable callback;

	public Message(int what, Object obj1, Object obj2) {
		super();
		this.what = what;
		this.obj1 = obj1;
		this.obj2 = obj2;
	}

	public Message(int what) {
		super();
		this.what = what;
	}

	public Message(Runnable run) {
		super();
		this.callback = run;
	}

}

‘信封’实体message,我这里只实现了message传消息常用的变量,对于android源码中的消息池我没有实现。

MessageQueue.java

package com.miqt.test;

import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class MessageQueue {
	DelayQueue<DelayRunnable> queue = new DelayQueue<DelayRunnable>();

	// 入队
	public void enqueue(final Message msg, long delayInMilliseconds) {
		if (msg.target == null) {
			throw new IllegalStateException("handler is null");
		}
		DelayRunnable runn = new DelayRunnable() {

			@Override
			public void run() {
				msg.target.dispatchMessage(msg);
			}
		};
		runn.setDelay(delayInMilliseconds);
		queue.add(runn);
	}

	// 出队
	public DelayRunnable next() {
		try {
			return queue.take();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
	}

	public abstract class DelayRunnable implements Delayed, Runnable {

		private long delay;

		public void setDelay(long delayInMilliseconds) {
			this.delay = System.nanoTime() + TimeUnit.NANOSECONDS.convert(delayInMilliseconds, TimeUnit.MILLISECONDS);
		}

		@Override
		public int compareTo(Delayed o) {
			DelayRunnable that = (DelayRunnable) o;
			if (this.delay > that.delay) {
				return 1;
			} else if (this.delay < that.delay) {
				return -1;
			}
			return 0;
		}

		@Override
		public long getDelay(TimeUnit unit) {
			long result = unit.convert(delay - System.nanoTime(), TimeUnit.NANOSECONDS);
			return result;
		}
	}

}

消息队列只实现了队列的入队和出队,遵循消息先进先出并且需要根据时间优先出队的原则,使用了DelayQueue来储存消息,另外创建DelayRunnable实现了Delayed接口的compareTo方法和getDelay方法,compareTo方法用来比较消息在队列中的顺序,getDelay方法是用来决定是否出队处理,当返回值<=0时,则会出队处理。

Handler.java

package com.miqt.test;

public class Handler {

	Looper mLooper;
	MessageQueue mQueue;
	private CallBack mCallback;

	public Handler(Looper mLooper, CallBack callBack) {
		super();
		this.mLooper = mLooper;
		this.mCallback = callBack;
		mQueue = mLooper.queue;
	}

	public void post(Runnable r) {
		Message msg = new Message(r);
		if (msg.target == null) {
			msg.target = this;
		}
		mQueue.enqueue(msg, 0);
	}

	public void postDelay(Runnable r, long delayMillis) {
		Message msg = new Message(r);
		if (msg.target == null) {
			msg.target = this;
		}
		mQueue.enqueue(msg, delayMillis);
	}

	public void sendMessage(Message msg) {
		if (msg.target == null) {
			msg.target = this;
		}
		mQueue.enqueue(msg, 0);
	}

	public void handleMessage(Message message) {

	}

	private static void handleCallback(Message message) {
		message.callback.run();
	}

	public void dispatchMessage(Message msg) {
		if (msg.callback != null) {
			handleCallback(msg);
		} else {
			if (mCallback != null) {
				if (mCallback.handleMessage(msg)) {
					return;
				}
			}
			handleMessage(msg);
		}
	}

	interface CallBack {
		boolean handleMessage(Message msg);
	}
}

最后就是handler了,它其实是最简单的,只负责调用之前封装好的就行了,我在这里按照android分发message的逻辑实现了dispatchMessage,这样的话调用起来几乎跟在android上一模一样,优先级也一样,先出发message里面的runnable运行,然后触发CallBack对象的handleMessage,如果返回false的话那么还会触发自己的方法public void handleMessage(Message message)。

到了这里就实现完成了,接下来就是测试一下是否可用。

这是我的测试代码。

package com.miqt.test;

import java.util.concurrent.DelayQueue;
import java.util.concurrent.TimeUnit;

public class Acty {

	protected static final int HELLO = 0;
	protected static Handler handler;

	public static void main(String[] args) {
		new Thread() {
			public void run() {
				Looper.prepare();
				handler = new Handler(Looper.myLooper(), new MyCallBack());
				Looper.loop();
			};
		}.start();
		// 创建线程不定时发送message
		test();
	}

	private static void test() {
		new Thread() {
			public void run() {
				for (;;) {
					try {
						TimeUnit.SECONDS.sleep(1);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					int type = (int) (Math.random() * 3);
					switch (type) {
					case 0:
						System.out.println("[发送方]:sendMessage");
						Message message = new Message(HELLO);
						message.obj1 = "001" + Math.random();
						handler.sendMessage(message);
						break;
					case 1:
						System.out.println("[发送方]:post");
						handler.post(new Runnable() {

							@Override
							public void run() {
								System.out.println("[接受方]post");
							}
						});
						break;
					case 2:
						long delaytime = (long) (Math.random() * 5000);
						System.out.println("[发送方]:postDelay:" + String.valueOf(delaytime));
						handler.postDelay(new Runnable() {

							@Override
							public void run() {
								System.out.println("[接受方]postDelay" + delaytime);
							}
						}, delaytime);
						break;

					default:
						break;
					}
				}
			};
		}.start();
	}

	public static class MyCallBack implements Handler.CallBack {

		@Override
		public boolean handleMessage(Message msg) {
			switch (msg.what) {
			case HELLO:
				String str = (String) msg.obj1;
				System.out.println("[接受方]" + str + "开始处理");
				System.out.println("[接受方]" + "hello,我来自消息队列");
				System.out.println("[接受方]" + str + "处理完成,出队");
				break;

			default:
				break;
			}
			return true;
		}
	}
}

开启了两个线程,一个线程作为接收处理消息的线程,一个线程负责随机一个时间并且用不同的方式来发送消息。

测试结果:

[发送方]:sendMessage
[接受方]0010.5516496029786089开始处理
[接受方]hello,我来自消息队列
[接受方]0010.5516496029786089处理完成,出队
[发送方]:post
[接受方]post
[发送方]:post
[接受方]post
[发送方]:sendMessage
[接受方]0010.45668723454926696开始处理
[接受方]hello,我来自消息队列
[接受方]0010.45668723454926696处理完成,出队
[发送方]:post
[接受方]post
[发送方]:sendMessage
[接受方]0010.43970565205942336开始处理
[接受方]hello,我来自消息队列
[接受方]0010.43970565205942336处理完成,出队
[发送方]:postDelay:3647
[发送方]:postDelay:4144
[发送方]:postDelay:497
[接受方]postDelay497
[发送方]:sendMessage
[接受方]0010.4489768565868292开始处理
[接受方]hello,我来自消息队列
[接受方]0010.4489768565868292处理完成,出队
[接受方]postDelay3647
[发送方]:post
[接受方]post
[发送方]:sendMessage
[接受方]0010.5013814373539061开始处理
[接受方]hello,我来自消息队列
[接受方]0010.5013814373539061处理完成,出队
[接受方]postDelay4144
[发送方]:postDelay:4358

可以看到不管是sendMessage、post还是postDelay,Handler都有条不紊的按照顺序进行处理,并且postDelay发出的消息会按照正确的顺序和时间点来运行。

通过这次的实现,感觉对android消息机制有了更深的了解,以后还是要多思考多学习呀。

发布了46 篇原创文章 · 获赞 62 · 访问量 30万+

猜你喜欢

转载自blog.csdn.net/qq_27512671/article/details/82804244