IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android全面解析Handler -> 正文阅读

[移动开发]Android全面解析Handler

前言:

由于HandlerBinder是Android开发的俩大利器之一,所以有必要来深入讲解一下Handler,关于Binder可以参考我上一篇博客《IPC机制 Binder》,废话不多说,今天我将图文并茂,一节一节解剖Handler,一节一节的总结Handler相关知识点。

Handler学习大纲
1:定义
2:作用
3:为什么要使用Handler消息传递机制
4:Handler必须掌握的相关概念
5:工作原理和源码解析
6:Handler的延伸
7:总结

一:Handler的定义

Handler的定义我觉得可以用一句话高度概括,那就是:Handler是Android提供的一套完整的消息传递机制。

二:Handler的作用

我们都知道,在Android种,一般情况下View是不能在工作线程更新的,除了surfaceview(特殊),那么如果我们非要在工作线程中更新UI那该怎么办了,那只能借助handler了,所以handler的作用就是:在多线程的应用场景中,将工作线程中需更新UI的操作信息传递到UI主线程,从而间接的实现工作线程对UI的更新处理,最终实现异步消息的处理。

更新UI的相关信息
传递工作线程更新UI的相关信息
在主线程更新UI
工作线程
Handler
主线程
更新结束

三:为什么要使用Handler消息传递机制

我们既然知道了使用handler可以间接实现工作线程对UI的更新,那你是否奇怪,为什么更新非要使用Handler,而不能是其它。那是因为,多个线程并发更新UI的同时要保证线程安全。详细描述见下表(图片来自:https://www.jianshu.com/p/f0b23ee5a922)
图片来自:https://www.jianshu.com/p/f0b23ee5a922

四:Handler必须掌握的相关概念

关于Handler的先关概念主要包括如下4大点。即 HandlerMessageMessage QueueLooper,希望大家先熟悉相关概念,下图大致高度介绍相关概念:(图片来自:https://www.jianshu.com/p/f0b23ee5a922)
在这里插入图片描述

五:Handler工作原理和源码分析

在第四步我们主要明白了几个相关概念,接下来,会深层次的揭破他们的源码,从源码里搞清他们之间的工作关系和工作原理。

5.1:Handler Looper Message 关系是什么?

5.1.1分析Handler

首先我们来分析分析一下 Handler 的用法,我们知道,要创建一个 Handler 对象
非常的简单明了,直接进行 new 一个对象即可,但是这里会隐
藏着什么注意点呢。现在可以试着写一下下面的一小段代码,然后自己运行看看:

public class MainActivity extends AppCompatActivity{
private Handler mHandler0;
private Handler mHandler1;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler0 = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
mHandler1 = new Handler();
}
}).start();
}

这一小段程序代码主要创建了两个 Handler 对象,其中,一个在主线程中创建,
而另外一个则在子线程中创建,现在运行一下程序,则你会发现,在子线程创建
的Handler对象竟然会导致程序直接崩溃。奔溃异常信息如下:

2021-07-23 12:06:19.034 14393-14451/com.pcl.lpr.debug E/AndroidRuntime: FATAL EXCEPTION: Thread-10
    Process: com.pcl.lpr.debug, PID: 14393
    java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-10,5,main] that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:207)
        at android.os.Handler.<init>(Handler.java:119)
        at com.pcl.ocr.ui.TestActivity$1.run(TestActivity.java:23)
        at java.lang.Thread.run(Thread.java:919)

提示的错误竟然是:Can't create handler inside thread that has not called Looper.prepare()
于是我们按照 logcat 中所说,在子线程中加入 Looper.prepare(),即代码如下:

public class TestActivity extends AppCompatActivity {
    private Handler mHandler0;
    private Handler mHandler1;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        mHandler0 = new Handler();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                mHandler1 = new Handler();
            }
        }).start();
        
    }
}

再次运行一下程序,发现程序不会再崩溃了,可是,单单只加这句Looper.prepare()是否就能解决问题了。我们探讨问题,就要知其然,才能了解得更多。我们还是先分析一下源码吧,看看为什么在子线程中没有加Looper.prepare()就会出现崩溃,而主线程中为什么不用加这句代码?我们看下Handler()构造函数:

public Handler() {
this(null, false);
}

构造函数非常简单,直接调用 this(null, false),于是接着看其调用的函数:

public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || kla
ss.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static o
r leaks might occur: " +
klass.getCanonicalName());
 }
}

mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Lo
oper.prepare()");
}
mQueue = mLooper.mQueue;mCallback = callback;
mAsynchronous = async;
}

不难看出,源码中调用了 mLooper = Looper.myLooper()方法获取一个 Looper对象,若此时 Looper 对象为 null,则会直接抛出一个“Can't create handlerinside thread that has not called Looper.prepare()”异常,那什么时候造成 mLooper 是为空呢?那就接着分析 Looper.myLooper()

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

这个方法在 sThreadLocal 变量中直接取出 Looper 对象,若 sThreadLocal 变量中存在 Looper 对象,则直接返回,若不存在,则直接返回 null,而 sThreadLocal变量是什么呢?

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

它是本地线程变量,存放在 Looper 对象,由这也可看出,每个线程只有存有一个 Looper 对象,可是,是在哪里给 sThreadLocal 设置 Looper 的呢,通过前面的试验,我们不难猜到,应该是在Looper.prepare()方法中,现在来看看它的源码:

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

由此看到,我们的判断是正确的,在 Looper.prepare()方法中给 sThreadLocal变量设置 Looper 对象,这样也就理解了为什么要先调用 Looper.prepare()方法,才能创建 Handler 对象,才不会导致崩溃。但是,仔细想想,为什么主线程就不用调用呢?不要急,我们接着分析一下主线程,我们查看一下ActivityThread中的 main()方法,代码如下:

public static void main(String[] args) {
		SamplingProfilerIntegration.start();// CloseGuard defaults to true and can be quite spammy
		// 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());
		Security.addProvider(new AndroidKeyStoreProvider());
		
		// Make sure TrustedCertificateStore looks in the right place for CAcertificates
		final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
		TrustedCertificateStore.setDefaultUserDirectory(configDir);
		Process.setArgV0("<pre-initialized>");
		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"));
		}
		
		Looper.loop();
		throw new RuntimeException("Main thread loop unexpectedly exited");
}

代码中调用了 Looper.prepareMainLooper()方法,而这个方法又会继续调用了Looper.prepare()方法,代码如下:

public static void prepareMainLooper() {
	prepare(false);
	synchronized (Looper.class) {
	if (sMainLooper != null) {
	  throw new IllegalStateException("The main Looper has already been prepared.");
	}	
	sMainLooper = myLooper();
}}

分析到这里已经真相大白,主线程中 google 工程师已经自动帮我们创建了一个Looper 对象了,因而我们不再需要手动再调用 Looper.prepare()再创建,而子线程中,因为没有自动帮我们创建 Looper 对象,因此需要我们手动添加,调用方法是 Looper.prepare(),这样,我们才能正确地创建 Handler 对象。

5.1.2发送消息

当我们正确的创建 Handler 对象后,接下来我们来了解一下怎么发送消息,有一点基础的朋友肯定对这个方法已经了如指掌了。具体是先创建出一个 Message对象,然后可以利用一些方法,如 setData()或者使用 arg 参数等方式来存放数据于消息中,再借助 Handler 对象将消息发送出去就可以了。

new Thread(new Runnable() {
	@Override
	public void run() {
		Message msg = Message.obtain();
		msg.arg1 = 1;
		msg.arg2 = 2;
		Bundle bundle = new Bundle();
		bundle.putChar("key", 'v');
		bundle.putString("key","value");
		msg.setData(bundle);
		mHandler0.sendMessage(msg);
		}
	}).start();

通过 Message 对象进行传递消息,在消息中添加各种数据,之后消息通过mHandler0 进行传递,之后我们再利用 Handler 中的 handleMessage()方法将此时传递的 Message 进行捕获出来,再分析得到存储在 msg 中的数据。但是,这个流程到底是怎么样的呢?具体我们还是来分析一下源码。首先分析一下发送方法sendMessage():

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

通过调用 sendMessageDelayed(msg, 0)方法:

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

再能过调用 sendMessageDelayed(Message msg, long delayMillis),方法中第一个参数是指发送的消息 msg,第二个参数是指延迟多少毫秒发送,我们着重看一下此方法:

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

由这里可以分析得出,原来消息 Message 对象是建立一个消息队列 MessageQueue, 这个对象MessageQueuemQueue 赋值,而由源码分析得出 mQueue =mLooper.mQueue,而 mLooper 则是 Looper 对象,我们由上面已经知道,每个线程只有一个 Looper,因此,一个 Looper 也就对应了一个 MessageQueue 对象,之后调用 enqueueMessage(queue, msg, uptimeMillis)直接入队操作:

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

方 法 通 过 调 用 MessageQueueenqueueMessage(Message msg, long uptimeMills)方法:

boolean enqueueMessage(Message msg, long when) {
	if (msg.target == null) {
    	throw new IllegalArgumentException("Message must have a target.");
	}
	
	if (msg.isInUse()) {
       throw new IllegalStateException(msg + " This message is already in use.");
	}


	synchronized (this) {
		if (mQuitting) {
		  IllegalStateException e = new IllegalStateException(msg.target + " sending message to a Handler on a dead thread");
		  Log.w("MessageQueue", e.getMessage(), e);msg.recycle();
		   return false;
		}
	msg.markInUse();
	msg.when = when;
	Message p = mMessages;
    boolean needWake;
    
	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;
	}
	   // We can assume mPtr != 0 because mQuitting is false.
		if (needWake) {
		nativeWake(mPtr);
		}
	}
   return true;
}

首先要知道,源码中用 mMessages 代表当前等待处理的消息,MessageQueue 也没有使用一个集合保存所有的消息。观察中间的代码部分,队列中根据时间 when来时间排序,这个时间也就是我们传进来延迟的时间 uptimeMills 参数,之后再根据时间的顺序调用 msg.next,从而指定下一个将要处理的消息是什么。如果只是通过 sendMessageAtFrontOfQueue()方法来发送消息

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

它也是直接调用 enqueueMessage()进行入队,但没有延迟时间,此时会将传递的此消息直接添加到队头处,现在入队操作已经了解得差不多了,接下来应该来了解一下出队操作,那么出队在哪里进行的呢,不要忘记 MessageQueue 对象是在 Looper 中赋值,因此我们可以在 Looper 类中找,来看一看Looper.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 quitting.
		    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);
	}
    msg.target.dispatchMessage(msg);
	if (logging != null) {
	   logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
	}
  // 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();

}
}

代码比较多,我们只挑重要的分析一下,我们可以看到下面的代码用 for(;;)进入了一个死循环,之后不断的从 MessageQueue 对象 queue 中取出消息 msg,而我们不难知道,此时的 next()就是进行队列的出队方法,next()方法代码有点长,有兴趣的话可以自行翻阅查看,主要逻辑是判断当前的 MessageQueue 是否存在待处理的 mMessages 消息,如果有,则将这个消息出队,然后让下一个消成为 mMessages,否则就进入一个阻塞状态,一直等到有新的消息入队唤醒。回看 loop()方法,可以发现当执行 next()方法后会执行msg.target.dispatchMessage(msg)方法,而不难看出,此时 msg.target 就是Handler 对象,继续看一下 dispatchMessage()方法:

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

先进行判断 mCallback 是否为空,若不为空则调用 mCallbackhandleMessage()方法,否则直接调用 handleMessage()方法,并将消息作为参数传出去。这样我们就完全一目了然,为什么我们要使用handleMessage()来捕获我们之前传递过去的信息。现在我们根据上面的理解,不难写出异步消息处理机制的线程了。

class myThread extends Thread{
	public Handler myHandler;
	@Override
	public void run() {
		Looper.prepare();
		myHandler = new Handler(){
			@Override
			public void handleMessage(Message msg) {
			super.handleMessage(msg);
			//处理消息
			}
		};
	   Looper.loop();
	}
}

当然除了发送消息外,还有以下几个方法可以在子线程中进行 UI 操作:

  1. View 的 post()方法
  2. Handler 的 post()方法
  3. Activity 的 runOnUiThread()方法

其实这几个方法的本质都是一样的,只要我们勤于查看这几个方法的源码,不难
看出最后调用的也是 Handler 机制,也是借用了异步消息处理机制来实现的。

5.1.3:总结

通过上面对异步消息处理线程的讲解,我们不难真正地理解到了 HandlerLooper以及 Message 之间的关系,概括性来说,Looper 负责的是创建一个 MessageQueue对象,然后进入到一个无限循环体中不断取出消息,而这些消息都是由一个或者多个 Handler 进行创建处理。

5.2:Messagequeue 的数据结构是什么?为什么要用这个数据结构?

5.2.1:为什么要用 Message Queue

为什么要用 Message Queue,我觉得要从一下十个方面考虑:

  1. 解耦
    在项目启动之初来预测将来项目会碰到什么需求,是极其困难的。消息队列在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口。这允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束.
  2. 冗余
    有些情况下,处理数据的过程会失败。除非数据被持久化,否则将造成丢失。消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。在被许多消息队列所采用的”插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理过程明确的指出该消息已经被处理完毕,确保你的数据被安全的保存直到你使用完毕。
  3. 扩展性
    因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的;只要另外增加处理过程即可。不需要改变代码、不需要调节参数。扩展就像调大电力按钮一样简单。
  4. 灵活性 & 峰值处理能力
    在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见;如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全。
    崩溃。
  5. 可恢复性
    当体系的一部分组件失效,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。而这种允许重试或者延后处理请求的能力通常是造就一个略感不便的用户和一个沮丧透顶的用户之间的区别。
  6. 送达保证
    消息队列提供的冗余机制保证了消息能被实际的处理,只要一个进程读取了该队列即可。在此基础上,IronMQ 提供了一个”只送达一次”保证。无论有多少进程在从队列中领取数据,每一个消息只能被处理一次。这之所以成为可能,是因为获取一个消息只是”预定”了这个消息,暂时把它移出了队列。除非客户端明确的表示已经处理完了这个消息,否则这个消息会被放回队列中去,在一段可配置的时间之后可再次被处理。
  7. 顺序保证
    在大多使用场景下,数据处理的顺序都很重要。消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。IronMO 保证消息通过 FIFO(先进先出)的顺序来处理,因此消息在队列中的位置就是从队列中检索他们的位置。
  8. 缓冲
    在任何重要的系统中,都会有需要不同的处理时间的元素。例如,加载一张图片比应用过滤器花费更少的时间。消息队列通过一个缓冲层来帮助任务最高效率的执行—写入队列的处理会尽可能的快速,而不受从队列读的预备处理的约束。该缓冲有助于控制和优化数据流经过系统的速度。
  9. 理解数据流
    在一个分布式系统里,要得到一个关于用户操作会用多长时间及其原因的总体印象,是个巨大的挑战。消息系列通过消息被处理的频率,来方便的辅助确定那些表现不佳的处理过程或领域,这些地方的数据流都不够优化。
  10. 异步通信
    很多时候,你不想也不需要立即处理消息。消息队列提供了异步处理机制,允许你把
    一个消息放入队列,但并不立即处理它。你想向队列中放入多少消息就放多少,然后
    在你乐意的时候再去处理它们。

5.2.2:Messagequeue 的数据结构是什么?

基础数据结构中“先进先出”的一种数据结构

5.3:如何在子线程中创建 Handler?

在子线程中创建 handler,只要确保子线程有 Looper,UI 线程默认包含 Looper。此时,我们需要用到一个特殊类HandlerThread。这个类可以轻松的创建子线程 handler

创建步骤如下:

  1. 创建一个 HandlerThread,即创建一个包含 Looper 的线程
    HandlerThread 的构造函数有两个:
public HandlerThread(String name) {
   super(name);
   mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
/**
* Constructs a HandlerThread. * @param name * @param priority The priority to run the thread at. The value supplied must be from
* {@link android.os.Process} and not from java.lang.Thread. */
public HandlerThread(String name, int priority) {super(name);
    mPriority = priority;
}

这里我们使用第一个就好:

HandlerThread handlerThread=new HandlerThread(“xuan”);
//创建 HandlerThread 后一定要记得 start();
handlerThread.start();
//通过 HandlerThread 的 getLooper 方法可以获取 Looper
Looper looper=handlerThread.getLooper();
//通过 Looper 我们就可以创建子线程的 handler 了
Handlr handler=new Handler(looper);
通过该 handler 发送消息,就会在子线程执行;

提示:如果要 handlerThread 停止:handlerThread.quit()。完整测试代码:

HandlerThread hanlerThread = new HandlerThread("子线程");
     hanlerThread.start();
     
     final Handler handler = new Handler(hanlerThread.getLooper()) {
		@Override
			public void handleMessage(Message msg) {
			super.handleMessage(msg);
			Log.d("----->", "线程:" + Thread.currentThread().getName());
		}
     };

	findViewById(R.id.bt).setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
			   handler.sendEmptyMessage(100);
			}
	});

intentService(子线程)中,如果要回掉在 UI 线程怎么办呢?

new Handler(getMainLooper()).post(new Runnable() {
	@Override
	public void run() {
		// person.getName() Realm objects can only be accessed on the thread they were created.         Toast.makeText(getApplicationContext(), "Loaded Person from
		broadcast-receiver->intent-service: " + info, Toast.LENGTH_LONG).show();
	}
});

5.4:Handler post 方法原理?

5.4.1:源码分析

5.4.1.1:点进去看 postDelayed()中的方法。里面调用 sendMessageDelayed 方法,和post() 里面调用的方法一样。

public final boolean postDelayed(Runnable r, long delayMillis)
{
return sendMessageDelayed(getPostMessage(r), delayMillis);
}

5.4.1.2:分析sendMessageDelayed()方法

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

里面调用了 sendMessageAtTime(),这里的 SystemClock.uptimeMillis()是获取系统从开机启动到现在的时间,期间不包括休眠的时间,这里获得到的时间是一个相对的时间,而不是通过获取当前的时间(绝对时间)。而之所以使用这种方式来计算时间,而不是获得当前 currenttime 来计算,在于handler 会受到阻塞,挂起状态,睡眠等,这些时候是不应该执行的;如果使用绝对时间的话,就会抢占资源来执行当前 handler 的内容,显然这是不应该出现的情况,所以要避免。

5.4.1.3:分析sendMessageAtTime()方法

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

追到这里依然没有看到,他在存放的时候有什么不同,但是显然证实了消息不是延迟放进 MessageQueen 的,那是肿么处理的,是在轮训的时候处理的吗?

5.4.1.4:我们点进 Looper 中看一下,主要代码,Looper 中的 looper 调用了 MessageQueen中的 next 方法,难道是在 next()方法中处理的?

public static void loop() {
···
 for (;;) {
    Message msg = queue.next(); // might block
	if (msg == null) {
		// No message indicates that the message queue is quitting.
	    return;
	}
}		

5.4.1.5:分析MessageQueen 中的 next()方法

for (;;) {
	if (nextPollTimeoutMillis != 0) {
	    Binder.flushPendingCommands();
	}
   nativePollOnce(ptr, nextPollTimeoutMillis);
···
		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;
	 }
  } else {
     // No more messages.
     nextPollTimeoutMillis = -1;
  }
 }
}

很贴心的给出了注释解释:“ Next message is not ready. Set a timeout to wake up when it is ready,翻译“下一条消息尚未准备好。设置一个超时,以便在准备就绪时唤醒。”

when 就是 uptimeMillisfor (;;) 相当于 while(true),如果头部的这个 Message是有延迟而且延迟时间没到的(now < msg.when),不返回 message 而且会计算一下时间(保存为变量nextPollTimeoutMillis),然后再循环的时候判断如果这个 Message 有延迟,就调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞。nativePollOnce()的作用类似与 object.wait()。得出结论是通过阻塞实现的。

5.4.1.6:但是如果在阻塞这段时间里有无延迟 message 又加入MessageQueen 中又是怎么实现立即处理这个 message 的呢?我们看MessageQueen 中放入消息enqueueMessage()方法

boolean enqueueMessage(Message msg, long when) {
	if (msg.target == null) {
	    throw new IllegalArgumentException("Message must have a target.");
	}
	
	if (msg.isInUse()) {
    	throw new IllegalStateException(msg + " This message is already in use.");
	}
	
	synchronized (this) {
		if (mQuitting) {
			IllegalStateException e = new IllegalStateException(msg.target + " sending message to a Handler on a dead thread");
			Log.w(TAG, e.getMessage(), e);
			msg.recycle();
			return false;
		}
	msg.markInUse();
	msg.when = when;
	Message p = mMessages;
	boolean needWake;
	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 inthe 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;
}
	// We can assume mPtr != 0 because mQuitting is false.
	if (needWake) {
		nativeWake(mPtr);
		}
	}
   return true;
}

在这里 p 是现在消息队列中的头部消息,我们看到 when < p.when 的时候它交换了放入 message 与原来消息队列头部 P 的位置,并且 needWake = mBlocked;(在 next()中当消息为延迟消息的时候mBlocked=true),继续向下看 ,当needWake =true 的时候 nativeWake(mPtr)(唤起线程)一切都解释的通了,如果当前插入的消息不是延迟 message,或比当前的延迟短,这个消息就会插入头部并且唤起线程来。

好了,经过上面一系列的源码解析,我们把我们跟踪的所有信息整理下。

5.4.2:跟踪整理

跟踪的所有信息整理如下:

  1. 消息是通过 MessageQueen 中的 enqueueMessage()方法加入消息队列中的,并且它在放入中就进行好排序,链表头的延迟时间小,尾部延迟时间最大
  2. Looper.loop()通过 MessageQueue 中的 next()去取消息
  3. next()中如果当前链表头部消息是延迟消息,则根据延迟时间进行消息队列会阻塞,不返回给 Looper message,知道时间到了,返回给 message
  4. 如果在阻塞中有新的消息插入到链表头部则唤醒线程
  5. Looper 将新消息交给回调给 handler 中的 handleMessage 后,继续调用MessageQueennext()方法,如果刚刚的延迟消息还是时间未到,则计算时间继续阻塞

5.4.3:总结

handler.postDelay() 的实现 是通过 MessageQueue 中执行时间顺序排列,消息队列阻塞,和唤醒的方式结合实现的。如果真的是通过延迟将消息放入到 MessageQueen 中,那放入多个延迟消息就要维护多个定时器。

5.5:Android 消息机制的原理及源码解析

5.5.1:消息机制概括

5.5.1.1:消息机制的简介

Android 中使用消息机制,我们首先想到的就是 Handler。没错,HandlerAndroid 消息机制的上层接口。Handler 的使用过程很简单,通过它可以轻松地将一个任务切换到 Handler 所在的线程中去执行。通常情况下,Handler 的使用场景就是更新 UI。

下面简单看一个消息机制的一个简单实例:

public class Activity extends android.app.Activity {
	private Handler mHandler = new Handler(){
		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			System.out.println(msg.what);
		}
	};

@Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
   super.onCreate(savedInstanceState, persistentState);
   setContentView(R.layout.activity_main);
		new Thread(new Runnable() {
		@Override
		public void run() {
			//...............耗时操作
				Message message = Message.obtain();
				message.what = 1;
				mHandler.sendMessage(message);
			}
		}).start();
      }
}

在子线程中,进行耗时操作,执行完操作后,发送消息,通知主线程更新 UI。这便是消息机制的典型应用场景。我们通常只会接触到 HandlerMessage 来完成消息机制,其实内部还有两大助手来共同完成消息传递。

5.5.1.2:消息机制的模型

消息机制主要包含:MessageQueueHandlerLooper这三大部分,以及Message,下面我们一一介绍。Message:需要传递的消息,可以传递数据;MessageQueue:消息队列,但是它的内部实现并不是用的队列,实际上是通过一个单链表的数据结构来维护消息列表,因为单链表在插入和删除上比较有优势。主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next)Handler:消息辅助类,主要功能向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage)Looper:不断循环执行(Looper.loop),从 MessageQueue 中读取消息,按分发机制将消息分发给目标处理者。

5.5.1.3:消息机制的架构

消息机制的运行流程:

在子线程执行完耗时操作,当 Handler 发送消息时,将会调用MessageQueue.enqueueMessage,向消息队列中添加消息。当通过Looper.loop开启循环后,会不断地从线程池中读取消息,即调用 MessageQueue.next,然后调用目标 Handler(即发送该消息的 Handler)的 dispatchMessage 方法传递消息,然后返回到 Handler 所在线程,目标 Handler 收到消息,调用 handleMessage 方法,接收消息,处理消息。
在这里插入图片描述
MessageQueueHandlerLooper 三者之间的关系:每个线程中只能存在一个LooperLooper 是保存在 ThreadLocal 中的。主线程(UI 线程)已经创建了一个 Looper,所以在主线程中不需要再创建 Looper,但是在其他线程中需要创建Looper。每个线程中可以有多个 Handler,即一个 Looper 可以处理来自多个Handler 的消息。 Looper 中维护一个 MessageQueue,来维护消息队列,消息队列中的Message 可以来自不同的 Handler
在这里插入图片描述
下面是消息机制的整体架构图,接下来我们将慢慢解剖整个架构。
在这里插入图片描述
从中我们可以看出:Looper 有一个 MessageQueue 消息队列;MessageQueue 有一组待处理的 MessageMessage 中记录发送和处理消息的 HandlerHandler 中有 LooperMessageQueue

5.5.2:消息机制的源码解析

5.5.2.1:Looper

1:初始化Looper
要想使用消息机制,首先要创建一个 Looper。初始化 Looper,无参情况下,默认调用 prepare(true);表示的是这个 Looper 可以退出,而对于false 的情况则表示当前 Looper 不可以退出。

public static void prepare() {
   prepare(true);
}

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

这里看出,不能重复创建 Looper,只能创建一个。创建 Looper,并保存在ThreadLocal。其中 ThreadLocal 是线程本地存储区(Thread Local Storage,简称为 TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的 TLS 区域。
2:开启Looper

public static void loop() {
  final Looper me = myLooper(); //获取 TLS 存储的 Looper 对象
	if (me == null) {
	  throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
	}
  final MessageQueue queue = me.mQueue; //获取 Looper 对象中的消息队列
  Binder.clearCallingIdentity();
  final long ident = Binder.clearCallingIdentity();
  for (;;) { //进入 loop 的主循环方法
	     Message msg = queue.next(); //可能会阻塞,因为 next()方法可能会无限循环
		 if (msg == null) { //消息为空,则退出循环
		   return;
		}
		//默认为 null,可通过 setMessageLogging()方法来指定输出,用于debug 功能
        Printer logging = me.mLogging; 
		if (logging != null) {
		    logging.println(">>>>> Dispatching to " + msg.target + " " +
	    	msg.callback + ": " + msg.what);
		}
       msg.target.dispatchMessage(msg); //获取 msg 的目标 Handler,然后用于分发 Message
		if (logging != null) {
		logging.println("<<<<< Finished to " + msg.target + " " + msg.
		callback);
		}
		final long newIdent = Binder.clearCallingIdentity();
		if (ident != newIdent) {
		  -------
		}
       msg.recycleUnchecked();
   }
}

loop()进入循环模式,不断重复下面的操作,直到消息为空时退出循环:读取 MessageQueue 的下一条 Message(关于 next(),后面详细介绍);把 Message 分发给相应的 target。当 next()取出下一条消息时,队列中已经没有消息时,next()会无限循环,产生阻塞。等待 MessageQueue 中加入消息,然后重新唤醒。主线程中不需要自己创建 Looper,这是由于在程序启动的时候,系统已经帮我们自动调用了 Looper.prepare()方法。查看 ActivityThread 中的 main()方法,代码如下所示:

public static void main(String[] args) {..........................
	Looper.prepareMainLooper();
	..........................
	Looper.loop();
	..........................
}

其中prepareMainLooper()方法会调用 prepare(false)方法。

5.5.2.2:Handler

创建 Handler:

public Handler() {
   this(null, false);
}

public Handler(Callback callback, boolean async) {
.................................
//必须先执行 Looper.prepare(),才能获取 Looper 对象,否则为 null.
mLooper = Looper.myLooper(); //从当前线程的 TLS 中获取 Looper 对象
if (mLooper == null) {
    throw new RuntimeException("");
}

mQueue = mLooper.mQueue; //消息队列,来自 Looper 对象
mCallback = callback; //回调方法
mAsynchronous = async; //设置消息是否为异步处理方式}

对于 Handler 的无参构造方法,默认采用当前线程 TLS 中的 Looper 对象,并且callback 回调方法为 null,且消息为同步处理方式。只要执行的 Looper.prepare()方法,那么便可以获取有效的 Looper 对象。

5.5.2.3:发送消息

发送消息有几种方式,但是归根结底都是调用了 sendMessageAtTime()方法。在子线程中通过 Handlerpost()方式或 send()方式发送消息,最终都是调用了sendMessageAtTime()方法。

  1. post 方法
public final boolean post(Runnable r)
{
   return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean postAtTime(Runnable r, long uptimeMillis)
{
  return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
{
return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}
public final boolean postDelayed(Runnable r, long delayMillis)
{
  return sendMessageDelayed(getPostMessage(r), delayMillis);
}
  1. send 方法
public final boolean sendMessage(Message msg)
{
  return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessage(int what)
{
  return sendEmptyMessageDelayed(what, 0);
}
 public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
	Message msg = Message.obtain();
	msg.what = what;
   return sendMessageDelayed(msg, delayMillis);
}
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
{
	Message msg = Message.obtain();
	msg.what = what;
	return sendMessageAtTime(msg, uptimeMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
	if (delayMillis < 0) {
	   delayMillis = 0;
	}
   return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

就连子线程中调用 Activity 中的 runOnUiThread()中更新 UI,其实也是发送消息通知主线程更新 UI,最终也会调用 sendMessageAtTime()方法。

public final void runOnUiThread(Runnable action) {
	if (Thread.currentThread() != mUiThread) {
	   mHandler.post(action);
	} else {
	   action.run();
	}
}

如果当前的线程不等于 UI 线程(主线程),就去调用 Handlerpost()方法,最终会调用 sendMessageAtTime()方法。否则就直接调用 Runnable 对象的 run()方法。下面我们就来一探究竟,到底 sendMessageAtTime()方法有什么作用?

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
	//其中 mQueue 是消息队列,从 Looper 中获取的
	MessageQueue queue = mQueue;
		if (queue == null) {
			RuntimeException e = new RuntimeException(
			this + " sendMessageAtTime() called with no mQueue");
			Log.w("Looper", e.getMessage(), e);
		return false;
	}
   //调用 enqueueMessage 方法
   return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
     msg.target = this;
	if (mAsynchronous) {
	   msg.setAsynchronous(true);
	}
    //调用 MessageQueue 的 enqueueMessage 方法
    return queue.enqueueMessage(msg, uptimeMillis);
}

可以看到 sendMessageAtTime()方法的作用很简单,就是调用MessageQueueenqueueMessage()方法,往消息队列中添加一个消息。下面来看enqueueMessage()`方法的具体执行逻辑。

boolean enqueueMessage(Message msg, long when) {
	
	// 每一个 Message 必须有一个 target
	if (msg.target == null) {
	    throw new IllegalArgumentException("Message must have a target.");
	}
	
	if (msg.isInUse()) {
	   throw new IllegalStateException(msg + " This message is already in use.");
	}
	synchronized (this) {
		if (mQuitting) {
		    //正在退出时,回收 msg,加入到消息池
		    msg.recycle();
		    return false;
		}
		msg.markInUse();
		msg.when = when;
		Message p = mMessages;
		boolean needWake;
		
	if (p == null || when == 0 || when < p.when) {
		//p 为 null(代表 MessageQueue 没有消息) 或者 msg 的触发时间是队列中最早的,则进入该该分支
		msg.next = p;
		mMessages = msg;
		needWake = mBlocked;
	} else {
	//将消息按时间顺序插入到 MessageQueue。一般地,不需要唤醒事件队列,除非消息队头存在 barrier,并且同时 Message 是队列中最早的异步消息。
	
	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;
	prev.next = msg;
	}
	if (needWake) {
	   nativeWake(mPtr);
	}
 }
return true;
}

MessageQueue 是按照 Message 触发时间的先后顺序排列的,队头的消息是将要最早触发的消息。当有消息需要加入消息队列时,会从队列头开始遍历,直到找到消息应该插入的合适位置,以保证所有消息的时间顺序。

5.5.2.4:获取消息

当发送了消息后,在 MessageQueue 维护了消息队列,然后在 Looper 中通过 loop()方法,不断地获取消息。上面对 loop()方法进行了介绍,其中最重要的是调用了 queue.next()方法,通过该方法来提取下一条信息。下面我们来看一下 next()方法的具体流程。
dispatchMessage():

Message next() {
    final long ptr = mPtr;
	if (ptr == 0) { //当消息循环已经退出,则直接返回
    	return null;
	}
	
	int pendingIdleHandlerCount = -1; // 循环迭代的首次为-1
	int nextPollTimeoutMillis = 0;
    for (;;) {
		if (nextPollTimeoutMillis != 0) {
		    Binder.flushPendingCommands();
		}
		//阻塞操作,当等待 nextPollTimeoutMillis 时长,或者消息队列被唤醒,都会返回
		nativePollOnce(ptr, nextPollTimeoutMillis);
		synchronized (this) {
		final long now = SystemClock.uptimeMillis();
		Message prevMsg = null;
		Message msg = mMessages;
		if (msg != null && msg.target == null) {
		//当消息 Handler 为空时,查询 MessageQueue 中的下一条异步消息 msg,为空则退出循环。
		do {
			prevMsg = msg;
			msg = msg.next;
		} while (msg != null && !msg.isAsynchronous());
		}
		if (msg != null) {
			if (now < msg.when) {
			     //当异步消息触发时间大于当前时间,则设置下一次轮询的超时时长
			     nextPollTimeoutMillis = (int) Math.min(msg.when - now,Integer.MAX_VALUE);
			} else {
			    // 获取一条消息,并返回
			    mBlocked = false;
				if (prevMsg != null) {
				    prevMsg.next = msg.next;
				} 
				else {
				    mMessages = msg.next;
				}
			    msg.next = null;
			    //设置消息的使用状态,即 flags |= FLAG_IN_USE
			    msg.markInUse();
			    return msg; //成功地获取 MessageQueue 中的下一条即将要执行的消息
			}
		} else {
			//没有消息
			nextPollTimeoutMillis = -1;
		    }
			//消息正在退出,返回 null
			if (mQuitting) {
		    	dispose();
		    	return null;
			}
			...............................
		}}

nativePollOnce 是阻塞操作,其中 nextPollTimeoutMillis 代表下一个消息到来前,还需要等待的时长;当 nextPollTimeoutMillis = -1 时,表示消息队列中无消息,会一直等待下去。可以看出 next()方法根据消息的触发时间,获取下一条需要执行的消息,队列中消息为空时,则会进行阻塞操作。

5.5.2.5:分发消息

loop()方法中,获取到下一条消息后,执行 msg.target.dispatchMessage(msg),来分发消息到目标 Handler 对象。下面就来具体看下 dispatchMessage(msg)方法的执行流程。

public void dispatchMessage(Message msg) {
	if (msg.callback != null) {
	   //当 Message 存在回调方法,回调 msg.callback.run()方法;
	   handleCallback(msg);
	} else {
		if (mCallback != null) {
			//当 Handler 存在 Callback 成员变量时,回调方法 handleMessage();
			if (mCallback.handleMessage(msg)) {
		    	return;
			}
	}
	//Handler 自身的回调方法 handleMessage()
	handleMessage(msg);
}}

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

分发消息流程:
Messagemsg.callback 不为空时,则回调方法 msg.callback.run();当 HandlermCallback 不为空时,则回调方法 mCallback.handleMessage(msg);最后调用 Handler 自身的回调方法handleMessage(),该方法默认为空,Handler子类通过覆写该方法来完成具体的逻辑:

消息分发的优先级:
Message 的回调方法:message.callback.run(),优先级最高;HandlerCallback 的回调方法:Handler.mCallback.handleMessage(msg),优先级仅次于 1;Handler 的默认方法:Handler.handleMessage(msg),优先级最低。对于很多情况下,消息分发后的处理方法是第 3 种情况,即Handler.handleMessage(),一般地往往通过覆写该方法从而实现自己的业务逻辑。

5.5.3:总结

以上便是消息机制的原理,以及从源码角度来解析消息机制的运行过程。可以简单地用下图来理解:
在这里插入图片描述

六:Handler的延伸

Handler 虽然简单易用,但是要用好它还是需要注意一点,另外 Handler 相关 还有些鲜为人知的知识技巧,比如 IdleHandler。由于 Handler 的特性,它在 Android 里的应用非常广泛,比如: AsyncTask
HandlerThreadMessengerIdleHandlerIntentService 等等。这些我会讲解一些,我没讲到的可以自行搜索相关内容进行了解。

6.1 Handler 引起的内存泄露原因以及最佳解决方案

Handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该Activity 会泄露。这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露。解决该问题的最有效的方法是:将 Handler 定义成静态的内部类,在内部持有Activity 的弱引用,并及时移除所有消息。示例代码如下:

private static class SafeHandler extends Handler {
	private WeakReference<HandlerActivity> ref;
	public SafeHandler(HandlerActivity activity) {
	this.ref = new WeakReference(activity);
}
@Override
public void handleMessage(final Message msg) {
	HandlerActivity activity = ref.get();
	if (activity != null) {
	  activity.handleMessage(msg);
	}
 }
}

并且再在 Activity.onDestroy() 前移除消息,加一层保障:

@Override
protected void onDestroy() {
	safeHandler.removeCallbacksAndMessages(null);
	super.onDestroy();
}

这样双重保障,就能完全避免内存泄露了。
注意

单纯的在 onDestroy 移除消息并不保险,因为 onDestroy 并不一定执行。

6.2 为什么我们能在主线程直接使用 Handler,而不需要创建 Looper ?

前面我们提到了每个 Handler 的线程都有一个 Looper ,主线程当然也不例外,但是我们不曾准备过主线程的 Looper 而可以直接使用,这是为何?注意:通常我们认为 ActivityThread 就是主线程。事实上它并不是一个线程,而是主线程操作的管理者,所以吧,我觉得把 ActivityThread 认为就是主线程无可厚非,另外主线程也可以说成 UI 线程。在 ActivityThread.main() 方法中有如下代码:

//android.app.ActivityThread
public static void main(String[] args) {
	//...
	Looper.prepareMainLooper();
	ActivityThread thread = new ActivityThread();
	thread.attach(false);
	if (sMainThreadHandler == null) {
	    sMainThreadHandler = thread.getHandler();
	}
	//...
    Looper.loop();
      throw new RuntimeException("Main thread loop unexpectedly exited");
}


//Looper.prepareMainLooper(); 代码如下:
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
    prepare(false);
	synchronized (Looper.class) {
		if (sMainLooper != null) {
		   throw new IllegalStateException("The main Looper has already been prepared.");
		}
    	sMainLooper = myLooper();
	}}

可以看到在 ActivityThread 里 调用了 Looper.prepareMainLooper() 方法,创建了主线程的 Looper ,并且调用了 loop() 方法,所以我们就可以直接使用Handler 了。

注意:

Looper.loop() 是个死循环,后面的代码正常情况不会执行。

6.3 主线程的 Looper 不允许退出

如果你尝试退出 Looper,你会得到以下错误信息:

Caused by: java.lang.IllegalStateException: Main thread not allowed to q
uit.
at android.os.MessageQueue.quit(MessageQueue.java:415)
at android.os.Looper.quit(Looper.java:240)

why? 其实原因很简单,主线程不允许退出,退出就意味 APP 要挂,那样就没有任何意义了。

6.4 Handler 里藏着的 Callback 能干什么?

Handler 的构造方法中有几个 要求传入 Callback ,那它是什么,又能做什么呢?来看看 Handler.dispatchMessage(msg) 方法:

public void dispatchMessage(Message msg) {
	//这里的 callback 是 Runnable
	if (msg.callback != null) {
	    handleCallback(msg);
	} else {
		//如果 callback 处理了该 msg 并且返回 true, 就不会再回调 handleMessage
		if (mCallback != null) {
			if (mCallback.handleMessage(msg)) {
			    return;
			}
		}
       handleMessage(msg);
  }
}

可以看到 Handler.Callback 有优先处理消息的权利 ,当一条消息被 Callback 处理并拦截(返回 true),那么 Handler 的 handleMessage(msg) 方法就不会被调用了;如果 Callback 处理了消息,但是并没有拦截,那么就意味着一个消息可以同时被 Callback 以及 Handler 处理。

这个就很有意思了,这有什么作用呢?

我们可以利用 Callback 这个拦截机制来拦截 Handler 的消息!
场景:Hook ActivityThread.mH , 在 ActivityThread 中有个成员变量 mH ,它是个 Handler,又是个极其重要的类,几乎所有的插件化框架都使用了这个方法。

6.5 创建 Message 实例的最佳方式

由于 Handler 极为常用,所以为了节省开销,AndroidMessage 设计了回收机制,所以我们在使用的时候尽量复用 Message ,减少内存消耗。方法有二种:

  1. 通过 Message 的静态方法 Message.obtain();获取.
  2. 通过 Handler 的公有方法 handler.obtainMessage().

6.6 子线程里弹 Toast 的正确姿势

有时候遇到过这种情况,当我们尝试在子线程里直接去弹 Toast 的时候,会 crash

java.lang.RuntimeException: Can't create handler inside thread that has
not called Looper.prepare()

本质上是因为 Toast 的实现依赖于 Handler,按子线程使用 Handler 的要求修改即可,同理的还有 Dialog。正确示例代码如下:

new Thread(new Runnable() {
	@Override
	public void run() {
		Looper.prepare();
		Toast.makeText(HandlerActivity.this, "不会崩溃啦!", Toast.LENGTH_SHORT).show();
		Looper.loop();
	}
});

6.7 妙用 Looper 机制

我们可以利用 Looper 的机制来帮助我们做一些事情:

  1. 将 Runnable post 到主线程执行;
  2. 利用 Looper 判断当前线程是否是主线程。

完整示例代码如下:

public final class MainThread {
	private MainThread() {
	}
    private static final Handler HANDLER = new Handler(Looper.getMainLooper());
    public static void run(@NonNull Runnable runnable) {
		if (isMainThread()) {
		   runnable.run();
		}else{
		    HANDLER.post(runnable);
		}
    }
    
	public static boolean isMainThread() {
	   return Looper.myLooper() == Looper.getMainLooper();
	}
}

七:总结

  1. Handler 的 背 后 有 Looper 、 MessageQueue 支 撑 , Looper 负 责 消 息 分 发 ,MessageQueue 负责消息管理
  2. 在创建 Handler 之前一定需要先创建 Looper
  3. Looper 有退出的功能,但是主线程的 Looper 不允许退出
  4. 异步线程的 Looper 需要自己调用 Looper.myLooper().quit(); 退出
  5. Runnable 被封装进了 Message,可以说是一个特殊的 Message
  6. Handler.handleMessage() 所在的线程是 Looper.loop() 方法被调用的线程,也可
    以说成 Looper 所在的线程,并不是创建 Handler 的线程
  7. 使用内部类的方式使用 Handler 可能会导致内存泄露,即便在 Activity.onDestroy
    里移除延时消息,必须要写成静态内部类

更好的博客可以参考:Android Handler:图文解析 Handler通信机制 的工作原理。

请点赞关注加品论!因为你的鼓励是我写作的最大动力!

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-07-24 11:36:36  更:2021-07-24 11:37:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/2 1:19:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码