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 代码:

  1. packagemark.zhang;
  2. importandroid.app.Activity;
  3. importandroid.content.Intent;
  4. importandroid.os.Bundle;
  5. importandroid.util.Log;
  6. importandroid.view.View;
  7. importandroid.view.View.OnClickListener;
  8. publicclassServiceToastActivityextendsActivity{
  9. @Override
  10. publicvoidonCreate(BundlesavedInstanceState){
  11. super.onCreate(savedInstanceState);
  12. setContentView(R.layout.main);
  13. Log.d("mark","activity:"+"\n"+"当前线程名称:"
  14. +Thread.currentThread().getName()+","+"当前线程名称:"
  15. +Thread.currentThread().getId());
  16. //启动服务
  17. findViewById(R.id.button_startservice).setOnClickListener(
  18. newOnClickListener(){
  19. @Override
  20. publicvoidonClick(Viewv){
  21. Intentintent=newIntent(ServiceToastActivity.this,
  22. MyService.class);
  23. startService(intent);
  24. }
  25. });
  26. //停止服务
  27. findViewById(R.id.button_stopservice).setOnClickListener(
  28. newOnClickListener(){
  29. @Override
  30. publicvoidonClick(Viewv){
  31. Intentintent=newIntent(ServiceToastActivity.this,
  32. MyService.class);
  33. stopService(intent);
  34. }
  35. });
  36. }
  37. }

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

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

  1. packagemark.zhang;
  2. importjava.util.Timer;
  3. importjava.util.TimerTask;
  4. importandroid.app.Service;
  5. importandroid.content.Intent;
  6. importandroid.os.Handler;
  7. importandroid.os.IBinder;
  8. importandroid.os.Looper;
  9. importandroid.util.Log;
  10. importandroid.widget.Toast;
  11. publicclassMyServiceextendsService{
  12. privateHandlerhandler=null;
  13. privateTimertimer;
  14. @Override
  15. publicIBinderonBind(Intentintent){
  16. returnnull;
  17. }
  18. @Override
  19. publicvoidonCreate(){
  20. super.onCreate();
  21. newThread(newRunnable(){
  22. publicvoidrun(){
  23. Log.d("mark","ServiceinThread:"+"\n"+"当前线程名称:"
  24. +Thread.currentThread().getName()+","+"当前线程名称:"
  25. +Thread.currentThread().getId());
  26. Toast.makeText(MyService.this,"Service中子线程启动!",Toast.LENGTH_LONG).show();
  27. }
  28. }).start();
  29. }
  30. @Override
  31. publicintonStartCommand(Intentintent,intflags,intstartId){
  32. Log.d("mark","Service:"+"\n"+"当前线程名称:"
  33. +Thread.currentThread().getName()+","+"当前线程名称:"
  34. +Thread.currentThread().getId());
  35. Toast.makeText(this,"启动服务成功!",Toast.LENGTH_LONG).show();
  36. returnsuper.onStartCommand(intent,flags,startId);
  37. }
  38. @Override
  39. publicvoidonDestroy(){
  40. super.onDestroy();
  41. }
  42. }

点击界面的“启动服务”,打印信息:

  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):ServiceinThread:
  6. D/mark(310):当前线程名称:Thread-8,当前线程名称:8
从打印信息可以看出,Service 与 Activity 在同一个线程程(main线程)。

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



  1. 10-0205:25:32.818:ERROR/AndroidRuntime(325):Uncaughthandler:threadThread-8exitingduetouncaughtexception
  2. 10-0205:25:32.828:ERROR/AndroidRuntime(325):java.lang.RuntimeException:Can'tcreatehandlerinsidethreadthathasnotcalledLooper.prepare()
  3. 10-0205:25:32.828:ERROR/AndroidRuntime(325):atandroid.os.Handler.<init>(Handler.java:121)
  4. 10-0205:25:32.828:ERROR/AndroidRuntime(325):atandroid.widget.Toast.<init>(Toast.java:68)
  5. 10-0205:25:32.828:ERROR/AndroidRuntime(325):atandroid.widget.Toast.makeText(Toast.java:231)
  6. 10-0205:25:32.828:ERROR/AndroidRuntime(325):atmark.zhang.MyService$1.run(MyService.java:34)
  7. 10-0205:25:32.828:ERROR/AndroidRuntime(325):atjava.lang.Thread.run(Thread.java:1096)

这下应该明白,在子线程直接 Toast 是错误的!根据提示信息,我们需要调用 Looper.prepare(),根据 Looper 的 Api 说明,我们还应该调用 Looper.loop(),那麽我们修改一下代码,将 new Thread中的代码修改如下:

  1. newThread(newRunnable(){
  2. publicvoidrun(){
  3. Log.d("mark","ServiceinThread:"+"\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 代码:

  1. packagemark.zhang;
  2. importjava.util.Timer;
  3. importjava.util.TimerTask;
  4. importandroid.app.Service;
  5. importandroid.content.Intent;
  6. importandroid.os.IBinder;
  7. importandroid.util.Log;
  8. importandroid.widget.Toast;
  9. publicclassMyServiceextendsService{
  10. privateTimertimer;
  11. privateTimerTasktask=newTimerTask(){
  12. @Override
  13. publicvoidrun(){
  14. Log.d("mark","task:"+"\n"+"当前线程名称:"
  15. +Thread.currentThread().getName()+","+"当前线程名称:"
  16. +Thread.currentThread().getId());
  17. Toast.makeText(getApplicationContext(),"呵呵,您好!",
  18. Toast.LENGTH_SHORT).show();
  19. }
  20. };
  21. @Override
  22. publicIBinderonBind(Intentintent){
  23. returnnull;
  24. }
  25. @Override
  26. publicvoidonCreate(){
  27. super.onCreate();
  28. //当前Task中线程的名称为myservice
  29. timer=newTimer("myservice");
  30. }
  31. @Override
  32. publicintonStartCommand(Intentintent,intflags,intstartId){
  33. //100ms之后,每隔5000ms启动定时器
  34. timer.scheduleAtFixedRate(task,100,5000);
  35. returnsuper.onStartCommand(intent,flags,startId);
  36. }
  37. @Override
  38. publicvoidonDestroy(){
  39. super.onDestroy();
  40. timer.cancel();
  41. }
  42. }

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

打印信息如下:

  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:

  1. packagemark.zhang;
  2. importjava.util.Timer;
  3. importjava.util.TimerTask;
  4. importandroid.app.Service;
  5. importandroid.content.Intent;
  6. importandroid.os.Handler;
  7. importandroid.os.IBinder;
  8. importandroid.os.Looper;
  9. importandroid.util.Log;
  10. importandroid.widget.Toast;
  11. publicclassMyServiceextendsService{
  12. privateHandlerhandler;
  13. privateTimertimer;
  14. privateTimerTasktask=newTimerTask(){
  15. @Override
  16. publicvoidrun(){
  17. handler.post(newRunnable(){
  18. @Override
  19. publicvoidrun(){
  20. Toast.makeText(getApplicationContext(),"呵呵,您好!",
  21. Toast.LENGTH_SHORT).show();
  22. Log.d("mark","serviceinHandlerrun:"+"\n"+"当前线程名称:"
  23. +Thread.currentThread().getName()+","
  24. +"当前线程名称:"+Thread.currentThread().getId());
  25. }
  26. });
  27. }
  28. };
  29. @Override
  30. publicIBinderonBind(Intentintent){
  31. returnnull;
  32. }
  33. @Override
  34. publicvoidonCreate(){
  35. super.onCreate();
  36. //为当前线程获得looper
  37. handler=newHandler(Looper.getMainLooper());
  38. //当前Task中线程的名称为myservice
  39. timer=newTimer("myservice");
  40. }
  41. @Override
  42. publicintonStartCommand(Intentintent,intflags,intstartId){
  43. //100ms之后,每隔5000ms启动定时器
  44. timer.scheduleAtFixedRate(task,100,5000);
  45. returnsuper.onStartCommand(intent,flags,startId);
  46. }
  47. @Override
  48. publicvoidonDestroy(){
  49. super.onDestroy();
  50. timer.cancel();
  51. }
  52. }
运行程序,一切ok!我们还是分析一下打印信息吧!

  1. D/mark(495):activity:
  2. D/mark(495):当前线程名称:main,当前线程名称:1
  3. D/mark(495):serviceinHandlerrun:
  4. D/mark(495):当前线程名称:main,当前线程名称:1
  5. D/mark(495):serviceinHandlerrun:
  6. D/mark(495):当前线程名称:main,当前线程名称:1
  7. D/mark(495):serviceinHandlerrun:
  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 子线程中显示对话框,可以这样做:

  1. Looper.prepare();
  2. showMyDialog();
  3. Looper.loop();
或者使用 Handler,呵呵!

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值