Android Looper Handler 机制浅析

2023-11-12

最近想写个播放器demo,里面要用到 Looper Handler,看了很多资料都没能理解透彻,于是决定自己看看相关的源码,并在此记录心得体会,希望能够帮助到有需要的人。

本文会以 猜想 + log验证 的方式来学习 Android Looper Handler,对于一些复杂的代码会进行跳过,能够理解它们的设计原理即可。本文观点皆个人拙见,如有错误恳请赐教。

请添加图片描述

1、Looper Handler MessageQueue

Looper 在整个消息处理机制中起着 消息等待与消息分发 的作用:

  • 消息等待:Looper 阻塞程序运行,等待即将到来的消息;
  • 消息分发:Looper 收到消息后,将消息分发给指定处理者在当前线程处理。

首先来看阻塞的问题,app 启动时会创建一个 Looper,并且调用 loop 函数阻塞 main 函数,这个 Looper 被称为主线程/主进程 Looper,代码参考 ActivityThread.java

    public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        Looper.prepareMainLooper();
        Looper.loop();
    }

主线程 Looper(或者又叫 mainLooper)有着阻止程序结束的作用,这里就会引申出其他问题,为什么它阻塞了程序还能继续运行呢?

上文的消息等待作用中,我们说到 Looper 阻塞程序运行时,会等待即将到来的消息,什么消息会到来呢?

我写了一个 demo,界面上只有一个 Button,点击图标启动 app 并且点击按钮,接着我们来观察 log:

2023-08-28 22:17:51.969 6768-6768/com.example.loopertest D/MainActivity: onCreate
2023-08-28 22:17:51.993 6768-6768/com.example.loopertest D/MainActivity: onStart
2023-08-28 22:17:51.993 6768-6768/com.example.loopertest D/MainActivity: onPostCreate
2023-08-28 22:17:51.994 6768-6768/com.example.loopertest D/MainActivity: onResume
2023-08-28 22:17:51.994 6768-6768/com.example.loopertest D/MainActivity: onPostResume
2023-08-28 22:17:54.424 6768-6768/com.example.loopertest D/MainActivity: onClick

可以看到 log 的线程号和进程号相同,也就是说所有的消息都是在主线程(UI线程)/主进程中进行处理的,那 Looper 收到有 Activity 的启动事件、按钮的点击事件,引申来说主进程将会收到并处理所有的 UI 事件。

Looper 是如何接收事件并处理的呢?loop 方法中有一个死循环,不断执行 loopOnce,阻塞也在此发生:

    public static void loop() {
        for (;;) {
            if (!loopOnce(me, ident, thresholdOverride)) {
                return;
            }
        }
    }

loopOnce 中会看到一个 MessageQueue 对象,这是 Looper 所持有的消息队列,它内部维护着一个链表,消息等待也由它完成。

    private static boolean loopOnce(final Looper me,
            final long ident, final int thresholdOverride) {
        Message msg = me.mQueue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return false;
        }
        ...
        try {
            msg.target.dispatchMessage(msg);
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } 
        ....
        return true;
	}

MessageQueue.next 是一个阻塞方法,当没有消息时,它将阻塞等待,可以防止上一层 for 循环空转;有消息时就返回 Message,分发给指定的 Handler 来处理。

我们再看下 loop 循环要如何退出?从上面的代码我们可以看到,当返回的 Message 是 null 时,loopOnce 返回 false,整个 loop 循环结束。

MessageQueue.next 代码如下:

    Message next() {
		......
        for (;;) {
			......
            nativePollOnce(ptr, nextPollTimeoutMillis);
            ......
            if (mQuitting) {
                dispose();
                return null;
            }   
        }   
    }

这里的代码比较长,核心内容是:

  1. 调用 nativePollOnce 在 native 层阻塞 poll 消息;
  2. 将 poll 到的消息根据指定的执行时间进行排序,如果没有指定时间则按消息到底的先后顺序排序;
  3. 如果链表中第一条消息需要延时,则继续调用 nativePollOnce,并且设定一个超时时间;
  4. 将需要处理的消息返回给 loopOnce;
  5. 如果调用了 quit 方法,则返回 null,终止循环。

以上代码我们看到消息的获取方式是调用 nativePollOnce 进行等待,这里的 native 消息可能就是 android 系统发送给 mainLooper 的,例如触摸点击事件等等。除此之外,我们也可以主动给 mainLooper 发消息,这就需要使用 Handler 了。

我们调用 Handler 的 sendMessage 方法,最终会执行到 enqueueMessage 中:

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

会调用 MessageQueue.enqueueMessage 将消息加入到 Message 链表中。问题来了,这里的 MessageQueue 是哪里来的?

这就需要追溯到 Handler 的构造函数,需要将一个 Looper 作为参数传递进去,MessageQueue 就是从 Looper 中获得的。

这也就意味着,每个 Handler 只能处理一个 Looper 的事务。为什么只能处理一个 Looper 的事务呢?我的理解是这样:Looper 将所有的消息或事务进行收集,接着再一条一条转发执行,虽然消息是异步发出的,但是 Handler 执行任务是同步的,这就没有了线程同步的问题。

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            }
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

进入到 MessageQueue.enqueueMessage 后,消息会被加入到队列当中,如果当前消息队列是空的,就会调用 nativeWake 来打断 nativePollOnce 的执行,从而立即处理我们主动 post 的消息。

Looper 进行消息分发时,应该交给谁来处理呢?答案是哪个 Handler send 的消息,消息就由哪个 Handler 处理,这一点可以查看 enqueueMessage 方法。

除了用 Handler post 消息外,Message 本身也有一个 sendToTarget 方法,可以将自己发送给指定的 Handler,由 Hander 再加入到 MessageQueue 队列中,有兴趣可以阅读相关代码。

除了用 Handler sendMessage 之外,我们还常常见到用 Handler post Runnable。Runnable是什么?

被 sendMessage 发出的 Message 常常会设定有 what 信息,之后 Handler 会根据 what 来做对应的处理,代码示例如下:

        Handler handler = new Handler(getMainLooper()) {
            @Override
            public void handleMessage (Message msg) {
                switch (msg.what) {
                    case 1:
                        break;
                    default:
                        break;
                }
            }
        };
        Message msg = Message.obtain();
        msg.what = 1;
        handler.sendMessage(msg);

有的时候我们并不希望 Handler 对我们发出的消息进行识别处理,而是仅仅是想完成一项任务,这时候我们可以去 post Runnable。Runnable 会以 callback 的形式封装称为一个 Message,在分发处理时直接执行 Runnable 中所写的事务,就不需要再进入到 handleMessage 方法中了。

    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

我们也可以 dump Looper 中的 Message,看到 MessageQueue 中的内容以及 Looper 当前的状态,以下是在 Button 的 onClick 方法中进行 dump,log 如下:

2023-08-28 23:50:15.953 7495-7495/com.example.loopertest D/MainActivity: onClick
2023-08-28 23:50:15.953 7495-7495/com.example.loopertest D/MainActivity: Looper (main, tid 2) {f4105a7}
2023-08-28 23:50:15.954 7495-7495/com.example.loopertest D/MainActivity:   Message 0: { when=-2ms callback=android.view.View$UnsetPressedState target=android.view.ViewRootImpl$ViewRootHandler }
2023-08-28 23:50:15.954 7495-7495/com.example.loopertest D/MainActivity:   (Total messages: 1, polling=false, quitting=false)

到这里,Looper Handler MessageQueue 的基本原理就讲完了,接下来我们来了解它们的实际是如何使用的。

2、How to use

2.1、Thread

在了解如何使用之前,有两点需要先注意:

  1. UI内容的处理只能放在主线程当中;
  2. 一个线程只能有一个 Looper;

第二点一个线程只能有一个 Looper,是因为每个线程只能有一处阻塞,如果有一个线程有两个 Looper,那么其中一个 Looper 的 loop 循环会被另一个 Looper 的 loop 循环所阻塞。

根据前面的内容我们了解到,app 在启动时会自动创建一个 MainLooper 用于处理 UI 相关的消息,但是在实际 app 编写中会有各种各样的任务,如果将所有的任务都放在 UI 线程中执行,那么可能就会影响 UI 事件的处理,出现 ANR 等情况。

例如我在 onCreate 中加入以下代码:

        new Handler(getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    try {
                        Log.d(LOG_TAG, "i = " + i +" thread id = " + Process.myTid());
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

可以看到,启动的时候需要等待 Runnable 执行结束才能渲染出 UI,这显然是不太合适的。为了解决一般事务占用 UI 线程的问题,常常会在一个新的线程中来处理与 UI 不相干的事务或者一些耗时的事务。

线程 与 Looper Handler 协同使用的最简单的方式如下:

		// 问题示例
		private Handler handler;
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                handler = new Handler(Looper.myLooper()) {
                    @Override
                    public void handleMessage (Message msg) {
                        switch (msg.what) {
                            case 1:
                                Log.d(LOG_TAG, "Process Message 1");
                                break;
                            default:
                                break;
                        }
                    }
                };
                Looper.loop();
            }
        }).start();
        Message msg = Message.obtain();
        msg.what = 1;
        handler.sendMessage(msg);

想要在子线程中使用 Looper Handler,我们有3件事需要做:

  1. 创建并启动线程;
  2. 在线程中创建 Looper,并且调用 Looper.loop 阻塞线程;
  3. 创建 Handler 并且与线程中的 Looper 相绑定;

以上代码是否有问题呢?我们将 app 跑起来看一下,呀!会有空指针的错误:

08-28 16:05:44.815  8436  8436 E AndroidRuntime: Process: com.example.loopertest, PID: 8436
08-28 16:05:44.815  8436  8436 E AndroidRuntime: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.loopertest/com.example.loopertest.MainActivity}: java.lang.NullPointerExceptio
n: Attempt to invoke virtual method 'boolean android.os.Handler.sendMessage(android.os.Message)' on a null object reference

冷静分析!这是因为 线程启动 会和 sendMessage 并列执行,当执行到 sendMessage 时,handler 可能还没有创建,所以会出现空指针的错误。解决办法是在 sendMessage 之前加一个 delay,保证 handler 已经被创建:

		// 解决办法
        Message msg = Message.obtain();
        msg.what = 1;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        handler.sendMessage(msg);

这时候可能有人要问了,我为什么不在线程外部先创建 Looper,然后在线程内部调用 Looper.loop 呢?好问题,还记得我们之前说过一个线程只能有一个 Looper 吗,外部是主线程,如果在外部创建,主线程就会有两个 Looper 了。来看 Looper 的声明:

public final class Looper {
	static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
	private static Looper sMainLooper;  // guarded by Looper.class
    public static void 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));
    }

sMainLooper 是一个静态变量,意味着我们的进程中只能存在一个 MainLooper,也就是我们所说的主线程 Looper;另外java还提供了一种线程的局部静态变量 sThreadLocal ,保证每个线程的静态变量是不同的。

prepare方法用于在线程中创建 Looper,如果一个线程中调用两次 prepare,那么就会抛出异常了,这也印证了我们上文所说的一个线程只能有一个 Looper。

这时候可能又有人要问了,为什么我不在不在外面创建 Handler 实例,然后将 Thread 的 Looper 与 Handler 绑定呢?不错,我们来试试:

    class MyThread extends Thread {
        @Override
        public void run() {
            Looper.prepare();
            Looper.loop();
        }

        Looper getLooper() {
            return Looper.myLooper();
        }
    }

	private MyThread myThread;
    myThread = new MyThread();
    myThread.start();
    handler = new Handler(myThread.getLooper()) {
        @Override
        public void handleMessage (Message msg) {
            switch (msg.what) {
                case 1:
                    Log.d(LOG_TAG, "Process Message 1");
                    break;
                default:
                    break;
            }
        }
    };
    Message msg = Message.obtain();
    msg.what = 1;
    handler.sendMessage(msg);

实际跑起来结果如下,没有问题!以后就这么用了

2023-08-29 21:27:16.583 9284-9284/com.example.loopertest D/MainActivity: Process Message 1

2.2、HandlerThread

是不是要为我们的机智鼓个掌呢?稍等!android 好像已经实现了我们所想的方法,这就是 HandlerThread!HandlerThread 简直就和我们的想法一模一样,所以用法也是一模一样!这里给出示例,就不再多做解释了。

        handlerThread = new HandlerThread("Test HandlerThread");
        handlerThread.start();
        Message msg = Message.obtain();
        msg.what = 1;
        new Handler(handlerThread.getLooper()) {
            @Override
            public void handleMessage (Message msg) {
                switch (msg.what) {
                    case 1:
                        Log.d(LOG_TAG, "Process Message 1");
                        break;
                    default:
                        break;
                }
            }
        }.sendMessage(msg);
2023-08-29 00:15:51.853 8743-8743/com.example.loopertest D/MainActivity: onCreate
2023-08-29 00:15:51.871 8743-8858/com.example.loopertest D/MainActivity: Process Message 1
2023-08-29 00:15:51.871 8743-8743/com.example.loopertest D/MainActivity: onStart
2023-08-29 00:15:51.872 8743-8743/com.example.loopertest D/MainActivity: onPostCreate
2023-08-29 00:15:51.872 8743-8743/com.example.loopertest D/MainActivity: onResume
2023-08-29 00:15:51.872 8743-8743/com.example.loopertest D/MainActivity: onPostResume

2.3、线程间互发消息

我们可能经常会听到 子线程往主线程发送消息,主线程往子线程发送消息,子线程往子线程发送消息,一看到这么多情况是不是有点晕?

其实这三种互发消息的原理是一致的,还记得我们上文说过,Handler 创建时要与一个 Looper 绑定:

public Handler(@NonNull Looper looper) {
	this(looper, null, false);
}

绑定 Looper 之后,Handler 只能处理绑定的 Looper 分发的消息了,那绑定的 Looper 中的消息怎么来的?除了 native 发送的消息外,我们是不是可以调用与 Looper 绑定的 Handler 来发送消息?

也就是说,要让消息在指定 Looper 线程中执行,只要调用与该线程 Looper 绑定的 Handler 的 post / sendMessage 方法。

就是这么简单!

2.4、Looper 如何停止

这里要谈一谈网上讲的最多的内存泄漏的问题,首先给个示例(不知道我理解的对不对哈):

我们在 onCreate 方法中增加以下代码,打开 app 后立刻关闭

        new Handler(getMainLooper()).postDelayed(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    try {
                        Log.d(LOG_TAG, "i = " + i +" thread id = " + Process.myTid());
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, 5000);

可以看到以下现象:

2023-08-29 21:51:30.568 9599-9599/com.example.loopertest D/MainActivity: onCreate
2023-08-29 21:51:30.586 9599-9599/com.example.loopertest D/MainActivity: onStart
2023-08-29 21:51:30.587 9599-9599/com.example.loopertest D/MainActivity: onPostCreate
2023-08-29 21:51:30.587 9599-9599/com.example.loopertest D/MainActivity: onResume
2023-08-29 21:51:30.587 9599-9599/com.example.loopertest D/MainActivity: onPostResume
2023-08-29 21:51:31.783 9599-9599/com.example.loopertest D/MainActivity: onKeyDown keyCode = 4
2023-08-29 21:51:31.873 9599-9599/com.example.loopertest D/MainActivity: onPause
2023-08-29 21:51:32.399 9599-9599/com.example.loopertest D/MainActivity: onStop
2023-08-29 21:51:32.399 9599-9599/com.example.loopertest D/MainActivity: onDestroy
2023-08-29 21:51:35.624 9599-9599/com.example.loopertest D/MainActivity: i = 0 thread id = 9599
2023-08-29 21:51:36.635 9599-9599/com.example.loopertest D/MainActivity: i = 1 thread id = 9599
2023-08-29 21:51:37.677 9599-9599/com.example.loopertest D/MainActivity: i = 2 thread id = 9599
2023-08-29 21:51:38.701 9599-9599/com.example.loopertest D/MainActivity: i = 3 thread id = 9599
2023-08-29 21:51:39.744 9599-9599/com.example.loopertest D/MainActivity: i = 4 thread id = 9599

嘿!明明已经调用了 onDestroy 了,为什么还能打印出循环内容呢?不知道这是不是其他博文中所说的内存泄漏,退出 activity 时仍占用着 activity 的资源,导致资源无法正常释放?

网上的一些解法是将 Handler 声明为静态类,这里我并不认为这是好的解法。

退出程序或者 activity 时,Looper 所在的线程正在运行,我们正常是需要停止线程的。由于 Looper.loop 正在阻塞运行,我们要调用 Looper.quit 或者 Looper.quitSafely 退出死循环。

这里我要提出一个问题,调用了 quit / quitSafely 后,Looper 就真的停止了吗?

答案是否定的,Looper 并不一定会立即停止,它需要执行完当前的任务才能停止!如果当前任务是一个耗时任务,那么会等他执行完 Looper 才会真正停止。

我们所要做的是什么呢?

  1. 把耗时工作放到 Looper 中执行时添加打断机制;
  2. ativity 结束时调用 Looper.quit;
  3. 调用 Thread.join 阻塞等待线程结束;

我觉得这样资源可以正常释放结束,不知道我理解的是否正确。接下来用 HandlerThread 给出一个示例:

在 onCreate 和 onDestroy 中添加以下代码:

	protected void onCreate(Bundle savedInstanceState) {
        handlerThread = new HandlerThread("Test HandlerThread quit");
        handlerThread.start();
        Message msg = Message.obtain();
        msg.what = 1;
        new Handler(handlerThread.getLooper()).post(new Runnable() {
            @Override
            public void run() {
                int i = 0;
                while (i < 10) {
                    try {
                        Log.d(LOG_TAG, "i = " + i +" thread id = " + Process.myTid());
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    i++;
                }
            }
        });
    }

    protected void onStop() {
        Log.d(LOG_TAG, "onStop");
        super.onStop();
        handlerThread.quitSafely();
    }

启动 app 后立刻退出 app,可以看到我们已经调用了 quitSafely,线程仍然没有立即停止,资源没有正常释放:

2023-08-29 22:43:24.101 10559-10559/com.example.loopertest D/MainActivity: onCreate
2023-08-29 22:43:24.203 10559-10586/com.example.loopertest D/MainActivity: i = 0 thread id = 10586
2023-08-29 22:43:24.207 10559-10559/com.example.loopertest D/MainActivity: onStart
2023-08-29 22:43:24.210 10559-10559/com.example.loopertest D/MainActivity: onPostCreate
2023-08-29 22:43:24.211 10559-10559/com.example.loopertest D/MainActivity: onResume
2023-08-29 22:43:24.212 10559-10559/com.example.loopertest D/MainActivity: onPostResume
2023-08-29 22:43:25.205 10559-10586/com.example.loopertest D/MainActivity: i = 1 thread id = 10586
2023-08-29 22:43:26.004 10559-10559/com.example.loopertest D/MainActivity: onKeyDown keyCode = 4
2023-08-29 22:43:26.207 10559-10586/com.example.loopertest D/MainActivity: i = 2 thread id = 10586
2023-08-29 22:43:26.231 10559-10559/com.example.loopertest D/MainActivity: onPause
2023-08-29 22:43:26.765 10559-10559/com.example.loopertest D/MainActivity: onStop
2023-08-29 22:43:26.766 10559-10559/com.example.loopertest D/MainActivity: onDestroy
2023-08-29 22:43:27.208 10559-10586/com.example.loopertest D/MainActivity: i = 3 thread id = 10586
2023-08-29 22:43:28.242 10559-10586/com.example.loopertest D/MainActivity: i = 4 thread id = 10586
2023-08-29 22:43:29.258 10559-10586/com.example.loopertest D/MainActivity: i = 5 thread id = 10586
2023-08-29 22:43:30.275 10559-10586/com.example.loopertest D/MainActivity: i = 6 thread id = 10586
2023-08-29 22:43:31.293 10559-10586/com.example.loopertest D/MainActivity: i = 7 thread id = 10586
2023-08-29 22:43:32.308 10559-10586/com.example.loopertest D/MainActivity: i = 8 thread id = 10586
2023-08-29 22:43:33.348 10559-10586/com.example.loopertest D/MainActivity: i = 9 thread id = 10586

我们在 quitSafely 后面加上 join,可以看到退出程序时,onStop 阻塞在 join 的位置上等待线程结束:

2023-08-29 22:43:56.616 10618-10618/com.example.loopertest D/MainActivity: onCreate
2023-08-29 22:43:56.745 10618-10644/com.example.loopertest D/MainActivity: i = 0 thread id = 10644
2023-08-29 22:43:56.748 10618-10618/com.example.loopertest D/MainActivity: onStart
2023-08-29 22:43:56.749 10618-10618/com.example.loopertest D/MainActivity: onPostCreate
2023-08-29 22:43:56.750 10618-10618/com.example.loopertest D/MainActivity: onResume
2023-08-29 22:43:56.750 10618-10618/com.example.loopertest D/MainActivity: onPostResume
2023-08-29 22:43:57.746 10618-10644/com.example.loopertest D/MainActivity: i = 1 thread id = 10644
2023-08-29 22:43:58.748 10618-10644/com.example.loopertest D/MainActivity: i = 2 thread id = 10644
2023-08-29 22:43:59.240 10618-10618/com.example.loopertest D/MainActivity: onKeyDown keyCode = 4
2023-08-29 22:43:59.354 10618-10618/com.example.loopertest D/MainActivity: onPause
2023-08-29 22:43:59.750 10618-10644/com.example.loopertest D/MainActivity: i = 3 thread id = 10644
2023-08-29 22:43:59.863 10618-10618/com.example.loopertest D/MainActivity: onStop
2023-08-29 22:44:00.784 10618-10644/com.example.loopertest D/MainActivity: i = 4 thread id = 10644
2023-08-29 22:44:01.797 10618-10644/com.example.loopertest D/MainActivity: i = 5 thread id = 10644
2023-08-29 22:44:02.840 10618-10644/com.example.loopertest D/MainActivity: i = 6 thread id = 10644
2023-08-29 22:44:03.881 10618-10644/com.example.loopertest D/MainActivity: i = 7 thread id = 10644
2023-08-29 22:44:04.922 10618-10644/com.example.loopertest D/MainActivity: i = 8 thread id = 10644
2023-08-29 22:44:05.926 10618-10644/com.example.loopertest D/MainActivity: i = 9 thread id = 10644
2023-08-29 22:44:06.948 10618-10618/com.example.loopertest D/MainActivity: onDestroy

我们再给线程退出加上一个条件:

        new Handler(handlerThread.getLooper()).post(new Runnable() {
            @Override
            public void run() {
                int i = 0;
                while (i < 10) {
                    if (isQuit)
                        break;
                    try {
                        Log.d(LOG_TAG, "i = " + i +" thread id = " + Process.myTid());
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    i++;
                }
            }
        });

可以看到,我们点击返回键退出 app 时,程序可以正常退出,并且子线程没有再打印出内容了,这样是不是就不会有内存泄漏了呢。

2023-08-29 22:48:03.391 10748-10748/com.example.loopertest D/MainActivity: onCreate
2023-08-29 22:48:03.525 10748-10773/com.example.loopertest D/MainActivity: i = 0 thread id = 10773
2023-08-29 22:48:03.528 10748-10748/com.example.loopertest D/MainActivity: onStart
2023-08-29 22:48:03.529 10748-10748/com.example.loopertest D/MainActivity: onPostCreate
2023-08-29 22:48:03.530 10748-10748/com.example.loopertest D/MainActivity: onResume
2023-08-29 22:48:03.530 10748-10748/com.example.loopertest D/MainActivity: onPostResume
2023-08-29 22:48:04.525 10748-10773/com.example.loopertest D/MainActivity: i = 1 thread id = 10773
2023-08-29 22:48:05.527 10748-10773/com.example.loopertest D/MainActivity: i = 2 thread id = 10773
2023-08-29 22:48:06.528 10748-10773/com.example.loopertest D/MainActivity: i = 3 thread id = 10773
2023-08-29 22:48:06.733 10748-10748/com.example.loopertest D/MainActivity: onKeyDown keyCode = 4
2023-08-29 22:48:06.854 10748-10748/com.example.loopertest D/MainActivity: onPause
2023-08-29 22:48:07.399 10748-10748/com.example.loopertest D/MainActivity: onStop
2023-08-29 22:48:07.531 10748-10748/com.example.loopertest D/MainActivity: onDestroy

好了,关于 Android Looper Handler 机制就分析到这里,如果觉得这篇文章有帮助,还请不要吝啬点赞关注哦,再见!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android Looper Handler 机制浅析 的相关文章

随机推荐

  • UE4中英文语言切换的三种方式(当然也可以多种语言)

    一 用ue4的Localization Dashboard 1 2 3 4 5 最后 必须独立运行游戏才能看到效果 二 使用WidgetSwitcher 1 2 3 4 用一个按钮点击进行Index的修改 就可以完成中英文切换 三 用两个T
  • 2-软件生命周期模型

    软件生命周期模型 软件工程过程 工程项目的PDCA循环 戴明环 美国质量管理专家戴明博士针对工程项目的质量目标 将全面质量管理思想引入工程项目过程 提出了PDCA循环 也称为戴明环 即Plan 规划 Do 执行 Check 检查 Actio
  • PTA9文件操作(python3)

    python程序设计09 文件操作 9 1 从文件中查找最长的单词 20分 请勿修改 20 分 9 2 求文件行数 20 分 9 3 analyze the character distribution of a document 20 分
  • 最大报文段长度——MSS

    1 概念 MSS Maximum Segment Size 最大报文长度 是TCP协议定义的一个选项 MSS选项用于在TCP连接建立时 收发双方协商通信时每一个报文段所能承载的最大数据长度 在以太网环境下 MSS MTU 20字节TCP报头
  • unity按钮点击无响应的处理方法

    在程序中给Button控件添加了点击事件后无反应 可能由于下列原因导致 按钮的interactable false或者enable false 父节点如果有CanvasGroup组件 还必须把CanvasGroup组件上的interacta
  • 【CentOS 7.9】基于VMware虚拟机的详细安装教程

    一 下载CentOS 7 9 镜像文件 Linux版本大家自己选择 目前市面还是7 X版本用的多 这里我就以7 9版本为例进行演示 镜像下载地址 https mirrors tuna tsinghua edu cn centos 7 9 2
  • Ubuntu16.04LTS64位安装64JDK9.0.1

    第一步 去Oracle下载JDK 根据自己的需要选择不同的版本 这里选择的JDK9 0 1 下载地址 http www oracle com technetwork java javase downloads jdk9 downloads
  • yolov8保存结果

    找到predictor py文件加上如下代码 for result in self results path result path split images 1 split 0 boxes result boxes box boxes c
  • oracle 数据库truncate,Oracle中的truncate用法

    语法 TRUNCATE TABLE table 在使用truncate语句 DDL语言 可以删除表中的所有记录 使用truncate语句删除数据时 通常要比使用delete语句快得多 这是因为使用truncate语句删除数据时 不会产生任何
  • Spring中进行事务管理的两种方式

    1 Spring中事务管理的API 事务是指逻辑上要么全部成功 要么全部失败的一组操作 例如用户A给用户B转账 则用户A账户余额减少 用户B账户增加这两个操作就是一组事务 必须全部成功或失败撤回操作 不能出现A账户余额减少 B增加失败的情况
  • gvim 编译选项没支持Python. gvim is not python supported

    windows 安装 vim tux 该版本开启了所有的编译支持选项
  • 软件测试工程师工作总结

    1 为什么要在一个团队中开展软件测试工作 因为没有经过测试的软件很难在发布之前知道该软件的质量 就好比ISO质量认证一样 测试同样也需要质量的保证 这个时候就需要在团队中开展软件测试的工作 在测试的过程发现软件中存在的问题 及时让开发人员得
  • 三维物体追踪笔记(1)-基于边缘的三维物体追踪——理论、公式推导与实现

    1 基于边缘检测的三维跟踪建模 三维物体追踪是已知图像中某个物体在已知一系列空间三维点位置 或者是一个3D 模型面片集 的信息下 将这些点通过一个恰到好处的位姿 R t 进行转换后投影到图像上 问题的求解目标是这个位姿 R t 难点是并不知
  • 华院计算

    郭守敬 1231年 1316年 字若思 河北邢台人 郭守敬出生于金哀宗正大八年 1231年 时为宋朝 960年 1279年 但当年的河北地域属于女真族人管治的金国 他40岁那年 大蒙古国忽必烈 1215年 1294年 打败了西夏和金国 建立
  • 北大igem生物计算机,喜讯|北京大学iGEM团队在国际大赛中取得金牌

    原标题 喜讯 北京大学iGEM团队在国际大赛中取得金牌 美国东部时间2017年11月13日下午 在波士顿Hynes会议中心举办的国际基因工程机器大赛 International Genetically Engineered Machine
  • YOLO 算法系列

    我们分享了YOLO系列的文章 包括前段时间刚刚发布的YOLOv7检测算法 虽然YOLOv7对象检测算法是YOLO最新的模型 且可以支持对象分割 人体姿态检测等 但是最新的YOLO系列检测模型也同步进行了更新 那就是刚刚开源的YOLOv8对象
  • 在Ubuntu20.10中搭建TFTP服务器

    TFTP Trivial File Transfer Protocol 是用来下载远程文件的最简单的网络协议 它基于UDP协议而实现 一 安装 sudo apt get install tftp hpa tftpd hpa tftp hpa
  • Apache Hive基础

    Apache Hive基础 Hive优势 提供了一个简单的优化模型 HQL类SQL语法 简化MR开发 支持在不同的计算框架上运行 支持在HDFS和HBase上临时查询数据 支持用户自定义函数 格式 成熟的JDBC和ODBC驱动程序 用于ET
  • matlab如何预测数据,matlab预测数据

    基于MATLAB 的人口预测模型 摘要 本文以 1980 2014 年中国年终总人口数据资料为依据 分别使用了一次拟合 灰色预 测模型和时间序列模型进行拟合 最终得出时间序列模型 基于MATLAB 的人口预测方法分析 易亮 摘要 摘要 本文
  • Android Looper Handler 机制浅析

    最近想写个播放器demo 里面要用到 Looper Handler 看了很多资料都没能理解透彻 于是决定自己看看相关的源码 并在此记录心得体会 希望能够帮助到有需要的人 本文会以 猜想 log验证 的方式来学习 Android Looper