Android学习笔记之Handler

1、谈谈消息机制Hander作用、有哪些要素、流程是怎样的?

  • 作用:当子线程中进行耗时操作后需要更新UI时,通过Handler将更新UI的操作切换到主线程中执行。

  • 四要素:

    1. Message: 消息对象,是线程间通讯的消息载体。
    2. MessageQueue:消息队列,用来存放Handler发送过来的消息,内部通过单链表的数据结构来维护消息列表,等待Looper的抽取。
    3. Looper:轮播器,负责管理线程的消息队列和消息循环。
    4. Handler:在主线程中负责发送和处理消息。
  • 工作流程:

    1. 当 Handler.sendMessage() 发送消息时,会通过调用 MessageQueue.enqueueMessage() 向消息队列MessageQueue加入一条消息。
    2. 然后通过轮播器 Looper.loop() 不断的在MessageQueue中取出消息,调用 Handler.dispatchMessage() 去将消息传递给目标 Handler;
    3. 目标 Handler 收到消息后调用 handlerMessage 进行消息真正的处理。
Handler.jpg

2、post(Runnable) 和sendMessage(msg)的区别

从Post方法的源码可以知道,其实质 post(Runnable) 这个方法只是在 sendMessage() 上进行了一层封装,通过 sendMessageDelayed 把 Runnable 对象添加到了 MessageQueue 中,而 sendMessage 也同样将 Message 加入到 MessageQueue,所以两者内部实现是一样的。

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

为什么系统不建议在子线程访问UI?

系统不建议在子线程访问UI的原因是,UI控件非线程安全,在多线程中并发访问可能会导致UI控件处于不可预期的状态。而不对UI控件的访问加上锁机制的原因有:

  1. 上锁会让UI控件变得复杂和低效
  2. 上锁后会阻塞某些进程的执行

一个线程可以有几个Looper?几个Handler?

  • 一个Thread只能有一个Looper,可以有多个Handler。
  • Looper有一个MessageQueue,可以处理来自多个Handler的Message;MessageQueue有一组待处理的Message,这些Message可来自不同的Handler;Message中记录了负责发送和处理消息的Handler;Handler中有Looper和MessageQueue;

如何将一个Thread线程变成Looper线程?Looper线程有哪些特点?

通过Looper.prepare()可将一个Thread线程转换成Looper线程。Looper线程和普通Thread不同,它通过MessageQueue来存放消息和事件、Looper.loop()进行消息轮询。

可以在子线程直接new一个Handler吗?那该怎么做?

可以,不同于主线程直接new一个Handler,由于子线程的Looper需要手动去创建,在创建Handler时需要多一些方法。

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();//为子线程创建Looper  
        new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                    //子线程消息处理
            }
        };
        Looper.loop(); //开启消息轮询
    }
}).start();

Message可以如何创建?哪种效果更好,为什么?

创建Message对象的几种方式:

  • Message msg = new Message();
  • Message msg = Message.obtain();
  • Message msg = handler1.obtainMessage();
    后两种方法都是从整个Messge池中返回一个新的Message实例,能有效避免重复Message创建对象,因此更鼓励这种方式创建Message

ThreadLocal有什么作用?

ThreadLocal类可实现线程本地存储的功能,把共享数据的可见范围限制在同一个线程之内,无须同步就能保证线程之间不出现数据争用的问题,这里可理解为ThreadLocal帮助Handler找到本线程的Looper。

  • 底层数据结构:每个线程的Thread对象中都有一个ThreadLocalMap对象,它存储了一组以ThreadLocal.threadLocalHashCode为key、以本地线程变量为value的键值对,而ThreadLocal对象就是当前线程的ThreadLocalMap的访问入口,也就包含了一个独一无二的threadLocalHashCode值,通过这个值就可以在线程键值值对中找回对应的本地线程变量。

主线程中Looper的轮询死循环为何没有阻塞主线程?

因为 Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者 Activity 的生命周期都是运行在 Looper.loop() 的控制之下,一旦退出消息循环,应用也就停止了。只能说是对某个消息的处理阻塞了Looper.loop()。

使用Hanlder的postDealy()后消息队列会发生什么变化?

post delay的Message并不是先等待一定时间再放入到MessageQueue中,而是直接进入并阻塞当前线程,然后将其delay的时间和队头的进行比较,按照触发时间进行排序,如果触发时间更近则放入队头,保证队头的时间最小、队尾的时间最大。此时,如果队头的Message正是被delay的,则将当前线程堵塞一段时间,直到等待足够时间再唤醒执行该Message,否则唤醒后直接执行。

Looper中 quit() 和 quitSafely() 的区别

  • 当 Looper的 quit() 或 quitSafely() 被调用时,就会调用 MessageQueue 的 quit() 方法,分别对应的是 removeAllMessagesLocked() 和 removeAllFutureMessagesLocked() 方法。
  • 区别在于,quit() 会清空所有消息(无论延时或非延时),quitSafely() 只清空延时消息,最终 next 方法都是返回 null
  • 注意:无论调用哪个方法,当 Looper 退出后,Handler 发送的消息都会失败。这时候再通过 Handler 调用 sendMessage 或 post 等方法发送消息时均返回 false,表示消息没有成功放入消息队列 MessageQueue 中,因为消息队列已经退出了。

Handler 引起的内存泄露以及解决方法

所创建的 Handler 不是静态内部类,它会隐匿持有 Activity 的引用。当 Activity 要回收的话,而 Handler 内部有可能在做耗时操作,所以 Handler 所持有的 Activity 的引用不能被释放。最终导致 Activity 没有被回收,停留在队列内存当中,造成内存泄露。
总结:静态内部类持有外部类的匿名引用,导致外部Activty 无法释放。
解决方法:

  • 将Handler 设置为静态内部类。
  • 在 Activity 的生命周期 onDestroy() 方法中,调用 handler.removeCallbacks() 方法。
  • handler 内部持有外部 activity 的弱引用。

Android中有哪些方便线程切换的类?

对Handler进一步的封装的几个类:

  • AsyncTask:底层封装了线程池和Handler,便于执行后台任务以及在子线程中进行UI操作。
  • HandlerThread:一种具有消息循环的线程,其内部可使用Handler。
  • IntentService:是一种异步、会自动停止的服务,内部采用HandlerThread。

AsyncTask相比Handler有什么优点?不足呢?

  • AsyncTask:创建异步任务更简单,过程可控。在使用多个异步操作并且需要进行 UI 变更时,变得复杂。
  • Handler:结构清晰,功能明确。对于多个后台任务时,清晰简单。在单个后台任务时,显得代码过多,结构复杂。多任务同时执行时不易精确控制线程。

子线程有哪些更新UI的方法?

  • 主线程中定义 Handler,子线程通过 handler 发送消息,主线程 Handler 的 handleMessage 更新UI。
  • View.post(Runnable r) 。
  • 用 Activity 对象的 runOnUiThread 方法。

使用AsyncTask需要注意什么?

  • AsyncTask 是一个抽象的泛型类,它提供了 Params、Progress 和 Result 这三个泛型参数,如果 AsyncTask 不需要传递具体的参数,那么三个参数可以用 Void 来代替。
  • AsyncTask提供了 4 个核心方法,除了doInBackground在线程池中执行其余在主线程中执行。

AsyncTask中使用的线程池大小?

在AsyncTask内部实现有两个线程池:

  • SerialExecutor:用于任务的排队,默认是串行的线程池,在3.0以前核心线程数为5、线程池大小为128,而3.0以后变为同一时间只能处理一个任务
  • THREAD_POOL_EXECUTOR:用于真正执行任务。

HandlerThread有什么特点?

HandlerThread是一个线程类,它继承自Thread。与普通Thread不同,HandlerThread具有消息循环的效果,这是因为它内部HandlerThread.run()方法中有Looper,能通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环。

在子线程中如何使用HandlerThread

  • 实例化一个HandlerThread对象,参数是该线程的名称;
  • 通过 HandlerThread.start()开启线程;
  • 实例化一个Handler并传入HandlerThread中的looper对象,使得与HandlerThread绑定;
  • 利用Handler即可执行异步任务;
  • 当不需要HandlerThread时,通过HandlerThread.quit()/quitSafely()方法来终止线程的执行。

IntentService的特点?

不同于线程,IntentService是服务,优先级比线程高,更不容易被系统杀死,因此较适合执行一些高优先级的后台任务;不同于普通Service,IntentService可自动创建子线程来执行任务,且任务执行完毕后自动退出。

为何不用bindService方式创建IntentService?

IntentService的工作原理是,在IntentService的onCreate()里会创建一个HandlerThread,并利用其内部的Looper实例化一个ServiceHandler对象;而这个ServiceHandler用于处理消息的handleMessage()方法会去调用IntentService的onHandleIntent(),这也是为什么可在该方法中处理后台任务的逻辑;当有Intent任务请求时会把Intent封装到Message,然后ServiceHandler会把消息发送出,而发送消息是在onStartCommand()完成的,只能通过startService()才可走该生命周期方法,因此不能通过bindService创建IntentService。

线程池的好处、原理、类型?

  • (1)线程池的好处:
    • 重用线程池中的线程,避免线程的创建和销毁带来的性能消耗;
    • 有效控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致阻塞现象;
    • 进行线程管理,提供定时/循环间隔执行等功能
  • (2)线程池的分类:
    • FixThreadPool:线程数量固定的线程池,所有线程都是核心线程,当线程空闲时不会被回收;能快速响应外界请求。
    • CachedThreadPool:线程数量不定的线程池(最大线程数为Integer.MAX_VALUE),只有非核心线程,空闲线程有超时机制,超时回收;适合于执行大量的耗时较少的任务
    • ScheduledThreadPool:核心线程数量固定,非核心线程数量不定;可进行定时任务和固定周期的任务。
    • SingleThreadExecutor:只有一个核心线程,可确保所有的任务都在同一个线程中按顺序执行;好处是无需处理线程同步问题。
  • (3)线程池的原理:实际上通过ThreadPoolExecutor并通过一系列参数来配置各种各样的线程池,具体的参数有:
    • corePoolSize核心线程数:一般会在线程中一直存活
    • maximumPoolSize最大线程数:当活动线程数达到这个数值后,后续的任务将会被阻塞
    • keepAliveTime非核心线程超时时间:超过这个时长,闲置的非核心线程就会被回收
    • unit:用于指定keepAliveTime参数的时间单位
    • workQueue任务队列:通过线程池的execute()方法提交的Runnable对象会存储在这个参数中。
    • threadFactory:线程工厂,可创建新线程
    • handler:在线程池无法执行新任务时进行调度

ThreadPoolExecutor的工作策略?

  • 若程池中的线程数量未达到核心线程数,则会直接启动一个核心线程执行任务。
  • 若线程池中的线程数量已达到或者超过核心线程数量,则任务会被插入到任务列表等待执行。
    • 若任务无法插入到任务列表中,往往由于任务列表已满,此时如果
      • 线程数量未达到线程池最大线程数,则会启动一个非核心线程执行任务;
      • 线程数量已达到线程池规定的最大值,则拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者。

你可能感兴趣的:(Android学习笔记之Handler)