Handler和Toast

来源:互联网 发布:美国东密西根大学知乎 编辑:程序博客网 时间:2024/05/18 02:26

http://blog.csdn.net/androidbluetooth/article/details/6384641Handler:更新UI的方法

分类: Android 1797人阅读 评论(11)收藏 举报

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


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


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

[java] view plaincopyprint?
  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 开头的回调方法

[java] view plaincopyprint?
  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() 方法。

[java] view plaincopyprint?
  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() 方法,源码:

[java] view plaincopyprint?
  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

[java] view plaincopyprint?
  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。

[java] view plaincopyprint?
  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 有说明:
[java] view plaincopyprint?
  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().  
 
看看该方法源码:
[java] view plaincopyprint?
  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 事件,然后再一次进行绘图动作。

[java] view plaincopyprint?
  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 等量来区别分别获取属于自己的消息,所以推荐使用这些量。



http://blog.csdn.net/androidbluetooth/article/details/6840283

Handler: Service中使用Toast


关于 android 的线程模型,建议阅读 http://blog.csdn.net/androidbluetooth/article/details/6547166,这只是一个建议,你看不看这篇博客都不会影响阅读本篇博客。

Handler 的使用在 android App 开发中用的颇多,它的作用也很大,使用 Handler 一般也会使用到多线程,相信大家对 Handler 不会陌生,在这篇博客中,重点说一下 android 组件之一 Service 与 TaskTimer 结合 Handler 使用,共享之!

*****************************************


阅读这篇博客,需要你知道的知识:


<1> 知道在 Activity 中如何启动、停止 Service 以及 Service 的生命周期。


<2> 使用过 TimerTask 的Api,不过这个不难,如果之前没有接触过现在拿出几分钟学习一下吧!


<3> Handler 基本用法,推荐博客:http://blog.csdn.net/androidbluetooth/article/details/6384641


<4> Looper 基本用法,推荐下载:http://download.csdn.net/detail/AndroidBluetooth/3650576 好好看看,肯定对你有用!


好嘞,开始说这篇博客的内容。

界面很简单,就是两个Button,启动之后,效果如下:




Activity 代码:

[java] view plaincopyprint?
  1. package mark.zhang;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.Intent;  
  5. import android.os.Bundle;  
  6. import android.util.Log;  
  7. import android.view.View;  
  8. import android.view.View.OnClickListener;  
  9.   
  10. public class ServiceToastActivity extends Activity {  
  11.   
  12.     @Override  
  13.     public void onCreate(Bundle savedInstanceState) {  
  14.         super.onCreate(savedInstanceState);  
  15.         setContentView(R.layout.main);  
  16.           
  17.         Log.d("mark""activity: " + "\n" + "当前线程名称:"  
  18.                 + Thread.currentThread().getName() + "," + "当前线程名称:"  
  19.                 + Thread.currentThread().getId());  
  20.         // 启动服务  
  21.         findViewById(R.id.button_startservice).setOnClickListener(  
  22.                 new OnClickListener() {  
  23.   
  24.                     @Override  
  25.                     public void onClick(View v) {  
  26.                         Intent intent = new Intent(ServiceToastActivity.this,  
  27.                                 MyService.class);  
  28.                         startService(intent);  
  29.                     }  
  30.                 });  
  31.         // 停止服务  
  32.         findViewById(R.id.button_stopservice).setOnClickListener(  
  33.                 new OnClickListener() {  
  34.   
  35.                     @Override  
  36.                     public void onClick(View v) {  
  37.                         Intent intent = new Intent(ServiceToastActivity.this,  
  38.                                 MyService.class);  
  39.                         stopService(intent);  
  40.                     }  
  41.                 });  
  42.     }  
  43. }  

上述代码就是给两个 Button 设置监听器,启动和停止服务。

然后,在 Service 中 开启一个线程,并在该线程中 Toast 一下!代码如下:

[java] view plaincopyprint?
  1. package mark.zhang;  
  2.   
  3. import java.util.Timer;  
  4. import java.util.TimerTask;  
  5.   
  6. import android.app.Service;  
  7. import android.content.Intent;  
  8. import android.os.Handler;  
  9. import android.os.IBinder;  
  10. import android.os.Looper;  
  11. import android.util.Log;  
  12. import android.widget.Toast;  
  13.   
  14. public class MyService extends Service {  
  15.   
  16.     private Handler handler = null;  
  17.     private Timer timer;  
  18.   
  19.       
  20.     @Override  
  21.     public IBinder onBind(Intent intent) {  
  22.         return null;  
  23.     }  
  24.   
  25.     @Override  
  26.     public void onCreate() {  
  27.         super.onCreate();  
  28.           
  29.         new Thread(new Runnable() {  
  30.             public void run() {  
  31.                 Log.d("mark""Service in Thread: " + "\n" + "当前线程名称:"  
  32.                         + Thread.currentThread().getName() + "," + "当前线程名称:"  
  33.                         + Thread.currentThread().getId());  
  34.                 Toast.makeText(MyService.this"Service中子线程启动!", Toast.LENGTH_LONG).show();  
  35.             }  
  36.         }).start();  
  37.     }  
  38.   
  39.     @Override  
  40.     public int onStartCommand(Intent intent, int flags, int startId) {  
  41.           
  42.         Log.d("mark""Service: " + "\n" + "当前线程名称:"  
  43.                 + Thread.currentThread().getName() + "," + "当前线程名称:"  
  44.                 + Thread.currentThread().getId());  
  45.           
  46.         Toast.makeText(this"启动服务成功!", Toast.LENGTH_LONG).show();  
  47.         return super.onStartCommand(intent, flags, startId);  
  48.     }  
  49.   
  50.     @Override  
  51.     public void onDestroy() {  
  52.         super.onDestroy();  
  53.     }  
  54. }  

点击界面的“启动服务”,打印信息:
[java] view plaincopyprint?
  1. D/mark    (  310): activity:   
  2. D/mark    (  310): 当前线程名称:main,当前线程名称:1  
  3. D/mark    (  310): Service:   
  4. D/mark    (  310): 当前线程名称:main,当前线程名称:1  
  5. D/mark    (  310): Service in Thread:   
  6. D/mark    (  310): 当前线程名称:Thread-8,当前线程名称:8  
从打印信息可以看出,Service 与 Activity 在同一个线程程(main线程)。

如果你复制我的代码实际运行一下,你会发现子线程中的 Toast 根本没有起作用,并且程序会崩溃,显示异常如下:



[java] view plaincopyprint?
  1. 10-02 05:25:32.818: ERROR/AndroidRuntime(325): Uncaught handler: thread Thread-8 exiting due to uncaught exception  
  2. 10-02 05:25:32.828: ERROR/AndroidRuntime(325): java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()  
  3. 10-02 05:25:32.828: ERROR/AndroidRuntime(325):     at android.os.Handler.<init>(Handler.java:121)  
  4. 10-02 05:25:32.828: ERROR/AndroidRuntime(325):     at android.widget.Toast.<init>(Toast.java:68)  
  5. 10-02 05:25:32.828: ERROR/AndroidRuntime(325):     at android.widget.Toast.makeText(Toast.java:231)  
  6. 10-02 05:25:32.828: ERROR/AndroidRuntime(325):     at mark.zhang.MyService$1.run(MyService.java:34)  
  7. 10-02 05:25:32.828: ERROR/AndroidRuntime(325):     at java.lang.Thread.run(Thread.java:1096)  

这下应该明白,在子线程直接 Toast 是错误的!根据提示信息,我们需要调用 Looper.prepare(), 根据 Looper 的 Api 说明,我们还应该调用 Looper.loop(),那麽我们修改一下代码,将 new Thread中的代码修改如下:
[java] view plaincopyprint?
  1. new Thread(new Runnable() {  
  2.             public void run() {  
  3.                 Log.d("mark""Service in Thread: " + "\n" + "当前线程名称:"  
  4.                         + Thread.currentThread().getName() + "," + "当前线程名称:"  
  5.                         + Thread.currentThread().getId());  
  6.                 Looper.prepare();  
  7.                 Toast.makeText(MyService.this"Service中子线程启动!", Toast.LENGTH_LONG).show();  
  8.                 Looper.loop();  
  9.             }  
  10.         }).start();  

该部分完整示例代码下载:http://download.csdn.net/detail/AndroidBluetooth/3653420


ok,这次一切正常。关于 Looper 作用在 http://download.csdn.net/detail/AndroidBluetooth/3650576 中说的很明白,这里不再赘述!


接着看看在Service中如何使用 TimerTask 以及 Toast。Activity 的代码不变,修改 Service 代码:

[java] view plaincopyprint?
  1. package mark.zhang;  
  2.   
  3. import java.util.Timer;  
  4. import java.util.TimerTask;  
  5.   
  6. import android.app.Service;  
  7. import android.content.Intent;  
  8. import android.os.IBinder;  
  9. import android.util.Log;  
  10. import android.widget.Toast;  
  11.   
  12. public class MyService extends Service {  
  13.   
  14.     private Timer timer;  
  15.   
  16.     private TimerTask task = new TimerTask() {  
  17.   
  18.         @Override  
  19.         public void run() {  
  20.   
  21.             Log.d("mark""task: " + "\n" + "当前线程名称:"  
  22.                     + Thread.currentThread().getName() + "," + "当前线程名称:"  
  23.                     + Thread.currentThread().getId());  
  24.             Toast.makeText(getApplicationContext(), "呵呵,您好!",  
  25.                     Toast.LENGTH_SHORT).show();  
  26.         }  
  27.     };  
  28.   
  29.     @Override  
  30.     public IBinder onBind(Intent intent) {  
  31.         return null;  
  32.     }  
  33.   
  34.     @Override  
  35.     public void onCreate() {  
  36.         super.onCreate();  
  37.         // 当前Task中线程的名称为myservice  
  38.         timer = new Timer("myservice");  
  39.     }  
  40.   
  41.     @Override  
  42.     public int onStartCommand(Intent intent, int flags, int startId) {  
  43.         // 100ms之后,每隔5000ms启动定时器  
  44.         timer.scheduleAtFixedRate(task, 1005000);  
  45.         return super.onStartCommand(intent, flags, startId);  
  46.     }  
  47.   
  48.     @Override  
  49.     public void onDestroy() {  
  50.         super.onDestroy();  
  51.         timer.cancel();  
  52.     }  
  53. }  

启动服务之后,启动 TimerTask,在 TimerTask 中每隔5秒中 Toast 一下,在终止服务的时候取消 Timer。

打印信息如下:

[java] view plaincopyprint?
  1. D/mark    (  441): activity:   
  2. D/mark    (  441): 当前线程名称:main,当前线程名称:1  
  3. D/mark    (  441): task:   
  4. D/mark    (  441): 当前线程名称:myservice,当前线程名称:8  
  5. D/mark    (  441): task:   
  6. D/mark    (  441): 当前线程名称:myservice,当前线程名称:8  
  7. D/mark    (  441): task:   
  8. D/mark    (  441): 当前线程名称:myservice,当前线程名称:8  
  9. D/mark    (  441): task:   
  10. D/mark    (  441): 当前线程名称:myservice,当前线程名称:8  
  11. D/mark    (  441): task:   
  12. D/mark    (  441): 当前线程名称:myservice,当前线程名称:8  

可以看出,TimerTask开启一个线程(名称为myservice,线程id是8),按照原来的想法一样,每隔五秒 TimerTask 的run() 方法会执行一次,直到 “停止服务”,但是 Toast 并没有起作用。看来我们需要修改代码。

当然,可以像上面那样使用 Looper 的两个静态方法prepare()、loop(),可以保证Toast 完美运行,但是肯定还有其它办法,仔细看来,呵呵!

修改 Service 代码,这次主要使用 Handler:

[java] view plaincopyprint?
  1. package mark.zhang;  
  2.   
  3. import java.util.Timer;  
  4. import java.util.TimerTask;  
  5.   
  6. import android.app.Service;  
  7. import android.content.Intent;  
  8. import android.os.Handler;  
  9. import android.os.IBinder;  
  10. import android.os.Looper;  
  11. import android.util.Log;  
  12. import android.widget.Toast;  
  13.   
  14. public class MyService extends Service {  
  15.     private Handler handler;  
  16.   
  17.     private Timer timer;  
  18.   
  19.     private TimerTask task = new TimerTask() {  
  20.   
  21.         @Override  
  22.         public void run() {  
  23.             handler.post(new Runnable() {  
  24.               
  25.             @Override  
  26.             public void run() {  
  27.                 Toast.makeText(getApplicationContext(), "呵呵,您好!",  
  28.                         Toast.LENGTH_SHORT).show();  
  29.                   
  30.                 Log.d("mark""service in Handler run: " + "\n" + "当前线程名称:"  
  31.                         + Thread.currentThread().getName() + ","  
  32.                         + "当前线程名称:" + Thread.currentThread().getId());  
  33.             }  
  34.             });  
  35.         }  
  36.     };  
  37.   
  38.     @Override  
  39.     public IBinder onBind(Intent intent) {  
  40.         return null;  
  41.     }  
  42.   
  43.     @Override  
  44.     public void onCreate() {  
  45.         super.onCreate();  
  46.         // 为当前线程获得looper  
  47.         handler = new Handler(Looper.getMainLooper());  
  48.         // 当前Task中线程的名称为myservice  
  49.         timer = new Timer("myservice");  
  50.     }  
  51.   
  52.     @Override  
  53.     public int onStartCommand(Intent intent, int flags, int startId) {  
  54.         // 100ms之后,每隔5000ms启动定时器  
  55.         timer.scheduleAtFixedRate(task, 1005000);  
  56.         return super.onStartCommand(intent, flags, startId);  
  57.     }  
  58.   
  59.     @Override  
  60.     public void onDestroy() {  
  61.         super.onDestroy();  
  62.         timer.cancel();  
  63.     }  
  64. }  
运行程序,一切ok!我们还是分析一下打印信息吧!
[java] view plaincopyprint?
  1. D/mark    (  495): activity:   
  2. D/mark    (  495): 当前线程名称:main,当前线程名称:1  
  3. D/mark    (  495): service in Handler run:   
  4. D/mark    (  495): 当前线程名称:main,当前线程名称:1  
  5. D/mark    (  495): service in Handler run:   
  6. D/mark    (  495): 当前线程名称:main,当前线程名称:1  
  7. D/mark    (  495): service in Handler run:   
  8. D/mark    (  495): 当前线程名称:main,当前线程名称:1  
我们使用 Handler 的 post(Runnable r) 方法,在 run 方法中 Toast,根据 Timer 的定时每隔5秒就会 Toast 一下。


该部分完整代码示例,下载地址:http://download.csdn.net/detail/AndroidBluetooth/3653469


这里还需要提醒大家一句:在子线程中我们不可以直接 new Handler(),但是在 TimerTask 的 run() 方法中直接 new Handler() 是没有问题的。TimerTask 的确开启一个子线程,但是为什么在这里可以直接创建 Handler 对象呢?


如果,你有兴趣可以继续:

学习 android 的 Looper 源码以及 TimerTask 设计理念。


多说一句:在 Service 中不可以显示对话框,如果想通过 Service 来显示对话框需要使用 Handler 通知 Activity 来显示对话框。

在 Activity 子线程中显示对话框,可以这样做:

[java] view plaincopyprint?
  1. Looper.prepare();  
  2. showMyDialog();  
  3. Looper.loop();  
或者使用 Handler,呵呵!

好嘞,这篇博客只是借助 Service 来说明在子线程中如何使用TimerTask、Toast,在Activity 中类似,说到这里吧!




原创粉丝点击