面试准备-打卡第三天-Android篇

234 阅读3分钟

什么是Android消息机制?

  • Android消息机制也是Handler机制,主要的作用是用来不同线程之间的通信,通常使用在子线程执行完成一些耗时操作,需要回到主线程更新界面UI时,通过handler将有关UI的操作切换到主线程
  • 由于Android中线程不可以进行耗时操作,所以我们的网络请求只能放在子线程中,又由于在子线程中不能更新UI,所以我们就需要使用Handler切换到主线程并更新UI

Handler的工作流程?

Handler由Handler、Looper、Messagequeue、Message四个主要部分组成

  • Looper:负责关联线程以及消息的分发,在该线程下从MessageQueue获取Message,分发给Handler
  • MessageQueue:是一个队列,负责消息的存储与管理,负责管理由Handler发送过来的Message
  • Handler:负责发送并处理消息,面向开发者,提供API,并隐藏背后实现的细节
  • Message: final类不可继承,实现了Parcelable序列化接口,可以在不同进程之间传递
  1. 应用程序启动的时候,在主线程会默认调用了Looper.prepare()方法,初始化Looper对象并绑定到当前线程中,并在Looper内部维护一个MessageQueue
  2. 接着调用handler.sendMessage()发送信息,会通过MessageQueue.enqueueMessage()向MessageQueue中添加一条消息
  3. 主线程调用Looper.loop()开启循环,不断轮询消息队列,通过MessageQueue.next()取出消息
  4. 取出的message不为空则调用msg.target.dispatchMessage()传递分发消息,目标handler收到消息后会执行handler.handlerMessage()方法处理消息

image.png

Handler线程是如何切换的?

(1)假设现在有一个线程A,在A线程中通过Looper.prepare和Looper.loop来开启Looper,并且在A线程中实例化出来一个Handler。Looper.prepare()方法被调用时会为会初始化Looper并为ThreadLocal 设置Looper,此时ThreadLocal中就存储了A线程的Looper。另外MessageQueue也会在Looper中被初始化。

(2)接着当调用Loop.loop方法时,loop方法会通过myLooper得到A线程中的Looper,进而拿到Looper中的MessageQueue,接着开启死循环等待执行MessageQueue中的方法。 (3)此时,再开启一个线程B,并在B线程中通过Handler发送出一个Message,这个Message最终会通过sendMessageAtTime方法调用到MessageQueue的equeueMessage方法将消息插入到队列。

(3)由于Looper的loop是一个死循环,当MessageQueue中被插入消息的时候,loop方法就会取出MessageQueue中的消息,并执行callback。而此时,Looper是A线程的Looper,进而调用的Message或者Handler的Callback都是执行在A线成中的。以此达到了线程的切换。

Handler内存泄漏的原因是什么?如何解决?

通常在使用Handler的时候会通过匿名内部类的方式来实例化Handler,而非静态的匿名内部类默认持有外部类的引用,即匿名内部类Handler持有了外部类,而导致内存泄漏的根本原因是因为Handler的生命周期与宿主的生命周期不一致

比如说在Activity中实例化了一个非静态的匿名内部类Handler,然后通过Handler发送了一个延迟消息,但是在消息还未执行时结束了Activity,此时由于Handler持有Activity,就会导致Activity无法被GC回收,也就是出现了内存泄漏的问题。

解决方式:可以把Handler声明为静态的匿名内部类,但这样的话,在Handler内部就没办法调用到Activity中的非静态方法或变量,那么最终的解决方案可以使用静态内部类+弱引用来解决

public class MainActivity extends AppCompatActivity {

    private MyHandler mMyHandler = new MyHandler(this);

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

    private void handleMessage(Message msg) {

    }

    static class MyHandler extends Handler {
        private WeakReference<Activity> mReference;

        MyHandler(Activity reference) {
            mReference = new WeakReference<>(reference);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = (MainActivity) mReference.get();
            if (activity != null) {
                activity.handleMessage(msg);
            }
        }
    }

    @Override
    protected void onDestroy() {
        mMyHandler.removeCallbacksAndMessages(null);
        super.onDestroy();
    }
} 

一个线程有几个Handler?一个线程有几个Looper?如何保证?

Handler的个数与所在线程无关,可以在线程中实例化任意多个Handler。一个线程中只有一个Looper,Looper的构造方法被声明为了private,我们无法通过new关键字来实例化Looper,唯一开放的可以实例化Looper的方法是prepare

ThreadLocal是一个线程内部的存储类,当某个线程调用prepare方法的时候,会首先通过ThreadLocal检查这个线程是否已经创建了Looper,如果还没创建,则实例化Looper并将实例化后的Looper保存到ThreadLocal中,而如果ThreadLocal中已经保存了Looper,则会抛出一个RuntimeException的异常,那么意味着在一个线程中最多只能调用一次prepare方法,这样就保证了Looper的唯一性。

多个handler绑定了一个Looper,如何区分哪个handler对应哪个message呢?

从handler发送消息,最终会调用Handler类中的enqueueMessage方法,方法里面有个msg.target = this,指向当前的Handler对象,然后msg被加入到消息队列,就会唤醒Looper,Looper会调用loop方法获取消息,消息中封装着发送消息的handler对象,取出后,通过dispatchMessage方法将消息分发出去,就可以将msg发送到指定的Handler.

Handler如何保证MessageQueue并发访问安全?

循环加锁,配合阻塞唤醒机制

MessageQueue其实是一个“生产者-消费者”模型,Handler不断地放入消息,Looper不断地取出,这就涉及到死锁问题,如果Looper拿到锁,但是队列中没有消息,就会一直等待,而Handler需要把消息放进去,锁却被Looper拿着无法入队,这就造成了死锁,Handler机制的解决方法是循环加锁,然后它的等待就是在锁外,当队列中没有消息的时候,它会释放锁,再进行等待,直到被唤醒

Handler的阻塞唤醒机制是怎么回事?

Handler的阻塞唤醒机制是基于Linux的阻塞唤醒机制

这个机制也是类似于handler机制的模式,在本地创建一个文件描述符,然后等待的一方则监听这个文件描述符,唤醒的一方只需要修改这个文件,那么等待的一方就会收到文件从而打破唤醒,和Looper监听MessageQueue,Handler添加message是比较类似的

能不能让一个Message加急被处理?什么是Handler同步屏障?

可以 一种可以使异步消息可以被更快处理的机制

如果向主线程发送一个UI更新的操作Message,而此时消息队列中的消息非常多,那么这个Message的处理就会变得缓慢,造成时间卡顿,所以通过同步屏障,可以使得UI绘制的Message更快被执行

这个“屏障”其实就是一个Message,插入在MessageQueue的链表头,且其target==null,Message入队的时候不是判断了target不能为null吗?不不不,添加同步屏障是另一种方法

如何在子线程中创建Handler?

在子线程中创建handler,要确保子线程中有Looper,UI线程默认包含Looper

我们需要一个特殊的类:HandlerThread

创建步骤:

创建一个包含Looper的线程:HandlerThread handlerThread = new HandlerThread("Test");创建后要记得start();通过HandlerThread的getLooper方法可以获取Looper:Looper looper = handlerThread.getLooper(),通过Looper的话我们就可以创建子线程的handler了:Handler handler = new Handler(looper);通过该handler发送消息,就会在子线程执行

HandlerThread hanlerThread = new HandlerThread("子线程");

hanlerThread.start();

final Handler handler = new Handler(hanlerThread.getLooper()) {

@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);

Log.d("----->", "线程:" + Thread.currentThread().getName());

}

};findViewById(R.id.bt).setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

handler.sendEmptyMessage(100);

}

});