Handler相关概念
什么Handler
- Handler允许您发送和处理与线程的MessageQueue关联的Message和Runnable对象。
- 每个Handler实例都与一个线程和该线程的消息队列相关联。
- 当你创建一个新的Handler时,它被绑定到正在创建它的线程的线程消息队列。
- 从那时起,它将message和runnable传递给该消息队列并在它们出现时执行。
Handler有两个主要用途:
- 在未来的某个时刻发送message和执行runnables;
- 2.将一个action放入消息队列,而不是你自己的线程。
post和sendMessage
-
和消息调度相关的方法post,postAtTime,postDelayed,sendEmptyMessage,sendMessage,sendMessageAtTime,sendMessageDelayed。
post是将Runnable对象放入message queue去调用; sendMessage是将一个包含数据的Message对象放入message queue,最后交给重写了handleMessage的Handler处理。 -
使用post和sendMessage,可以在message queue就绪时处理或者延时处理。延时处理可以做一些超时、倒计时和其他时间相关的行为。
原理
当为您的应用程序创建进程时,其主线程运行了一个消息队列,负责管理顶级应用程序对象(activity,broadcastReceiver等)和他们创建的window。您可以创建自己的线程,并通过Handler与主线程进行通信。这是通过像以前一样调用相同的post或sendMessage方法来完成的,但是来自你的新线程。然后,将在Handler的消息队列中调度给定的Runnable或Message ,并在适当时进行处理。
构造方法干了啥
public Handler(Callback callback, boolean async) {
//潜在内存泄漏判断
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
获取Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
//mLooper消息队列赋值
mQueue = mLooper.mQueue;
//自定义回调实现
mCallback = callback;
//是否同步,默认同步
mAsynchronous = async;
}
问题:
1.为什么Looper.myLooper()可能为空?
/**
* 返回和当前线程关联的Looper。如果没有关联,返回null
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
looper顶部的注释有写到:
- Looper是给线程循环消息用的。
- 线程默认是没有Looper和他们关联的,在线程中调用prepare创建一个与线程关联的looper执行消息循环,调用loop处理消息,直到循环停止。
所以在新线程需要这么用:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
2.为什么主线程就可以直接创建Handler,主线程什么时候创建了与之关联的Looper ?
我们可以看到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) {
//如果sMainLooper已经创建,再手动创建就报错啦
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
注释中说这个main looper是android环境创建的,我们看看是哪里调用的
public static void main(String[] args) {
//......................
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
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");
}
可以看到是在ActivityThread的main方法中调用了Looper.prepareMainLooper()和Looper.loop(),
而这个main方法就是应用程序的入口,即应用程序创建时就给主线程创建了main looper,所以我们就可以直接在activity创建handler。
3.发送消息时,new message和obtainMessage有什么区别
看下源码
/**
* Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
* creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
* If you don't want that facility, just call Message.obtain() instead.
*/
public final Message obtainMessage()
{
return Message.obtain(this);
}
从全局message pool中返回一个message。比创建分配新实例更加高效,因为避免了创建过多实例。返回的message会将handler设置给这个实例(Message.target == this)。如果您不想要该设置,就调用Message.obtain()。
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;//这里把handler赋值给message的target
return m;
}
实际调用还是在Message
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
可以判断出sPool是一个链表结构,sPool本身就是Message。
我们来看看message的成员
public final class Message implements Parcelable {
// sometimes we store linked lists of these things
/*package*/ Message next;
private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;
}
可以看出sPool就是一个全局的链表结构的消息池,next记录链表中的下一个元素,sPoolSize记录链表长度,MAX_POOL_SIZE表示链表的最大长度为50。
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
message对象回收时,会将对象的属性置空,小于50会放入sPool中,否则交给gc处理。
4.为什么Android要设计只能通过Handler机制更新UI?
最根本的目的就是解决多线程并发问题。
假设如果在一个Activity当中,有多个线程去更新UI,并且都没有加锁机制,那么会产生什么样的问题?(更新界面错乱)如果对更新UI的操作都进行加锁处理,又会产生什么样的问题?(性能下降)
基于对以上目的的考虑,android给我们提供了一套更新UI的机制,我们只需遵循这样的机制,无需考虑多线程问题。
5.为什么handler会带来内存泄漏?如何避免?
原因
- 被延时处理的 message 持有 Handler 的引用,Handler 持有对 Activity 的引用,形成了message – handler – activity 这样一条引用链,导致 Activity 的泄露
如何避免
-
onDestory的时候移除message和runnable
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
} -
静态内部类+弱引用
public static class MyHandler extends Handler {
WeakReference<Activity> mActivityReference;MyHandler(Activity activity) { mActivityReference= new WeakReference<Activity>(activity); } @Override public void handleMessage(Message msg) { final Activity activity = mActivityReference.get(); }
}
6.什么是ThreadLocal
- ThreadLocal用于实现在不同的线程中存储线程私有数据的类。
- 在多线程的环境中,当多个线程需要对某个变量进行频繁操作,同时各个线程间不需要同步,此时,各个子线程只需要对存储在当前线程中的变量的拷贝进行操作即可,程序的运行效率会很高,即所谓的空间换时间。
- ThreadLocal实现线程本地存储的原理:
- 在当前线程中调用get方法时,通过ThreadLocal的initialValue方法创建当前线程的一个本地数据拷贝,将此拷贝添加到当前线程本地数据的table数组当中;
- 或者在调用set方法时,将当前线程的本地数据存储到当前线程的table数组中.
- 当前线程通过调用ThreadLocal对象的get方法即得到当前线程本地数据对象。