handle原理 面试题

handle原理 面试题

1、首先Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。

2、Looper.loop()会让当前线程进入一个无限循环,不端从MessageQueue的实例中读取消息,然后回调msg.target.dispatchMessage(msg)方法。

3、Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue想关联。

4、Handler的sendMessage方法,会给msg的target赋值为handler自身,然后加入MessageQueue中。

5、在构造Handler实例时,我们会重写handleMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法。

参考

https://blog.csdn.net/itachi85/article/details/8035333

  • 消息池的好处
  • handler利用消息池
  • handler looper messageQueue 初始化流程
  • handler looper ui线程 通信

https://blog.csdn.net/feather_wch/article/details/81136078#threadlocal7

handler 相关面试题

  • handler是什么:Handler是Android SDK中处理异步类消息的核心类,其作用是让子线程通过与UI通信来更新UI界面
  • 消息机制是什么:Android中的消息机制主要指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑
  • 为什么不能在子线程中访问UI?:因为Android的UI控件并不是线程安全的,如果多线程中并发访问可能导致UI控件处于不可预期的状态.
  • 那为什么系统不对UI控件的访问加上锁机制呢?:首先,加上锁机制会让UI访问的逻辑变得复杂;其次锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行.
  • 为什么通过Handler能实现线程的切换:UI线程通过threadLocal创建looper,实现线程单例,looper循环从messageQueue里面获取消息执行,子线程获取handler发送消息到messageQueue里面。
  • Handler.post的逻辑在哪个线程执行的,是由Looper所在线程还是Handler所在线程决定的?:由Looper所在线程决定的,最终逻辑是在Looper.loop()方法中,从MsgQueue中拿出msg,并且执行其逻辑,这是在Looper中执行的,因此有Looper所在线程决定。
  • Looper和Handler一定要处于一个线程吗?子线程中可以用MainLooper去创建Handler吗?:可以的。子线程中Handler handler = new Handler(Looper.getMainLooper());,此时两者就不在一个线程中。
  • Handler的post方法发送的是同步消息吗?可以发送异步消息吗?:用户层面发送的都是同步消息,不能发送异步消息,异步消息只能由系统发送。
  • MessageQueue.next()会因为发现了延迟消息,而进行阻塞。那么为什么后面加入的非延迟消息没有被阻塞呢?:调用 MessageQueue.next() 方法的时候会调用 Native 层的 nativePollOnce() 方法进行精准时间的阻塞。在 Native 层,将进入 pullInner() 方法,使用 epoll_wait 阻塞等待以读取管道的通知。如果没有从 Native 层得到消息,那么这个方法就不会返回。此时主线程会释放 CPU 资源进入休眠状态。当我们加入消息的时候,会调用 MessageQueue.enqueueMessage() 方法,添加完 Message 后,如果消息队列被阻塞,则会调用 Native 层的 nativeWake() 方法去唤醒。它通过向管道中写入一个消息,结束上述阻塞,触发上面提到的 nativePollOnce() 方法返回,好让加入的 Message 得到分发处理。
  • Handler为什么要有Callback的构造方法?:不需要派生Handler
  • MessageQueue中底层是采用:采用单链表的数据结构来维护消息队列,而不是采用队列
  • MessageQueue的enqueueMessage()方法的原理,如何进行线程同步的?: 就是单链表的插入操作,如果消息队列被阻塞回调用nativeWake去唤醒。,用synchronized代码块去进行同步。
  • MessageQueue的next()方法内部的原理?:1.当首次进入或所有消息队列已经处理完成,由于此刻队列中没有消息(mMessages为null),这时nextPollTimeoutMillis = -1 ,然后会处理一些不紧急的任务(IdleHandler),之后线程会一直阻塞,直到被主动唤醒(插入消息后根据消息类型决定是否需要唤醒)。2.读取列表中的消息,如果发现消息屏障,则跳过后面的同步消息。3.如果拿到的消息还没有到时间,则重新赋值nextPollTimeoutMillis = 延时的时间,线程会阻塞,直到时间到后自动唤醒4.如果消息是及时消息或延时消息的时间到了,则会返回此消息给looper处理。
  • quit和quitSafely有什么区别:(1)当我们调用Looper的quit方法时,实际上执行了MessageQueue中的removeAllMessagesLocked方法,该方法的作用是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)还是非延迟消息。(2)当我们调用Looper的quitSafely方法时,实际上执行了MessageQueue中的removeAllFutureMessagesLocked方法,通过名字就可以看出,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。(3)无论是调用了quit方法还是quitSafely方法只会,Looper就不再接收新的消息。即在调用了Looper的quit或quitSafely方法之后,消息循环就终结了,这时候再通过Handler调用sendMessage或post等方法发送消息时均返回false,表示消息没有成功放入消息队列MessageQueue中,因为消息队列已经退出了。
  • MessageQueue的next方法什么时候会返回null? 退出的时候

https://www.cnblogs.com/yishaochu/p/6882387.html

  • Looper myLooper()
  • ActivityThread main()
  • Looper prepareMainLooper()
  • Looper prepare()
  • Looper 构造方法
  • Looper loop()
  • Handler dispatchMessage()
  • Handler handleCallback()
  • Handler post()
  • Handler getPostMessage()
  • Handler enqueueMessage()

好了,总结完成,大家可能还会问,那么在Activity中,我们并没有显示的调用Looper.prepare()和Looper.loop()方法,为啥Handler可以成功创建呢,这是因为在Activity的启动代码中,已经在当前UI线程调用了Looper.prepare()和Looper.loop()方法。

  • 其中looper.prepare()源码分析

code

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

  • new Looper(quitAllowed) 源码

code

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

从代码中看出 looper构造函数是私有的,其他类是不能通过new 来创建,只能通过Looper.prepare()来创建,所以只能创建一个messageQueue

  • new MessageQueue(quitAllowed) 源码分析

code

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

从代码分析,quitAllowed 是消息队列是否能够被退出,源码

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);
    }
}
  • looper.loop() 源码分析

code

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for (;;) {
		Message msg = queue.next(); // might block
		................
		try {
            msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
		................

代码中获取当前的looper,和当前的looper的消息队列,无限循环消息队列,从消息队列中取出消息然后执行

  • msg.target.dispatchMessage(msg) 源码解析

首先msg 是消息,在message类找target Handler target;,显然tag就是Handler,那么dispatchMessage 源码

/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

其中 callback是 Runnable ,如果为空,则执行handleMessage方法,显然handleMessage就是消费消息的方法,能够重写,那么消息中target是什么时候赋值

public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, 0);
}

…sendMessageDelayed

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

…sendMessageAtTime

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

…enqueueMessage

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

一层层找下来,看到 msg.target = this,还有 callback 是什么赋值的

public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}

… getPostMessage

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

找到了,在 getPostMessage 方法里面赋值的

面试题总结

  • 说下handle原理,为什么会出现内存泄漏,为什么继承Handle就不会出现能存泄漏

什么是内存泄露

Java通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收。也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收;另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。

private Handler handler = new Handler(){
      public void handleMessage(android.os.Message msg)
     {
            if (msg.what == 1) 
        {
                noteBookAdapter.notifyDataSetChanged();
             }
        }
};

上面是一段简单的Handler的使用。当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然你怎么可能通过Handler来操作Activity中的View?)。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。

解决方案

使用Handler导致内存泄露的解决方法

方法一:通过程序逻辑来进行保护。
1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。
2.如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。

方法二:将Handler声明为静态类。
PS:在Java 中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用,静态的内部类不会持有外部类的引用。
静态类不持有外部类的对象,所以你的Activity可以随意被回收。由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference)。

  • Handler机制

Handler的消息处理主要有五个部分组成,Message,Handler,Message Queue,Looper和ThreadLocal。首先简要的了解这些对象的概念

Message:Message是在线程之间传递的消息,它可以在内部携带少量的数据,用于线程之间交换数据。Message有四个常用的字段,what字段,arg1字段,arg2字段,obj字段。what,arg1,arg2可以携带整型数据,obj可以携带object对象。

Handler:它主要用于发送和处理消息的发送消息一般使用sendMessage()方法,还有其他的一系列sendXXX的方法,但最终都是调用了sendMessageAtTime方法,除了sendMessageAtFrontOfQueue()这个方法

而发出的消息经过一系列的辗转处理后,最终会传递到Handler的handleMessage方法中。

Message Queue:MessageQueue是消息队列的意思,它主要用于存放所有通过Handler发送的消息,这部分的消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。

Looper:每个线程通过Handler发送的消息都保存在,MessageQueue中,Looper通过调用loop()的方法,就会进入到一个无限循环当中,然后每当发现Message Queue中存在一条消息,就会将它取出,并传递到Handler的handleMessage()方法中。每个线程中只会有一个Looper对象。

ThreadLocal:MessageQueue对象,和Looper对象在每个线程中都只会有一个对象,怎么能保证它只有一个对象,就通过ThreadLocal来保存。Thread Local是一个线程内部的数据存储类,通过它可以在指定线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储到数据,对于其他线程来说则无法获取到数据。

  • HandlerThread说明

1、HandlerThread 本质上是一个线程类,继承自Thread;

2、HandlerThread 有自己的内部Looper对象,可以进行Looper循环,创建Handler;

3、通过获取HandlerThread的Looper对象传递给Handler对象,可以在Handler中的handlerMessage方法中执行耗时任务;

4、优点是异步不会阻塞UI线程,减少了对性能的损耗,缺点是不能同时进行多任务处理,需要按顺利依次处理,处理效率较低;

5、与线程池注重并发不同,HandlerThread是一个串行队列,它的背后只有的1个线程;

  • 消息机制:Handler 源码(结合Looper、MessageQueue),以及取不到消息时会怎样?

1.使用HandlerThread 时会发生这样的事,原因是
sendEmptyMessage() --> Handler.enqueueMessage --> MessageQueue.enqueueMessage(这个方法里面有mQuiting,当为true时会抛出异常,终止方法执行)

2.跟踪代码 找到 mQuitingquit()方法里面赋值为true,这样以后发生的消息就不会添加到消息队列里面

原文地址 https://blog.csdn.net/ilygjl/article/details/51137346

  • Handler的实现,Looper怎么终止。

1.当我们调用Looper的quit方法时,实际上执行了MessageQueue中的removeAllMessagesLocked方法,该方法的作用是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)还是非延迟消息。

2.调用Looper的quitSafely方法时,实际上执行了MessageQueue中的removeAllFutureMessagesLocked方法,通过名字就可以看出,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息

3.无论是调用了quit方法还是quitSafely方法只会,Looper就不再接收新的消息。即在调用了Looper的quit或quitSafely方法之后,消息循环就终结了,这时候再通过Handler调用sendMessage或post等方法发送消息时均返回false,表示消息没有成功放入消息队列MessageQueue中,因为消息队列已经退出了。

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

参考:https://www.zhihu.com/question/34652589/answer/90344494

  • Handler机制,主线程如何向子线程发送消息,Handler能否多进程通信

主线程可以给子线程发送消息,实例如下

package activity.wyc.com.looperthreaddemo;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;


public class MainActivity extends ActionBarActivity {

    private String MyTag = "MyTag";
    private int num = 0;

    private TextView tvObj;
    private Button btnObj;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        tvObj = (TextView) findViewById(R.id.tvid);
        btnObj = (Button) findViewById(R.id.btnid);

        final LooperThread looperThread = new LooperThread();

        looperThread.start();

        btnObj.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Message message = Message.obtain();
                message.arg1 = num;
                tvObj.setText("主线程发送了 :"+String.valueOf(message.arg1));
                looperThread.handler.sendMessage(message);
                num++;
            }
        });


    }

    class LooperThread extends Thread {

        public Handler handler;

        @Override
        public void run() {
            super.run();

            Looper.prepare();

            handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);

                    Toast.makeText(MainActivity.this,"LooperThread handler 收到消息 :"+msg.arg1,Toast.LENGTH_LONG).show();
                    Log.i(MyTag, "LooperThread handler 收到消息 :" + msg.arg1);
                }
            };

            Looper.loop();//loop()会调用到handler的handleMessage(Message msg)方法,所以,写在下面;
        }
    }

}

Handler不能在多进程通信

  • bugly 是干嘛用的?Handler 怎么处理内存泄漏,除了使用弱引用。你还知道哪些地方需要注意内存泄漏?

Bugly为解决开发者紧急修复线上bug,而无需重新发版让用户无感知就能把问题修复的一项能力。Bugly目前采用微信Tinker的开源方案,开发者只需要集成我们提供的SDK就可以实现自动下载补丁包、合成、并应用补丁的功能,我们也提供了热更新管理后台让开发者对每个版本补丁进行管理。

内存泄漏和优化请查看

https://blog.csdn.net/gyh790005156/article/details/65630284
https://blog.csdn.net/gyh790005156/article/details/65631402
https://blog.csdn.net/gyh790005156/article/details/66972858

你可能感兴趣的:(android,学习,面试,android,移动端开发)