Handler消息机制的使用与(部分源码分析),不断更新

1.作用,背景,产生原因

1.1 作用

在多线程的应用场景中,将工作线程中需更新UI的操作信息 传递到 UI主线程,从而实现 工作线程对UI的更新处理,最终实现异步消息的处理.

Handler消息机制的使用与(部分源码分析),不断更新

1.2 产生原因

  • 答:多个线程并发更新UI的同时 保证线程安全
  • 在子线程中更新UI会产生“程序无法响应ANR”

Handler消息机制的使用与(部分源码分析),不断更新

2.相关概念

handler: 处理者,管理者
关于Handler的相关概念如下:

在下面的讲解中,我将直接使用英文名讲解,即 Handler、Message、Message Queue、Looper,希望大家先熟悉相关概念

Handler消息机制的使用与(部分源码分析),不断更新

2.1 原理图

Handler消息机制的使用与(部分源码分析),不断更新

2.2 注意

  • 线程(Thread)、循环器(Looper)、处理者(Handler)之间的对应关系如下:
    1. 1个线程(Thread)只能绑定 1个循环器(Looper),但可以有多个处理者(Handler)
    2. 1个循环器(Looper) 可绑定多个处理者(Handler)
    3. 1个处理者(Handler) 只能绑定1个1个循环器(Looper)

Handler消息机制的使用与(部分源码分析),不断更新

3.初级使用

3.1 知识储备:

Message

  1. obtain: 实例化
  2. obj:用户指定,msg.obj = “AA”;// 消息存放
  3. what: 用户定义的int型消息代码,用来描述消息
  4. target: 处理消息的Handler

3.2 具体使用

  1. 由于Handler的作用 = 将工作线程需操作UI的消息 传递
    到主线程,使得主线程可根据工作线程的需求更新UI,从而避免线程操作不安全的问题
  2. 故下文的实例 = 1个简单 “更新UI操作” 的案例
  3. 主布局文件相同 = 1个用于展示的TextView,具体如下:
  4. 注意: 以下例子还未考虑内存优化,只是初级使用

3.2.1 使用Handler.sendMessage()

public class MainActivity extends AppCompatActivity {
    
    public TextView mTextView;
    public Handler mHandler;

    // 步骤1:(自定义)新创建Handler子类(继承Handler类) & 复写handleMessage()方法
    class Mhandler extends Handler {

        // 通过复写handlerMessage() 从而确定更新UI的操作
        @Override
        public void handleMessage(Message msg) {
            // 根据不同线程发送过来的消息,执行不同的UI操作
            // 根据 Message对象的what属性 标识不同的消息
            switch (msg.what) {
                case 1:
                    mTextView.setText("执行了线程1的UI操作");
                    break;
                case 2:
                    mTextView.setText("执行了线程2的UI操作");
                    break;
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextView = (TextView) findViewById(R.id.show);

        // 步骤2:在主线程中创建Handler实例
        mHandler = new Mhandler();
       
        // 采用继承Thread类实现多线程演示
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                 // 步骤3:创建所需的消息对象
                 Message msg = Message.obtain();
                 msg.what = 1; // 消息标识
                 msg.obj = "A"; // 消息内存存放

                 // 步骤4:在工作线程中 通过Handler发送消息到消息队列中
                 mHandler.sendMessage(msg);
            }
        }.start();
        // 步骤5:开启工作线程(同时启动了Handler)

        // 此处用2个工作线程展示
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(6000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 通过sendMessage()发送
                 // a. 定义要发送的消息
                 Message msg = Message.obtain();
                 msg.what = 2; //消息的标识
                 msg.obj = "B"; // 消息的存放
                 // b. 通过Handler发送消息到其绑定的消息队列
                 mHandler.sendMessage(msg);
            }
        }.start();

    }
}

3.2.2 匿名类 (不是正规的使用方法)

public class MainActivity extends AppCompatActivity {
    
    public TextView mTextView;
    public Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextView = (TextView) findViewById(R.id.show);

        // 步骤1:在主线程中 通过匿名内部类 创建Handler类对象
        mHandler = new Handler(){
            // 通过复写handlerMessage()从而确定更新UI的操作
            @Override
            public void handleMessage(Message msg) {
                // 根据不同线程发送过来的消息,执行不同的UI操作
                switch (msg.what) {
                    case 1:
                        mTextView.setText("执行了线程1的UI操作");
                        break;
                    case 2:
                        mTextView.setText("执行了线程2的UI操作");
                        break;
                }
            }
        };
        // 采用继承Thread类实现多线程演示
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                 // 步骤3:创建所需的消息对象
                 Message msg = Message.obtain();
                 msg.what = 1; // 消息标识
                 msg.obj = "A"; // 消息内存存放

                 // 步骤4:在工作线程中 通过Handler发送消息到消息队列中
                 mHandler.sendMessage(msg);
            }
        }.start();
        // 步骤5:开启工作线程(同时启动了Handler)

        // 此处用2个工作线程展示
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(6000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 通过sendMessage()发送
                 // a. 定义要发送的消息
                 Message msg = Message.obtain();
                 msg.what = 2; //消息的标识
                 msg.obj = "B"; // 消息的存放
                 // b. 通过Handler发送消息到其绑定的消息队列
                 mHandler.sendMessage(msg);
            }
        }.start();

    }

}

对以上使用方式的总结:

  • 在主线程使用handler很简单,只需在主线程创建一个handler对象,在子线程通过在主线程创建的handler对象发送Message,在handleMessage()方法中接受这个Message对象进行处理。通过handler很容易的从子线程切换回主线程了.

4. 原理解析:

  • 可以看出,这里在Looper构造函数中创建出了一个MessageQueue对象和保存了当前线程。从上面可以看出一个线程中只有一个Looper对象,而Message Queue对象是在Looper构造函数创建出来的,因此每一个线程也只会有一个MessageQueue对象。
  • 可以看到,到looper对象为null,抛出 “Can’t create handler inside thread that has not called Looper.prepare()”异常由这里可以知道,当我们在子线程使用Handler的时候要手动调用Looper.prepare()创建一个Looper对象,之所以主线程不用,是系统启动的时候帮我们自动调用了Looper.prepare()方法。
    csdn中一篇对handler源码的研究

4.1 在子线程中使用Handler

  • 在子线程中,首先要实例化looper :Looper.prepare
  • 其次是开启循环: Looper.loop
 public class TestHandlerActivity extends AppCompatActivity {


        private static final String TAG = "TestHandlerActivity";

        //主线程的Handler
        private Handler mHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //获得刚才发送的Message对象,然后在这里进行UI操作
                Log.e(TAG,"------------> msg.what = " + msg.what);
            }
        };
        //子线程中的Handler
        private Handler mHandlerThread = null;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_handler_test);
            initData();
        }

        private void initData() {

            //开启一个线程模拟处理耗时的操作
            new Thread(new Runnable() {
                @Override
                public void run() {

                    SystemClock.sleep(2000);
                    //通过Handler发送一个消息切换回主线程(mHandler所在的线程)
                    mHandler.sendEmptyMessage(0);

                    //调用Looper.prepare()方法
                    Looper.prepare();

                    mHandlerThread = new Handler(){
                        @Override
                        public void handleMessage(Message msg) {
                            super.handleMessage(msg);
                            Log.e("sub thread","---------> msg.what = " + msg.what);
                        }
                    };

                    mHandlerThread.sendEmptyMessage(1);

                    //调用Looper.loop()方法
                    Looper.loop();
                }
            }).start();

        }

5.工作原理

5.1 Handler的组成部分

  • Handler的消息处理主要有五个部分组成,Message,Handler,Message Queue,Looper和ThreadLocal。首先简要的了解这些对象的概念
  1. Message:Message是在线程之间传递的消息,它可以在内部携带少量的数据,用于线程之间交换数据。Message有四个常用的字段,what字段,arg1字段,arg2字段,obj字段。what,arg1,arg2可以携带整型数据,obj可以携带object对象
  2. Handler:它主要用于发送和处理消息的发送消息一般使用sendMessage()方法,还有其他的一系列sendXXX的方法,但最终都是调用了sendMessageAtTime方法,除了sendMessageAtFrontOfQueue()这个方法而发出的消息经过一系列的辗转处理后,最终会传递到Handler的handleMessage方法中。
  3. Message Queue:MessageQueue是消息队列的意思,它主要用于存放所有通过Handler发送的消息,这部分的消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。
  4. Looper:每个线程通过Handler发送的消息都保存在,MessageQueue中,Looper通过调用loop()的方法,就会进入到一个无限循环当中,然后每当发现Message Queue中存在一条消息,就会将它取出,并传递到Handler的handleMessage()方法中。每个线程中只会有一个Looper对象。
  5. ThreadLocal:MessageQueue对象,和Looper对象在每个线程中都只会有一个对象,怎么能保证它只有一个对象,就通过ThreadLocal来保存。Thread Local是一个线程内部的数据存储类,通过它可以在指定线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储到数据,对于其他线程来说则无法获取到数据。

5.2 Handler的运行过程

  1. Looper 的准备:
    • 每一个线程绑定一个looper
    • 每个handler可以绑定一个looper
    • looper可以绑定多个handler
    • 首先:Looper.prepare
  2. 实例化一个handler
    • 在handler内部会创建一个looper的对象,如果没有,就会报错。“Can’t create handler inside thread that has not called Looper.prepare()”
    • 同时,会创建一个MessageQueue的对象=looper.mQueue
  3. 通过调用send或者post发送消息。
  • 本质是通过send()方法来实现的。
  • 最终会调用MessageQueue中的enquueMessage()方法,进行插入操作。
  • enqeueMessage()方法中,msg.target=this :绑定队列
  • 还有读取:mQueue.next() 读取,并伴随着删除操作。
  1. 取出消息:
    • 在Looper的loop方法中,从MessageQueue中取出消息调msg.target.dispatchMessage(msg);这里其实就是调用了Handler的dispatchMessage(msg)方法
  2. 在handler中的handleMessage()方法中进行消息的处理。

  1. handler 可以看做looper的一个接口
  2. target: handler对象
  3. handleCallback; runnable对象

6.谈谈优化

6.1 常见的内存溢出以及优化

  • 一个Android应用启动的时候,会自动创建一个供应用主线程使用的Looper实例。Looper的主要工作就是一个一个处理消息队列中的消息对象。在Android中,所有Android框架的事件(比如Activity的生命周期方法调用和按钮点击等)都是放入到消息中,然后加入到Looper要处理的消息队列中,由Looper负责一条一条地进行处理。主线程中的Looper生命周期和当前应用一样长。
  • 当一个Handler在主线程进行了初始化之后,我们发送一个target为这个Handler的消息到Looper处理的消息队列时,实际上已经发送的消息已经包含了一个Handler实例的引用,只有这样Looper在处理到这条消息时才可以调用Handler#handleMessage(Message)完成消息的正确处理
  • 在Java中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用。静态的内部类不会持有外部类的引用。
public class SampleActivity extends Activity {
 
  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
 
    // Post a message and delay its execution for 10 minutes.
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { /* ... */ }
    }, 1000 * 60 * 10);
 
    // Go back to the previous Activity.
    finish();
  }
}
  1. 分析一下上面的代码,当我们执行了Activity的finish方法,被延迟的消息会在被处理之前存在于主线程消息队列中10分钟,而这个消息中又包含了Handler的引用,而Handler是一个匿名内部类的实例,其持有外面的SampleActivity的引用,所以这导致了SampleActivity无法回收,进行导致SampleActivity持有的很多资源都无法回收,这就是我们常说的内存泄露。
  2. 注意上面的new Runnable这里也是匿名内部类实现的,同样也会持有SampleActivity的引用,也会阻止SampleActivity被回收。
  3. 要解决这种问题,思路就是不适用非静态内部类,继承Handler时,要么是放在单独的类文件中,要么就是使用静态内部类。因为静态的内部类不会持有外部类的引用,所以不会导致外部类实例的内存泄露。当你需要在静态内部类中调用外部的Activity时,我们可以使用弱引用来处理。另外关于同样也需要将Runnable设置为静态的成员属性。注意:一个静态的匿名内部类实例不会持有外部类的引用。 修改后不会导致内存泄露的代码如下
public class SampleActivity extends Activity {
  /**
   * Instances of static inner classes do not hold an implicit
   * reference to their outer class.
   */
  private static class MyHandler extends Handler {
    private final WeakReference<SampleActivity> mActivity;
    public MyHandler(SampleActivity activity) {
      mActivity = new WeakReference<SampleActivity>(activity);
    }
    @Override
    public void handleMessage(Message msg) {
      SampleActivity activity = mActivity.get();
      if (activity != null) {
        // ...
      }
    }
  }
  private final MyHandler mHandler = new MyHandler(this);
  /**
   * Instances of anonymous classes do not hold an implicit
   * reference to their outer class when they are "static".
   */
  private static final Runnable sRunnable = new Runnable() {
      @Override
      public void run() { /* ... */ }
  };
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Post a message and delay its execution for 10 minutes.
    mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
    
    // Go back to the previous Activity.
    finish();
  }
} 
其实在Android中很多的内存泄露都是由于在Activity中使用了非静态内部类导致的,就像本文提到的一样,所以当我们使用时要非静态内部类时要格外注意,如果其实例的持有对象的生命周期大于其外部类对象,那么就有可能导致内存泄露。个人倾向于使用文章的静态类和弱引用的方法解决这种问题。

Handler消息机制的使用与(部分源码分析),不断更新

  • 具体实现方案:
public class MainActivity extends AppCompatActivity {

    public static final String TAG = "carson:";
    private Handler showhandler;

    // 主线程创建时便自动创建Looper & 对应的MessageQueue
    // 之后执行Loop()进入消息循环
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //1. 实例化自定义的Handler类对象->>分析1
        //注:
            // a. 此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue;
            // b. 定义时需传入持有的Activity实例(弱引用)
        showhandler = new FHandler(this);

        // 2. 启动子线程1
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // a. 定义要发送的消息
                Message msg = Message.obtain();
                msg.what = 1;// 消息标识
                msg.obj = "AA";// 消息存放
                // b. 传入主线程的Handler & 向其MessageQueue发送消息
                showhandler.sendMessage(msg);
            }
        }.start();

        // 3. 启动子线程2
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // a. 定义要发送的消息
                Message msg = Message.obtain();
                msg.what = 2;// 消息标识
                msg.obj = "BB";// 消息存放
                // b. 传入主线程的Handler & 向其MessageQueue发送消息
                showhandler.sendMessage(msg);
            }
        }.start();

    }

    // 分析1:自定义Handler子类
    // 设置为:静态内部类
    private static class FHandler extends Handler{

        // 定义 弱引用实例
        private WeakReference<Activity> reference;

        // 在构造方法中传入需持有的Activity实例
        public FHandler(Activity activity) {
            // 使用WeakReference弱引用持有Activity实例
            reference = new WeakReference<Activity>(activity); }

        // 通过复写handlerMessage() 从而确定更新UI的操作
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    Log.d(TAG, "收到线程1的消息");
                    break;
                case 2:
                    Log.d(TAG, " 收到线程2的消息");
                    break;


            }
        }
    }
}

5.3 解决方法2

5.3.1 原理

不仅使得 “未被处理 / 正处理的消息 -> Handler实例 -> 外部类” 的引用关系 不复存在,同时 使得 Handler的生命周期(即 消息存在的时期) 与 外部类的生命周期 同步

5.3.2 具体方案

当 外部类(此处以Activity为例) 结束生命周期时(此时系统会调用onDestroy()),清除 Handler消息队列里的所有消息(调用removeCallbacksAndMessages(null))

5.3.3 具体代码

@Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
        // 外部类Activity生命周期结束时,同时清空消息队列 & 结束Handler生命周期
    }

源码分析

  1. 构造方法
  • handler()
    -> Handler(Callback callback, boolean async)
// 在这里是进行了looper的绑定 MessageQueue的绑定
 mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
  • Looper
    • static final ThreadLocal sThreadLocal = new ThreadLocal();
    • static Looper myLooper() ->return sThreadLocal.get()
      Handler消息机制的使用与(部分源码分析),不断更新

说明 通过Looper.myLooper()方法,拿到了当前线程的Looper.这里,sThreadLocal中有ThreadLocalMap这是用于存储线程以及线程对应的Looper.

Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
  • looper
    • loop()
      说明 如果looper是在主线程中,那么发送的消息就会分发到主线车中执行,这就达到了在主线程进行更新ui的效果。
  for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            if (logSlowDispatch) {
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }
  1. sendMessage()
    -> sendMessageDelayed(msg, 0)
    -> sendMessageAtTime()
    -> sendMessageAtTime(Message msg, long uptimeMillis)
    // 这个方法进行了入队操作 message在队列中的排序是按照when来进行排序的。
    -> boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)
    -> queue.queueMessage(msg, uptimeMillis)
    • MessageQueue(class)
      • ->queueMessage(msg, uptimeMillis)
  2. dispatchMessage()
    说明
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
  1. public void handleMessage(Message msg) {
    }
    说明 在handleMessage中什么都没有干,如果我们新建了个handler对象,如果重写了这个方法,重写的方法会覆盖这个方法。这个时候就走到了我们自己写的方法中。

相关问题

  1. 为什么在主线程中没用对Handler的looper进行声明,但是不报错呢?
    因为在应用启动的时候,ActivityThread的入口函数main()就为我们声明了:looper.prepareMainLooper. //准备。
    Looper.loop() // 循环

Handler消息机制的使用与(部分源码分析),不断更新

csdn的handler总结
carson_ho的总结