如何更了解hanlder从这些面试问题中更容易掌握;
handler作用,更新UI,延时任务;
一,说下 handler 机制?
答:它主要依靠一下四个类的:
- Handler:用来发送消息和接收消息;
- Message:是容纳任意数据的容器;
- MessageQueue: 无界的, 单向链表的消息体对象 ;它按照时序将消息插入队列,最小的时间
戳(开机时间多久执行时间)将会被首先处理;
- Looper:轮询器,它会调用Looper.loop();无限循环取消息;
流程:
- 消息如何加入队列的,handler.sendMessage(msg),最终由MessageQueue中的queue.enqueueMessage(msg)加入队列;
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
- 主线程会Looper.prepareMainLooper().Looper.loop()执行死循环去获取消息;
//App的打开是从ActivityThread的Main方法开始的
public static void main(String[] args) {
···
//创建Loop对象
Looper.prepareMainLooper();
//Looper走你!
Looper.loop()
···
}
for (;;) {
Message msg = queue.next(); // might block
}
3,消息获取,没有消息的时候根据nextPollTimeoutMillis时间进行休眠(这里可以理解为休眠),让出cpu;
Message next() {
...代码省略..
int nextPollTimeoutMillis = 0;
for (;;) {
...代码省略..
// nextPollTimeoutMillis为休眠时间, 没有消息时为-1为无限时间;
nativePollOnce(ptr, nextPollTimeoutMillis);
}
二,假设发送的消息是一个小时,几分钟后,我再次发送一个消息无延时消息,他都进入阻塞了岂不是发不出去了?
答:发送新消息时,在加入队列过程中, 会调用nativeWake()进行唤醒;从而从新计算休眠阻塞时间;
boolean enqueueMessage(Message msg, long when) {
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
三,Looper 通过 MessageQueue 取消息,消息队列是先进先出模式,那我延迟发两个消息,第一个消息延迟2个小时,第二个消息延迟1个小时,那么第二个消息需要等3个小时才能取到吗?
答:MessageQueue 消息入队的方式会有一个when时间,它是时间戳+延时时间组成;然后会根据时间大小链式结构排序;取消息的时候,每次都是会取队头 Message时间处理;
时间延时多久是SystemClock.uptimeMillis() + delayMillis;
- SystemClock.uptimeMillis()是指从开机到现在的毫秒数
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
// 这里是发送消息最后的时间确定方法
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
- 队头的消息大于目标消息,排对头
- 遍历messgeque找到插入的位置;
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;
}
取消息逻辑
Message next() {
synchronized (this) {
final long now = SystemClock.uptimeMillis();
if (msg != null) {
if (now < msg.when) { // 时间未到,计算执行nextPollTimeoutMillis 时间
// 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;
}
}
四,handler 是死循环的,为什么不会卡死?
nativePollOnce(ptr, nextPollTimeoutMillis);时,进行阻塞,可以理解为休眠,linux底层实现,让出CPU;
-
1.如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
-
2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
-
3.如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。
五,Handler的post和sendMessage的区别?
handler.post和handler.sendMessage本质上是没有区别的,都是发送一个消息到消息队列中,而且消息队列和handler都是依赖于同一个线程的。
为什么没有区别呢?看源码
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
最终他们都是调的同一个方法;只是它把runnable转化成了一个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);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
六,在子线程中如何使用Looper 和 Handler?
Handler mHandler;
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();//Looper初始化
//Handler初始化 需要注意, Handler初始化传入Looper对象是子线程中缓存的Looper对象
mHandler = new Handler(Looper.myLooper());
Looper.loop();//死循环
//注意: Looper.loop()之后的位置代码在Looper退出之前不会执行,(并非永远不执行)
}
}).start();
mHandler.getLooper().quit();//终止 Looper.looper() 死循环, 执行 quit后Handler机制将失效, 执行时如
//果MessageQueue中还有Message未执行, 将不会执行未执行Message, 直接退出, 调用quit后将不能发消息给Handler
mHandler.getLooper().quitSafely();//Android4.3, API Level 18 后加入的方法, 作用同 quit(), 但终止
//Looper之前会先执行完消息队列中所有非延时任务, 调用quit后将不能发消息给Handler
七,Handler引起的内存泄漏以及解决办法?
在java中,非静态的内部类和匿名内部类都会隐式的持有一个外部类的引用,静态内部类则不会持有外部类的引用。当 Activity finish 时,Handler可能并未执行完,从而引起 Activity 的内存泄漏;
- 如果你需要在Handler内部调用外部Activity的方法,你可以让这个Handler持有这个Activity的弱引用,这样便不会出现内存泄漏的问题了
public class SampleActivity extends Activity {
/**
* Instances of static inner classes do not hold an implicit
* reference to their outer class.
* 弱引用的方式
*/
private static class MyHandler extends Handler {
private final WeakReference<SampleActivity> mActivity;
public MyHandler(SampleActivity activity) {
mActivity = new WeakReference<SampleActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
SampleActivity activity = mActivity.get();
if (activity != null) {
//to Something
}
}
}
- 对于匿名类Runnable,我们同样可以设置成静态的,因为静态内部类不会持有外部类的引用。
//定义成static的,因为静态内部类不会持有外部类的引用
private final MyHandler mHandler = new MyHandler(this);
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() {//to something}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
finish();
}
}
这篇文章的这道题能更好的明白(里面有一小点的代码错误)标题
八, MessageQueue是什么时候创建的?
主线程创建的时候,调用prepareMainLooper,创建Looper时同时会创建 MessageQueue;
九,ThreadLocal在Handler机制中的作用?
保证Looper的唯一,在Looper准备的时候,它是以ThreadLocal保存的,ThreadLocal有是key,value保存的,key是指它所在的线程。
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));
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
十,MessageQueue里面的数据结构是什么类型的,为什么不是Map或者其他什么类型的?
十一,说说handle用到的享元模式?
我们去获取Message对象时,是从消息池中取的,消息使用完成后回收到消息池中;
public static Message obtain() {
synchronized (sPoolSync) {
/**请注意!我们可以看到Message中有一个next字段指向下一个Message,这里就明白了,Message消息池中
没有使用Map这样的容器,而是使用的链表!
*/
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();
}
这遍文章讲的很详细了
https://blog.csdn.net/qq_33768280/article/details/81160502
网友评论