Android Handler

来源:互联网 发布:淘宝退货卖家没收到货 编辑:程序博客网 时间:2024/06/07 22:01

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()方法。

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public boolean post(Runnable action) {  
  2.     final AttachInfo attachInfo = mAttachInfo;  
  3.     if (attachInfo != null) {  
  4.         return attachInfo.mHandler.post(action);  
  5.     }  
  6.     // Assume that post will succeed later  
  7.     ViewRootImpl.getRunQueue().post(action);  
  8.     return true;  
  9. }  
       方法中调用了attachInfo.mHandler.post(action)去发送消息,其实就是调用了Handler的post()方法,继续看这个方法:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final boolean post(Runnable r)  
  2. {  
  3.    return  sendMessageDelayed(getPostMessage(r), 0);  
  4. }  
       post方法里使用的就是sendMessge()的方式发送消息。

       再来看一下runOnUiThread()方法,

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final void runOnUiThread(Runnable action) {  
  2.     if (Thread.currentThread() != mUiThread) {  
  3.         mHandler.post(action);  
  4.     } else {  
  5.         action.run();  
  6.     }  
  7. }  
       首先判断是否是主线程,如果是就直接执行代码逻辑,如果不是,依然是调用了Handler 的post()方法。从这里可以看出,所有可以实现子线程更新UI的方法的原理都是一样的。

主线程怎么使用Handler给子线程发送消息?

       我们知道Handler是需要与一个Looper绑定的,主线程是默认启动了Looper的。Android程序的执行入口是在ActivityThread类的main方法中,我们从这个方法入手来查找。

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public static void main(String[] args) {  
  2.     ......  
  3.   
  4.     Looper.prepareMainLooper();  
  5.   
  6.     ActivityThread thread = new ActivityThread();  
  7.     thread.attach(false);  
  8.   
  9.     if (sMainThreadHandler == null) {  
  10.         sMainThreadHandler = thread.getHandler();  
  11.     }  
  12.   
  13.     AsyncTask.init();  
  14.   
  15.     if (false) {  
  16.         Looper.myLooper().setMessageLogging(new  
  17.                 LogPrinter(Log.DEBUG, "ActivityThread"));  
  18.     }  
  19.   
  20.     Looper.loop();  
  21.   
  22.     throw new RuntimeException("Main thread loop unexpectedly exited");  
  23. }  
       代码显示,在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()。下面展示一段典型的实现代码。

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class MainActivity extends Activity {  
  2.     private LooperThread  looperThread;  
  3.   
  4.     @Override  
  5.     public void onCreate(Bundle savedInstanceState) {  
  6.         super.onCreate(savedInstanceState);  
  7.         setContentView(R.layout.main_layout);  
  8.         looperThread = new LooperThread();  
  9.         looperThread.start();  
  10.     looperThread.getHandler().sendEmptyMessage(1);  
  11.     }  
  12.       
  13.       
  14.     class LooperThread extends Thread {  
  15.         private Handler mHandler;  
  16.         private final Object mLock = new Object();  
  17.   
  18.         public void run() {  
  19.             Looper.prepare();  
  20.             synchronized (mLock) {  
  21.                 mHandler = new Handler(){  
  22.                     @Override  
  23.                     public void handleMessage(Message msg) {  
  24.                         .....  
  25.                     }  
  26.                 };  
  27.                 mLock.notifyAll();  
  28.             }  
  29.             Looper.loop();  
  30.         }  
  31.           
  32.         public Handler getHandler() {  
  33.             synchronized (mLock) {  
  34.                 if (mHandler == null) {  
  35.                     try {  
  36.                         mLock.wait();  
  37.                     } catch (InterruptedException e) {  
  38.                     }  
  39.                 }  
  40.                 return mHandler;  
  41.             }  
  42.         }  
  43.         public void exit() {  
  44.             getHandler().post(new Runnable(){  
  45.                 public void run() {  
  46.                     Looper.myLooper().quit();  
  47.                 }});  
  48.         }  
  49.     }  
  50. }  

不使用Handler真的不能更新UI吗?

       首先来看下面一段代码

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. new Thread(new Runnable() {  
  2.      @Override  
  3.      public void run() {  
  4.          btn.setText("aaaaaaaaa");  
  5.      }  
  6.  }).start();  
  7.   
  8.  new Thread(new Runnable() {  
  9.      @Override  
  10.      public void run() {  
  11.          try {  
  12.              Thread.sleep(5000);  
  13.          } catch (InterruptedException e) {  
  14.              e.printStackTrace();  
  15.          }  
  16.          btn.setText("cccccccc");  
  17.      }  
  18.  }).start();  

       这段代码在Activity的onCreate回调中执行,btn是我们定义的一个Button对象。从执行结果看,第一段代码顺利执行且UI正确更新,却在执行第二段代码的时候发生了崩溃,这是为什么呢?

       来看一下崩溃栈信息

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.  
  2. at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6063)  
  3. at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:885)  
  4. at android.view.ViewGroup.invalidateChild(ViewGroup.java:4320)  
  5. at android.view.View.invalidate(View.java:10946)  
  6. at android.view.View.invalidate(View.java:10901)  
  7. at android.widget.TextView.checkForRelayout(TextView.java:6594)  
  8. at android.widget.TextView.setText(TextView.java:3820)  
  9. at android.widget.TextView.setText(TextView.java:3678)  
  10. at android.widget.TextView.setText(TextView.java:3653)  
  11. at com.enjoy.vicleedemo.service.ServiceUpdateActivity$3.run(ServiceUpdateActivity.java:121)  
  12. at java.lang.Thread.run(Thread.java:841)  
       我想每个Android开发者都对这个异常不会陌生。根据堆栈信息,更新UI时会调用View的invalidate()方法,继而调用ViewRootImpl的invalidateChildInParent()方法,其中调用了checkThread(),其中会检查当前是否为主线程,否则抛出上面的那个异常。

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public ViewParent invalidateChildInParent(int[] location, Rect dirty) {  
  2.         checkThread();  
  3.         if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);  
  4.   
  5.         if (dirty == null) {  
  6.             invalidate();  
  7.             return null;  
  8.         } else if (dirty.isEmpty() && !mIsAnimating) {  
  9.             return null;  
  10.         }......}  
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. void checkThread() {  
  2.     if (mThread != Thread.currentThread()) {  
  3.         throw new CalledFromWrongThreadException(  
  4.                 "Only the original thread that created a view hierarchy can touch its views.");  
  5.     }  
  6. }  
也就是说是在ViewRootImpl这个类中进行了线程的检查。只是在onCreate()中更新UI的时候,ViewRootImpl的对象还没有创建,因此不会进行这个检查,也就出现了上面例子中的现象。那么它是在什么时候创建的呢?ActivityThread类会调用handleResumeActivity方法将顶层视图DecorView添加到PhoneWindow中,我们就从handleResumeActivity开始看起。

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) {  
  2.             ..................  
  3.             if (r.window == null && !a.mFinished && willBeVisible) {  
  4.                 r.window = r.activity.getWindow();  
  5.                 View decor = r.window.getDecorView();  
  6.                 decor.setVisibility(View.INVISIBLE);  
  7.                 //得当当前Activity的WindowManagerImpl对象  
  8.                 ViewManager wm = a.getWindowManager();  
  9.                 WindowManager.LayoutParams l = r.window.getAttributes();  
  10.                 a.mDecor = decor;  
  11.                 l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;  
  12.                 l.softInputMode |= forwardBit;  
  13.                 if (a.mVisibleFromClient) {  
  14.                     a.mWindowAdded = true;  
  15.                     wm.addView(decor, l);  
  16.   
  17.             .....................  
代码中,ViewManager wm = a.getWindowManager()这个得到的是一个WindowManagerImpl类的对象,然后调用了WindowManagerImpl类的addView方法。查看addView方法,又调用了mGlobal的addView方法,mGlobal是WindowManagerGlobal类的对象。
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. public void addView(View view, ViewGroup.LayoutParams params) {  
  3.     mGlobal.addView(view, params, mDisplay, mParentWindow);  
  4. }  
然后看WindowManagerGlobal类的addView方法:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public void addView(View view, ViewGroup.LayoutParams params,  
  2.             Display display, Window parentWindow) {  
  3.         ............  
  4.         ViewRootImpl root;  
  5.         ............  
  6.          root = new ViewRootImpl(view.getContext(), display);  
  7.         ...........  
  8.         // do this last because it fires off messages to start doing things  
  9.         try {  
  10.             root.setView(view, wparams, panelParentView);  
  11.         } catch (RuntimeException e) {  
  12.           ...........  
  13.         }  
  14.     }  

       终于看到了ViewRootImpl,它正式在这里创建的。也就是说,ViewRootImpl是在onResume执行之后创建的。这也就解释了为什么直接在Activity的onCreate中在子线程更新UI不会报错,而在延时一段时间后却出现崩溃


转自:http://blog.csdn.net/goodlixueyong/article/details/50831958

0 0
原创粉丝点击