之前写了一篇文章讲解Handler机制,但是我觉得理论总是不比实践来的深刻,所以今天我们来自己实现一个Handler。如果没看过之前的文章,建议先看一下Android Handler详解–理论篇。
首先实现MyMessageQueue,当然在这之前我们要实现MyMeessage类
public class MyMessage {
public MyHandler target;
}
为了方便,我只写了一个Handler成员变量
接下来就是MyMessageQueue的实现
public class MyMessageQueue {
private volatile List queue;
public MyMessageQueue() {
queue = new ArrayList<>();
}
public void add(MyMessage message) {
queue.add(message);
}
/**
* 取出队头的消息并出队,队列位空的时候阻塞
* @return
*/
public MyMessage next() {
while (true) {
if (!queue.isEmpty()) {
break;
}
}
MyMessage message = null;
synchronized (MyMessageQueue.this) {
message = queue.get(0);
queue.remove(0);
}
return message;
}
}
代码很简单,有两个方法,入队和出队,对列为空的时候出队方法next()会被阻塞直到有新的消息进来。
MyLooper的实现依赖ThreadLocal,我们提供prepare()、getLooper()、loop()三个静态方法,在内部维护一个静态的ThreadLocal< MyLooper >变量,通过这个变量设置或获取当前线程的Looper实例。代码如下
public class MyLooper {
private static ThreadLocal sLooper = new ThreadLocal<>();
public MyMessageQueue queue;
public MyLooper() {
queue = new MyMessageQueue();
}
public static void prepare() {
if (sLooper.get() == null) {
sLooper.set(new MyLooper());
}
}
public static MyLooper getLooper() {
return sLooper.get();
}
public static void loop() {
if (sLooper.get() == null) {
throw new RuntimeException("no looper exist");
}
while (true) {
MyMessage myMessage = sLooper.get().queue.next();
//Log.d("Debug", myMessage.toString());
myMessage.target.handleMessage(myMessage);
break;
}
}
}
在Looper内部维护一个MessageQueue变量,在loop()方法中无限循环查询queue,有消息就调用Message.target.handleMessage()。但是我们这里在取出一条消息处理之后就跳出循环了,这是为什么呢?不要着急,下面会详细解释,虽然只取一条,但是也足够展示在子线程更新UI的效果了。
MyHandler持有当前线程的Looper,在创建的时候根据当前线程获得,如果当前线程没有Looper则报错。在sendMessage方法中将Message的target赋值,然后入队。
public abstract class MyHandler {
private MyLooper looper;
private MyMessageQueue queue;
public MyHandler() {
looper = MyLooper.getLooper();
if (looper == null) {
throw new RuntimeException("current thread does not have looper");
}
queue = looper.queue;
}
public void sendMessage(MyMessage message) {
message.target = MyHandler.this;
queue.add(message);
}
public abstract void handleMessage(MyMessage message);
}
好了,现在我们在布局文件中放一个TextView显示Hello,World
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:layout_centerInParent="true"/>
RelativeLayout>
MainActivity代码如下
public class MainActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.text);
MyLooper.prepare();
final MyHandler myHandler = new MyHandler() {
@Override
public void handleMessage(MyMessage message) {
textView.setText("change");
}
};
new Thread(new Runnable() {
@Override
public void run() {
myHandler.sendMessage(new MyMessage());
}
}).start();
MyLooper.loop();
}
}
虽然我们完成了子线程更新UI,但是还是有一些问题的,假设我们将Looper的loop方法设为无限循环,而不是取到一条消息就退出循环,那会怎么样?其实都不用试,想一下就知道那主线程就被阻塞了,无法响应用户操作,然后报ANR错误。这样另一个问题就出来了,那为什么主线程原本的Looper不会阻塞呢?他的源码里也是无限循环啊?接下来根据源码来说明一下这个问题。
大家都知道java程序有一个入口点public static void main(String[] args),那既然Android是用java写的,大家一直就不疑惑入口点在哪吗?其实android程序的入口点在ActivityThread这个类当中,代码如下
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("" );
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
我们可以看到在代码的最后开启了loop,这里就无限循环了,假设一下,如果没有无限循环,那么main方法就结束了,那程序不就结束了么。。。。
Android是基于事件驱动的,也就是说我们的整个应用就是运行在这个最大的Looper循环中的,比如现在系统有一个Activity,在某一情况下需要调用他的onPause方法,怎么办?系统会通过本身的Handler发送一个消息,然后这个Looper获取然后处理(也就是调用Activity的onPause回调方法)。再比如现在啥都不干,那系统就阻塞着,也不会ANR,直到我们有什么动作(比如有触摸事件产生),那么Looper就继续处理。
那ANR在什么情况下会发生?ANR只会在handler处理一条消息超过一定时间才会发生,而不是阻塞就会发生(系统基于事件驱动,说明有事件才会动,没事件就阻塞)。