Android Handler 知识总结


安卓的 UI 操作是线程不安全的,为了避免多线程操作 UI 组件时出现线程安全问题,安卓只允许在 UI 线程中对 UI 组件进行操作。但是在安卓实际开发中,许多时候我们需要根据其它线程的值或者事件对 UI 组件进行操作,此时就可以借助 Handler 来实现消息传递。

一、Handler 使用实例

先看一下 Handler 的使用实例:

class MainActivity : AppCompatActivity() {
    
    
    private lateinit var textView: TextView
    private val myHandler = MyHandler(WeakReference(this))

    class MyHandler(private val activity: WeakReference<MainActivity>):Handler(){
    
    
        // 解决接收的消息
        override fun handleMessage(msg: Message) {
    
    
            // 根据信息的标识做出反应
            if (msg.what == 0x1234){
    
    
                activity.get()?.textView?.setText(msg.obj.toString())
            }
        }
    }

    inner class MyThread(): Thread(){
    
    
        override fun run() {
    
    
            // 让线程睡眠2秒,让我们能够看见文字的变化
            Thread.sleep(2000)
            // 创建一个信息并标记为 0x1234
            val message = myHandler.obtainMessage(0x1234)
            message.obj = "A Message"
            // 发送消息
            myHandler.sendMessage(message)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        textView = findViewById(R.id.textview)
        // 创建一个新线程
        val thread = MyThread()
        thread.start()
    }
}

xml 文件中只有一个用来显示信息的 TextView 控件,打开应用可以看到屏幕中间有一个“Hello World!”,过两秒钟之后变成了“A Message”。可以看到我们实现了在根据其它线程的信息修改了 UI 组件。

二、Handler 原理

从上面的例子可以看到 Handler 的实际作用就是发送消息,接收并处理消息。一个 Handler 要正常工作离不开以下这些组件:

  • Message:用于发送和接收的消息对象。
  • Looper:每个线程只能拥有一个,通过一个死循环不断的从 MessageQueue 中取出消息,然后将消息交给 Handler 处理。
  • MessageQueue:消息队列,采用先进先出的方式来管理消息,在创建 Looper 对象时会在创建 MessageQueue 对象。

上面提到了线程中要用一个 Looper 对象,但是上述 Handler 使用实例中并没有看到 Looper 的创建过程,因为应用在初始化时会创建主线程 ActivityThread,在 ActivityThread 的 main 方法中会初始化 Looper 对象,ActivityThread.java 中的 main 方法如下(为了方便看到 Looper 相关的代码,将其余代码用/**/进行了注释):

public static void main(String[] args) {
    
    
    /*Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

    // Install selective syscall interception
    AndroidOs.install();

    // CloseGuard defaults to true and can be quite spammy.  We
    // disable it here, but selectively enable it later (via
    // StrictMode) on debug builds, but using DropBox, not logs.
    CloseGuard.setEnabled(false);

    Environment.initForCurrentUser();

    // Make sure TrustedCertificateStore looks in the right place for CA certificates
    final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
    TrustedCertificateStore.setDefaultUserDirectory(configDir);

    // Call per-process mainline module initialization.
    initializeMainlineModules();

    Process.setArgV0("<pre-initialized>");*/

    Looper.prepareMainLooper();  //初始化 Looper 对象

    /*// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
    // It will be in the format "seq=114"
    long startSeq = 0;
    if (args != null) {
        for (int i = args.length - 1; i >= 0; --i) {
            if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                startSeq = Long.parseLong(
                        args[i].substring(PROC_START_SEQ_IDENT.length()));
            }
        }
    }
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);*/
    Looper.loop();  // 调用 loop 方法

    /*throw new RuntimeException("Main thread loop unexpectedly exited");*/
}

可以看到初始化 Looper 对象时是调用 prepare() 方法 而不是构造函数,这是为了确保每个线程只有一个 Looper 对象,Looper.java 中的 构造函数和 prepare() 方法如下:

// 将构造函数设置为私有隐藏起来
private Looper(boolean quitAllowed) {
    
    
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
// 通过 prepare() 方法保证只有一个 Looper
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));
}

因此如果是在自己创建的线程中使用 Handler,必须自己使用 prepare() 方法创建 Looper 对象,并调用 loop() 方法启动。
接下来看一下 loop() 方法:

public static void loop() {
    
    
    final Looper me = myLooper();
    if (me == null) {
    
    
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    if (me.mInLoop) {
    
    
        Slog.w(TAG, "Loop again would have the queued messages be executed"
                + " before this one completed.");
    }

    me.mInLoop = true;
    // 创建消息队列
    final MessageQueue queue = me.mQueue;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    // Allow overriding a threshold with a system prop. e.g.
    // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
    final int thresholdOverride =
            SystemProperties.getInt("log.looper."
                    + Process.myUid() + "."
                    + Thread.currentThread().getName()
                    + ".slow", 0);

    boolean slowDeliveryDetected = false;

	// 通过死循环不断取出消息
    for (;;) {
    
    
    	// 当消息队列为空时会阻塞
        Message msg = queue.next(); // might block
        if (msg == null) {
    
    
            // No message indicates that the message queue is quitting.(消息为null表示消息队列正在退出)
            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);
        }
        // Make sure the observer won't change while processing a transaction.
        final Observer observer = sObserver;

        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;
        Object token = null;
        if (observer != null) {
    
    
            token = observer.messageDispatchStarting();
        }
        long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
        try {
    
    
            msg.target.dispatchMessage(msg);
            if (observer != null) {
    
    
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } catch (Exception exception) {
    
    
            if (observer != null) {
    
    
                observer.dispatchingThrewException(token, msg, exception);
            }
            throw exception;
        } finally {
    
    
            ThreadLocalWorkSource.restore(origWorkSource);
            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();
    }
}

三、Handler 类中用于处理消息的方法

  • handleMessage(msg: Message):处理消息的方法,一般用于重写。
  • hasMessage(what: Int):判断消息队列中是否包含 what 为指定值的消息。
  • hasMessage(what: Int, object: Any):判断消息队列中是否包含 what 为指定值,并且 object 为指定对象的消息。
  • 多个重载的 Message obtainMessage():创建消息。
  • sendEmptyMessage(what: Long):发送空消息。
  • sendEmptyMessageDelayed(what: Long, delayMillis: Long):指定多少毫秒后发送空消息。
  • sendMessage(msg: Message):发送消息。
  • sendMessageDelayed(msg: Message, delayMillis: Long):指定多少毫秒后发送消息。

四、Handler 使用细节

4.1 创建消息

使用 Handler 的 obtainMessage()方法或者直接调用 Message 类的构造函数都可以创建一个新消息。下面是 Handler 的 obtainMessage() 方法的代码:

public final Message obtainMessage()
{
    
    
    return Message.obtain(this);
}

可以看到 obtainMessage() 方法中调用了 Message 类的静态方法 obtain() 来获取消息。看一下 Message 类静态方法 obtain() 的代码:

public static Message obtain() {
    
    
	// 线程同步
    synchronized (sPoolSync) {
    
    
        if (sPool != null) {
    
    
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

可以看出 Message 类中维护了一个消息池(类似于线程池),当程序创建新消息时,会首先复用消息池中的消息,消息池为空时再创建新的消息,当旧消息使用结束时会被回收到消息池中等待下一次调用。回收消息的操作在消息处理完后会自动调用,其代码如下:

public void recycle() {
    
    
    if (isInUse()) {
    
    
        if (gCheckRecycle) {
    
    
            throw new IllegalStateException("This message cannot be recycled because it "
                    + "is still in use.");
        }
        return;
    }
    recycleUnchecked();
}

void recycleUnchecked() {
    
    
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = UID_NONE;
    workSourceUid = UID_NONE;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
    
    
        if (sPoolSize < MAX_POOL_SIZE) {
    
    
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

4.2 避免内存泄漏

重新看会 Handler 使用实例中 Handler 类的实现:

class MyHandler(private val activity: WeakReference<MainActivity>):Handler(){
    
    
    ...
}

采用了静态内部类和弱引用的方式:

  • 静态内部类不会持有外部类的的引用,避免其它线程持有该 Handler 时,Handler 所在的 Activity 被 Handler 持有而无法被回收。
  • 通过弱引用获取到外部类进行相关操作,同时避免对象无法被回收。

关于 Java 的引用方式的内容可以查看此博客

Guess you like

Origin blog.csdn.net/qingyunhuohuo1/article/details/109647388