Android - Handler使用问题总结

一、使用Handler.postDelayed(Runnable)接口时,Runnable有时候没有执行。

1、问题出现场景:

class MyHandler extends Handler {
	//msg的值为0
    private static final int MSG_UPDATE = 0;
    
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
         case MSG_UPDATE:
              mActivityMainBinding.tvMessage.setText("update text!");
              break;
        }
    }
}

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
    mActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater());
    View rootView = mActivityMainBinding.getRoot();
    setContentView(rootView);
    
	mMyHandler = new MyHandler();
    mMyHandler.postDelayed(new Runnable() {
	    @Override
	    public void run() {
	    	mActivityMainBinding.tvMessage.setText("Runnable text!");
		}
	}, 2 * 1000);
	//msg移除
	mMyHandler.removeMessages(MyHandler.MSG_UPDATE);
}

上面代码中,mMyHandlerpost一个Runnable,2s后执行。后面又执行了removeMessages操作,把what为0的Message移除掉了。2s后Runnable的内容没有执行。

2、问题原因:

看下源码,postDelayed方法执行:

public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

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

再看Message.obtain()

public int what;

public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

可以看到,postDelayed(Runnable)实际上也是创建一个Message,从代码中看到,该Message要不就是从sPool池中获取,要不就是new出来的,如果new出来的,其成员变量自然被初始化为0。

如果从sPool中获取的话,sPool中的Message都是经过recycle()之后才能使用的,我们看源码:

    public void recycle() {
   		 ...
        clearForRecycle();
    }

    void clearForRecycle() {
        flags = 0;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        when = 0;
        target = null;
        callback = null;
        data = null;

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

设置的也是0,所以移除的时候就把Runnable所在的Message给移除掉了,所以不会执行。不管是post,还是postDelayed都有这个问题。

所以在定义Handler中的what值的时候,不要从0开始。

参考文章:
[原创]Android Handler使用Message的一个注意事项

二、子线程直接new一个Handler,发送信息后没有执行。

1、问题出现场景:

private Handler mThreadHandler;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ...
    new Thread(new Runnable() {
            @Override
            public void run() {
                if (mThreadHandler == null){                    
                    mThreadHandler= new Handler(){
                        @Override
                        public void handleMessage(Message msg) {
                            super.handleMessage(msg);
                            Log.i(TAG,"handle Message");
                        }
                    };              
                }
            }
        }).start();
}

public void sendMsg() {
	if (mThreadHandler != null){
		mThreadHandler.sendMessage(mThreadHandler.obtainMessage());
	}    
}

2、问题原因:

原因很简单,在子线程中新建Handler,要想让消息机制跑起来,需要先调用Looper.prepare()new一个Handler后,还需要调用Looper.loop()方法。修改上面代码如下:

private Handler mThreadHandler;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
     
    new Thread(new Runnable() {
            @Override
            public void run() {
                if (mThreadHandler == null){
                    Looper.prepare();
                    mThreadHandler= new Handler(){
                        @Override
                        public void handleMessage(Message msg) {
                            super.handleMessage(msg);
                            Log.i(TAG,"handle Message");
                        }
                    };               
                    Looper.loop();              
                }
            }
        }).start();
}

public void sendMsg() {
	if (mThreadHandler != null){
		mThreadHandler.sendMessage(mThreadHandler.obtainMessage());
	}    
}

这个是之前老代码中的一个bug,因为在主线程中我们可以直接new一个Handler,这样写习惯了,可能会忽视这个点。

3、为什么主线程可以new Handler?

来看下源码,在ActivityThread.java里有一个main()函数,它是Android每一个应用最早执行的函数。

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().setMessageLogging(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");
    ......
}

来看下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 = myLooper();
    }
}

后面执行的loop()函数,for循环不断的调用next()函数,去轮询的MessageQueue,拿取消息并执行。

public static void loop() {
    final Looper me = myLooper();
    ...
    for (;;) {
        Message msg = queue.next(); // might block
    }
}

从上面的源码能看到,主线程一启动,在main()函数中,系统已经帮我们完成了Looper机制的创建,我们主线程中的所有代码,全都运行在这两个函数(prepare()loop())之间。

下面看下在主线程中new一个Handler的源码如下:

private Handler mMainHandler = new Handler();

//无参构造
public Handler() {
   this(null, false);
}

//构造函数中获取mLooper 
public Handler(@Nullable Callback callback, boolean async) {
	...
	mLooper = Looper.myLooper();
	...
}

//获取当前线程的Looper 
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

在主线程new一个Handler的时候,默认使用的是当前线程的Looper,就是系统为我们创建好的MainLooper。所以不需要自己在手动写prepare()loop()函数。

4、子线程中new Handler的完整用法

private Handler mThreadHandler;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
     
    new Thread(new Runnable() {
            @Override
            public void run() {
                if (mThreadHandler == null){
                    Looper.prepare();
                    mThreadHandler= new Handler(){
                        @Override
                        public void handleMessage(Message msg) {
                            super.handleMessage(msg);
                            Log.i(TAG,"handle Message");
                        }
                    };
                    Log.i(TAG,"*** loop() - UP");               
                    Looper.loop(); 
                    Log.i(TAG,"*** loop() - DOWN");             
                }
            }
        }).start();
}

public void sendMsg() {
	if (mThreadHandler != null){
		mThreadHandler.sendMessage(mThreadHandler.obtainMessage());
	}    
}

public void stopLooper() {
	if (mThreadHandler != null){
		mThreadHandler.getLooper().quit();
	}    
}

如果只执行sendMsg()后,log打印如下:

*** loop() - UP
handle Message

从打印可以看到,loop()后的log没有打印,因为现在Looper处于一直循环处理消息的状态,这就意味着这个Looper一直处于一个阻塞状态。

所以在使用完子线程退出的时候,还需要将Looper循环停掉,进行资源释放。Looper退出的时候,有两个函数:quit()quitsafely()可调用,具体区别:(细节解析可以看这篇文章:Android Handler机制Looper的quit和quitSafely区别(3))

  • Looper.quit():调用后直接终止Looper,不在处理任何Message,所有尝试把Message放进消息队列的操作都会失败,比如Handler.sendMessage()会返回 false,但是存在不安全性,因为有可能有Message还在消息队列中没来的及处理就终止Looper了。

  • Looper.quitsafely():调用后会在所有消息都处理后再终止Looper,所有尝试把Message放进消息队列的操作也都会失败。

在回到上面的代码,执行完sendMsg()后,再执行stopLooper()函数,log打印如下:

*** loop() - UP
handle Message
handle Message
*** loop() - DOWN

总结下:如果在子线程中创建了一个Handler,那么就必须做三个操作: prepare()、 loop()、quit()。

参考文章子线程中:new Handler需要做哪些准备?消息队列中无消息的时候,Looper的处理方案是什么?

你可能感兴趣的:(Android开发问题,android)