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面试必备知识点:Android中Handler八大问题汇总 -> 正文阅读

[移动开发]Android面试必备知识点:Android中Handler八大问题汇总

}

sendMessage vs post

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

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代码调用链:

调用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结构:

也就是说`msg.target.dispatchMessa

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

ge方法其实就是调用的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的线程就会进入阻塞状态。

下面看下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();

}

nativePollOnce(ptr, nextPollTimeoutMillis);

Binder.flushPendingCommands();这个方法的作用可以看源码里面给出的解释:

/**

  • Flush any Binder commands pending in the current thread to the kernel

  • driver. This can be

  • useful to call before performing an operation that may block for a long

  • time, to ensure that any pending object references have been released

  • in order to prevent the process from holding on to objects longer than

  • it needs to.

*/

也就是说在用户线程要进入阻塞之前跟内核线程发送消息,防止用户线程长时间的持有某个对象。再看看下面这个方法:nativePollOnce(ptr, nextPollTimeoutMillis);nextPollingTimeOutMillis=-1时,这个native方法会阻塞当前线程,线程阻塞后,等下次有消息入队才会重新进入可运行状态,所以Looper并不会一直死循环消耗运行内存,对队列中的颜色消息还没到时间时也会阻塞当前线程,但是会有一个阻塞时间也就是nextPollingTimeOutMillis>0的时间。

当消息队列中没有消息的时候looper肯定是被消息入队唤醒的。

消息入队

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

}

上面可以看到消息入队之后会有一个

if (needWake) {

nativeWake(mPtr);

}

方法,调用这个方法就可以唤醒线程了。另外消息入队的时候是根据消息的delay时间来在链表中排序的,delay时间长的排在后面,时间短的排在前面。如果时间相同那么按插入时间先后来排,插入时间早的在前面,插入时间晚的在后面。

4、android的Handle机制,Looper关系,主线程的Handler是怎么判断收到的消息是哪个Handler传来的?


Looper是如何判断Message是从哪个handler传来的呢?其实很简单,在1中分析过,handlersendMessage的时候会构建一个Message对象,并且把自己放在Messagetarget里面,这样的话Looper就可以根据Message中的target来判断当前的消息是哪个handler传来的。

5、Handler机制流程、Looper中延迟消息谁来唤醒Looper?


从3中知道在消息出队的for循环队列中会调用到下面的方法。

nativePollOnce(ptr, nextPollTimeoutMillis);

如果是延时消息,会在被阻塞nextPollTimeoutMillis时间后被叫醒,nextPollTimeoutMillis就是消息要执行的时间和当前的时间差。

6、Handler是如何引起内存泄漏的?如何解决?


在子线程中,如果手动为其创建Looper,那么在所有的事情完成以后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待的状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper

Looper.myLooper().quit()

那么,如果在HandlerhandleMessage方法中(或者是run方法)处理消息,如果这个是一个延时消息,会一直保存在主线程的消息队列里,并且会影响系统对Activity的回收,造成内存泄露。

具体可以参考Handler内存泄漏分析及解决

总结一下,解决Handler内存泄露主要2点

1 、有延时消息,要在Activity销毁的时候移除Messages

2、 匿名内部类导致的泄露改为匿名静态内部类,并且对上下文或者Activity使用弱引用。

7、handler机制中如何确保Looper的唯一性?


Looper是保存在线程的ThreadLocal里面的,使用Handler的时候要调用Looper.prepare()来创建一个Looper并放在当前的线程的ThreadLocal里面。

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

}

可以看到,如果多次调用prepare的时候就会报Only one Looper may be created per thread,所以这样就可以保证一个线程中只有唯一的一个Looper

8、Handler 是如何能够线程切换,发送Message的?


handler的执行跟创建handler的线程无关,跟创建looper的线程相关,加入在子线程中创建一个Handler,但是Handler相关的Looper是主线程的,这样,如果handler执行post一个runnable,或者sendMessage,最终的handle Message都是在主线程中执行的。

Thread thread=new Thread(new Runnable() {

@Override

public void run() {

Looper.prepare();

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-12-11 15:50:25  更:2021-12-11 15:52:38 
 
开发: 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年11日历 -2024/11/24 7:50:24-

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