- Handler的作用?
- 为什么Android设计只能UI线程更新UI?
- Handler相关的异常?
- Handler、Looper、MessageQueue之间的关系?
- HandlerThread分析?
- 主线程向子线程发消息?
- 在子线程中更新UI?
例一
package com.imeiren.handlertest;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Button button = new Button(this);
button.setText("1");
new Thread(new Runnable() {
@Override
public void run() {
button.setText("2");
}
}).start();
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
button.setText("3");
}
}).start();
}
});
setContentView(button);
}
}
在子线程中更新UI问题。第一个线程设置内容为“2”,按钮点击之后显示内容为“3”。但是我们点击后会直接出现FC(Force Closed)。
11-10 19:31:46.186: E/AndroidRuntime(32488): FATAL EXCEPTION: Thread-54412
11-10 19:31:46.186: E/AndroidRuntime(32488): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
11-10 19:31:46.186: E/AndroidRuntime(32488): at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:5945)
11-10 19:31:46.186: E/AndroidRuntime(32488): at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:878)
11-10 19:31:46.186: E/AndroidRuntime(32488): at android.view.ViewGroup.invalidateChild(ViewGroup.java:4264)
11-10 19:31:46.186: E/AndroidRuntime(32488): at android.view.View.invalidate(View.java:10545)
11-10 19:31:46.186: E/AndroidRuntime(32488): at android.view.View.invalidate(View.java:10500)
11-10 19:31:46.186: E/AndroidRuntime(32488): at android.widget.TextView.checkForRelayout(TextView.java:6554)
11-10 19:31:46.186: E/AndroidRuntime(32488): at android.widget.TextView.setText(TextView.java:3800)
11-10 19:31:46.186: E/AndroidRuntime(32488): at android.widget.TextView.setText(TextView.java:3658)
11-10 19:31:46.186: E/AndroidRuntime(32488): at android.widget.TextView.setText(TextView.java:3633)
11-10 19:31:46.186: E/AndroidRuntime(32488): at com.imeiren.handlertest.MainActivity$2$1.run(MainActivity.java:60)
11-10 19:31:46.186: E/AndroidRuntime(32488): at java.lang.Thread.run(Thread.java:841)
由ViewRootImpl.checkThread方法抛出的异常。据初步了解,设置为“2”的线程执行的时候,ViewRootImpl类还没有实例化,故没有执行checkThread方法,所以开始的时候在子线程中是可以更新UI的。但是在后来需要刷新View的时候,ViewRootImpl会执行checkThread方法,故抛出异常。
例二
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new Button(this));
new Thread(new Runnable() {
@Override
public void run() {
new Handler();
}
}).start();
}
}
在子线程中创建一个Handler,这段代码会抛出异常。java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()。异常来源于下面这段源码:
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
检查成员变量mLooper为空,则抛异常。再跟进去:
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static Looper myLooper() {
return sThreadLocal.get();
}
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
/**
* Implements a thread-local storage, that is, a variable for which each thread
* has its own value. All threads share the same {@code ThreadLocal} object,
* but each sees a different value when accessing it, and changes made by one
* thread do not affect the other threads. The implementation supports
* {@code null} values.
*
* @see java.lang.Thread
* @author Bob Lee
*/
public class ThreadLocal<T> {
/* Thanks to Josh Bloch and Doug Lea for code reviews and impl advice. */
/**
* Creates a new thread-local variable.
*/
public ThreadLocal() {}
使用ThreadLocal来维护一份线程本地变量。get方法返回是null。代码注释中出现了几位大神的名字 Josh Bloch和 Doug Lea.这些都是推动java技术向前发展的历史性人物!
如何让Handler的handleMessage方法在子线程中执行?我们参考Google同学写的HandlerThread来写一个简单的例子
package com.imeiren.handlertest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity {
class CustomThread extends Thread {
protected static final String TAG = "CustomThread";
Looper mLooper;
@Override
public void run() {
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
Log.d(TAG, "run() trying to notifyAll waiting object");
notifyAll();
}
Looper.loop();
}
/**
* This method returns the Looper associated with this thread. If this
* thread not been started or for any reason is isAlive() returns false,
* this method will return null. If this thread has been started, this
* method will block until the looper has been initialized.
*
* @return The looper.
*/
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been
// created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
Log.d(TAG, "getLooper() trying to wait");
wait();
Log.d(TAG, "getLooper() after wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return mLooper;
}
}
protected static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final CustomThread thread = new CustomThread();
thread.start();
final Handler handler = new Handler(thread.getLooper()) {
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "handle msg in " + Thread.currentThread());
}
};
Button button = new Button(this);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
handler.sendEmptyMessage(1);
}
});
setContentView(button);
}
}
每一个子线程只能有一个Looper,可以有多个Handler,多个Handler引用同一个Looper,向同一个MessageQueue中发送消息,由同一个Looper来取出消息并分发到各个Handler来处理。所以要向某一个子线程发消息,那就是向它的Looper发消息。在子线程中创建Handler的前提是必须有Looper。上面的代码中其实创建的是主线程中的Handler对象,引用的是子线程中的Looper,故发送的消息是到了子线程。