Android实战开发Handler机制深度解析

本文为自己多年来在Android实战开发过程中总结归纳的一些常见问题,现在分享出来希望对初学者有所帮助。

本文出自门心叼龙的博客,转载请注明出处: https://blog.csdn.net/geduo_83/article/details/86560330

目录

[1.异步消息处理线程存在的意义?]
[2.异步消息处理线程都会涉及到哪些类?架构图?]
[3.Handler实现消息的异步收发流程?]
[4.Handler使用场景?]
[5.为什么在子线程中直接实例化一个Hanlder会导致程序崩溃?而再UI主线程则不会?]
[6.子线程中进行UI操作的其他方法]
[7.什么是消息发送所导致的内存泄漏?如果解决?]
[8. 一个标准的异步消息处理线程应该怎么写?]
[9.Android UI主线程为什么要用Loop.loop死循环?]
[10. ActivityThread是线程吗?]
[11.主线程的死循环一直运行是不是特别消耗CPU资源呢?]
[12. 结合图说说Activity生命周期,比如暂停Activity,流程如下?]
[13. 消息分发的优先级?]


1.异步消息处理线程存在的意义?

通过异步消息处理机制实现同一进程间不同线程间的通信,Android有大量的通过消息驱动方式来进行交互,比如Android的四剑客Activity, Service, Broadcast, ContentProvider 的启动过程的交互,都离不开消息机制,Android某种意义上也可以说成是一个以消息驱动的系统

2.异步消息处理线程都会涉及到哪些类?架构图?

  • 2.1 Message:消息实体
  • 2.2 MessageQueue:消息队列存储消息
  • 2.3 Looper:消息循环器,处理消息
  • 2.4 Handler:消息收发器

Hanlder中有Looper
Looper中有MessageQueue
MessageQueue中有Message

image
image.gif

3.Handler实现消息的异步收发流程?

  • 3.1 消息发送【Handler.sendMessage】
public boolean sendMessageAtTime(Message msg,long uptimeMillis){
    boolean sent =false;
    MessageQueuequeue= mQueue;
    if(queue!= null){
        msg.target =this;
        sent = queue.enqueueMessage(msg, uptimeMillis);
    }
    else{
        RuntimeException e =new RuntimeException(
            this+" sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
    }
    return sent;
}
image.gif
  • 3.2 消息入列【MessageQueue.enqueueMessage】
final boolean enqueueMessage(Message msg,long when){
    if(msg.when !=0){
        throw new AndroidRuntimeException(msg +" This message is already in use.");
    }
    if(msg.target == null &&!mQuitAllowed){
        throw new RuntimeException("Main thread not allowed to quit");
    }
    synchronized (this){
        if(mQuiting){
            RuntimeException e = new RuntimeException(msg.target +" sending message to a Handler on a dead thread");
            Log.w("MessageQueue", e.getMessage(), e);
            returnfalse;
        }elseif(msg.target == null){
            mQuiting =true;
        }
        msg.when = when;
        Message p = mMessages;
        if(p == null || when ==0|| when < p.when){
            msg.next = p;
            mMessages = msg;
            this.notify();
        }else{
            Message prev = null;
            while(p != null && p.when <= when){
                prev = p;
                p = p.next;
            }
            msg.next = prev.next;
            prev.next = msg;
            this.notify();
        }
    }
    returntrue;
}
image.gif
  • 3.3 消息循环器取消息【Loop.loop】
publicstatic final void loop(){
    Looper me = myLooper();
    MessageQueue queue= me.mQueue;
    while(true){
        Message msg =queue.next();// might block
        if(msg != null){
            if(msg.target == null){
                return;
            }
            if(me.mLogging!= null) me.mLogging.println(
                    ">>>>> Dispatching to "+ msg.target +" "
                    + msg.callback +": "+ msg.what
                    );
            msg.target.dispatchMessage(msg);
            if(me.mLogging!= null) me.mLogging.println(
                    "<<<<< Finished to    "+ msg.target +" "
                    + msg.callback);
            msg.recycle();
        }
    }
}
image.gif
  • 3.4 消息出列【MessageQueue.next】
Message msg = queue.next();// might block
image.gif
  • 3.5 消息分发【Handler.dispatchMessage】
publicvoid dispatchMessage(Message msg){
    if(msg.callback != null){
        handleCallback(msg);
    }else{
        if(mCallback != null){
            if(mCallback.handleMessage(msg)){
                return;
            }
        }
        handleMessage(msg);
    }
}
image.gif
  • 3.6 消息处理【Handler.handlerMessage】
mHandler = new Handler(){
      publicvoid handleMessage(Message msg){
     // process incoming messages here
  }
};
image.gif
image
image.gif

4.Handler使用场景?

  • 4.1 定时器
  • 4.2 子线程向主线程发消息
  • 4.3 主线程向子线程发消息
  • 4.4 子线程向子线程发消息

5.为什么在子线程中直接实例化一个Hanlder会导致程序崩溃?而再UI主线程则不会?

因为在子线程实例化Hanlder的时候会去判断Loop为不为空,如果有空就直接抛出异常了,UI主线程中直接直接实例化Hanlder是因为,在ActivityThread的main函数中已经创建了Loop对象

public class MainActivity extends Activity {
    private Handler handler1;
    private Handler handler2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        handler1 = new Handler();
        new Thread(new Runnable() {
            @Override
            public void run() {
                handler2 = new Handler();
            }
        }).start();
    }
}
image.gif

程序崩溃了,原因如下:

public Handler(){
    if(FIND_POTENTIAL_LEAKS){
       ...
    mLooper = Looper.myLooper();
    if(mLooper == null){
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = null;
}
image.gif

为什么在activity中创建的Handler时,并没有创建Looper对象,而程序没有崩溃,原因如下:

public static void main(String[] args) {
        SamplingProfilerIntegration.start();
        CloseGuard.setEnabled(false);
        Environment.initForCurrentUser();
        EventLogger.setReporter(newEventLoggingReporter());
        Process.setArgV0("<pre-initialized>");
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if (sMainThreadHandler == null) {
            MainThreadHandler = thread.getHandler();
        }
        AsyncTask.init();
        if (false) {
            Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));
        }
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
}
image.gif

6.子线程中进行UI操作的其他方法

另外除了发送消息之外,我们还有以下几种方法可以在子线程中进行UI操作,本质都是调用了Handler Post方法

  • 6.1 Handler的post()方法
  • 6.2 View的post()方法
  • 6.3 Activity的runOnUiThread()方法

7.什么是消息发送所导致的内存泄漏?如果解决?

在Activity还没有收到消息的情况下就已经关闭了Activity,此时Message中携带了Handler的引用,而Hanlder中又隐式的持有了Activity的引用,垃圾回收机制发现Activity还在被引用着,此时该Activity就不会被销毁,Activity所占用内存了就成了垃圾内存,此时就造成了内存泄漏

  • 7.1 使用清除消息进行解决
@Override
publicvoid onDestroy(){
        mHandler.removeMessages(MESSAGE_1);
        mHandler.removeCallbacks(mRunnable);
        Handler.removeCallbacksAndMessages(null);
}   
image.gif
  • 7.2 使用弱引用解决
public class HandlerActivity2 extends Activity{
    private final Handler mHandler = new MyHandler(this);
    @Override
    publicvoid onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler.sendMessageDelayed(Message.obtain(),60000);
        // just finish this activity
        finish();
    }
    publicvoid todo(){
    };
    private static class MyHandler extends Handler{
        private final WeakReference<HandlerActivity2> mActivity;
        public MyHandler(HandlerActivity2 activity){
            mActivity = new WeakReference<HandlerActivity2>(activity);
        }
        @Override
        publicvoid handleMessage(Message msg){
            System.out.println(msg);
            if(mActivity.get()== null){
                return;
            }
            mActivity.get().todo();
        }
    }
image.gif

8. 一个标准的异步消息处理线程应该怎么写?

  • 方法1:
class LooperThread extends Thread {
      public Handler mHandler;

      public void run() {
          Looper.prepare();
          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };
          Looper.loop();
      }
}
image.gif
  • 方法2:
  // Step 1: 创建并启动HandlerThread线程,内部包含Looper
    HandlerThread handlerThread = new HandlerThread("gityuan.com");
    handlerThread.start();

    // Step 2: 创建Handler
    Handler handler = new Handler(handlerThread.getLooper()) {
       public void handleMessage(Message msg) {
         // process incoming messages here
       }
     };
    // Step 3: 发送消息
     handler.post(new Runnable() {
        @Override
        public void run() {
            System.out.println("thread id="+Thread.currentThread().getId());
        }
    });
image.gif

9.Android UI主线程为什么要用Loop.loop死循环?

这个死循环会不会卡死UI主线程?既然是死循环又怎么去处理其他事务呢?
  • 9.1 对于UI主线程绝对不希望运行一段时间就自动退出,那就需要可执行代码一直执行下去,最简单的方法就是死循环
  • 9.2 当然它不是简单的死循环,无消息会处于休眠状态,在onCreate、onStart,onResume里面有比较耗时的操作有可能会导致主线程卡死,Loop.loop并不会导致应用卡死
  • 9.3 在UI主线程会创建Binder子线程去处理其他事务,thread.attach(false);便会创建一个Binder线程(具体是指ApplicationThread,Binder的服务端,用于接收系统服务AMS发送来的事件),该Binder线程通过Handler将Message发送给主线程

10. ActivityThread是线程吗?

ActivityThread实际上并非线程,不像HandlerThread类,ActivityThread并没有真正继承Thread类,只是往往运行在主线程,该人以线程的感觉,其实承载ActivityThread的主线程就是由Zygote fork而创建的进程

11.主线程的死循环一直运行是不是特别消耗CPU资源呢?

其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,详情见Android消息机制1-Handler(Java层),此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

12. 结合图说说Activity生命周期,比如暂停Activity,流程如下?

image
image.gif

  • 12.1 线程1的AMS中调用线程2的ATP;(由于同一个进程的线程间资源共享,可以相互直接调用,但需要注意多线程并发问题)
  • 12.2 线程2通过binder传输到App进程的线程4;
  • 12.3 线程4通过handler消息机制,将暂停Activity的消息发送给主线程;
  • 12.4 主线程在looper.loop()中循环遍历消息,当收到暂停Activity的消息时,便将消息分发给ActivityThread.H.handleMessage()方法,再经过方法的调用,最后便会调用到Activity.onPause(),当onPause()处理完后,继续循环loop下去。

13. 消息分发的优先级?

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

推荐阅读更多精彩内容