Android 异步任务和消息机制面试题和答案分析

1.HandlerThread 的使用场景和用法?

HandlerThread 是 Android 封装的一个线程类,将 Thread 跟 Handler 封装。使用步骤如下:

创建 HandlerThread 实例对象:

    HandlerThread mHandlerThread = new HandlerThread("mHandlerThread");

启动线程:

mHandlerThread .start();

创建Handler对象,重写handleMessage方法:

Handler mHandler= new Handler( mHandlerThread.getLooper() ) {
           @Override
           public boolean handleMessage(Message msg) {
               //消息处理
               return true;
           }
     });

使用工作线程Handler向工作线程的消息队列发送消息:

Message  message = Message.obtain();
     message.what = “2”
     message.obj = "骚风"
     mHandler.sendMessage(message);

结束线程,即停止线程的消息循环:

 mHandlerThread.quit();

 2.IntentService 的应用场景和使用姿势?

        IntentService 是 Service 的子类,默认为我们开启了一个工作线程,使用这个工作线程逐一处理所有启动请求,在任务执行完毕后会自动停止服务,使用简单,只要实现一个方法 onHandleIntent,该方法会接收每个启动请求的 Intent,能够执行后台工作和耗时操作。可以启动 IntentService 多次,而每一个耗时操作会以队列的方式在 IntentService 的 onHandlerIntent 回调方法中执行,并且,每一次只会执行一个工作线程,执行完第一个再执行第二个。并且等待所有消息都执行完后才终止服务。

IntentService 适用于 APP 在不影响当前用户的操作的前提下,在后台默默的做一些操作。

IntentService源码:

  1. 通过 HandlerThread 单独开启一个名为 IntentService 的线程
  2. 创建一个名叫 ServiceHandler 的内部 Handler
  3. 把内部Handler与HandlerThread所对应的子线程进行绑定
  4. 通过 onStartCommand() 传递给服务 intent,依次插入到工作队列中,并逐个发送给 onHandleIntent()
  5. 通过 onHandleIntent() 来依次处理所有 Intent 请求对象所对应的任务

示例代码:

public class MyIntentService extends IntentService {
    public static final String TAG ="MyIntentService";
    public MyIntentService() {
        super("MyIntentService");
    }
 
    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
 
       boolean isMainThread =  Thread.currentThread() == Looper.getMainLooper().getThread();
        Log.i(TAG,"is main thread:"+isMainThread); // 这里会打印false,说明不是主线程
 
        // 模拟耗时操作
        download();
    }
 
    /**
     * 模拟执行下载
     */
    private void download(){
       try {
           Thread.sleep(5000);
           Log.i(TAG,"下载完成...");
       }catch (Exception e){
           e.printStackTrace();
       }
    }
}

 3.AsyncTask的优点和缺点?

        AsyncTask是一个抽象类,主要由Handler+2个线程池构成,SERIAL_EXECUTOR是任务队列线程池,用于调度任务,按顺序排列执行,THREAD_POOL_EXECUTOR是执行线程池,真正执行具体的线程任务。Handler用于工作线程和主线程的异步通信。

优点:AsyncTask操作简单,轻便,适用简单的异步操作

缺点:存在新开大量线程,消耗系统资源的风险

4.谈谈你对 Activity.runOnUiThread 的理解?

        一般是用来将一个runnable绑定到主线程,在runOnUiThread源码里面会判断当前runnable是否是主线程,如果是直接run,如果不是,通过一个默认的空构造函数handler将runnable post 到looper里面,创建构造函数handler,会默认绑定一个主线程的looper对象

5.子线程能否更新UI?为什么?

子线程是不能直接更新UI的

注意这句话,是不能直接更新,不是不能更新(极端情况下可更新)

绘制过程要保持同步(否则页面不流畅),而我们的主线程负责绘制ui,极端情况就是,在Activity的onResume(含)之前的生命周期中子线程都可以进行更新ui,也就是 onCreate,onStart和onResume,此时主线程的绘制还没开始。

6.谈谈 Handler 机制和原理?

        说道handler就不得不提 LooperMessageQueue 和 Handler

Looper:它在消息机制里是用来把普通线程转成looper线程的(Looper.prepare()),并担当消息循环的任务,当然,UI 线程是通过Looper.PrepareMainLoper()。
它会开启无限循环(Loper.loop())并不停的从 MessageQueue 中查看是否有新消息,如果有就拿出来处理,如果没有呢,就阻塞(其实真正的阻塞在 MessageQueue 的 next 里)。Loper在构造的时候会构建一个 MessageQueue 并持有它的引用。

MessageQueue: 消息队列,它用来插入(enqueueMessage),读取消息(next)。它虽然是个消息队列,但实际实现是个单链表,因为单链表在插入和读取上有优势。它插入是根据时间戳排序来的,根据时间戳的排序来指定next的下一条消息。而当next读取的时候,如果有消息,就取出来,如果没有,就阻塞。如果 next 取出来是 null,那么就表示整个app可以结束运行了。

Handler 的主要工作就是负责消息的发送和接收了。发送消息是 post 和 send 的一堆函数,其实到最后 enqueueMessage() 来进行入队,接收消息处理则是通过msg的next里返回的 looper 在交给 handler 的 dispatchMessage() 方法。Handler 在哪个线程之下构造就会持有当前这个线程的 looper 引用。Handler 工作的时候首先会判断 msg.callback(Runnable对象)是不是 null,不为 null 就会变为 handlerCallback(执行msg的callback),然后检查 mCallback 是不是 null,不为 null 执行 handlerMessage。而这个 mCallback 则又是通过 msg 的 target(对应的handler)来进行回调调用的。

那么整个流程是:activityThread 里 main 方法执行即 app 启动的时候,会把当前线程转为 UI 线程(Loper.PrepareMainLoper()),并且会构造 activityThread(下面简称actThread)实例,它的 attach 方法会创建 Binder 线程通道(ApplicationThread,用来接受系统进程传递过来的信息的线程)。然后通过 Looper.loop 来开启无限循环。无限循环开启后,需要一个 handler 和消息队列进行交互,这个handler (ActivityThread.H,简称mH)主要用来管理四大组件的生命周期,启动,停止等消息类型。actThread 通过之前创建的bind线程与ams进行通信,ams执行完actThread的请求后会回调 binder 线程的 bind 方法,然后 binder 线程会向 mH 发送消息,mH 收到消息后会回到 actThread 中去执行。

 7.为什么在子线程中创建Handler会抛异常?

        主线程默认执行了looper.prepare方法,此时使用Handler就可以往相信队列中不断进行发送消息和取出消息处理,反之没有执行looper.prepare方法,就会抛出异常,这个在源码中有所体现

8.从源码角度分析Handler的post和sendMessage方法的区别和应用场景?

        handler.post和handler.sendMessage方法最后都会调用sendMessageAtTime方法进行消息的发送,但是在post方法中message是通过getPostMessage(Runnable r)这个方法获取的message,在这个方法中有这样一句代码m.callback = r ,给message的callback赋值为runnable对象,而在dispatchMessage这个方法中对消息进行分发的时候,先进行了msg.callback != null的判断,如果不为null,消息是通过handleCallback(msg);这个方法处理的,在这个方法中message.callback.run();调用的是post方法传递过来的runnable内的run方法处理消息,如果为空,再进行handler内部的callback判断mCallback != null,如果handler内的callback不为空,执行mCallback.handleMessage(msg)这个处理消息并判断返回是否为true,如果返回true,消息处理结束,如果返回false,消息交给handler的handleMessage(msg)处理。
        所以区别就是调用post方法的消息是在post传递的Runnable对象的run方法中处理,而调用sendMessage方法需要重写handleMessage方法或者给handler设置callback,在callback的handleMessage中处理并返回true。

9.Handler中有Loop死循环,为什么没有阻塞主线程,原理是什么?

        可以这样简单的来理解一下,一个Thread对应一个Looper和一个MessageQueue
这个MessageQueue是个一个阻塞队列,类似BlockingQueue,不同之处在于MessageQueue的阻塞方式是通过Pipe机制实现的。
阻塞队列,就是当队列里没有数据时,如果调用获取队首数据的方法时,当前线程会被阻塞(相当于执行了线程的wait方法),如果队列里面有了插入了新数据,则会唤醒被阻塞的方法(相当于执行了线程的notify方法),并返回该数据。再来看MessageQueue,这里的数据指的就是是每一个消息,这个消息则是通过handler来发送的。

综上所述,线程并没有一直死循环的工作,而是在没消息时被暂时挂起了,当有新消息进来的时候,就会又开始工作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值