通过一个独立线程下载图片,主线程中更新UI,在主线程中更新imageView显示的例子,解析Handler的原理和机制。
功能需求
从网络上下载一张图片,将其显示到ImageView上。
设计思路
考虑到需要从网络下载图片,下载操作较耗时,不能放在主线程操作,因此设计一个线程去执行下载动作,下载完成后再更新图片。代码编写如下:
public class TestImgActivity extends Activity {
protected static final String TAG = "TestActivity";
protected static final int MSG_LOAD_IMAGE = 1;
private String URL ="http://img5.imgtn.bdimg.com/it/u=854852651,3145367340&fm=23&gp=0.jpg";
private Button mTestBtn;
private ImageView mImgView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_img);
mImgView = (ImageView) findViewById(R.id.test_img);
mTestBtn = (Button) findViewById(R.id.test_btn);
mTestBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new LoadImageRunnable(mHandler, MSG_LOAD_IMAGE, URL)).start();
}
});
}
public class LoadImageRunnable implements Runnable {
private int mThreadId ;
private Handler mHandler ;
private String sUrl;
public LoadImageRunnable(Handler h, int id, String str) {
mHandler = h;
mThreadId = id;
sUrl = str;
}
@Override
public void run() {
Log.i(TAG, "LoadImageRunnable-----"+Thread.currentThread().getName());
Bitmap bmp = loadImageFromNetwork();
refreshImageView(bmp);
}
/**更新imageview图片显示*/
private void refreshImageView(Bitmap bmp) {
if (bmp!=null) {
mImgView.setImageBitmap(bmp);
}
}
/**从外部链接加载图片*/
private Bitmap loadImageFromNetwork() {
Bitmap bmp = null;
try {
URL url = new URL(sUrl);
URLConnection conn = url.openConnection();
InputStream is = conn.getInputStream();
bmp = BitmapFactory.decodeStream(is);
} catch (MalformedURLException e){
e.printStackTrace();
mHandler.sendEmptyMessage(10+mThreadId);
} catch(IOException e) {
mHandler.sendEmptyMessage(10+mThreadId);
e.printStackTrace();
}
return bmp;
}
}
}
实际跑一下,crash发生了。log如下:
查看异常代码堆栈,发现是CalledFromWrongThreadException,即只有在UI主线程才能操作View控件!
想起Handler来了(Handler可以更新UI),修改代码,下载完成后,向Handler发送一条消息,在handler的消息处理方法中进行ImageView更新。
/**更新imageview图片显示*/
private void refreshImageView(Bitmap bmp) {
mHandler.obtainMessage(mThreadId, bmp).sendToTarget();
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
switch (msg.what) {
case MSG_LOAD_IMAGE:
if (msg.obj instanceof Bitmap) {
Bitmap bmp = (Bitmap) msg.obj;
mImgView.setImageBitmap(bmp);
Log.i(TAG, "refresh image bitmap.");
}
break;
default:
break;
}
}
};
再运行一遍,还真是可以正常更新图片了。日志如下:
概述
Handler可以用在UI主线程接收工作线程传递过来的消息,执行更新UI操作。其示意图如下:
由此例子,引发以下问题:
1. Handler的作用是什么
Handler在主线程上维护一个消息队列,并可以接收其他线程发送过来的消息,在主线程上依次处理消息(通常是更新UI)。一般情况下,在主线程中创建Handler,并在某事件触发时创建新的线程用于完成耗时的操作,当子线程中的工作完成之后,向Handler发送一个消息,在Handler中处理该消息,执行更新UI的操作。当然,Handler提供的是一种线程间通信的方式,不仅限于更新UI。
2. Handler是否意味着主线程?
虽然通常使用Handler来更新UI,但Handler并不等同于主线程。
3. Handler.post(Runnable r)中r的执行是在哪个线程?
此方法中的参数r是一个Runnable类型,那么是否其作用是开启一个独立的线程来执行r呢?
并不是。
此方法的作用只是将一个操作封装到一个runnable对象中,其执行仍然是在Handler所属线程,而没有开辟一个独立的线程来执行。
4. 是否任意线程中都能使用Handler机制,有何注意事项?
实际上,可以在任意线程Thread上使用Handler(通过handler接收其他线程发送过来的消息)。当然,需要注意其使用规范:
Looper.prepare();
Handler mHandler = new Handler();
Looper.loop();
5. 一个线程中能否拥有多个Handler?
一个线程中可以创建多个handler,它们互相独立,但引用的是该线程上的同一个MessageQueue和Looper,消息队列中的各个消息根据其对应的时间顺序,依次被处理。
Looper.prepare();
Handler mHandler1 = new Handler();
Handler mHandler2 = new Handler();
Looper.loop();
详细分析
带着以上问题,通过阅读分析Handler及其相关类的源码来解析Handler的工作原理。
- 首先分析Handler及其相关的类图。
- 再看看Looper.prepare()的源码。
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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
从Looper类的static(全局)变量sThreadLocal中取出此线程对应的looper,若存在,说明此线程已经创建过looper,抛异常;否则,新建一个looper对象(同时创建一个MessageQueue,并将该线程的引用赋值给mThread),并将该looper对象设置进入sThreadLocal。
由此可以看出,一个线程只能对应一个looper,所有线程上创建的looper都记录在一个静态变量sThreadLocal中,可以简单将sThreadLocal看成是一张looper登记表。其类ThreadLocal在java.lang包里面,此类在android中是对之前的JDK中的版本改良过的。主要提供两个方法,get()和set(),对外透明,可以直接存储进入和从中取出两个操作。对于同一个线程的looper,取出的对象和存入的是同一个。
深入进去看源码,可以发现ThreadLocal类中定义了一个内部类Values,其中持有一个Object[] table,仔细分析代码,发现这个ThreadLocal类并没有持有Values实例,而是在线程Thread类中持有该对象。在ThreadLocal的get方法中,首先取得当前线程对象currentThread,然后从currentThread中取得其Values对象,再从Values中的table来查找当前线程的looper。
- 再来看看Handler的构造方法。
public Handler() {
this(null, false);
}
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(); //获取到自己的looper(当前线程所对应的looper)
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; //将looper的MessageQueue引用过来
mCallback = callback;
mAsynchronous = async;
}
Handler的成员mLooper 是直接引用的自己的looper (当前线程所对应的looper)。Looper类的myLooper方法如下:
public static Looper myLooper() {
return sThreadLocal.get();
}
最终还是从ThreadLocal类的静态变量sThreadLocal中获取looper。贴上ThreadLocal的set和get两个方法:
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
void put(ThreadLocal<?> key, Object value) {
cleanUp();
// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}
// Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
}
// Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}
ThreadLocal类中有一个静态变量hashCounter,每个ThreadLocal对象中有一个hash值,每次利用hashCounter创建一个hash值(来标记当前线程),hashCounter值自增0x61c88647的两倍(它是一个魔数,使得下一个hash值总是跟以前产生的不同!),每次对新的线程产生一个hash值,将ThreadLocal的弱引用当作key,而线程的Values值一起存入线程Values值的表table中。下次取的时候,依据ThreadLocal来获取Values值;
这种设计很巧妙,线程中有各自的table,而把ThreadLocal实例作为key,一方面,当线程销毁时相关的线程局部变量会被销毁另一方面,这种利用数组方式存取键值对的方法,与JDK中旧的HashMap方法相比性能更高。这种设计很巧妙,线程中有各自的map,而把ThreadLocal实例作为key,这样除了当线程销毁时相关的线程局部变量被销毁之外,还让性能提升了很多。
如此,一个线程中就可以创建多个handler对象,而这些handler对象都关联同一个Looper:
附上JDK的ThreadLocal类源码,有兴趣的可以对比看一下。
- 最后来看loop方法
顾名思义,loop()方法体里是一个无限循环,反复从消息队列MessageQueue中取出一条消息并处理,直到looper线程退出。实际执行处理消息动作的,是从消息msg的target成员执行dispathMessage,而target就是此msg所引用的Handler对象。
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;
// 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();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(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.recycle();
}
}
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
在dispatchMessage处理消息时,msg的callback回调存在时就执行callback操作,否则执行handler的mCallback的handleMessage操作。
再进入Handler的源码看,当对handler进行发送消息时,最终都会sendMessage或是postRunnable时,会对其msg的callback赋值:postRunnable就将r赋给callback,sendMessage就将callback置空。
以下面这句代码(向mHandler发送一条消息)为例:
mHandler.obtainMessage(mThreadId, bmp).sendToTarget();
public final Message obtainMessage(int what, Object obj)
{
return Message.obtain(this, what, obj);
}
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
//获取一条message
public static Message obtain(Handler h, int what, Object obj) {
Message m = obtain();
m.target = h;
m.what = what;
m.obj = obj;
return m;
}
//从消息池中取出一条消息,若消息池空则新建一条消息
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
sPoolSize--;
return m;
}
}
return new Message();
}
public void sendToTarget() {
target.sendMessage(this);
}
向Handler发送一条消息的过程为,从消息池MessageQueue中获取一条消息,将其target指向此handler,然后将此消息msg入列到消息池中(按照消息需要被执行的时间顺序)。
到此,Handler的消息池创建过程已经清晰了。当有新的消息被压入到消息池中后,Looper的处理处理循环会依次处理各条消息。
然则,回到最初的demo,为何可以在Activity中直接创建Handler而不必执行Looper.prepare()和Looper.loop()?这就要看Activity相关的源码了。
在Activity中有一个成员
ActivityThread mMainThread;
而ActivityThread的主函数main中,执行了Looper相关的准备和循环操作。
public static void main(String[] args) {
SamplingProfilerIntegration.start();
// 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();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
Security.addProvider(new AndroidKeyStoreProvider());
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
实际上,Android应用程序的各个组件(Activity、Service、BroadcastReceiver、ContentProvider)的生命周期都是在其APP主线程内,系统在创建APP进程时其java代码执行堆栈:
NaiveStart.main()
ZygoteInit.main
ZygoteInit$MethodAndArgsCall.run
Method.Invoke method.invokeNative
ActivityThread.main()
Looper.loop()
每个应用程序都以ActivityThread.main()为入口进入到消息循环处理。对于一个进程来讲,需要这个闭合的处理框架。 Android.app.ActivityThread进程建立后,将跳入到ActivityThread的main函数开始运行,进入消息循环。
- 在非UI线程中使用Handler有一个很好的例子,直接调用HandlerThread类即可。HandlerThread将线程Thread和Looper结合封装起来,使得在此类线程中可以直接创建Handler并使用。
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
- 我们将最初的需求改一下,把1个按钮变为3个按钮,点击分别下载不同的网络图片。
如下图:
设计思路:
在UI主线程和HandlerThread下载线程中分别创建Handler,点击按钮时,UI主线程向HandlerThread发送一条消息以启动下载,下载完成后HandlerThread向UI主线程发送一条消息以更新ImageView显示。
代码如下:
TestImgActivity .java
public class TestImgActivity extends Activity implements Callback {
protected static final String TAG = "TestActivity";
protected static final int MSG_LOAD_IMAGE = 1;
private String[] mURLs = new String[]{
"http://img5.duitang.com/uploads/item/201412/22/20141222204442_4CfEy.jpeg",//高圆圆
"http://imgsrc.baidu.com/forum/pic/item/e1d46c380cd79123d79756b4ad345982b3b7804b.jpg",//范冰冰
"http://np29.yule.com.cn/html/UploadPic/2009-6/200961514323957477.jpg"//林心如
};
private Button mTestBtn1;
private Button mTestBtn2;
private Button mTestBtn3;
private ImageView mImgView;
private Handler mHandler = new WeakReferenceHandler(this);
static final int MSG_LOAD_IMG_FINISH = 2;
LoadImageHandlerThread mHandlerThread;
private OnClickListener mOnClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.test_btn1:
mHandlerThread.LoadImage(mURLs[0]);
Log.i(TAG, "Download gaoyuanyuan!");
break;
case R.id.test_btn2:
mHandlerThread.LoadImage(mURLs[1]);
Log.i(TAG, "Download fanbingbing!");
break;
case R.id.test_btn3:
mHandlerThread.LoadImage(mURLs[2]);
Log.i(TAG, "Download linxinru!");
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_img);
mImgView = (ImageView) findViewById(R.id.test_img);
mTestBtn1 = (Button) findViewById(R.id.test_btn1);
mTestBtn1.setOnClickListener(mOnClickListener);
mTestBtn2 = (Button) findViewById(R.id.test_btn2);
mTestBtn2.setOnClickListener(mOnClickListener);
mTestBtn3 = (Button) findViewById(R.id.test_btn3);
mTestBtn3.setOnClickListener(mOnClickListener);
if (null == mHandlerThread) {
mHandlerThread = new LoadImageHandlerThread(TAG, mHandler);
mHandlerThread.start();
}
}
@Override
public boolean handleMessage(Message msg) {
// TODO Auto-generated method stub
switch (msg.what) {
case MSG_LOAD_IMG_FINISH:
if (msg.obj instanceof Bitmap) {
Bitmap bmp = (Bitmap) msg.obj;
mImgView.setImageBitmap(bmp);
Log.i(TAG, "display star!");
}
break;
default:
break;
}
return false;
}
}
LoadImageHandlerThread.java
public class LoadImageHandlerThread extends HandlerThread implements Callback {
private WeakReference<Handler> mMainUiHandlerRef;
private Handler mInnerHandler;
static final int MSG_LOAD_IMG_START = 1;
private static final String TAG = "LoadImageHandlerThread";
public LoadImageHandlerThread(String name, Handler mainUiHandler) {
super(name);
mMainUiHandlerRef = new WeakReference<Handler>(mainUiHandler);
}
@Override
protected void onLooperPrepared() {
// TODO Auto-generated method stub
mInnerHandler = new WeakReferenceHandler(this);
super.onLooperPrepared();
}
public void LoadImage(String url) {
mInnerHandler.obtainMessage(MSG_LOAD_IMG_START, url).sendToTarget();
}
/**请求主线程更新imageview图片显示*/
private void refreshImageView(Bitmap bmp) {
Handler extHandler = mMainUiHandlerRef.get();
if (extHandler!=null) {
extHandler.obtainMessage(TestImgActivity.MSG_LOAD_IMG_FINISH, bmp).sendToTarget();
}
}
/**从外部链接加载图片*/
private Bitmap loadImageFromNetwork(String url_str) {
Bitmap bmp = null;
try {
URL url = new java.net.URL(url_str);
URLConnection conn = url.openConnection();
InputStream is = conn.getInputStream();
bmp = BitmapFactory.decodeStream(is);
} catch (MalformedURLException e){
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
}
return bmp;
}
@Override
public boolean handleMessage(Message msg) {
// TODO Auto-generated method stub
switch (msg.what) {
case MSG_LOAD_IMG_START:
if (msg.obj instanceof String) {
String url = (String) msg.obj;
Log.i(TAG, "Download star: "+url);
Bitmap bmp = loadImageFromNetwork(url);
if (bmp!=null) {
refreshImageView(bmp);
}
}
break;
default:
break;
}
return true;
}
}
运行后,功能正常,log如下:
总结
Handler提供了一种安全的线程间通信的机制,广泛应用于UI主线程与工作线程之间的配合。UI主线程的handler消息处理是在主线程中被调用的,要注意不能有耗时的操作,以避免ANR;通常将耗时的操作放在非主线程中执行,完成后通过handler通知主线程更新UI。