美文网首页Android架构师
Android 异步任务和消息机制面试题

Android 异步任务和消息机制面试题

作者: 星邪Ara | 来源:发表于2022-04-06 08:18 被阅读0次

    1.1 HandlerThread 的使用场景和用法?

    HandlerThread 本质上是一个在子线程的handler (HandlerThread=Handler+Thread);
    它的使用:
    步骤1:创建HandlerThread实例对象

    HandlerThread mHandlerThread = new HandlerThread("handlerThread");
    

    步骤2:启动线程

    mHandlerThread.start();
    

    步骤3:创建工作线程Handler & 复写handleMessage()

    Handler workHandler = new Handler(
    handlerThread.getLooper() ) {
        @OverRide
        public boolean handleMessage(Message msg) {
            ...//消息处理
            return true;
        }
    });
    

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

    Message msg = Message.obtain();
    msg.what = 2; //消息的标识
    msg.obj = "B"; // 消息的存放
    // b. 通过Handler发送消息到其绑定的消息队列
    workHandler.sendMessage(msg);
    

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

    mHandlerThread.quit();
    

    优势:

    1. 将loop运行在子线程中处理,减轻了主线程的压力,使主线程更流畅
    2. 串行执行,开启一个线程起到多个线程的作用
    3. 有自己的消息队列,不会干扰UI线程

    劣势:

    1. 由于每一个任务队列逐步执行,一旦队列耗时过长,消息延时
    2. 对于IO等操作,线程等待,不能并发

    1.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();
            }
        }
    }
    

    1.3 AsyncTask的优点和缺点?

    AsyncTask的实现原理:

    1. AsyncTask是一个抽象类,主要由Handler+2个线程池构成,SERIAL_EXECUTOR是任务队列线程池,用于调度任务,按顺序排列执行,THREAD_POOL_EXECUTOR是执行线程池,真正执行具体的线程任务。Handler用于工作线程和主线程的异步通信。
    2. AsyncTask<Params,Progress,Result>,其中Params是doInBackground()方法的参数类型,Result是doInBackground()方法的返回值类型,Progress是onProgressUpdate()方法的参数类型。
    3. 当执行execute()方法的时候,其实就是调用SERIAL_EXECUTOR的execute()方法,就是把任务添加到队列的尾部,然后从头开始取出队列中的任务,调用THREAD_POOL_EXECUTOR的execute()方法依次执行,当队列中没有任务时就停止。
    4. AsyncTask只能执行一次execute(params)方法,否则会报错。但是SERIAL_EXECUTOR和
      THREAD_POOL_EXECUTOR线程池都是静态的,所以可以形成队列。

    Q:AsyncTask只能执行一次execute()方法,那么为什么用线程池队列管理 ?

    因为SERIAL_EXECUTOR和THREAD_POOL_EXECUTOR线程池都是静态的,所有的AsyncTask实例都共享这2个线程池,因此形成了队列。

    Q:AsyncTask的onPreExecute()、doInBackground()、onPostExecute()方法的调用流程?

    AsyncTask在创建对象的时候,会在构造函数中创建mWorker(workerRunnable) mFuture(FutureTask)对象。
    mWorker实现了Callable接口的call()方法,在call()方法中,调用了doInBackground()方法,并在最后调用了postResult()方法,也就是通过Handler发送消息给主线程,在主线程中调用AsyncTask的finish()方法,决定是调用onCancelled()还是onPostExecute().

    mFuture实现了Runnable和Future接口,在创建对象时,初始化成员变量mWorker,在run()方法中,调用mWorker的call()方法。

    当asyncTask执行execute()方法的时候,会先调用onPreExecute()方法,然后调用SERIAL_EXECUTOR的execute(mFuture),把任务加入到队列的尾部等待执行。执行的时候调用THREAD_POOL_EXECUTOR的execute(mFuture).

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

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

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

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

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

    1.6 谈谈 Handler 机制和原理?

    首先在UI线程我们创建了一个Handler实例对象,无论是匿名内部类还是自定义类生成的Handler实例对象,我们都需要对handleMessage方法进行重写,在handleMessage方法中我们可以通过参数msg来写接受消息过后UI线程的逻辑处理,接着我们创建子线程,在子线程中需要更新UI的时候,新建一个Message对象,并且将消息的数据记录在这个消息对象Message的内部,比如arg1,arg2,obj等,然后通过前面的Handler实例对象调用sendMessge方法把这个Message实例对象发送出去,之后这个消息会被存放于MessageQueue中等待被处理,此时MessageQueue的管家Looper正在不停的把MessageQueue存在的消息取出来,通过回调dispatchMessage方法将消息传递给Handler的handleMessage方法,最终前面提到的消息会被Looper从MessageQueue中取出来传递给handleMessage方法。

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

    不能在还没有调用 Looper.prepare() 方法的线程中创建Handler。

    因为抛出异常的地方,在mLooper 对象为null的时候,会抛出异常。说明这里的Looper.myLooper();的返回值是null。 只有调用了Looper.prepare()方法,才会构造一个Looper对象并在 ThreadLocal 存储当前线程的Looper 对象。

    这样在调用 Looper.myLooper() 时,获取的结果就不会为null。

    1.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。

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

    主线程挂起

    Looper 是一个死循环, 不断的读取MessageQueue中的消息, loop 方法会调用 MessageQueue 的 next 方法来获取新的消息,next 操作是一个阻塞操作,当没有消息的时候 next 方法会一直阻塞, 进而导致 loop 一直阻塞,理论上 messageQueue.nativePollOnce 会让线程挂起-阻塞-block 住, 但是为什么, 在发送 delay 10s 的消息, 假设消息队列中, 目前只有这一个消息;

    那么为什么在这 10s 内, UI是可操作的, 或者列表页是可滑动的, 或者动画还是可以执行的?
    先不讲 nativePollOnce 是怎么实现的阻塞, 我们还知道, 另外一个 nativeWake, 是实现线程唤醒的;
    那么什么时候会, 触发这个方法的调用呢, 就是在有新消息添加进来的时候, 可是并没有手动添加消息啊?
    display 每隔16毫秒, 刷新一次屏幕;
    SurfaceFlingerVsyncChoreographer 每隔16毫秒, 发送一个 vSync 信号;
    FrameDisplayEventReceiver 收到信号后, 调用 onVsync方法, 通过 handler 消息发送到主线程处理, 所以就会有消息添加进来, UI线程就会被唤醒;

    事实上, 安卓系统, 不止有一个屏幕刷新的信号, 还有其他的机制, 比如输入法和系统广播, 也会往主线程的MessageQueue 添加消息;

    所以, 可以理解为, 主线程也是随时挂起, 随时被阻塞的;

    系统怎么实现的阻塞与唤醒

    这种机制是通过pipe(管道)机制实现的;
    简单来说, 管道就是一个文件在管道的两端, 分别是两个打开文件的, 文件描述符, 这两个打开文件描述符, 都是对应同一个文件, 其中一个是用来读的, 别一个是用来写的;
    一般的使用方式就是, 一个线程通过读文件描述符, 来读管道的内容, 当管道没有内容时, 这个线程就会进入等待状态,
    而另外一个线程, 通过写文件描述符, 来向管道中写入内容,写入内容的时候, 如果另一端正有线程, 正在等待管道中的内容, 那么这个线程就会被唤醒;
    这个等待和唤醒的操作是如何进行的呢, 这就要借助 Linux系统中的 epoll 机制了, Linux 系统中的 epoll 机制为处理 大批量句柄而作了改进的 poll,是 Linux 下多路复用 IO 接口 select/poll 的增强版本, 它能显著减少程序, 在大量并发连接中, 只有少量活跃的情况下的系统 CPU 利用率;
    即当管道中有内容可读时, 就唤醒当前正在等待管道中的内容的线程;

    怎么证明, 线程被挂起了

        @Override
        public void onCreateData(@Nullable Bundle
                                         bundle) {
            new Thread() {
                @SuppressLint("HandlerLeak")
                @Override
                public void run() {
                    super.run();
                    LogTrack.v("thread.id = " +
                            Thread.currentThread().getId());
                    Looper.prepare();
                    Handler handler = new
                            Handler(Looper.getMainLooper()) {
                                @Override
                                public void
                                handleMessage(Message msg) {
                                    super.handleMessage(msg);
                                    LogTrack.v("thread.id = "
                                                    + Thread.currentThread().getId() + ", what =
                                            " + msg.what);
                                }
                            };
                    LogTrack.w("loop.之前"); // 执行了
                    Looper.loop(); // 执行了
                    LogTrack.w("loop.之后"); // 无法执行
                }
            }.start();
        }
    
    

    相关文章

      网友评论

        本文标题:Android 异步任务和消息机制面试题

        本文链接:https://www.haomeiwen.com/subject/rmsnjrtx.html