探索 Android——Handler

本文摘抄至 : 掘金翻译计划 (翻译不易,欢迎 Star 支持)掘金翻译计划 (翻译不易,欢迎 Star 支持)

摘要 : Handler 常与一些 UI 组件相关联,而这些 UI 组件通常持有对 Activity 的引用。Handler 持有的对这些组件的引用可能会导致潜在的 Activity 泄露。可以使用弱引用、静态 修饰符来防止内存泄漏。但是由于垃圾回收机制是看系统心情,这样也可能会造成崩溃,通过在handleMessage() 方法中添加判断条件activity.isFinishing() || activity.isDestroyed()从而进一步避免。

   如果你想要一个 Android 应用程序反应灵敏,那么你必须防止它的 UI 线程被阻塞。同样的,将这些阻塞的或者计算密集型任务转到工作线程去执行也提高程序的响应灵敏性。
    然而,这些任务的执行结果通常需要更新 UI 组件的显示,但该操作只能在 UI 线程中去执行。有一些方法解决了 UI 线程的阻塞问题。例如阻塞队列,共享内存以及管道技术。
    在Android中,为了解决这个问题,提供了一种自有的消息传递机制——Handler。 Handler 是Android Framework 架构中的一个基础组件,它实现了一种非阻塞的消息传递机制,在消息转换的过程中,消息的生产者和消费者都不会阻塞。

虽然 Handler 被使用的频率非常高,它的工作原理却很容易被忽视。本篇文章深入地剖析 Handler 众多内部组件的实现,它将会向您揭示 Handler 的强大之处,而不仅仅作为一个工作线程和 UI 线程通信的工具。

Handler 图片浏览示例


让我们从一个例子开始了解如何在应用中使用 Handler。设想一个 Activity 需要从网络上获取图片并显示。有几种方式来做这件事,在下面的例子中,我们创建一个新的 工作线程去执行网络请求以获取图片。

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

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);
            }
        }) ;
    }
}
}
另一种方法则是使用 Handler Messages 来代替 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);
        }
    }
在上面的第二例子中,工作线程从网络获取一张图片,一旦下载完成,我们需要使用下载好的 bitmap 去更新 ImageView 显示内容。我们知道不能在非UI线 中更新UI组件,因此,我们使用 Hanler。 Handler 扮演了工作线程和 UI线程的中间人角色。消息在工作线程中被 Handler 加入队列,随后在 UI线程中被 Handler处理 。

了解 Handler


Handler 由以下部分组成 :
  • Handler
  • Message
  • MessageQueue
  • Looper
我们接下来将学习各个组件以及它们之间的交互

Handler

Handler 是线程间传递消息的即时接口,生产线程和消费线程调用以下操作来使用 Handler :
  • 在消息队列中创建、插入或移除消息
  • 在消费线程中处理消息
The android.os.Handler component

每一个 Handler 都有一个与之关联的 Looper 和 消息队列<MessageQueue>。有两种创建 Handler的方式 :

  • 通过默认的构造方法,使用当前线程中关联的 Looper
  • 显示地指定使用的 Looper

没有指定 Looper的 Handler是无法正常工作的,因为它无法将消息放到消息队列中。同样地,它无法获取要 处理的消息。

 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;
    }
上面的代码段展示了创建一个新的Handler 的逻辑。Handler 在创建时检查了当前的线程有没有可用的 Looper对象。 如果没有,它会抛出一个运行时异常。 如果正常的话,Handler 则会持有 Looper中消息队列的引用。 注意 :同一线程中的多个 Handle 分享一个同样的消息队列,因为它们分享是同一个 Looper对象。 Callback 参数是一个可选参数,如果提供的话,它将会处理由 Looper 分发过来的消息 。

Message

Message是容纳任意数据的容器。生产线程发送消息给 Handler, Handler 将消息加入到消息队列中。消息提供了三种额外的信息,以提供 Handler 和消息队列处理时使用 :
  • what——一种标识符,Handler 能使用它来区分不同消息,从而采取不同的处理方法。
  • time——告知消息队列何时处理消息。
  • target——表示哪一个 Handler 应当处理消息。
The android.os.Message component

消息一般是通过 Handler 中的以下方法来创建的 :

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)
消息从消息池中获取得到,方法中提供的参数会放到消息体对应字段中。 Handler 同样可以设置消息的目标为其自身,这样允许我们可以进行链式调用,比如 :
mHandler.obtainMessge(MSG_SHOW_IMAGE,mBitmap).sendToTarget( ) ;
消息池回一个消息体对象的 LinkedList 集合,它的最大长度是 50。在Handler 处理完这条消息之后,消息队列把这个对象返回到消息池中,并且重置其所有字段。 当使用 Handler调用 post() 方法来执行一个 Runnable时,Handler 隐式的创建一个新的消息,并且设置 callback参数来存储这个 Runnable 。
Message m = Message.obtain() ;
m.callback = r ;
生产线程发送消息给 Handler 的交互

在上图,我们能看见生产线程和 Handler 的交互。生产者创建了一个消息,并且发送给了 Handler,随后 Handler将这个消息加入消息队列中。在未来的某个时间,Handler会在消费线程中处理至二个消息 。

MessageQueue

MessageQueue是一个消息体对象的无界的 LinkedList 集合,它按时序将消息插入队列,最小的时间戳将会被首先处理 。

The android.os.MessageQueue component

消息队列通过 SystemClock.uptimeMillis 获取当前时间,维护着一个阻塞阈值(dispatch barrier)。当一个消息体的时间戳低于这个值的时候,消息就会被分发给 Handler 进行处理。

Handler 提供了三种方式来发送消息:

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

以延迟的方式发送消息,是设置了消息体的 time 字段为SystemClock.uptimeMillis() + delayMillis 。
延迟发送的消息设置了其时间字段为 SystemClock.uptimeMillis() + delayMillis。
然后把消息插入到队首,并会将其他时间字段设置为 0,消息会在下一次轮询时被处理。
需要谨慎使用这个方法,因为它可能会影响消息队列,造成顺序问题,或是其它不可预料的副作用。

Handler 常与一些 UI组件想关联,而这写 UI组件通常持有对 Activity的引用。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 中展示图片。工作线程通过 UIHandler 去通知 UI 更新,这样就会持有了对 View的引用,以便更新这些 View的状态(切换可见性、设置图片等)。

让我们假设工作线程由于网络差,需要很长的时间去下载图片。在工作线程下载完成之前销毁这个 ACtivity会导致 Activity的泄露。在本例中,有两个强引用关系,一个在工作线程和 UIHandler之间,另一个在 UIHandler 和 View之间。这样阻止了垃圾回收机制回收 Activity的引用。

现在,让我们来看看另外一个例子 :

    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));
        }
    }

在这个例子中,将按顺序发生如下事件 :

  • PingHandler 被创建
  • Activity 发送一个延迟的消息给 Handler,随后消息加入到消息队列中
  • Activity 在消息到达之前被销毁
  • 消息被分发,并被 UIHandler 处理,输出一条日志

虽然起初看起来不是那么明显,但本例中的 ACtivity也存在着泄露。

在销毁 Activity之后,Handler 应当可以被垃圾回收,然而当创建一个消息对象之后,它也会持有对 Handler的引用 :

    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对象当消息从消息队列出对时,选择哪一个 Handler来对其进行处理。

消息加入消息队列后,消息队列就获取得了对消息的引用。它同样有一个与之关联的 Looper。一个自定义的 Looper对象的生命周期一直持续到它被结束,然而主线程中 Looper在程序的生命周期内一直存在。因此,消息中持有的对 Handler的引用会一直维持到该消息被消息队列回首之前,一旦消息被回收,它内部的各字段,包括目标 target的引用都会被清空。

虽然 Handler能存活很长时间,但是当 Activity发生泄漏时,Handler不会被清空。为了检查是否发生泄漏,我们必须检查 Handler是否在本类范围类持有 Activity的引用。在本例中,它确实持有 : 非静态内部类持有一个对其外部类的隐式引用。明确一点来说,PingHandler没有定义成一个静态类,所以它持有一个隐式的 Activity引用。

通过结合使用弱引用和静态修饰符可以阻止 Handler导致的 Activity泄漏。当
Activity被销毁时,弱引用允许垃圾回收器去回收你想要留存的对象(通常来说是 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中的 UI组件交互时,我们需要从 mActivityRef中获得一个 ACtivity的强引用。由于我们正在使用一个弱引用,我们必须小心翼翼的去访问 Activity。如果仅仅能通过弱引用的方式去访问 Activity,垃圾回收器也许已经将其回收了。因此,我们需要检查回收是否发生。如果确实被回收,Handler实际上已经与 Activity无关了,那么这条消息就因该被丢弃。

虽然这个逻辑解决了内存泄露问题,但是仍旧存在一个问题。Activity已经被销毁,但是垃圾回收机制还没有来得及回收引用,依赖操作系统运行时的状况。这可能会使你的程序导致潜在的崩溃。为了解决这个问题,我们需要获取 Activity当前的状态。

让我们我更新 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 || 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;
                }
            }
        }
    }
现在,我们可以概括消息队列、handler、生产线程的交互 :
Handler 与消息队列和 Looper 直接交互的整体流程

消息队列、Handler、生产线程的交互

在上图中,多个生产线程提交消息到不同的 Handler中,然而,不同的Handler都与同一个 Looper对象关联,因此所有的消息都加入到同一个消息队列中。这一点很重要,Android中创建许多不同的 Handler都关联到主线程的 Looper :

  • The Choreographer: 处理垂直同步与帧更新
  • The ViewRoot: 处理输入和窗口事件,配置修改等等
  • The InputMethodManager: 处理键盘触摸事件及其它

小贴士:确保生产线程不会大量生成消息,因为之二可能会抑制处理系统生成消息

主线程 Looper 分发消息的小示例

调试帮助:可以通过附加一个 LogPrinter到 Looper上来debug/dump 被 Looper分发的消息 :

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

同样地,你可以 debug/dump 所有在消息队列中等待的消息,通过在与消息队列相关联的 Handler 上附加一个 LogPrinter 来实现:

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

Looper

Looper从消息队列中读取消息,然后分发给对应的 Handler处理。一旦消息通过阻塞阀,那么 Looper就会在下一轮读取过程中读取到它。Looper在没有消息分发的时候就会变为阻塞状态,当有消息可用时会继续轮询。
每个线程只能关联一个 Looper,给线程附加另外的 Looper会导致运行时异常。通过使用 Looper类中的 ThreadLocl对象可以保证每一个线程只关联一个 Looper对象。
调用 Looper.quit() 方法会立刻终止 Looper,并且会丢弃消息队列中通过阻塞阀的所有消息。调用 Looper.quitSafely() 方法能够保证所有带分发的消息在队列中等待的消息被丢弃前得到处理 。
Handler 与消息队列和 Looper 直接交互的整体流程

Looper 应在线程的 run() 方法中初始化。调用静态方法 Looper.prepare() 会检查线程是否与一个已存在的 Looper关联。这个过程的实现是通过 Looper类中的 ThreadLocal对象来检查 Looper对象是否存在。如果 Looper不存在,将会创建一个新 Looper对象和一个新的消息队列。 Android 代码中的如下片段展示了这个过程。

注意:公有的 prepare 方法会默认会调用 prepare(true)。

    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));
    }

并没有必要去自己创建关联的 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,当HandlerThread启动后,它准备创建Looper与它的线程关联,随后 Looper开始处理 HandlerThread的消息队列中消息

注意:当 Activity 被销毁时,结束 HandlerThread 是很重要的,这个动作也会终止关联的 Looper。

总结

Android 中的 Handler 在应用的生命周期中扮演着不可缺少的角色。它是构成半同步/半异步模式架构的基础。许多内部和外部的代码都依赖 Handler 去异步地分发事件,它能以最小的代价去维持线程安全。

更深入地理解组件的工作方式能够帮助解决疑难杂症。这也能让我们以最佳的方法使用组件的 API。我们通常将 Handler 作为工作线程和UI线程间的通信机制,但 Handler 并不仅限于此。它出现在 IntentService, 和 Camera2和许多其它的 API 中。在这些 API 调用中,Handler 更多情形下是被用作任意线程间的通信工具。

在深入理解了 Handler 的原理后,我们能运用其构建更有效率、更简洁、更健壮的应用程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值