关于 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 代码:
- packagemark.zhang;
- importandroid.app.Activity;
- importandroid.content.Intent;
- importandroid.os.Bundle;
- importandroid.util.Log;
- importandroid.view.View;
- importandroid.view.View.OnClickListener;
- publicclassServiceToastActivityextendsActivity{
- @Override
- publicvoidonCreate(BundlesavedInstanceState){
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- Log.d("mark","activity:"+"\n"+"当前线程名称:"
- +Thread.currentThread().getName()+","+"当前线程名称:"
- +Thread.currentThread().getId());
- //启动服务
- findViewById(R.id.button_startservice).setOnClickListener(
- newOnClickListener(){
- @Override
- publicvoidonClick(Viewv){
- Intentintent=newIntent(ServiceToastActivity.this,
- MyService.class);
- startService(intent);
- }
- });
- //停止服务
- findViewById(R.id.button_stopservice).setOnClickListener(
- newOnClickListener(){
- @Override
- publicvoidonClick(Viewv){
- Intentintent=newIntent(ServiceToastActivity.this,
- MyService.class);
- stopService(intent);
- }
- });
- }
- }
上述代码就是给两个 Button 设置监听器,启动和停止服务。
然后,在 Service 中 开启一个线程,并在该线程中 Toast 一下!代码如下:
- packagemark.zhang;
- importjava.util.Timer;
- importjava.util.TimerTask;
- importandroid.app.Service;
- importandroid.content.Intent;
- importandroid.os.Handler;
- importandroid.os.IBinder;
- importandroid.os.Looper;
- importandroid.util.Log;
- importandroid.widget.Toast;
- publicclassMyServiceextendsService{
- privateHandlerhandler=null;
- privateTimertimer;
- @Override
- publicIBinderonBind(Intentintent){
- returnnull;
- }
- @Override
- publicvoidonCreate(){
- super.onCreate();
- newThread(newRunnable(){
- publicvoidrun(){
- Log.d("mark","ServiceinThread:"+"\n"+"当前线程名称:"
- +Thread.currentThread().getName()+","+"当前线程名称:"
- +Thread.currentThread().getId());
- Toast.makeText(MyService.this,"Service中子线程启动!",Toast.LENGTH_LONG).show();
- }
- }).start();
- }
- @Override
- publicintonStartCommand(Intentintent,intflags,intstartId){
- Log.d("mark","Service:"+"\n"+"当前线程名称:"
- +Thread.currentThread().getName()+","+"当前线程名称:"
- +Thread.currentThread().getId());
- Toast.makeText(this,"启动服务成功!",Toast.LENGTH_LONG).show();
- returnsuper.onStartCommand(intent,flags,startId);
- }
- @Override
- publicvoidonDestroy(){
- super.onDestroy();
- }
- }
点击界面的“启动服务”,打印信息:
- D/mark(310):activity:
- D/mark(310):当前线程名称:main,当前线程名称:1
- D/mark(310):Service:
- D/mark(310):当前线程名称:main,当前线程名称:1
- D/mark(310):ServiceinThread:
- D/mark(310):当前线程名称:Thread-8,当前线程名称:8
如果你复制我的代码实际运行一下,你会发现子线程中的 Toast 根本没有起作用,并且程序会崩溃,显示异常如下:
- 10-0205:25:32.818:ERROR/AndroidRuntime(325):Uncaughthandler:threadThread-8exitingduetouncaughtexception
- 10-0205:25:32.828:ERROR/AndroidRuntime(325):java.lang.RuntimeException:Can'tcreatehandlerinsidethreadthathasnotcalledLooper.prepare()
- 10-0205:25:32.828:ERROR/AndroidRuntime(325):atandroid.os.Handler.<init>(Handler.java:121)
- 10-0205:25:32.828:ERROR/AndroidRuntime(325):atandroid.widget.Toast.<init>(Toast.java:68)
- 10-0205:25:32.828:ERROR/AndroidRuntime(325):atandroid.widget.Toast.makeText(Toast.java:231)
- 10-0205:25:32.828:ERROR/AndroidRuntime(325):atmark.zhang.MyService$1.run(MyService.java:34)
- 10-0205:25:32.828:ERROR/AndroidRuntime(325):atjava.lang.Thread.run(Thread.java:1096)
这下应该明白,在子线程直接 Toast 是错误的!根据提示信息,我们需要调用 Looper.prepare(),根据 Looper 的 Api 说明,我们还应该调用 Looper.loop(),那麽我们修改一下代码,将 new Thread中的代码修改如下:
- newThread(newRunnable(){
- publicvoidrun(){
- Log.d("mark","ServiceinThread:"+"\n"+"当前线程名称:"
- +Thread.currentThread().getName()+","+"当前线程名称:"
- +Thread.currentThread().getId());
- Looper.prepare();
- Toast.makeText(MyService.this,"Service中子线程启动!",Toast.LENGTH_LONG).show();
- Looper.loop();
- }
- }).start();
该部分完整示例代码下载:http://download.csdn.net/detail/AndroidBluetooth/3653420
接着看看在Service中如何使用 TimerTask 以及 Toast。Activity 的代码不变,修改 Service 代码:
- packagemark.zhang;
- importjava.util.Timer;
- importjava.util.TimerTask;
- importandroid.app.Service;
- importandroid.content.Intent;
- importandroid.os.IBinder;
- importandroid.util.Log;
- importandroid.widget.Toast;
- publicclassMyServiceextendsService{
- privateTimertimer;
- privateTimerTasktask=newTimerTask(){
- @Override
- publicvoidrun(){
- Log.d("mark","task:"+"\n"+"当前线程名称:"
- +Thread.currentThread().getName()+","+"当前线程名称:"
- +Thread.currentThread().getId());
- Toast.makeText(getApplicationContext(),"呵呵,您好!",
- Toast.LENGTH_SHORT).show();
- }
- };
- @Override
- publicIBinderonBind(Intentintent){
- returnnull;
- }
- @Override
- publicvoidonCreate(){
- super.onCreate();
- //当前Task中线程的名称为myservice
- timer=newTimer("myservice");
- }
- @Override
- publicintonStartCommand(Intentintent,intflags,intstartId){
- //100ms之后,每隔5000ms启动定时器
- timer.scheduleAtFixedRate(task,100,5000);
- returnsuper.onStartCommand(intent,flags,startId);
- }
- @Override
- publicvoidonDestroy(){
- super.onDestroy();
- timer.cancel();
- }
- }
启动服务之后,启动 TimerTask,在 TimerTask 中每隔5秒中 Toast 一下,在终止服务的时候取消 Timer。
打印信息如下:
- D/mark(441):activity:
- D/mark(441):当前线程名称:main,当前线程名称:1
- D/mark(441):task:
- D/mark(441):当前线程名称:myservice,当前线程名称:8
- D/mark(441):task:
- D/mark(441):当前线程名称:myservice,当前线程名称:8
- D/mark(441):task:
- D/mark(441):当前线程名称:myservice,当前线程名称:8
- D/mark(441):task:
- D/mark(441):当前线程名称:myservice,当前线程名称:8
- D/mark(441):task:
- D/mark(441):当前线程名称:myservice,当前线程名称:8
可以看出,TimerTask开启一个线程(名称为myservice,线程id是8),按照原来的想法一样,每隔五秒 TimerTask 的run() 方法会执行一次,直到 “停止服务”,但是 Toast 并没有起作用。看来我们需要修改代码。
当然,可以像上面那样使用 Looper 的两个静态方法prepare()、loop(),可以保证Toast 完美运行,但是肯定还有其它办法,仔细看来,呵呵!
修改 Service 代码,这次主要使用 Handler:
- packagemark.zhang;
- importjava.util.Timer;
- importjava.util.TimerTask;
- importandroid.app.Service;
- importandroid.content.Intent;
- importandroid.os.Handler;
- importandroid.os.IBinder;
- importandroid.os.Looper;
- importandroid.util.Log;
- importandroid.widget.Toast;
- publicclassMyServiceextendsService{
- privateHandlerhandler;
- privateTimertimer;
- privateTimerTasktask=newTimerTask(){
- @Override
- publicvoidrun(){
- handler.post(newRunnable(){
- @Override
- publicvoidrun(){
- Toast.makeText(getApplicationContext(),"呵呵,您好!",
- Toast.LENGTH_SHORT).show();
- Log.d("mark","serviceinHandlerrun:"+"\n"+"当前线程名称:"
- +Thread.currentThread().getName()+","
- +"当前线程名称:"+Thread.currentThread().getId());
- }
- });
- }
- };
- @Override
- publicIBinderonBind(Intentintent){
- returnnull;
- }
- @Override
- publicvoidonCreate(){
- super.onCreate();
- //为当前线程获得looper
- handler=newHandler(Looper.getMainLooper());
- //当前Task中线程的名称为myservice
- timer=newTimer("myservice");
- }
- @Override
- publicintonStartCommand(Intentintent,intflags,intstartId){
- //100ms之后,每隔5000ms启动定时器
- timer.scheduleAtFixedRate(task,100,5000);
- returnsuper.onStartCommand(intent,flags,startId);
- }
- @Override
- publicvoidonDestroy(){
- super.onDestroy();
- timer.cancel();
- }
- }
- D/mark(495):activity:
- D/mark(495):当前线程名称:main,当前线程名称:1
- D/mark(495):serviceinHandlerrun:
- D/mark(495):当前线程名称:main,当前线程名称:1
- D/mark(495):serviceinHandlerrun:
- D/mark(495):当前线程名称:main,当前线程名称:1
- D/mark(495):serviceinHandlerrun:
- D/mark(495):当前线程名称:main,当前线程名称:1
该部分完整代码示例,下载地址:http://download.csdn.net/detail/AndroidBluetooth/3653469
这里还需要提醒大家一句:在子线程中我们不可以直接 new Handler(),但是在 TimerTask 的 run() 方法中直接 new Handler() 是没有问题的。TimerTask 的确开启一个子线程,但是为什么在这里可以直接创建 Handler 对象呢?
如果,你有兴趣可以继续:
学习 android 的 Looper 源码以及 TimerTask 设计理念。
多说一句:在 Service 中不可以显示对话框,如果想通过 Service 来显示对话框需要使用 Handler 通知 Activity 来显示对话框。
在 Activity 子线程中显示对话框,可以这样做:
- Looper.prepare();
- showMyDialog();
- Looper.loop();
好嘞,这篇博客只是借助 Service 来说明在子线程中如何使用TimerTask、Toast,在Activity 中类似,说到这里吧!