Android Handler 从使用到进阶全解析,阿里P8架构师的Android大厂面试题总结

tvTest.setText((String) msg.obj);

}

break;

}

return false;

}

});

private Handler mHandler2 = new Handler() {

@Override

public void handleMessage(@NonNull Message msg) {

switch (msg.what) {

case MESSAGA_TEST_1:

if (tvTest != null) {

//传递消息obj

tvTest.setText((String) msg.obj);

}

break;

}

}

};

private void onClick() {

//当点击事件执行,就会在子线程发送消息更新textview

new Thread(new Runnable() {

@Override

public void run() {

clickEndMessage();

}

}).start();

}

/**

  • 可以在子线程发送

*/

private void clickEndMessage() {

//obtain享元模式

Message message = Message.obtain(mHandler);

//what相当于标记

message.what = MESSAGA_TEST_1;

//obj传数据

message.obj = new String(“baozi”);

//普通发送message

mHandler.sendMessage(message);

.

.

.

//发送标记,不带其他内容,内部会封装成只有标记的Message对象

mHandler.sendEmptyMessage();

//尾部带有Delayed,发送延迟消息,单位毫秒

mHandler.sendMessageDelayed(message, 1000);

//尾部带有AtTime,发送消息的时间跟Delayed差别就是Delayed是执行的当前时间+传进去的时间,AtTime就传进去的绝对时间

mHandler.sendMessageAtTime();

//在队列头插入消息

mHandler.sendMessageAtFrontOfQueue(message);

}

@Override

protected void onDestroy() {

if(mHandler!=null){

//关闭activity时,移除消息

mHandler.removeMessages(MESSAGA_TEST_1);

mHandler = null;

}

super.onDestroy();

}

复制代码

2.3 view.post()

  • 比如view,post()、postDelayed() 方法,可以延迟五秒后更新UI,实际就是使用Handler。

tvTest.postDelayed(new Runnable() {

@Override

public void run() {

tvTest.setText(“5s”);

}

},5*1000);

/**

  • View 源码

*/

public boolean postDelayed(Runnable action, long delayMillis) {

final AttachInfo attachInfo = mAttachInfo;

if (attachInfo != null) {

return attachInfo.mHandler.postDelayed(action, delayMillis);

}

// Postpone the runnable until we know on which thread it needs to run.

// Assume that the runnable will be successfully placed after attach.

getRunQueue().postDelayed(action, delayMillis);

return true;

}

复制代码

2.4 runOnUiThread

  • 经常用的 runOnUiThread() 方法也是用Handler。

runOnUiThread(new Runnable() {

@Override

public void run() {

//更新UI

}

});

/**

  • Activity 源码

*/

public final void runOnUiThread(Runnable action) {

if (Thread.currentThread() != mUiThread) {

mHandler.post(action);

} else {

action.run();

}

}

复制代码

3.子线程中使用


3.1 子线程直接创建Handler错误

  • 子线程不能直接创建Handler,会报异常,因为Looper还没创建,而主线程默认就初始化好Looper

  • 应该先Looper.

private Handler handler2;

/**

  • 子线程

*/

private void thread() {

new Thread(new Runnable() {

@Override

public void run() {

handler2 = new Handler(new Handler.Callback() {

@Override

public boolean handleMessage(@NonNull Message msg) {

return false;

}

});

}

}).start();

}

复制代码

  • 提示的错误。

子线程创建Handler

3.2 主线程默认初始化Looper

  • ActivityThread 类就能主线程找到Looper初始化,Looper.prepareMainLooper();

public static void main(String[] args) {

.

.

Looper.prepareMainLooper();

.

.

ActivityThread thread = new ActivityThread();

.

.

}

复制代码

3.3 Handler构造方法查看

  • 构造方法中可以看到 mLooper = Looper.myLooper(); 获取的 mLooper 为null,就报上面的那个异常了。

public Handler(@Nullable Callback callback, boolean async) {

.

.

mLooper = Looper.myLooper();

if (mLooper == null) {

throw new RuntimeException(

"Can’t create handler inside thread " + Thread.currentThread()

  • " that has not called Looper.prepare()");

}

.

.

}

复制代码

3.4 子线程正确的创建

  • 先执行,Looper.prepare(); 初始化Looper,然后调用loop()方法,再创建Handler。

  • 每个线程**Looper.prepare();**只能调用一次,否则会报错。

private Handler handler2;

/**

  • 子线程

*/

private void thread() {

new Thread(new Runnable() {

@Override

public void run() {

Looper.prepare();

Looper looper = Looper.myLooper();

looper.loop();

handler2 = new Handler(looper, new Handler.Callback() {

@Override

public boolean handleMessage(@NonNull Message msg) {

return false;

}

});

}

}).start();

}

复制代码

4.Message


  • Message就是一个存放消息的类,是一个链表结构。

4.1 基本参数

public final class Message implements Parcelable {

//用于handler标记处理的

public int what;

//可以传递的int参数1

public int arg1;

//可以传递的int参数2

public int arg2;

//可以传递的obj参数

public Object obj;

//执行时间

public long when;

//传递的bundle

Bundle data;

//Message绑定的Handler

Handler target;

//Handler.post()时传的callback

Runnable callback;

//链表结构

Message next;

复制代码

4.2 享元模式obtain()

  • obtain() 可以重用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();

}

复制代码

4.3 回收recycle()

  • 如果发送的延迟消息,或者消息在执行,就会报错,一般我们不用调用recycle方法。

/**

  • Return a Message instance to the global pool.

  • You MUST NOT touch the Message after calling this function because it has

  • effectively been freed. It is an error to recycle a message that is currently

  • enqueued or that is in the process of being delivered to a Handler.

*/

public void recycle() {

if (isInUse()) {

if (gCheckRecycle) {

throw new IllegalStateException("This message cannot be recycled because it "

  • “is still in use.”);

}

return;

}

recycleUnchecked();

}

复制代码

  • Message用完并不是内存回收,只是把里面的内容清空,等下次复用。

  • 这个链表也不是无限的,最多就50个节点

private static final int MAX_POOL_SIZE = 50;

@UnsupportedAppUsage

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 = UID_NONE;

workSourceUid = UID_NONE;

when = 0;

target = null;

callback = null;

data = null;

synchronized (sPoolSync) {

//最多50个

if (sPoolSize < MAX_POOL_SIZE) {

next = sPool;

sPool = this;

sPoolSize++;

}

}

}

复制代码

5.MessageQueue


  • 这是一个阻塞队列。

  • 队列是在不停的for循环的,是一个死循环,那不就一直占用着cpu?所以就有了native的方法,处理休眠唤醒。

5.1 MessageQueue每个线程只有一个

  • 消息队列每个线程只有一个,跟Looper绑定在一起,在分析Looper时会一起分析。

5.2 消息入队

  • 前面我们知道Handler所有消息入队最后都是调用 enqueueMessage(Message msg, long when)

  • 判断插入消息的位置,还判断是否唤醒操作。

boolean enqueueMessage(Message msg, long when) {

//handler为空就报异常

if (msg.target == null) {

throw new IllegalArgumentException(“Message must have a target.”);

}

//加锁

synchronized (this) {

//消息正在使用会报错

if (msg.isInUse()) {

throw new IllegalStateException(msg + " This message is already in use.");

}

//判断线程是否存活

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) {

//如果队列为空,或者消息延迟时间为0,或者延迟时间小于mMessage的,就插入在头部

// 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;

}

复制代码

5.3 消息出队

  • 消息出队其实应该放在 Looper.loop() 里面分析更合适,这里先写,后面结合 loop() 一起看会更好。

  • 前面 return null; 看注释的意思是,如果looper已经退出和释放,就返回null。

  • 这里无限循环,就是一定要取到消息,有消息,阻塞时间为消息的执行时间减去当前时间,如果没消息就阻塞, nativePollOnce(ptr, nextPollTimeoutMillis)

  • 这 next() 方法只有一个地方返回msg,关注这里就行了。

@UnsupportedAppUsage

Message next() {

// Return here if the message loop has already quit and been disposed.

// This can happen if the application tries to restart a looper after quit

// which is not supported.

final long ptr = mPtr;

if (ptr == 0) {

return null;

}

int pendingIdleHandlerCount = -1; // -1 only during first iteration

int nextPollTimeoutMillis = 0;

for (;😉 {

if (nextPollTimeoutMillis != 0) {

Binder.flushPendingCommands();

}

//没消息取就阻塞

nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized (this) {

// Try to retrieve the next message. Return if found.

final long now = SystemClock.uptimeMillis();

Message prevMsg = null;

Message msg = mMessages;

if (msg != null && msg.target == null) {

// Stalled by a barrier. Find the next asynchronous message in the queue.

do {

prevMsg = msg;

msg = msg.next;

} while (msg != null && !msg.isAsynchronous());

}

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

//只有这里返回msg

return msg;

}

} else {

//没有更多消息,设置为-1,阻塞

// No more messages.

nextPollTimeoutMillis = -1;

}

.

.

}

}

复制代码

5.4 退出

  • 主线程是不能退出。

  • 传入的 safe 处理,分别是移除消息未执行的消息,和移除全部消息。

void quit(boolean safe) {

//主线程是不能退出的

if (!mQuitAllowed) {

throw new IllegalStateException(“Main thread not allowed to quit.”);

}

synchronized (this) {

if (mQuitting) {

return;

}

mQuitting = true;

//移除消息

if (safe) {

removeAllFutureMessagesLocked();

} else {

removeAllMessagesLocked();

}

// We can assume mPtr != 0 because mQuitting was previously false.

nativeWake(mPtr);

}

}

复制代码

/**

  • 移除全部消息

*/

private void removeAllMessagesLocked() {

Message p = mMessages;

while (p != null) {

Message n = p.next;

p.recycleUnchecked();

p = n;

}

mMessages = null;

}

/**

  • 移除未执行的消息,正在运行的等待完成再回收

*/

private void removeAllFutureMessagesLocked() {

final long now = SystemClock.uptimeMillis();

Message p = mMessages;

if (p != null) {

if (p.when > now) {

//如果执行时间还未到,即未执行的消息,移除回收

removeAllMessagesLocked();

} else {

//等待在执行的消息执行完再回收移除

Message n;

for (;😉 {

n = p.next;

if (n == null) {

return;

}

if (n.when > now) {

break;

}

p = n;

}

p.next = null;

do {

p = n;

n = p.next;

p.recycleUnchecked();

} while (n != null);

}

}

}

复制代码

6.Looper


  • Handler要负责发送消息,MessageQueue消息队列存放消息,Looper就是负责消息循环。

  • 商场的扶手电梯大家应该都知道吧,可以把扶手电梯的电机看成Looper,一梯一梯看成消息队列MessageQueue,坐电梯的人看成Message,就这样不停的循环,把消息送去处理。

6.1 ThreadLocal

  • Android 开发也要掌握的Java知识 -ThreadLocal 可以看下ThreadLocal原理。

  • Looper就是用到了ThreadLocal,Looper内部直接就有一个,还定义成static,final了,也就意味着Android里面获取的ThreadLocal只有这一个。

@UnsupportedAppUsage

static final ThreadLocal sThreadLocal = new ThreadLocal();

复制代码

6.2 初始化prepare(),为何只能调用一次

  • 在prepare()方法可以看到,如果初始化过,就调用就会报错了,所以每个线程最多只有一个Looper,但Handler可以有很多个。

/** Initialize the current thread as a looper.

  • This gives you a chance to create handlers that then reference

  • this looper, before actually starting the loop. Be sure to call

  • {@link #loop()} after calling this method, and end it by calling

  • {@link #quit()}.

*/

public static void prepare() {

prepare(true);

}

private static void prepare(boolean quitAllowed) {

//每个线程只能有一个looper

if (sThreadLocal.get() != null) {

throw new RuntimeException(“Only one Looper may be created per thread”);

}

//初始化后设置给sThreadLocal

sThreadLocal.set(new Looper(quitAllowed));

}

复制代码

6.3 绑定当前线程,创建消息对列

  • 创建消息对列,Looper绑定到当前Thread。

  • quitAllowed消息队列是否可销毁,主线程的是不可销毁的,子线程默认是可销毁。

private Looper(boolean quitAllowed) {

//创建消息队列

mQueue = new MessageQueue(quitAllowed);

//绑定当前线程

mThread = Thread.currentThread();

}

复制代码

6.4 拿到当前线程的looper

/**

  • Return the Looper object associated with the current thread. Returns

  • null if the calling thread is not associated with a Looper.

*/

public static @Nullable Looper myLooper() {

return sThreadLocal.get();

}

复制代码

6.5 loop()

  • 首先拿到当前线程的looper,如果没有prepare()那就是空,报异常。

  • 然后就是不停的循环,取出消息,再处理消息。

  • Android中为什么主线程不会因为Looper.loop()里的死循环卡死?

/**

  • Run the message queue in this thread. Be sure to call

  • {@link #quit()} to end the loop.

*/

点击这里免费领取吧!

adLocal.get() != null) {

throw new RuntimeException(“Only one Looper may be created per thread”);

}

//初始化后设置给sThreadLocal

sThreadLocal.set(new Looper(quitAllowed));

}

复制代码

6.3 绑定当前线程,创建消息对列

  • 创建消息对列,Looper绑定到当前Thread。

  • quitAllowed消息队列是否可销毁,主线程的是不可销毁的,子线程默认是可销毁。

private Looper(boolean quitAllowed) {

//创建消息队列

mQueue = new MessageQueue(quitAllowed);

//绑定当前线程

mThread = Thread.currentThread();

}

复制代码

6.4 拿到当前线程的looper

/**

  • Return the Looper object associated with the current thread. Returns

  • null if the calling thread is not associated with a Looper.

*/

public static @Nullable Looper myLooper() {

return sThreadLocal.get();

}

复制代码

6.5 loop()

  • 首先拿到当前线程的looper,如果没有prepare()那就是空,报异常。

  • 然后就是不停的循环,取出消息,再处理消息。

  • Android中为什么主线程不会因为Looper.loop()里的死循环卡死?

/**

  • Run the message queue in this thread. Be sure to call

  • {@link #quit()} to end the loop.

*/
[外链图片转存中…(img-fheN0dDg-1727044482194)]

[外链图片转存中…(img-ligOF3Ew-1727044482195)]

点击这里免费领取吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值