Android Handler 的内部构造

Android Handler 的内部构造

本文译自:
https://medium.com/@jagsaund/android-handler-internals-b5d49eba6977#.bhi8m3wj7

对于一个安卓应用来说,你需要预防UI线程被阻塞。所以,耗时任务一般都会被放至在异步线程之中。这些操作的结果经常需要更新用户界面组件,这些组件必须在UI线程上执行。为了解决阻塞队列、内存共享、管道对用户UI线程的阻塞问题,Android 提供了自己的线程消息传递机制–Handler。Handler是Android Framework中的基本组成部分,它提供了一个非阻塞的消息传递机制。无论是UI线程还是耗时任务线程在进行消息通信。
虽然我们经常有用到Handler,但是让人很容易忽略其工作原理。这篇文章主要对Handler的各个组件进行深入的解析。它解释了为何Handler会如此的强大,可以实现工作线程和UI线程之间的通信。

Image Viewer Example

下面我们来个例子来示例如何在程序中使用Handler,假设现有一Activity需要从网络上获取图片并显示。这有许多方式去实现,在这个例子中,我们将会开启一个新线程来进行网络请求并显示图片。

public void ImageFetcherActivity  extends AppCompactActivity {
    class WorkerThread extends Thread {
            void fetchImage(String url) {
                // network logic to create and execute request
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        imageView.setImageBitmap(image);
                    }
                });
            }
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        // prepare the view, maybe setContentView, etc
        new WorkerThread().fetchImage(imageUrl);
    }
}

另一种方式则是使用Handler来代替使用Runnable

public class ImageFetcherAltActivity extends AppCompactActivity {
    class WorkerThread extends Thread {
        void fetchImage(String url) {
            handler.sendEmptyMessage(MSG_SHOW_LOADER);
            // network call to load image
            handler.obtainMessage(MSG_SHOW_IMAGE, imageBitmap).sendToTarget();
        }
    }

    class UIHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SHOW_LOADER: {
                    progressIndicator.setVisibility(View.VISIBLE);
                    break;
                }
                case MSG_HIDE_LOADER: {
                    progressIndicator.setVisibility(View.GONE);
                    break;
                }
                case MSG_SHOW_IMAGE: {
                    progressIndicator.setVisibility(View.GONE);
                    imageView.setImageBitmap((Bitmap) msg.obj);
                    break;
                }
            }
        }
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        // prepare the view, maybe setContentView, etc
        new WorkerThread().fetchImage(imageUrl);
    }       
}

在第二个例子中,由工作线程来下载图片,当图片下载完成需要更新ImageView的时候,我们知道无法在非UI线程中更新UI界面,所以,我们使用了Handler。Handler在这扮演了连接工作线程和UI线程的中介。消息可以由工作线程传递至UI线程,并在UI线程当中处理。

深入了解Handler

Handler的各个组成部分

  • Handler
  • Message
  • Message Queue
  • Looper

我们接下来仔细看看每个组成部分以及他们之间是如何互相作用。

Handler

Handler是线程通信直接的接口。UI线程和工作线程与Handler互相通过调用以下操作:

  • 新建,增加或者将消息从Message Queue中移除
  • 在UI线程中处理消息

The android.os.Handler component

每个Handler都需要和一个Looper、Message Queue互相关联。以下是两种方式来创建一个Handler:

  • 使用默认构造函数来,用此方法创建的Handler将会和当前线程的Looper进行关联。
  • 通过显示指定线程来创建Handler

如果一个Handler没有关联到一个Looper,那么它将无法工作。因为它无法讲消息存进Message Queue,因此,它将不会收到任何需要处理的消息。

public Handler(Callback callback, boolean async) {
  // code removed for simplicity
   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 = callback;
   mAsynchronous = async;
}

从Android源码中的片段可以得出一个创建新的Handler的逻辑。Handler在创建时,会自动检测当前线程中是否存在有效的Looper,如果不存在,那么会抛出一个RuntimeException,若存在,则将Looper的消息队列赋值给它。

Note:多个Handler关联到同一线程,那么将会共享同一Message Queue。因为他们共享同一Looper。
Callback是一个可选的参数,如果存在,则会在callback中处理消息。

Message

Message 扮演着数据承担者的角色。耗时任务线程会将Messages发送至从Handler,消息进入Message Queue。Message提供三个可选参数,由Handler和Message Queue来处理这些参数。

  • what —Handler用于区分和处理消息的一个标识符。
  • time — Message Queue 由此来决定何时来处理一个Message
  • target —指示哪个Handler来处理消息。
    这里写图片描述

创建Message通常使用以下方法之一:

public final Message obtainMessage()
public final Message obtainMessage(int what)
public final Message obtainMessage(int what, Object obj)
public final Message obtainMessage(int what, int arg1, int arg2)
public final Message obtainMessage(int what, int arg1, int arg2, Object obj)

Message还可以从消息池中获取。Handler默认设置Message的目标为它自己,所以我们可以通过以下方式来进行Message的获取和发送:

mHandler.obtainMessage(MSG_SHOW_IMAGE, mBitmap).sendToTarget();

消息池是一个以Message为对象,最大数为50的LinkedList。当Handler处理完Message之后,Message Queque会将此对象返回至消息池中,并重置其所有字段。

当通过调用post(Runnable r)发送一个Runnable对象至Handler时,Handler将会隐式的创建一个Message对象,同时也会设置callback字段来维系Runnable。

 private static Message getPostMessage(Runnable r) {
            Message m = Message.obtain();
            m.callback = r;
            return m;
    }

这里写图片描述

从上图我们可以看出,耗时任务线程是如何与Handler相互作用的。耗时任务创建一个Message并将之发送至Handler,Handler将Message压入Message Queue中。在之后的某个时间将在UI线程中由Handler来处理这个Message。

Message Queue

Message Queue是以Message为对象的一个无限大的LinkedList。Message Queue遵循FIFO原则,对进入的消息以时间戳为条件进行排序。
这里写图片描述

Message Queue的机制是根据 SystemClock.uptimeMillis()来确定当前时间值,当message的时间戳小于这个值的时候,那么Message将会被移出栈并由Handler来进行处理。
Handler提供了三种不同的方式来发送一个Message。

public final boolean sendMessageDelayed(Message msg, long delayMillis)
public final boolean sendMessageAtFrontOfQueue(Message msg)
public boolean sendMessageAtTime(Message msg, long uptimeMillis)

Sending a message with a delay sets the Message’s time field as SystemClock.uptimeMillis() + delayMillis.
Messages sent with a delay have the time field set to SystemClock.uptimeMillis() + delayMillis. Whereas, Messages sent to the front of the queue have the time field set to 0, and process on the next Message loop iteration. Use this method with care as it can starve the message queue, cause ordering problems, or have other unexpected side-effects.

Handlers 通常和UI组件相互关联,那么Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用。这就很有可能在耗时任务线程未处理完,activty关闭,由于handler持有activity的引用导致内存泄露。我们假设以下几种情况:

public class MainActivity extends AppCompatActivity {
    private static final String IMAGE_URL = "https://www.android.com/static/img/android.png";

    private static final int MSG_SHOW_PROGRESS = 1;
    private static final int MSG_SHOW_IMAGE = 2;

    private ProgressBar progressIndicator;
    private ImageView imageView;
    private Handler handler;

    class ImageFetcher implements Runnable {
        final String imageUrl;

        ImageFetcher(String imageUrl) {
            this.imageUrl = imageUrl;
        }

        @Override
        public void run() {
            handler.obtainMessage(MSG_SHOW_PROGRESS).sendToTarget();
            InputStream is = null;
            try {
                // Download image over the network
                URL url = new URL(imageUrl);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();

                conn.setRequestMethod("GET");
                conn.setDoInput(true);
                conn.connect();
                is = conn.getInputStream();

                // Decode the byte payload into a bitmap
                final Bitmap bitmap = BitmapFactory.decodeStream(is);
                handler.obtainMessage(MSG_SHOW_IMAGE, bitmap).sendToTarget();
            } catch (IOException ignore) {
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException ignore) {
                    }
                }
            }
        }
    }

    class UIHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SHOW_PROGRESS: {
                    imageView.setVisibility(View.GONE);
                    progressIndicator.setVisibility(View.VISIBLE);
                    break;
                }
                case MSG_SHOW_IMAGE: {
                    progressIndicator.setVisibility(View.GONE);
                    imageView.setVisibility(View.VISIBLE);
                    imageView.setImageBitmap((Bitmap) msg.obj);
                    break;
                }
            }
        }
    }

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

        progressIndicator = (ProgressBar) findViewById(R.id.progress);
        imageView = (ImageView) findViewById(R.id.image);

        handler = new UIHandler();

        final Thread workerThread = new Thread(new ImageFetcher(IMAGE_URL));
        workerThread.start();
    }
}

在这个例子当中,activity开启了一个新的线程来下载图片并显示到ImageView上。该线程通过持有这个ImageViewd的引用的UIHandler来在UI线程当中更新视图。
我们假设该线程由于网络很慢而导致下载迟迟未完成,并且Actiivty在下载过程中关闭。正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用,这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。

Now, let’s look at another example:

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "Ping";

    private Handler handler;

    class PingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            Log.d(TAG, "Ping message received");
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        handler = new PingHandler();

        final Message msg = handler.obtainMessage();
        handler.sendEmptyMessageDelayed(0, TimeUnit.MINUTES.toMillis(1));
    }

In this example, the following sequence of events occur:
在这个例子中,以下事件将会依次触发:
- 创建PingHandler
- activity发送一个延迟时间的消息到Handler,并压入Message Queue。
- activity在消息调度之前销毁。
- 消息被分发至PingHandler,被PingHandler处理,打印出日志。
虽然第一时间无法明显的观察到,但仍然可以注意到activity的内存泄露了。
当acitivity销毁之后,handler持有的引用也应该被GC回收,但是,当我们创建一个Message对象的时候,handler仍然保留了该activity的引用。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

以上的android源代码显示,发送到Handler调用的所有消息最终都会调用enqueueMessage(),注意Handler的引用明确分配到了msg.target。这是在告诉Looper当消息从Message Queue中压出的时候,哪个Handler来处理它。
当Message被添加到一个持有它引用的Message Queue。Message Queue同时和Loooper互相关联,一个Looper会有生命周期,而UI线程的生命周期贯穿了整个applicaition。Handler也会一直持有Message的引用直到它被Message Queue回收。一旦Message被回收,那么它的属性,包括引用都会被清除。
尽管Handler持有一个Activity的强引用,但是它不清楚何时会出现内存泄露。为了检测内存泄露,我们必须明确知道Activity里是否有Handler对象。在这个例子中,答案是肯定的。由非静态类成员变量保留到其封闭类的隐式引用。具体地说,PingHandler没有声明为静态类,所以它具有Activity的隐式引用。
结合使用软引用和静态修饰符可以防止内存泄露。当Acticity销毁的时候,软引用允许垃圾回收器回收你想要保存的对象(通常是一个Activity)。在Handler的内部类里的静态修饰符可以防止对父类的隐式引用。
让我们来修改一下UIHandler来解决这个问题:

static class UIHandler extends Handler {
    private final WeakReference<ImageFetcherActivity> mActivityRef;

    UIHandler(ImageFetcherActivity activity) {
        mActivityRef = new WeakReference(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        final ImageFetcherActivity activity = mActivityRef.get();
        if (activity == null) {
            return
        }

        switch (msg.what) {
            case MSG_SHOW_LOADER: {
                activity.progressIndicator.setVisibility(View.VISIBLE);
                break;
            }
            case MSG_HIDE_LOADER: {
                activity.progressIndicator.setVisibility(View.GONE);
                break;
            }
            case MSG_SHOW_IMAGE: {
                activity.progressIndicator.setVisibility(View.GONE);
                activity.imageView.setImageBitmap((Bitmap) msg.obj);
                break;
            }
        }
    }
}

现在,UIHandler的构造器里需要传递一个Activity的参数,这个参数被声明为软引用。
也就意味着它允许垃圾回收器在Activity销毁时回收这个Activity的引用。为了更新这个Activity里面的UI组件,我们需要从mActivityRef这个参数获取Activity的强引用。由于我们使用的是软引用,我们必须小心谨慎的处理Activity。如果获取Activity的引用的唯一途径是软引用,那么很有可能垃圾回收器已经回收了该Activity。我们需要检测是否已经发生了上述行为。如果是的,那么Handler需要忽略传递过来的Message。
尽管在逻辑上我们已经处理完了内存泄露,但是这里仍然存在一个问题。如果Activity已经销毁了,但是垃圾回收器还未回收该Activity对象,Message正在被处理的时候,很有可能会导致你的应用程序崩溃。为了解决这个问题,我们需要清楚Activity的状态。

static class UIHandler extends Handler {
    private final WeakReference<ImageFetcherActivity> mActivityRef;

    UIHandler(ImageFetcherActivity activity) {
        mActivityRef = new WeakReference(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        final ImageFetcherActivity activity = mActivityRef.get();
        if (activity == null || activity.isFinishing() || activity.isDestroyed()) {
            removeCallbacksAndMessages(null);
            return
        }

        switch (msg.what) {
            case MSG_SHOW_LOADER: {
                activity.progressIndicator.setVisibility(View.VISIBLE);
                break;
            }
            case MSG_HIDE_LOADER: {
                activity.progressIndicator.setVisibility(View.GONE);
                break;
            }
            case MSG_SHOW_IMAGE: {
                activity.progressIndicator.setVisibility(View.GONE);
                activity.imageView.setImageBitmap((Bitmap) msg.obj);
                break;
            }
        }
    }
}

现在我们可以来概括一下MessageQueue,Handler以及耗时任务线程之间的关系:

这里写图片描述

在上图中,多个线程提交了不同的Message至不同的Handler。然而每个handler都关联到了同一Looper,所以所有的Message都是被push到了同一Messagequeue。这很重要,因为Android创建了一些Handler来关联至Main Looper:

  • The Choreographer: 处理Android的动画、组件等的同步刷新工作。
  • The ViewRoot: 负责处理输入和窗体事件,配置更改等等。
  • The InputMethodManager: 处理键盘触摸事件。
  • And several others
    Tip: 确保耗时任务线程不会产生过多的Message。因为Message过多可能导致系统产生的Message被阻塞。
    这里写图片描述

Debugging Tips:
You can debug/dump all Messages dispatched by a Looper by attaching a LogPrinter:

final Looper looper = getMainLooper();
looper.setMessageLogging(new LogPrinter(Log.DEBUG, "Looper"));

Similarly, you can debug/dump all pending Messages in a MessageQueue associated with your Handler by attaching a LogPrinter to your Handler:

handler.dump(new LogPrinter(Log.DEBUG, "Handler"), "");

Looper

Looper从MessageQueue中读取消息,并将之派分到其目的Handler。一旦Message通过了dispatch barrier,那么它就会在下一次循环中再次被读取。如果消息队列中没有任何可用的消息,那么Looper将会被阻塞,直到下一个可用的消息出现。
一个Looper只能关联到一个线程。如果该线程还关联到另外一个Looper,那么将会抛出RuntimeException。在Looper类中使用了一个静态的ThreadLocal对象来保证线程和Looper的一一对应关系。
调用Looper.quit方法将会立即结束Looper的生命,同样可用于抛弃所有的已经处理的消息。调用Looper.quitSafely 方法可以安全的退出Looper。
这里写图片描述

在线程的run放法中设置Looper。首先我们需要调用静态方法Looper.prepare来检测是否之前已经存在一个Looper和Thread 关联。它是通过Looper的ThreadLocal来检测是否已经存在了一个Looper对象。如果没有,那么一个新的Looper对象和新的消息队列将被创建。Android源码(如下)说明了这点。

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException(“Only one Looper may be created per thread”);
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

Handler现在可以接收消息并且将它们添加到消息队列中。执行Looper.loop方法将会从消息队列中读取消息,每次循环迭代下一条消息发送至目标Handler,并将之收回到消息池中。Looper将会持续这个动作直到结束。Android源码片段说明了这点:

public static void loop() {
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        msg.target.dispatchMessage(msg);
        msg.recycleUnchecked();
    }
}

自己手动创建一个线程并关联上Looper是没有必要的。Android提供了一个非常方便的类—-HandlerThread。它继承了Thread类并创建好了一个Looper。以下代码片段展示了其用法:

private final Handler handler;
private final HandlerThread handlerThread;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate();
    handlerThread = new HandlerThread("HandlerDemo");
    handlerThread.start();
    handler = new CustomHandler(handlerThread.getLooper());
}
@Override
protected void onDestroy() {
    super.onDestroy();
    handlerThread.quit();
}

在onCreate方法中构造了一个新的HandlerThread。当调用了start方法,会自动创建一个Looper并关联上该线程。Looper则可以开始处理将消息从消息队列中压出。
Note: 当activity销毁的时候,必须要结束HandlerThread,同样也会结束Looper。

总结

Handler在android的应用生命周期起着不可或缺的作用。它是半同步半异步模式的基础组成。不同的内部或外部都依赖于Handler来处理各种异步事件的调度,因为它可以最大限度的减少开销并且保证线程安全。
深入了解一个组件的工作原理可以帮助我们更好的解决难题。同样可以是我们更好的使用组件的API。我们经常使用Handler来UI线程通信。但是它的功能并不仅仅如此。它还出现在IntentService、Camera2 等等API中。在这些API中,它更普遍用于任意线程之间的通信。
我们可以更深入的理解的Handler,以建立更高效,简单,和强大的应用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值