欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

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

来源: 开发者 投稿于  被查看 42512 次 评论:174

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


目录
  • 1.1 HandlerThread 的使用场景和用法?
  • 1.2 IntentService 的应用场景和使用姿势?
  • 1.3 AsyncTask的优点和缺点?
    • Q:AsyncTask只能执行一次execute()方法,那么为什么用线程池队列管理 ?
    • Q:AsyncTask的onPreExecute()、doInBackground()、onPostExecute()方法的调用流程?
  • 1.4 谈谈你对 Activity.runOnUiThread 的理解?
    • 1.5 子线程能否更新UI?为什么?
      • 1.6 谈谈 Handler 机制和原理?
        • 1.7 为什么在子线程中创建Handler会抛异常?
          • 1.8 试从源码角度分析Handler的post和sendMessage方法的区别和应用场景?
            • 1.9 Handler中有Loop死循环,为什么没有阻塞主线程,原理是什么?
              • 主线程挂起
              • 系统怎么实现的阻塞与唤醒
              • 怎么证明, 线程被挂起了

            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();
            

            优势:

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

            劣势:

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

            1.2 IntentService 的应用场景和使用姿势?

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

            IntentService源码:

            • 通过 HandlerThread 单独开启一个名为IntentService 的线程
            • 创建一个名叫 ServiceHandler 的内部 Handler
            • 把内部Handler与HandlerThread所对应的子线程进行绑定
            • 通过 onStartCommand() 传递给服务 intent,依次插入到工作队列中,并逐个发送给 onHandleIntent()
            • 通过 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的实现原理:

            • AsyncTask是一个抽象类,主要由Handler+2个线程池构成,SERIAL_EXECUTOR是任务队列线程池,用于调度任务,按顺序排列执行,THREAD_POOL_EXECUTOR是执行线程池,真正执行具体的线程任务。Handler用于工作线程和主线程的异步通信。
            • AsyncTask<Params,Progress,Result>,其中Params是doInBackground()方法的参数类型,Result是doInBackground()方法的返回值类型,Progress是onProgressUpdate()方法的参数类型。
            • 当执行execute()方法的时候,其实就是调用SERIAL_EXECUTOR的execute()方法,就是把任务添加到队列的尾部,然后从头开始取出队列中的任务,调用THREAD_POOL_EXECUTOR的execute()方法依次执行,当队列中没有任务时就停止。
            • 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 异步任务和消息机制面试题分析的详细内容,更多关于Android 异步任务消息机制的资料请关注3672js教程其它相关文章!

            您可能感兴趣的文章:
            • Android-AnsyncTask异步任务的使用
            • Android 异步任务 设置 超时使用handler更新通知功能
            • Android带进度条的下载图片示例(AsyncTask异步任务)
            • Android AsyncTack 异步任务实例详解
            • Android消息机制Handler用法总结
            • 详解Android 消息处理机制
            • Android编程实现异步消息处理机制的几种方法总结

            用户评论