Android Handler 工作原理剖析

前言

我们知道,为了优化性能,安卓的 UI 线程并不是线程安全的,为了避免出现问题,安卓不允许在子线程中操作 UI。测试如下:

public class MainActivity extends AppCompatActivity {

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

        new Thread(new Runnable() {
            @Override
            public void run() {
                TextView textView = findViewById(R.id.text);
                textView.setText("子线程");
            }
        }).start();
    }
}

但是结果却是正常运行,text也被成功修改,难道子线程中可以修改 UI?别急,我们再将子线程稍作修改:

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                TextView textView = findViewById(R.id.text);
                textView.setText("子线程");
            }
        }).start();

这个时候,程序终于如愿的崩溃了:
在这里插入图片描述
这是为什么呢,我们可以看到,异常是由 ViewRootImpl 的 checkThread()方法抛出的,所以可能的原因是在 onCreate() 方法执行的时候,ViewRootImpl 对象并未创建完毕,查看 ActivityThread 的源码,我们发现 ViewRootImpl 对象是在 handleResumeActivity(…) 方法中创建的,所以在 onResume() 方法执行前子线程中是可以修改 UI 属性的,但并不代表这是线程安全的!

final void handleResumeActivity(IBinder token, boolean clearHide,boolean isForward, boolean reallyResume, int seq, String reason) {
    ...
    ViewRootImpl impl = decor.getViewRootImpl()...
}

子线程中不允许修改 UI 属性,那么访问呢,将上述代码子线程修改如下:

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                TextView textView = findViewById(R.id.text);
                Log.d("tag", "run: " + textView.getText().toString());
            }
        }).start();

此时程序正常运行:
在这里插入图片描述
所以,子线程中不能更新 UI,但是可以访问 UI。

那么,为了不阻塞 UI 线程,影响用户体验,我们只能把耗时操作放在其他线程当中,在其他线程当中我们如何更新 UI 呢。

使用 Handler 实现线程中的通信,便是其中的一种方法。

Handler 工作原理

我们先了解几个的组件的概念:

Handler
负责发送消息到 Looper 管理的 MessageQueue 中,并处理 Looper 分配的消息。

Message
Handler 接收和处理的消息对象。
拥有两个 int 型的参数 arg1 和 arg2,可以直接设置;
可以携带一个 Object 对象,通过 msg.obj 直接设置、获取;
可以携带 Bundle 对象,通过 setData() 和 getData() 设置、获取;
含有一个 int 型的 what 变量,用来区分 message。

MessageQueue
用于存放消息的队列。

Looper
消息泵,用于管理消息队列。
loop()方法负责读取消息队列中的消息,并交给发送该消息的 Handler 处理。

我们再来看一个 Handler 的例子:

public class MainActivity extends AppCompatActivity {

    private TextView textView;

    private Handler handler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            //如果是指定的消息,则进行处理
            if(msg.what == 0x123){
                textView.setText("" + (int)msg.obj);
            }
        }
    };

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

        textView = findViewById(R.id.text);

		//获取消息对象
        Message msg = Message.obtain();
        msg.obj = 10;
        msg.what = 0x123;
		//发送消息
        handler.sendMessage(msg);
    }
}

上述代码是很常规的应用,但是我们我们发现 handler 的定义代码是一片黄的,AS 给出了警告:
在这里插入图片描述

这是由于这样的代码可能造成内存泄漏:Activity 结束后,消息泵和消息队列依然存在,如果消息队列中仍有消息存在,那么就存在其对 handler 的引用,而 handler 中又引用了 Activity,故导致 Activity 不会被 GC 系统回收。

为了避免发生内存泄漏问题,我们应创建 Handler 的静态子类,并让其持有对 Activity 的弱引用,方便 GC 系统回收 Activity。

上述代码标准写法如下:

public class MainActivity extends AppCompatActivity {

    private TextView textView;

    static class MyHandler extends Handler{
        private WeakReference<MainActivity> activity;

        public MyHandler(WeakReference<MainActivity> activity){
            this.activity = activity;
        }
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            if(msg.what == 0x123){
                activity.get().textView.setText("" + (int)msg.obj);
            }
        }
    }

    MyHandler handler = new MyHandler(new WeakReference<>(this));
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = findViewById(R.id.text);

        Message msg = Message.obtain();
        msg.obj = 10;
        msg.what = 0x123;

        handler.sendMessage(msg);
    }
}

继续观察上述代码,我们发现,获取消息并不是直接 new 出来的,而是通过 Message.obtain() 来获取,这是为什么呢,我们来看看 obtain() 方法的代码:

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    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();
    }

注释中指明了我们应避免 new Message,如果消息池中是存在 Message 对象的,直接取出来使用即可,这样就节省了内存的使用。

另外同一个消息是不能被多次处理的,在某个 handler 处理时,就会将其标记,使之不能再直接使用。

handler 的使用前提是当前线程中必须拥有 Looper 对象,在 UI 线程中,已经写好了一个 Looper 对象,不必再创建,在其他线程中,必须手动创建 Looper 对象并将之启动。

Looper 的构造方法是 private 权限的,我们需要调用 Looper.prepare() 方法来创建 Looper 对象。

    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 的代码如下:

public class MainActivity extends AppCompatActivity {

    private TextView textView;
    private Button button;

    MyThread myThread;
    Message msg;

    class MyThread extends Thread{
        private Handler handler;
        @Override
        public void run() {
            super.run();
            Looper.prepare();
            handler = new Handler(){
                @Override
                public void handleMessage(@NonNull Message msg) {
                    super.handleMessage(msg);
                    if(msg.what == 0x123){
                        Toast.makeText(MainActivity.this, "Information", Toast.LENGTH_SHORT).show();
                    }
                }
            };
            Looper.loop();
        }
    }

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

        textView = findViewById(R.id.text);
        button = findViewById(R.id.button);

        myThread = new MyThread();
        myThread.start();

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                msg = Message.obtain();
                msg.what = 0x123;
                myThread.handler.sendMessage(msg);
            }
        });
    }
}

子线程中的 handler 也不能更新 UI ,只能用于线程通信,但是如果要在子线程中使用 Toast 的话,子线程中必须准备好 Looper 对象。

我们一般会在 UI 线程中使用 Handler,然后在子线程中进行耗时操作,最后将结果发送给 UI 线程进行处理更新 UI。(不能在 UI 线程的 handler 中进行耗时操作,其本身就是在 UI 线程中执行的)

我们也可以在子线程中使用主线程的 handler.post(Runnable r) 方法更新 UI:

	new Thread(new Runnable(){
		@Override
		public void run(){
			/*
			 * 耗时操作
			 */
			//使用UI线程的handler,此处并不是开启了新的线程,而是将Runnable对象放入了消息队列中
			handler.post(new Runnable(){
				textView.setText("post 更新 UI");
			});
		}
	}).start();

看到许多人提问说为什么 Looper.loop() 死循环为什么不会阻塞 UI 线程,我们看 Looper.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.");
        }
        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);
            	...
            }
            ...
		}
		...
	}

UI 线程的监听模式是轮询监听,在轮询的过程中会调用 Looper.loop() 方法,如果 Looper 持有的 MessageQueue 中存在消息,则通知对应的 Handler 对象处理,若无消息,则直接 return。

总结

Android 不允许在子线程中更新 UI,Handler 是一套非常重要的子线程通知 View 所持有线程更新 UI 的机制,但是如果处理不当的话 ,也会造成非常头疼的内存泄漏问题。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值