Handler在Android开发中的使用频率很高,在子线程中使用它可以实现更新UI的功能。为了更好的理解Handler机制,本文将对Handler进行一些深入的探讨,探讨主要针对几个问题来展开。至于Handler的基础原理和使用方法,请自行Google。
为什么Android要求子线程只能使用Handler更新UI?
如果不使用Handler的方式更新UI,而是子线程直接修改UI,就会面临线程安全的问题。线程安全问题的解决方法,就是在进行操作的时候加锁。这么做有两个明显的弊端,一个是代码实现复杂并容易出错,另外就是加锁导致程序的运行效率会下降。因此,Android对UI的更新限制不能在子线程更新,而是发送到主线程,由主线程统一处理。
子线程更新UI的方式有哪些,它们有什么联系?
常用的子线程更新UI的方式主要有以下几种:
Handler.post(Runnable)
Handler.sendMessage()
View.post(Runnable)
AsyncTask
Activity.runOnUiThread()
这几种方式看起来差别很大,但实际上它们的原理都是相同的,最终都是通过Handler的sendMessage方法来发送消息,然后在UI线程中处理Handler的回调。只是Google帮我们对Handler机制进行了不同程度的封装以适应不同的应用场景。
为了说明这个问题,我们先看一下View.post()方法。
public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null ) { return attachInfo.mHandler.post(action); } ViewRootImpl.getRunQueue().post(action); return true ; }
方法中调用了attachInfo.mHandler.post(action)去发送消息,其实就是调用了Handler的post()方法,继续看这个方法:
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0 ); }
post方法里使用的就是sendMessge()的方式发送消息。
再来看一下runOnUiThread()方法,
public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { action.run(); } }
首先判断是否是主线程,如果是就直接执行代码逻辑,如果不是,依然是调用了Handler 的post()方法。从这里可以看出,所有可以实现子线程更新UI的方法的原理都是一样的。
主线程怎么使用Handler给子线程发送消息?
我们知道Handler是需要与一个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(); } AsyncTask.init(); if ( false ) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread" )); } Looper.loop(); throw new RuntimeException( "Main thread loop unexpectedly exited" ); }
代码显示,在ActivityThread的main()方法中创建了Looper对象并启动了Looper循环,主线程中创建的Handler默认都是与这个Looper绑定的。
由于子线程默认是不会创建Looper对象的,如果将Handler与子线程绑定,就要在绑定前先调用Looper.prepare()和Looper.loop()方法启动Looper循环,然后才能通过Handler向其所关联的MessageQueue中发送消息,否则就会报如下异常:Can't create handler inside thread that has not calledLooper.prepare()。下面展示一段典型的实现代码。
public class MainActivity extends Activity { private LooperThread looperThread; @Override public void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.main_layout); looperThread = new LooperThread(); looperThread.start(); looperThread.getHandler().sendEmptyMessage(1 ); } class LooperThread extends Thread { private Handler mHandler; private final Object mLock = new Object(); public void run() { Looper.prepare(); synchronized (mLock) { mHandler = new Handler(){ @Override public void handleMessage(Message msg) { ..... } }; mLock.notifyAll(); } Looper.loop(); } public Handler getHandler() { synchronized (mLock) { if (mHandler == null ) { try { mLock.wait(); } catch (InterruptedException e) { } } return mHandler; } } public void exit() { getHandler().post(new Runnable(){ public void run() { Looper.myLooper().quit(); }}); } } }
不使用Handler真的不能更新UI吗?
首先来看下面一段代码
new Thread( new Runnable() { @Override public void run() { btn.setText("aaaaaaaaa" ); } }).start(); new Thread( new Runnable() { @Override public void run() { try { Thread.sleep(5000 ); } catch (InterruptedException e) { e.printStackTrace(); } btn.setText("cccccccc" ); } }).start();
这段代码在Activity的onCreate回调中执行,btn是我们定义的一个Button对象。从执行结果看,第一段代码顺利执行且UI正确更新,却在执行第二段代码的时候发生了崩溃,这是为什么呢?
来看一下崩溃栈信息
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6063 ) at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:885 ) at android.view.ViewGroup.invalidateChild(ViewGroup.java:4320 ) at android.view.View.invalidate(View.java:10946 ) at android.view.View.invalidate(View.java:10901 ) at android.widget.TextView.checkForRelayout(TextView.java:6594 ) at android.widget.TextView.setText(TextView.java:3820 ) at android.widget.TextView.setText(TextView.java:3678 ) at android.widget.TextView.setText(TextView.java:3653 ) at com.enjoy.vicleedemo.service.ServiceUpdateActivity$3 .run(ServiceUpdateActivity.java: 121 ) at java.lang.Thread.run(Thread.java:841 )
我想每个Android开发者都对这个异常不会陌生。根据堆栈信息,更新UI时会调用View的invalidate()方法,继而调用ViewRootImpl的invalidateChildInParent()方法,其中调用了checkThread(),其中会检查当前是否为主线程,否则抛出上面的那个异常。
public ViewParent invalidateChildInParent( int [] location, Rect dirty) { checkThread(); if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty); if (dirty == null ) { invalidate(); return null ; } else if (dirty.isEmpty() && !mIsAnimating) { return null ; }......}
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views." ); } }
也就是说是在ViewRootImpl这个类中进行了线程的检查。只是在onCreate()中更新UI的时候,
ViewRootImpl的对象还没有创建,因此不会进行这个检查,也就出现了上面例子中的现象。那么它是在什么时候创建的呢?ActivityThread类会调用handleResumeActivity方法将顶层视图DecorView添加到PhoneWindow中,我们就从handleResumeActivity开始看起。
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { .................. if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; if (a.mVisibleFromClient) { a.mWindowAdded = true ; wm.addView(decor, l); .....................
代码中,ViewManager wm = a.getWindowManager()这个得到的是一个WindowManagerImpl类的对象,然后调用了WindowManagerImpl类的addView方法。查看addView方法,又调用了mGlobal的addView方法,mGlobal是WindowManagerGlobal类的对象。
@Override public void addView(View view, ViewGroup.LayoutParams params) { mGlobal.addView(view, params, mDisplay, mParentWindow); }
然后看
WindowManagerGlobal类的addView方法:
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ............ ViewRootImpl root; ............ root = new ViewRootImpl(view.getContext(), display); ........... try { root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { ........... } }
终于看到了ViewRootImpl,它正式在这里创建的。也就是说,ViewRootImpl是在onResume执行之后创建的。这也就解释了为什么直接在Activity的onCreate中在子线程更新UI不会报错,而在延时一段时间后却出现崩溃
转自:http://blog.csdn.net/goodlixueyong/article/details/50831958