关于AsyncTask、HandlerThread的理解

在这之前 我们来先理解下消息循环机制


image.png
  • 作用

实现多线程
在工作线程中执行任务,如 耗时任务
异步通信、消息传递
实现工作线程 & 主线程(UI线程)之间的通信,即:将工作线程的执行结果传递给主线程,从而在主线程中执行相关的UI操作

1、AsyncTask

1.21 基本使用
  private class MyTask extends AsyncTask<String, Integer, String> {
        // 方法1:onPreExecute()
        // 作用:执行 线程任务前的操作
        @Override
        protected void onPreExecute() {
            Log.w("TAG", "---開始加載---onPreExecute----");
            // 执行前显示提示
        }

        // 方法3:onProgressUpdate()
        // 作用:在主线程 显示线程任务执行的进度
        @Override
        protected void onProgressUpdate(Integer... progresses) {
            Log.w("TAG", "---执行中---onProgressUpdate----"+ + progresses[0] + "%");

        }

        // 方法4:onPostExecute()
        // 作用:接收线程任务执行结果、将执行结果显示到UI组件
        @Override
        protected void onPostExecute(String result) {
            // 执行完毕后,则更新UI
            Log.w("TAG", "---执行完成---onPostExecute----"+result);

        }

        // 方法5:onCancelled()
        // 作用:将异步任务设置为:取消状态
        @Override
        protected void onCancelled() {
            Log.w("TAG", "------onCancelled----");
        }

        @Override
        protected String doInBackground(String... strings) {
            try {
                int count = 0;
                int length = 1;
                while (count<99) {
                    count += length;
                    // 可调用publishProgress()显示进度, 之后将执行onProgressUpdate()
                    publishProgress(count);
                    // 模拟耗时任务
                    Thread.sleep(50);
                }
            }catch (InterruptedException e) {
                e.printStackTrace();
            }

            return null;
        }
    }

在需要的地方調用

 /**
                 * 步骤:手动调用execute(Params... params) 从而执行异步线程任务
                 * 注:
                 *    a. 必须在UI线程中调用
                 *    b. 同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常
                 *    c. 执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute()
                 *    d. 不能手动调用上述方法
                 */
            task=new MyTask();
                task.execute();

//执行结果
 W/TAG: ---開始加載---onPreExecute----
 W/TAG: ---执行中---onProgressUpdate----1%
 W/TAG: ---执行中---onProgressUpdate----2%
 W/TAG: ---执行中---onProgressUpdate----3%
 W/TAG: ---执行中---onProgressUpdate----4%
 W/TAG: ---执行中---onProgressUpdate----5%
....................
.....................
....................
 W/TAG: ---执行完成---onPostExecute----null

1.2 具体原理介绍
  • AsyncTask的实现原理 = 线程池 + Handler

其中:线程池用于线程调度、复用 & 执行任务;Handler 用于异步通信

  • 其内部封装了2个线程池 + 1个Handler,具体介绍如下:
image.png

执行任务前,通过 任务队列 线程池类(SerialExecutor)将任务按顺序放入到队列中;
通过同步锁 修饰execute()从而保证AsyncTask中的任务是串行执行的
SerialExecutor是使用ArrayDeque这个队列来管理Runnable对象的,ArrayDeque的offer()方法将传入的Runnable对象添加到队列的尾部,然后看mActive是不是空的,是空的就调用scheduleNext()在头部拿一个给它,也就是说每次当一个任务执行完毕后,下一个任务才会得到执行

private static class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }

//通过同步锁 修饰execute()从而保证AsyncTask中的任务是串行执行的
        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

任務自行完成后通过

    private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }
  private static class InternalHandler extends Handler {
        public InternalHandler(Looper looper) {
            super(looper);
        }

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

handleMessage中对消息的类型进行了判断,如果这是一条MESSAGE_POST_RESULT消息,就会去执行finish()方法,如果这是一条MESSAGE_POST_PROGRESS消息,就会去执行onProgressUpdate()方法。那么finish()方法的源码如下所示:

    private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

总结

  • 静态代码块 创建了THREAD_POOL_EXECUTOR 线程池,用来执行任务

  • 任务队列 线程池 SerialExecutor,内部维护一个双向队列,任务调度,按需执行多个线程,按顺序排列
    通过同步锁execute保证任务是串行执行,SerialExecutor是使用ArrayDeque这个队列来管理Runnable对象的,ArrayDeque的offer()方法将传入的Runnable对象添加到队列的尾部。然后看mActive是不是空的,是空的就调用scheduleNext()在头部拿一个给它,也就是说每次当一个任务执行完毕后,下一个任务才会得到执行。

  • 任務自行完成后通过调用 postResult(Result result) ,通过handler发送消息,在handleMessage
    对消息的类型进行了判断,如果这是一条MESSAGE_POST_RESULT消息,就会去执行finish()方法,如果这是一条MESSAGE_POST_PROGRESS消息,就会去执行onProgressUpdate()方法。

2、HandlerThread

线程间通信的时候,比如Android中常见的更新UI,涉及到的是子线程和主线程之间的通信,实现方式就是Handler+Looper,但是要自己手动操作Looper,不推荐,所以谷歌封装了HandlerThread类(类似于AsyncTask类)。

2、1 基本使用
      //这个是主线程Handler 实例 还有一个知识点就是如果new Handler时候没有给她传递looper的话,他会默认的去当前运行线程的looper进行联系
       MainHandler mainHandler = new MainHandler(this);


       HandlerThread handlerThread = new HandlerThread("first");
       handlerThread.start();
        //这是子线程联系的handler 传入了handlerThread的looper
        final Handler childHandler = new Handler(handlerThread.getLooper()) {
            int count = 0;
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                System.out.println("子线程 的handler 接受到数据  现在 将数据发送给 主线程 执行 "+Thread.currentThread().getName());
                //发送消息给 主线程的handler 让她更新ui
                Message.obtain(mainHandler, 1, ++count).sendToTarget();
            }
        };



    // 开启一个子线程 调用childHandler 发送信息   
    button.setOnClickListener(new View.OnClickListener() {
         @Override
        public void onClick(View v) {

           new Thread(new Runnable() {
              @Override
               public void run() {
                System.out.println("子线程发送数据 " +  Thread.currentThread().getName());
                //发送给handlerThread的handler 
                 childHandler.sendEmptyMessage(1);
              }
         }).start();
     }
  });


    //这个是主线程的Handler 定义成这种形式,可以防止内存泄露
   static class MainHandler extends Handler{
        private WeakReference<MainActivity> mainActivityWeakReference;
        
        MainHandler(MainActivity mainActivity) {
            this.mainActivityWeakReference =new WeakReference<>(mainActivity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            mainActivityWeakReference.get().textView.setText(msg.obj+"");
        }
    }


HandlerThread是不能更新UI的

正确使用是 我们仍然需要主线程的handler来更新ui,HandlerThread来接受其他子线程发送过来的消息,这些消息的处理不需要更新ui
在上面我们可以看见,我们开启了一个子线程,在子线程里面调用了 HandlerThread的childHandler发送信息,childHandler接受到消息,再调用主线程的mainHandler刷新ui。

通过上面的例子来看 ,大家肯定感觉 这不是多此一举么。一开始 我也是这么想的,后来 我仔细思考,这个HandlerThread 其实可以应用于这些情况下面:

比如,你同时有好几个子线程,你需要得到他们运算时候发出的信息,以前使用接口回调,还得实现的 ,如果 好几个子线程 岂不是要好几个接口。
比如 还有 一个子线程运算完 你想知道结果,也可以通过这个来实现。以前你使用callable,接口的话 也还是 比较复杂一点的。

2、1 工作原理

内部原理 = Thread类 + Handler类机制,即:

通过继承Thread类,快速地创建1个带有Looper对象的新工作线程
通过封装Handler类,快速创建Handler & 与其他线程进行通信


HandlerThread退出销毁

 // 方式1:quit() 
  // 特点:效率高,但线程不安全
  public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit(); 
            return true;
        }
        return false;
    }

  // 方式2:quitSafely()
  // 特点:效率低,但线程安全
  public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    
   
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    
   
    protected void onLooperPrepared() {
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
    
   
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

    
    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }

   
    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }

  
    public int getThreadId() {
        return mTid;
    }
}

首先我们可以看到HandlerThread继承自Thread,因此在run()中的逻辑都是在子线程中运行的。

接下来就是两个关键的方法,run()和getLooper():
run()中可以看到是很简单的创建Looper以及让Looper工作的逻辑。
run()里面当mLooper创建完成后有个notifyAll(),getLooper()中有个wait(),这有什么用呢?因为的mLooper在一个线程中执行创建,而我们的handler是在UI线程中调用getLooper()初始化的。
也就是说,我们必须等到mLooper创建完成,才能正确的返回。getLooper();wait(),notify()就是为了解决这两个线程的同步问题。

推荐阅读更多精彩内容