0
点赞
收藏
分享

微信扫一扫

Android面试:Handler八大问题汇总,Android程序员必经的实践之路

sullay 2022-03-20 阅读 68

//主线程

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

@Override

public boolean handleMessage(@NonNull Message msg) {

if (msg.what == 1) {

//doing something

}

return false;

}

});

Message msg = Message.obtain();

msg.what = 1;

mHandler.sendMessage(msg);

上面是在主线程中使用handler,因为在Android中系统已经在主线程中生成了Looper,所以不需要自己来进行looper的生成。如果上面的代码在子线程中执行,就会报

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

  • " that has not called Looper.prepare()

如果想着子线程中处理handler的操作,就要必须要自己生成Looper了。

例2 、子线程中使用handler

Thread thread=new Thread(new Runnable() {

@Override

public void run() {

Looper.prepare();

Handler handler=new Handler();

handler.post(new Runnable() {

@Override

public void run() {

}

});

Looper.loop();

}

});

上面在Thread中使用handler,先执行Looper.prepare方法,来在当前线程中生成一个Looper对象并保存在当前线程的ThreadLocal中。 看下Looper.prepare()中的源码:

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

private Looper(boolean quitAllowed) {

mQueue = new MessageQueue(quitAllowed);

mThread = Thread.currentThread();

}

可以看到prepare方法中会先从sThreadLocal中取如果之前已经生成过Looper就会报错,否则就会生成一个新的Looper并且保存在线程的ThreadLocal中,这样可以确保每一个线程中只能有一个唯一的Looper

另外:由于Looper中拥有当前线程的引用,所以有时候可以用Looper的这种特点来判断当前线程是不是主线程。

@RequiresApi(api = Build.VERSION_CODES.KITKAT)

boolean isMainThread() {

return Objects.requireNonNull(Looper.myLooper()).getThread() ==

Looper.getMainLooper().getThread();

}

sendMessage vs post

先来看看sendMessage的代码调用链:

Android面试:Handler八大问题汇总,Android程序员必经的实践之路

-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2RvbmdyaW1hb21hb3l1,size_16,color_FFFFFF,t_70)

enqueueMessage源码如下:

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,

long uptimeMillis) {

msg.target = this;

msg.workSourceUid = ThreadLocalWorkSource.getUid();

return queue.enqueueMessage(msg, uptimeMillis);

}

enqueueMessage的代码处理很简单,msg.target = this;就是把当前的handler对象给message.target。然后再讲message进入到队列中。

post代码调用链:

Android面试:Handler八大问题汇总,Android程序员必经的实践之路

调用post时候会先调用getPostMessage生成一个Message,后面和sendMessage的流程一样。下面看下getPostMessage方法的源码:

private static Message getPostMessage(Runnable r) {

Message m = Message.obtain();

m.callback = r;

return m;

}

可以看到getPostMessage中会先生成一个Messgae,并且把runnable赋值给messagecallback.消息都放到MessageQueue中后,看下Looper是如何处理的。

for (;;) {

Message msg = queue.next(); // might block

if (msg == null) {

return;

}

msg.target.dispatchMessage(msg);

}

Looper中会遍历message列表,当message不为null时调用msg.target.dispatchMessage(msg)方法。看下message结构:

Android面试:Handler八大问题汇总,Android程序员必经的实践之路

也就是说msg.target.dispatchMessage方法其实就是调用的Handler中的dispatchMessage方法,下面看下dispatchMessage方法的源码:

public void dispatchMessage(@NonNull 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();

}

因为调用post方法时生成的message.callback=runnable,所以dispatchMessage方法中会直接调用 message.callback.run();也就是说直接执行post中的runnable方法。 而sendMessage中如果mCallback不为null就会调用mCallback.handleMessage(msg)方法,否则会直接调用handleMessage方法。

总结 post方法和handleMessage方法的不同在于,postrunnable会直接在callback中调用run方法执行,而sendMessage方法要用户主动重写mCallback或者handleMessage方法来处理。

[]( )3、Looper会一直消耗系统资源吗?

首先给出结论,Looper不会一直消耗系统资源,当LooperMessageQueue中没有消息时,或者定时消息没到执行时间时,当前持有Looper的线程就会进入阻塞状态。

Android面试:Handler八大问题汇总,Android程序员必经的实践之路

下面看下looper所在的线程是如何进入阻塞状态的。looper阻塞肯定跟消息出队有关,因此看下消息出队的代码。

消息出队

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 nextPollTimeoutMillis = 0;

for (;;) {

if (nextPollTimeoutMillis != 0) {

Binder.flushPendingCommands();

}

nativePollOnce(ptr, nextPollTimeoutMillis);

// While calling an idle handler, a new message could have been delivered

// so go back and look again for a pending message without waiting.

if(hasNoMessage)

{

nextPollTimeoutMillis =-1;

}

}

}

上面的消息出队方法被简写了,主要看下面这段,没有消息的时候nextPollTimeoutMillis=-1

if(hasNoMessage)

{

nextPollTimeoutMillis =-1;

}

看for循环里面这个字段所其的作用:

if (nextPollTimeoutMillis != 0) {

Binder.flushPendingCommands();

}

最后

下面是辛苦给大家整理的学习路线

Android面试:Handler八大问题汇总,Android程序员必经的实践之路

举报

相关推荐

0 条评论