关于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()就是为了解决这两个线程的同步问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,873评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,483评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,525评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,595评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,018评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,958评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,118评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,873评论 0 208
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,643评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,813评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,293评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,615评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,306评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,170评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,968评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,107评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,894评论 2 278

推荐阅读更多精彩内容