Android HandlerThread 解析

简介

  • 我们知道如果在子线程中创建Handler的话,需要准备一个Looper对象,并且开启消息的轮询,如下
    new Thread(new Runnable() {
             @Override
             public void run() {
                 Looper.prepare();
                 Handler handler = new Handler();
                 Looper.loop();
             }
         }).start();
    
  • 那么我们在再说说这个HandlerThread
    • HanderThread是继承至Thread,并且内部创建了一个Looper对象存储在当前线程的本地存储区中,然后调用了Looper中的loop方法。
    • 其实Android提供这个HandlerThread就是为了避免在子线程中创建Handler时去自己创建Looper对象。

HandlerThread基本用法

  //1. 创建一个HandlerThread,
  //其中name是一个标记,声明线程名字,因为HandlerThread直接继承至Thread
  HandlerThread mHandlerThread = new HandlerThread("name");
  //2. 开启ThreadHandler,调用start()最终会回调内部实现的run方法。
 mHandlerThread.start(); 

  //3. 在Ui线程中声明一个UI线程中的Handler
  Handler uiHandler = new Handler();
  
  //4. 声明一个工作线程的Handler, 重写handleMessage方法。
   Handler mWorkHandler = new Handler(mHandlerThread.getLooper()){
        handleMessage(Message msg){
        //此时这个handleMessage方法是运行在HandlerThread线程中的。(反正不是UI线程中的)
        
          // 6.  调用UI线程的Handler来更新UI
          uiHandler.post(new Runnable(){
                  run(){
                      //更新UI
                    }  
          })
        
      }
  }

  //5. 调用工作线程发送消息
    Message msg = Message.obtain();
    msg.what = 1;
    msg.obj = "Hello World";
    mWorkHandler.sendMessage(msg)

源码解析

HandlerThread的源码分析就按照用法的顺序来进行分析了

  • 创建HandlerThread 看它的构造方法
    public HandlerThread(String name) {
      //其中name是一个标记声明线程的名字
      super(name);
      //在这里指定线程的优先级 这里的优先级 是Process 而不是Java里面的Thread.
      mPriority = Process.THREAD_PRIORITY_DEFAULT;
      }
    /**
    * Constructs a HandlerThread.
    * @param name
    * @param priority The priority to run the thread at. The value supplied must be from 
    * {@link android.os.Process} and not from java.lang.Thread.
    */
    public HandlerThread(String name, int priority) {
      super(name);
      //我们可以自定义线程的优先级,默认是THREAD_PRIORITY_DEFAULT
      mPriority = priority;
    }
    
  • 调用start方法开启线程

因为HandlerThread继承Thread调用start最终会回调run所以我们看内部的run方法就行。

  • run()
    public void run() {
        // 获取到当前线程的ID
        mTid = Process.myTid();
        // 创建一个Looper & MeesageQueue对象。
        Looper.prepare();
        //通过持有锁的机制,来获取到Looper对象
        synchronized (this) {
            mLooper = Looper.myLooper();
            //发出通知,Looper对象已经创建成功了。
            //这里调用notifyAll是为了唤醒下文中getLooper的方法。
            notifyAll();
        }
        //设置当前线程的优先级
        Process.setThreadPriority(mPriority);
        
        //这是一个空方法,在执行线程之前进行初始化的操作,调用者可以重写该方法自行实现内部的方法。
        onLooperPrepared();
        //循环获取MessageQueue中的消息,并且将消息分发出去。
        Looper.loop();
        mTid = -1;
    }
    
  • getLooper()
    public Looper getLooper() {
         //如果当前线程没有存活,就返回null
       If (!isAlive()) {
           return null;
       }
    
       // If the thread has been started, wait until the looper has been created.
       //运用到锁机制
       synchronized (this) {
           //如果当前线程是存活状态 & mLooper对象并没有创建成功!
          //就阻塞线程
           while (isAlive() && mLooper == null) {
               try {
                
                 // run方法中的notifyAll就是为了通知wait方法,Looper对象创建成功了,这时就返回Looper对象了。
                // 因为工作Handler调用了 ThreadHandler中的getLooper()f方法,WorkHandler是创建在UI线程的,而ThreadHandler调用start()方法是运行在子线程中的,
               //当UI线程要获取Looper对象的时候,如果这时子线程并没有创建好Looper对象,那么这时就处于阻塞的状态,直到Looper对象创建成功,返回Looper对象。
                   wait();
               } catch (InterruptedException e) {
               }
           }
       }
       return mLooper;
    }
    
  • quit 和 quitSafely
      // quit()
      //在消息机制中分析过,Looper调用了quit最终是在MessageQueue中调用了,removeAllMessagesLocked()方法,
      //该方法是将消息队列中的所有消息退出,包括运行中的和没有运行的
        public boolean quit() {
          Looper looper = getLooper();
          if (looper != null) {
              looper.quit();
              return true;
          }
          return false;
      }
      //quiSafely
      // Looper调用了quitSafely()最终调用了MessageQueue中的removeAllFutureMessagesLocked()
      // 该方法是将消息队列中未运行的消息给 退出,运行中的九让它继续运行了。
      public boolean quitSafely() {
          Looper looper = getLooper();
          if (looper != null) {
              looper.quitSafely();
              return true;
          }
          return false;
      }
    

问题记录

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