Android Handler机制

在Android中,UI线程不能执行耗时操作的,否则可能会出现ARN。而在工作线程中是不能更新UI的,否则程序会抛出异常。这样,我们就需要在工作线程中执行完耗时操作后来通知UI线程更新UI。这个时候就可以使用Handler。

Handler的简单用法
通常我们使用Handler会在Activity里面新建一个类继承Handler,并重写它的handleMessage()方法。然后在声明的时候直接初始化,或者是在onCreate()中初始化Handler的实例。

class MainActivity : AppCompatActivity() {

    private lateinit var btn: Button
    private val handler = MyHandler()

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

    private fun initViews() {
        btn = findViewById(R.id.btn)
        btn.setOnClickListener {
            Thread(Runnable {
                //子线程模拟耗时操作
                Thread.sleep(3000L)
                //耗时操作完成通知UI线程
                handler.sendEmptyMessage(0)
            }).start()
        }
    }

    inner class MyHandler : Handler() {
        override fun handleMessage(msg: Message) {
            //更新UI            
        }
    }
}

Handler机制几个比较相关的类
Handler  主要用于创建消息,发送消息,处理消息,删除消息
Looper  在该类内部维护了一个MessageQueue(消息队列),Handler发送的消息会发送到这个消息队列里
MessageQueue  消息队列
Message  消息

Handler
我们首先来看看我们使用Handler时常用的构造方法(Hanlder还有别的构造方法)

public Handler() {
    this(null, false);
}

public Handler(@Nullable Callback callback, boolean async) {
    ...
    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;
}

在上面构造方法中,主要是分别为mLooper,mQueue,mCallback这三个属性赋值。其中mLooper赋值为当前线程保存Looper对象。mQueue赋值为当前线程保存的Looper对象的MessageQueue,mCallback在这里被赋值为null。

首先来看一下为mLooper属性数值的Looper.myLooper()方法:

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

这个方法很简单,直接获取当前线程保存的Looper对象。sThreadLocal是一个ThreadLocal对象,该对象可用于在一个线程中存储变量。
然后判断了一下mLooper是不是空。如果是空的话则会抛出异常。这里就说明如果我们的当前线程没有创建Looper对象的话是无法直接使用Handler的。那为啥可以在主线程中直接使用Handler而不抛出异常呢?这是因为Android系统在应用程序启动时已经在主线程创建了一个Looper对象。

主线程中的Looper对象
Android程序的入口在ActivityThread类的静态main()方法。

public static void main (String[] args) {
    ...
    Looper.prepareMainLooper();
    
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    if (false) {
        Looper.myLooper().setMessageLoggong(new 
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }
    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

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

在这个main()方法中我们看到首先会调用Looper.prepareMainLooper(),接着后来调用Looper.loop()。那这两个方法的作用是什么?为什么调用这两个方法后我们就可以直接在主线程中使用Handler。
首先我们先来看Looper.prepareMainLooper()。该方法的作用主要是创建一个Looper对象。

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        //为sMainLooper赋值
        sMainLooper = myLooper();
    }
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //创建一个Looper对象并保存进sThreadLocal中
    sThreadLocal.set(new Looper(quitAllowed));
}

首先会获取sThreadLocal中的Looper对象,如果不为空的话则会抛出异常。这表明prepare()方法只能被调用一次。同时也保证每一个线程只会拥有一个Looper实例。最后将创建的Looper实例放入sThreadLocal中。下面的是Looper的构造方法,在Looper的构造方法中创建了一个消息队列并赋值给mQueue属性。

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

接着是Looper.loop()。该方法的作用是不断的从MessageQueue中取出消息来执行。

public static void loop() {
    final Looper me = myLooper();
    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;
        }
        ...
        try {
            msg.target.dispatchMessage(msg);
            ...
        } catch (Exception exception) {
            if (observer != null) {
                observer.dispatchingThrewException(token, msg, exception);
            }
            throw exception;
        } finally {
            ThreadLocalWorkSource.restore(origWorkSource);
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        ...
        msg.recycleUnchecked();
    }
}

首先会通过myLooper()获取当前线程的Looper对象,如果为空的话则会抛出异常。这说明该方法必须在prepae()方法之后执行。接着获取该Looper对象的MessageQueue(消息队列)。然后进入一个无线循环体中,在该循环体中,会从MessageQueue中取出一个消息。该方法可能会被阻塞。如果消息不为空,接着会调用msg.target.dispatchMessage(msg)方法。msg.target是一个Handler对象。最后释放消息所占据的资源。
在loop()方法执行后,我们就可以用Handler对象的sendMessage(Message msg)或sendEmptyMessage(int what)方法来发送一个消息了。

不管是用上面的哪种方法发送的消息最终都会调用到sendMessageAtTime()方法

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
            this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

该方法内部直接获取了MessageQueue,接着调用了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);
}

在该方法中首先为msg.target属性赋值为this,也就是把当前的Handler对象作为msg的target属性。前面说到Looper的loop方法时将取出的消息交由msg的target去执行。这里就是为target属性赋值。接着,调用queue.enqueueMessage()方法将Message添加到消息队列里面。

在loop()方法中,取出消息后,会回调创建这个消息的Handler的dispathMessage()方法。

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

首先判断了一下msg.callback是不是为空,那这个msg.callback是什么呢?其实这个msg.callback是一个Runnable对象,当我们通过Handler的post()方法发送消息时

public final boolean post(@NonNull Runnable r) {
    return  sendMessageDelayed(getPostMessage(r), 0);
}

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

可以看到当我们使用post()方法发送消息时,会将Runnable对象赋值给msg的callback属性。在这里注意一下,产生一个Message对象,可以直接调用构造方法,也可以使用obtain()静态方法。推荐使用obtain()静态方法,因为Message内部会维护一个消息池,避免重新创建Message对象时的内存重新分配。

当msg.callback不为空时,会直接执行它的run()方法。

private static void handleCallback(Message message) {
    message.callback.run();
}

而当msg.callback为空时,会首先检查一下mCallback是不是为空,这个mCallback是一个Handler.Callback对象。该属性是在Handler的构造方法时初始化的,通常情况下,该属性为空。在mCallback为空时会调用handleMessage(msg)方法。该方法时一个空方法,因为具体的实现是由我们自己控制的。

public void handleMessage(@NonNull Message msg) {
}

到这里,Handler的工作流程是,
1.首先,Looper会调用prepare()方法在线程中保存一个Looper实例。该实例中会保存一个MessageQueue对象。因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中也只有一个实例。
2.调用Looper.loop()方法,让当前线程进入到一个无线循环,不断的从MessageQueue中取出消息,然后回调msg.target,dispatchMessage(msg)方法。
3 在Handler的构造方法中,会首先获取当前线程中保存的Looper实例。进而与Looper实例中的MessageQueue相关联。
4 通过调用Handler的sendMessage()方法或post()方法,会给msg的target属性赋值为Handler自身,然后会加入到MessageQueue中。
5 从MessageQueue中取出msg,并回调该handler实例的dispatchMessage()方法。
6 通过判断msg中的callback属性是否为空,并最终决定是执行handleCallback()方法还是handleMessage()方法。

使用Handler时的一些注意点
如果按上面的方法使用Handler,编译器会提示我们Handler会造成内存泄露。造成内存泄露的原因是因为,主线程Looper实例的生命周期为整个应用的生命周期,如果我们关闭Handler所在的Activity时,此时Looper的MessageQueue里面存在还没有执行的Message对象时。由于Looper持有了MessageQueue,MessageQueue又持有了Message,Message又持有了Handler,Handler又持有了Activity。所以会造成我们的Activity内存泄露。解决办法也挺简单,将Handler改为静态内部类,在Handler内部使用软引用来持有Activity。在Activity的onDestory()方法执行时,将MessageQueue里面的Message清空。

class MainActivity : AppCompatActivity() {

    private lateinit var btn: Button
    private val handler = MyHandler()

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

    private fun initViews() {
        btn = findViewById(R.id.btn)
        btn.setOnClickListener {
            Thread(Runnable {
                //子线程模拟耗时操作
                Thread.sleep(3000L)
                //耗时操作完成通知UI线程
                handler.sendEmptyMessage(0)
            }).start()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        handler.removeCallbacksAndMessages(null)
    }

    class MyHandler(activity: MainActivity) : Handler() {
        private val weakReference: WeakReference<MainActivity> = WeakReference(activity)

        override fun handleMessage(msg: Message) {
            val mainActivity = weakReference.get()
            if (mainActivity != null) {
                //更新UI
            }
        }
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值