zoukankan      html  css  js  c++  java
  • [转载]Handler:更新UI的方法

    总是感觉 android 中 UI 更新很让人纠结!自己小结一下,算是抛砖引玉。读这篇文章之前,假设你已经明白线程、Handler 的使用。

     

    在文章的最后,附录一张草图,主要用于说明 Handler、Message、MessageQueue、Looper 之间的关系。

     

    1. 在 onCreate() 方法中开启线程更新 UI

    1. public class MasterActivity extends Activity {  
    2.     TextView tv = null;  
    3.     Button btn = null;  
    4.       
    5.     @Override  
    6.     public void onCreate(Bundle savedInstanceState) {  
    7.         super.onCreate(savedInstanceState);  
    8.         setContentView(R.layout.main);  
    9.         System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId());  
    10.         tv = (TextView)findViewById(R.id.text);  
    11.         btn = (Button)findViewById(R.id.btn);  
    12.           
    13.         /*onCreate中开启新线程,更新UI。没有报错或者异常信息!*/  
    14.        Thread thread = new Thread(new Runnable() {  
    15.               
    16.             @Override  
    17.             public void run() {  
    18.                 System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId());  
    19.                 tv.setText("update UI is success!");  
    20.                 btn.setText("update UI is success!");  
    21.             }});  
    22.         thread.start();  
    23.     }  

    随便折腾,不会报错或者异常!以为开启的线程和 UI 线程(主线程)是同一个线程,但是很不幸,他们的线程id根本是风牛马不相及!

    不知道为什么在这里开启子线程更新UI就没有问题!真的想不明白????

     

    2. 在 activity 如 onResume、onStart、反正是以 on 开头的回调方法

    1. @Override  
    2.     protected void onRestart() {  
    3.         super.onRestart();  
    4.          /*onRestart中开启新线程,更新UI*/  
    5.         Thread thread = new Thread(new Runnable() {  
    6.               
    7.             @Override  
    8.             public void run() {  
    9.                 System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId());  
    10.                 tv.setText("update UI is success!");  
    11.                 btn.setText("update UI is success!");  
    12.             }});  
    13.         thread.start();  
    14.     }  

      

    不好意思,按下返回按钮在启动程序,或者按 Home 键再启动程序,就这么折腾几下,就会包异常!信息如下:

    android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

     

    意思是:只有主线程才可以更新 UI。

    解决办法:加上 postInvalidate() 方法。

    1. @Override  
    2.     protected void onRestart() {  
    3.         super.onRestart();  
    4.          /*onRestart中开启新线程,更新UI*/  
    5.         Thread thread = new Thread(new Runnable() {  
    6.               
    7.             @Override  
    8.             public void run() {  
    9.                 System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId());  
    10.                 tv.postInvalidate();  
    11.                 btn.postInvalidate();  
    12.                 tv.setText("update UI is success!");  
    13.                 btn.setText("update UI is success!");  
    14.             }});  
    15.         thread.start();  
    16.     }  

    postInvalidate() 方法,源码:

    1. public void postInvalidate() {  
    2.         postInvalidateDelayed(0);  
    3.     }  
    4. public void postInvalidateDelayed(long delayMilliseconds) {  
    5.         // We try only with the AttachInfo because there's no point in invalidating  
    6.         // if we are not attached to our window  
    7.         if (mAttachInfo != null) {  
    8.             Message msg = Message.obtain();  
    9.             msg.what = AttachInfo.INVALIDATE_MSG;  
    10.             msg.obj = this;  
    11.             mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);  
    12.         }  
    13.     }  

    其实,是调用了 Handler 的处理消息的机制!该方法可以在子线程中直接用来更新UI。还有一个方法 invalidate (),稍候再说!

     

     3.  在 Button 的事件中开启线程,更新 UI

    1. public class MasterActivity extends Activity {  
    2.     TextView tv = null;  
    3.     Button btn = null;  
    4.       
    5.     @Override  
    6.     public void onCreate(Bundle savedInstanceState) {  
    7.         super.onCreate(savedInstanceState);  
    8.         setContentView(R.layout.main);  
    9.         System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId());  
    10.         tv = (TextView)findViewById(R.id.text);  
    11.         btn = (Button)findViewById(R.id.btn);  
    12.         btn.setOnClickListener(new OnClickListener() {  
    13.               
    14.             @Override  
    15.             public void onClick(View v) {  
    16.                 Thread thread = new Thread(new Runnable() {  
    17.                       
    18.                     @Override  
    19.                     public void run() {  
    20.                         System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId());  
    21.                         tv.setText("update UI is success!");  
    22.                         btn.setText("update UI is success!");  
    23.                     }});  
    24.                 thread.start();  
    25.             }  
    26.         });  
    27. }  

     Sorry,报错!即使你加上 postInvalidate() 方法,也会报这个错误。

     android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

     

     

    4. 使用 Handler 结合多线程更新 UI
     
    a. 开启一个线程,在 run 方法中通知 Handler
     
    b. Handler 中使用 handleMessage 方法更新 UI。
     
    1. public class MasterActivity extends Activity {  
    2.     TextView tv = null;  
    3.     Button btn = null;  
    4.       
    5.     Handler mHandler = new Handler() {  
    6.         @Override  
    7.         public void handleMessage(Message msg) {  
    8.             if(msg.what == 1) {  
    9.                 tv.setText("update UI is success!");  
    10.                 btn.setText("update UI is success!");  
    11.             }  
    12.             super.handleMessage(msg);  
    13.         }  
    14.     };  
    15.       
    16.     @Override  
    17.     public void onCreate(Bundle savedInstanceState) {  
    18.         super.onCreate(savedInstanceState);  
    19.         setContentView(R.layout.main);  
    20.         System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId());  
    21.         tv = (TextView)findViewById(R.id.text);  
    22.         btn = (Button)findViewById(R.id.btn);  
    23.         btn.setOnClickListener(new OnClickListener() {  
    24.               
    25.             @Override  
    26.             public void onClick(View v) {  
    27.                 Thread thread = new Thread(new Runnable() {  
    28.                       
    29.                     @Override  
    30.                     public void run() {  
    31.                         System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId());  
    32.                         Message msg = mHandler.obtainMessage();  
    33.                         msg.what = 1;  
    34.                         msg.sendToTarget();  
    35.                     }});  
    36.                 thread.start();  
    37.             }  
    38.         });  
    39. }  
     
    5. Handler 和 invalidate 方法结合多线程更新 UI
     
    方法 invalidate 主要用在主线程中(即UI 线程中),不可以用于子线程如果在子线程中需要使用 postInvalidate 方法。
    sdk 的 api 有说明:
    1. public void invalidate ()  
    2. Since: API Level 1  
    3. Invalidate the whole view. If the view is visible, onDraw(Canvas) will be called at some point in the future. This must be called from a UI thread. To call from a non-UI thread, call postInvalidate().  
     
    看看该方法源码:
    1. public void invalidate() {  
    2.         if (ViewDebug.TRACE_HIERARCHY) {  
    3.             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);  
    4.         }  
    5.         if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {  
    6.             mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;  
    7.             final ViewParent p = mParent;  
    8.             final AttachInfo ai = mAttachInfo;  
    9.             if (p != null && ai != null) {  
    10.                 final Rect r = ai.mTmpInvalRect;  
    11.                 r.set(00, mRight - mLeft, mBottom - mTop);  
    12.                 // Don't call invalidate -- we don't want to internally scroll  
    13.                 // our own bounds  
    14.                 p.invalidateChild(this, r);  
    15.             }  
    16.         }  
    17.     }  
     
    invalidate 方法如果你直接在主线程中调用,是看不到任何更新的。需要与Handler结合!
    感谢这位“雷锋”,一个不错的例子:http://disanji.net/2010/12/12/android-invalidate-ondraw/
     
    只是被我修改了一点,加入times,看看 onDraw 到底运行多少次。

    Android 在 onDraw 事件处理绘图,而 invalidate() 函数可以再一次触发 onDraw 事件,然后再一次进行绘图动作。

    1. public class MasterActivity extends Activity {  
    2.     static int times = 1;  
    3.    
    4.     /** Called when the activity is first created. */  
    5.     @Override  
    6.     public void onCreate(Bundle savedInstanceState) {  
    7.         super.onCreate(savedInstanceState);  
    8.    
    9.         setContentView( new View(null){  
    10.    
    11.             Paint vPaint = new Paint();  //绘制样式物件  
    12.             private int i = 0;           //弧形角度  
    13.    
    14.             @Override  
    15.             protected void onDraw (Canvas canvas) {  
    16.                 super.onDraw(canvas);  
    17.                 System.out.println("this run " + (times++) +" times!");  
    18.    
    19.                 // 设定绘图样式  
    20.                 vPaint.setColor( 0xff00ffff ); //画笔颜色  
    21.                 vPaint.setAntiAlias( true );   //反锯齿  
    22.                 vPaint.setStyle( Paint.Style.STROKE );  
    23.    
    24.                 // 绘制一个弧形  
    25.                 canvas.drawArc(new RectF(60120260320), 0, i, true, vPaint );  
    26.    
    27.                 // 弧形角度  
    28.                 if( (i+=10) > 360 )  
    29.                     i = 0;  
    30.    
    31.                 // 重绘, 再一次执行onDraw 程序  
    32.                 invalidate();  
    33.             }  
    34.         });  
    35.     }  
    36. }  
     

    经过测试,发现 times 一直在++,说明 onDraw 被多次调用,并且一致在画图!

     

    SDK 的 API 有时候让人很郁闷,无语.....关于 invalidate 的使用,还待探索。革命尚未成功,同志仍需努力!

     

     

    博客更新,推荐文章:

    View编程(2): invalidate()再探

    View编程(3): invalidate()源码分析

    附录: Handler、Message、MessageQueue、Looper 之间的关系

    这里说明:

    1. Looper 使用无限循环取出消息,是有 android os 控制的。

    2. android 线程是非安全的,即不要在子线程中更新 UI。

    3. Looper 取出来的消息,handler 可以通过 what、obj 等量来区别分别获取属于自己的消息,所以推荐使用这些量。

  • 相关阅读:
    nginx 添加response响应头
    2018年 js 简易控制滚动条滚动的简单方法
    handsontable 常用 配置项 笔记
    使用react-handsontable
    node 常用模块及方法fs,url,http,path
    POST application/json 适用于传递多层的json
    react 子元素修改父元素值的一个偏方,虽然简单,但是不建议用,
    mysql 连接出错 'mysqladmin flush-hosts'
    solr7.3.1定时增量索引
    mysql8修改密码加密方式
  • 原文地址:https://www.cnblogs.com/yuxiang204/p/2687755.html
Copyright © 2011-2022 走看看