Android基础总结(8)——服务
服务(Service)是Android中实现程序后台运行的解决方案,它非常适合用于去执行哪些不需要和用户交互而且还要长期运行的任务。服务的运行不依赖任何用户界面,即使当程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。不过需要注意的是:服务并不是运行在一个独立的进程当中,而是依赖于创建服务的应用程序进程,当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。
此外,我们也不要被服务的后台概念所迷惑,实际上服务并不会自动开启线程,所有的代码都是默认运行在主线程中。也就是说,我们需要自己手动地区创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞住的情况。
1、线程的基本用法
Android多线程的编程其实并不比Java多线程特殊,基本都是使用相同的语法。常用的三种创建线程的方法可以参考自:Java基础——多线程
和许多其他的GUI库一样,Android的UI也是线程不安全的。也就是说,如果想要更新应用程序中的UI元素,则必须在主线程中进行,否则就会出现异常。
2、解决在子线程中进行UI操作的问题
但是有些时候,我们必须利用多线程过去执行一些耗时任务,然后根据任务执行的结果开更新相应的UI控件,那么,Android提供了两种方法来解决在子线程中进行UI操作的问题:
- 异步消息处理机制,完美解决了在子线程中进行UI操作的问题。
- 使用AsyncTask
异步消息机制的原理解析可以参考自:Android基础总结(7)——异步消息处理
3、Service服务的基本用法
- 创建Service子类
-
首先,Service类是抽象类,我们要使用它就必须先创建一个自定义类继承自Service类,然后实现其唯一的抽象方法public IBinder onBind(Intent intent),然后我们就可以用这个自定义的子类去来开启服务了。此外,由于onCreate()、onStartCommand()、onDestroy() 非常重要,在Service类中对这些方法也都是空方法,所以我们通常也会重写这三个方法。其中onCreate()方法会在服务创建的时候调用;onStartCommand()方法会在每次启动服务的时候调用;onDestroy()方法会在服务销毁的时候调用。
1 class MyService extends Service{ 2 @Override 3 public IBinder onBind(Intent intent) { 4 // TODO Auto-generated method stub 5 return null; 6 } 7 }
-
- 四大组件都是需要注册的,所以相同的Service也需要注册
1 <service 2 android:name=".MyService"> 3 </service>
- 启动我们创建的MyService:Service的启动有两种方式:context.startService(Intent intent) 和 context.
bindService(Intent service, ServiceConnection conn, int flags)
,对应地,我们一般说是Intent方法和Bind方法。
- Intent方法:使用startService() 启动Service是会会经历:context.startService() --> onCreate() --> onStartCommand() --> Service running --> context.stopService() --> onDestroy() --> Service stop 。此外,采用这方法启动的服务,我们必须显示地调用context.stopService(Intent intent)方法来关闭服务。
- 如果Service第一次启动,则android先调用onCreate()然后调用onStartCommand();如果Service已经运行,则只调用onStartCommand(),所以一个Service的onStartCommand()方法可能会重复调用多次;而onCreate()方法和onDestroy()方法只会调用一次。
- stopService的时候直接onDestroy,如果是调用者自己直接退出而没有调用stopService的话,Service会一直在后台运行。该Service的调用者再启动起来后可以通过stopService关闭Service。
- 所以调用startService的生命周期为:onCreate --> onStart(可多次调用) --> onDestroy
1 public class MainActivity extends Activity implements OnClickListener{ 2 3 private Button startService; 4 private Button stopService; 5 @Override 6 protected void onCreate(Bundle savedInstanceState) { 7 super.onCreate(savedInstanceState); 8 setContentView(R.layout.activity_main); 9 startService=(Button) findViewById(R.id.start_service); 10 stopService=(Button) findViewById(R.id.stop_service); 11 startService.setOnClickListener(this); 12 stopService.setOnClickListener(this); 13 } 14 15 @Override 16 public void onClick(View v) { 17 // TODO Auto-generated method stub 18 switch(v.getId()){ 19 case R.id.start_service: 20 Intent startIntent =new Intent(this,MyService.class); 21 startService(startIntent);//启动服务 22 break; 23 case R.id.stop_service: 24 Intent stopIntent =new Intent(this,MyService.class); 25 stopService(stopIntent);//停止服务 26 break; 27 default: 28 break; 29 } 30 } 31 32 }
- :使用context.bindService()启动Service会经历:
- 。也可以通过unbindService()进行解绑。但是如果不解绑,则与之绑定的活动借结束,则该服务也结束。
- onBind将返回给客户端一个IBind接口实例,IBind允许客户端回调服务的方法,比如得到Service运行的状态或其他操作。这个时候把调用者(Context,例如Activity)会和Service绑定在一起,Context退出了,Srevice就会调用onUnbind->onDestroy相应退出。
- 所以调用bindService的生命周期为:onCreate --> onBind(只一次,不可多次绑定) --> onUnbind --> onDestory。
- 在Service每一次的开启关闭过程中,只有onStart可被多次调用(通过多次startService调用),其他onCreate,onBind,onUnbind,onDestory在一个生命周期中只能被调用一次。
- Intent方法:使用startService() 启动Service是会会经历:context.startService() --> onCreate() --> onStartCommand() --> Service running --> context.stopService() --> onDestroy() --> Service stop 。此外,采用这方法启动的服务,我们必须显示地调用context.stopService(Intent intent)方法来关闭服务。
4、Service服务生命周期
Service完整的生命周期从调用onCreate()开始直到调用onDestroy()结束。
Service有两种使用方法:(详细资料可见http://www.cnblogs.com/mengdd/archive/2013/03/24/2979944.html)
-
- 以调用Context.startService()启动,而以调用Context.stopService()结束。这种情况下的Service是通过其他组件调用 startService()被创建。这种service可以无限地运行下去,必须调用stopSelf()方法或者其他组件调用stopService()方法来停止它。当service被停止时,系统会销毁它。
- 以调用Context.bindService()方法建立,以调用Context.unbindService()关闭。这种情况下的Service是通过其他组件(一个客户)调用bindService()来创建的。客户可以通过一个IBinder接口和service进行通信。客户可以通过 unbindService()方法来关闭这种连接。一个service可以同时和多个客户绑定,当多个客户都解除绑定之后,系统会销毁service。
5、活动与服务之间进行通信
对于两种启动服务的方式,其中通过Context.startService()启动服务的方式将活动启动之后,服务的onCreate()和onStartCommand()方法得到执行,之后服务会一直处于运行状态直到活动调用stopService()结束活动,但是具体运行的是什么逻辑,活动就控制不了。这就类似于活动通知了服务一下:“你可以启动了!”然后服务启动后就去忙自己的事情了,但是活动并不知道服务到底去做了什么事情,以及如何完成的。这显然是不够理想的。。。
如果我们想要让活动和服务的关系跟紧密一点,例如在活动中指挥服务去做什么,服务就会去做什么,要实现这样的通信,我们只能采用Context.bindService()方法来建立活动和服务之间的联系,然后通过创建一个专门的Binder对象来对通信信息进行管理。目前我们希望在MyService里提供一个下载的功能,然后在活动中可以决定何时开始下载,以及随时查看下载进。实现这个功能的思路是创建一个专门的Binder对象来对下载功能进行管理,修改MyService中的代码:如下所示:
1 public class MyService extends Service { 2 3 private DownloadBinder mBinder=new DownloadBinder(); 4 5 class DownloadBinder extends Binder{ 6 public void startDownload(){ 7 Log.d(MyService, startdownload executed); 8 } 9 10 public int getProgress(){ 11 Log.d(MyService, getProgress executed); 12 return 0; 13 } 15 }
16 @Override 17 public IBinder onBind(Intent intent) { 18 // TODO Auto-generated method stub 19 return mBinder; 20 } 21 22 @Override 23 public void onCreate() { 24 // TODO Auto-generated method stub 25 super.onCreate(); 26 Log.d(MyService, onCreate()); 27 } 28 29 @Override 30 public void onDestroy() { 31 // TODO Auto-generated method stub 32 super.onDestroy(); 33 Log.d(MyService, onDestroy()); 34 } 35 36 @Override 37 public int onStartCommand(Intent intent, int flags, int startId) { 38 // TODO Auto-generated method stub 40 Log.d(MyService, onStartCommand); 42 return super.onStartCommand(intent, flags, startId); 44 } 48 }
这里我们新建了一个DownloadBinder类,并让它继承自Binder,然后再它的内部提供开始下载以及查看下载进度的方法。当然这只是两个模拟的方法,并没有实现真正的功能,我们在这两个方法中分别打印了一行日志。
接着,在MyService中创建了DownloadBinder的实例,然后再onBind()方法里返回了这个实例,这样MyService中的工作就全部完成了。
下面我们需要在活动中调用服务里的方法,首先需要在布局文件中新增两个按钮,修改activity_main.xml中的代码,代码就省略了。这两个按钮用于在活动中进行绑定和取消绑定服务,当一个活动和服务绑定了之后,就可以调用该服务里的Binder提供的方法了,修改MainActivity中的代码,如下所示:
1 public class MainActivity extends Activity implements OnClickListener{ 2 3 private Button startService; 4 private Button stopService; 5 private Button bindService; 6 private Button unbindService; 7 private MyService.DownloadBinder downloadBinder; 8 private ServiceConnection connection = new ServiceConnection() { 9 /* 10 * 这里创建了一个ServiceConnection的匿名类,在这里重写了onServiceConnected方法和 11 * onServiceDisconnected方法,这两个方法分别会在活动与服务成功绑定以及解除绑定的时候调用。 12 * 在onServiceConnected方法中,我们又通过向下转型得到了DownloadBinder的实例,有了这个 13 * 实例,活动和服务之间的关系就变得非常紧密了,现在我们可以在活动中根据具体的场景来调用DownloadBinder 14 * 中的任何public方法,及实现了指挥服务干什么,服务就干什么的功能,这里只做了简单的测试,在onServiceConnected 15 * 中调用了DownloadBinder的startDownload(),getProgress()方法。 16 * */ 17 @Override 18 public void onServiceDisconnected(ComponentName name) { 19 // TODO Auto-generated method stub 21 } 22 23 @Override 24 public void onServiceConnected(ComponentName name, IBinder service) { 25 // TODO Auto-generated method stub 26 downloadBinder=(MyService.DownloadBinder) service; 27 downloadBinder.startDownload(); 28 downloadBinder.getProgress(); 29 } 30 }; 31 @Override 32 protected void onCreate(Bundle savedInstanceState) { 33 super.onCreate(savedInstanceState); 34 setContentView(R.layout.activity_main); 35 startService=(Button) findViewById(R.id.start_service); 36 stopService=(Button) findViewById(R.id.stop_service); 37 startService.setOnClickListener(this); 38 stopService.setOnClickListener(this); 39 40 bindService = (Button) findViewById(R.id.bind_service); 41 unbindService = (Button) findViewById(R.id.unbind_service); 42 bindService.setOnClickListener(this); 43 unbindService.setOnClickListener(this); 44 45 } 46 47 @Override 48 public void onClick(View v) { 49 // TODO Auto-generated method stub 50 switch(v.getId()){ 51 case R.id.start_service: 52 Intent startIntent =new Intent(this,MyService.class); 53 startService(startIntent);//启动服务 54 break; 55 case R.id.stop_service: 56 Intent stopIntent =new Intent(this,MyService.class); 57 stopService(stopIntent);//停止服务 58 break; 59 case R.id.bind_service: 60 /* 61 *现在我们需要进行活动和服务的绑定,构建一个Intent对象,然后调用bindService()方法将 62 *MainActivity()和MyService进行绑定。 bindService方法接收三个参数,第一个参数就是 63 *上面创建出的Intent对象,第二个参数就是前面创建出的ServiceConnection的实例,第三个 64 *参数则是一个标志位,这里传入BIND_AUTO_CREATE表示在活动和服务进行绑定后自动创建服务。 65 *这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行。 66 * */ 67 Intent bindIntent=new Intent(this,MyService.class); 68 bindService(bindIntent, connection, BIND_AUTO_CREATE);//绑定服务 69 break; 70 case R.id.unbind_service: 71 /* 72 * 如果我们想解除活动和服务之间的绑定,调用一下unbindService()方法就可以了。 73 * */ 74 unbindService(connection);//解绑服务 75 break; 76 default: 77 break; 78 } 79 } 81 }
可以看到,首先是MyService的onCreate()方法得到了执行,然后startDownload和getProgeress方法得到了执行,说明我们确实已经在活动力成功的调用了服务里提供的方法了。另外需要注意的,任何一个服务在整个应用程序范围内都是通用的,即MyService不仅可以和MainActivity绑定,还可以和任何一个其他的活动进行绑定,而且在绑定完成之后他们都可以获取到相同的DownloadBinder实例。
六、使用前台服务
服务几乎都是在后台运行的,一直以来它都是默默的做着辛苦的工作。但是服务的系统优先级还是比较低的,当系统出现内存不足的情况时,就有可能会回收掉正在后台运行的服务。如果你希望服务可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台服务。前台服务和普通服务最大的区别就在于,它会一直有一个正在运行的系统状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。
下面我们创建一个前台服务吧,修改MyService中的代码,如下所示:
1 public class MyService extends Service { 2 3 private DownloadBinder mBinder=new DownloadBinder(); 4 class DownloadBinder extends Binder{ 5 public void startDownload(){ 6 Log.d(MyService, startdownload executed); 7 } 8 9 public int getProgress(){ 10 Log.d(MyService, getProgress executed); 11 return 0; 12 } 13 14 } 15 @Override 16 public IBinder onBind(Intent intent) { 17 // TODO Auto-generated method stub 18 return mBinder; 19 } 20 21 @Override 22 public void onCreate() { 23 // TODO Auto-generated method stub 24 super.onCreate(); 25 @SuppressWarnings(deprecation) 26 Notification notification=new Notification(R.drawable.ic_launcher, 27 Notification comes,System.currentTimeMillis()); 28 Intent notificationIntent=new Intent(this,MainActivity.class); 29 PendingIntent pendingIntent=PendingIntent.getActivity(this, 0,notificationIntent, 30 0); 31 notification.setLatestEventInfo(this, this is title, this is content, 32 pendingIntent); 33 startForeground(1, notification); 34 /* 35 可以看到,这里只是修改了onCreate()方法中的代码,相信这部分代码你会非常眼熟。这就是我们前面学习的 36 创建通知的方法。只不过这次在构建出Notification对象并没有使用NotificationManager来将通知显示 37 出来,而是调用了startForeground()方法。这个方法接收两个参数,第一个参数是通知的id,类似于notify()方法 38 的第一个参数,第二个参数则是构建出来的Notification对象。调用startForeground()方法后就会让MyService变成 39 一个前台服务,并在系统状态显示出来。 41 */ 42 Log.d(MyService, onCreate()); 43 } 44 45 @Override 46 public void onDestroy() { 47 // TODO Auto-generated method stub 48 super.onDestroy(); 49 Log.d(MyService, onDestroy()); 50 } 51 52 @Override 53 public int onStartCommand(Intent intent, int flags, int startId) { 54 // TODO Auto-generated method stub 56 Log.d(MyService, onStartCommand); 58 return super.onStartCommand(intent, flags, startId); 60 } 61 62 }
七、使用IntentService
我们知道服务中的代码都是默认运行在主线程当中,如果直接在服务里去处理一些耗时的逻辑,就很容易出现ANR(Application Not Responding)的情况。所以这个时候,就需要用到Android多线程编程的技术了,我们应该在服务的每个具体的方法里开启一个子线程,然后再这里去处理那些耗时的逻辑。因此,一个比较标准的服务就可以写成如下形式了:
1 public class MyService extends Service { 2 3 @Override 4 public IBinder onBind(Intent intent) { 5 // TODO Auto-generated method stub 6 return null; 7 } 8 9 10 @Override 11 public int onStartCommand(Intent intent, int flags, int startId) { 12 // TODO Auto-generated method stub 13 14 Log.d(MyService, onStartCommand); 15 new Thread(new Runnable(){ 16 17 @Override 18 public void run() { 19 // TODO Auto-generated method stub 20 //处理具体的逻辑 21 } 23 }).start(); 24 return super.onStartCommand(intent, flags, startId); 25 26 } 27 28 }
但是,这种服务一旦启动之后,就会一直处于运行状态,必须调用stopService()或者stopSelf()方法才能让服务停止下来。所以,如果想要实现一个服务执行完毕后自动停止的功能,就可以这样写:
1 public class MyService extends Service { 2 3 @Override 4 public IBinder onBind(Intent intent) { 5 // TODO Auto-generated method stub 6 //return mBinder; 7 return null; 8 } 9 10 @Override 11 public int onStartCommand(Intent intent, int flags, int startId) { 12 // TODO Auto-generated method stub 13 14 Log.d(MyService, onStartCommand); 15 new Thread(new Runnable(){ 17 @Override 18 public void run() { 19 // TODO Auto-generated method stub 20 //处理具体的逻辑 21 stopSelf(); 22 } 24 }).start(); 25 return super.onStartCommand(intent, flags, startId); 27 } 29 }
虽然这种写法并不复杂,但是总会有一些人忘记开启线程,或者忘记调用stopSelf()方法。为了可以简单地创建一个异步的,会自动停止的服务,android专门提供了一个IntentService类,这个类就很好的解决了前面所提到的两种尴尬,下面我们来看下它的用法。
新建一个MyIntentService类继承IntentService,代码如下所示:
1 public class MyIntentService extends IntentService { 2 3 /* 4 这里首先是提供了一个无参的构造函数,并且必须在其内部调用父类的有参构造函数。然后要在子类中去实现 5 onHandleIntent()这个抽象方法,在这个方法中可以处理一些具体的逻辑,而且不用担心ANR的问题,因为 6 这个方法已经是在子线程中运行的了。这里为了证实一下,我们在onHandleIntent()方法中打印了当前线程的id。 7 另外根据IntentService的特性,这个服务在运行结束后应该是会自动停止的,所以我们又重写了onDestroy()方法,在 8 这里也打印l一行日志,以证实是不是停止掉了。 9 */ 10 public MyIntentService() { 11 super(“MyIntentService”);//调用父类的有参构造函数 12 // TODO Auto-generated constructor stub 13 } 14 15 @Override 16 protected void onHandleIntent(Intent arg0) { 17 // TODO Auto-generated method stub 18 //打印当前线程的id 19 Log.d(MyIntentService, Thread id is +Thread.currentThread().getId()); 20 } 21 22 @Override 23 public void onDestroy() { 24 // TODO Auto-generated method stub 25 super.onDestroy(); 26 Log.d(MyIntentService, onDestroy() executed); 27 } 28 }
接下来修改activity_main.xml中的代码,加入一个用于启动MyIntentService这个服务的按钮,然后修改MainActivity中的代码,如下所示:
1 public class MainActivity extends Activity implements OnClickListener{ 2 3 private Button startService; 4 private Button stopService; 5 private Button bindService; 6 private Button unbindService; 7 private Button startIntentService; 8 //private MyService.DownloadBinder downloadBinder; 9 private ServiceConnection connection=new ServiceConnection() { 10 /* 11 * 这里创建了一个ServiceConnection的匿名类,在这里重写了onServiceConnected方法和 12 * onServiceDisconnected方法,这两个方法分别会在活动与服务成功绑定以及解除绑定的时候调用。 13 * 在onServiceConnected方法中,我们又通过向下转型得到了DownloadBinder的实例,有了这个 14 * 实例,活动和服务之间的关系就变得非常紧密了,现在我们可以在活动中根据具体的场景来调用DownloadBinder 15 * 中的任何public方法,及实现了指挥服务干什么,服务就干什么的功能,这里只做了简单的测试,在onServiceConnected 16 * 中调用了DownloadBinder的startDownload(),getProgress()方法。 17 * */ 18 @Override 19 public void onServiceDisconnected(ComponentName name) { 20 // TODO Auto-generated method stub 21 22 } 23 24 @Override 25 public void onServiceConnected(ComponentName name, IBinder service) { 26 // TODO Auto-generated method stub 27 /*downloadBinder=(MyService.DownloadBinder) service; 28 downloadBinder.startDownload(); 29 downloadBinder.getProgress();*/ 30 } 31 }; 32 @Override 33 protected void onCreate(Bundle savedInstanceState) { 34 super.onCreate(savedInstanceState); 35 setContentView(R.layout.activity_main); 36 startService=(Button) findViewById(R.id.start_service); 37 stopService=(Button) findViewById(R.id.stop_service); 38 startService.setOnClickListener(this); 39 stopService.setOnClickListener(this); 40 41 bindService = (Button) findViewById(R.id.bind_service); 42 unbindService = (Button) findViewById(R.id.unbind_service); 43 bindService.setOnClickListener(this); 44 unbindService.setOnClickListener(this); 45 46 47 startIntentService=(Button) findViewById(R.id.start_intent_service); 48 startIntentService.setOnClickListener(this); 49 50 51 } 52 53 @Override 54 public boolean onCreateOptionsMenu(Menu menu) { 55 // Inflate the menu; this adds items to the action bar if it is present. 56 getMenuInflater().inflate(R.menu.main, menu); 57 return true; 58 } 59 60 @Override 61 public void onClick(View v) { 62 // TODO Auto-generated method stub 63 switch(v.getId()){ 64 case R.id.start_service: 65 Intent startIntent =new Intent(this,MyService.class); 66 startService(startIntent);//启动服务 67 break; 68 case R.id.stop_service: 69 Intent stopIntent =new Intent(this,MyService.class); 70 stopService(stopIntent);//停止服务 71 break; 72 case R.id.bind_service: 73 /* 74 *现在我们需要进行活动和服务的绑定,构建一个Intent对象,然后调用bindService()方法将 75 *MainActivity()和MyService进行绑定。 bindService方法接收三个参数,第一个参数就是 76 *上面创建出的Intent对象,第二个参数就是前面创建出的ServiceConnection的实例,第三个 77 *参数则是一个标志位,这里传入BIND_AUTO_CREATE表示在活动和服务进行绑定后自动创建服务。 78 *这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行。 79 * */ 80 Intent bindIntent=new Intent(this,MyService.class); 81 bindService(bindIntent, connection, BIND_AUTO_CREATE);//绑定服务 82 break; 83 case R.id.unbind_service: 84 /* 85 * 如果我们想解除活动和服务之间的绑定,调用一下unbindService()方法就可以了。 86 * */ 87 unbindService(connection);//解绑服务 88 break; 89 case R.id.start_intent_service: 90 //打印主线程的id 91 Log.d(MainActivity, Thread id is+Thread.currentThread().getId()); 92 Intent intentService=new Intent(this,MyIntentService.class); 93 startService(intentService); 94 break; 95 default: 96 break; 97 } 98 } 100 }
可以看到,我们在start intentservice按钮的点击事件里面去启动MyIntentService这个服务,并在这里打印了一下主线程的id,其实IntentService的用法和普通的服务没什么两样。
服务也有自己的生命周期,前面我们使用到的onCreate(),onStartCommand(),onBind()和onDestroy()等方法
都是在服务的生命周期内可能回掉的方法。一旦在项目的任何位置调用了Context的startService()方法,相应的服务就会启动起来,并回调onStartCommand()。如果这个服务之前还没创建过,onCreate()方法会先于onStartCommand()方法执行。服务启动了之后一直保持运行状态,直到stopService()或stopSelf()方法被调用。注意虽然每调用一次startService()方法,onStartCommand()就会执行一次,但实际上每个服务都只会存在一个实例。所以不管你调用了多少次startService()方法,只需调用一次stopService()或stopSelf()方法,服务就会停止下来了。
另外,还可以调用Context的bindService()来获取一个服务的持久连接,这时就会回调服务中的onBind()方法。类似地,
如果这个服务之前还没有创建过,onCreate()方法会先于onBind()方法执行。之后,调用方可以获取到onBind()方法里返回的IBinder对象的实例,这样就能自由地和服务进行通信了。只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态。当调用了startService()方法后,又去调用stopService()方法,这时服务中的onDestroy()方法就会执行,表示服务已经销毁了。类似地,当调用了bindService()方法后,又去调用unbindService()方法,onDestroy()方法也会执行,这两种情况都很好理解。
但是需要注意,我们是完全有可能对一个服务既调用了startService()方法,又调用了bindService()方法的,
这种情况下该如何才能让服务销毁掉?根据android系统的机制,一个服务只要被启动或者绑定了之后就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被销毁。所以,这种情况下需要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行。八、服务的最佳实践----后台执行的定时任务
Android中实现定时任务一般有两种方式,
-
- 一种是使用java api里提供的Timer类,
- 一种是使用android的Alarm机制。
这两种方式在多数情况下都能实现类似的效果,但是Timer有一个明显的短板,它并不太适用于那些需要长期在后台运行的定时任务。我们都知道,为了能让电池更加耐用,每种手机都会有自己的休眠策略,andorid手机就会在长时间不操作的情况下自动让cpu进入的到睡眠状态,这就有可能导致Timer中的定时任务无法正常运行。而Alarm机制不存在这种情况,它具有唤醒cpu的功能,即可以保证每次需要执行定时任务的时候cpu都能正常工作。需要注意,这里的唤醒cpu和唤醒屏幕完全不是同一个概念,不要弄混淆了。
我们来看看Alarm机制的用法吧,主要需要借助AlarmManager类来实现。这个类和NotificationManager有点类似,都是通过调用Context的getSystemService()方法来获取实例的,只是这里需要传入的参数是Context.ALARM_SERVICE。因此,获取一个AlarmManager的实例就可以写成:
1 AlarmManager manager=(AlarmManager)getSystemService(Context.ALARM_SERVICE);
接下来调用AarmManager对象的set()方法就可以设置一个定时任务了,比如说想要设定一个任务在10秒后执行,就可以写成:
1 long triggerAtTime=SystemClock.elapsedRealtime()+10*1000; 2 manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendingIntent);
- 第一个参数是一个整形参数,用于指定AlarmManager的工作类型,有四种值可选,分别是ELAPSED_REALTIME,ELAPSED_REALTIME_WAKEUP,RTC 和 RTC_WAKEUP。其中ELAPSED_REALTIME表示让定时任务的触发从系统开机开始算起,但不会唤醒cpu。ELAPSED_REALTIME_WAKEUP同样表示让定时任务的触发时间从系统开机开始算起,但会唤醒cpu。RTC表示让定时任务的触发时间从1970年1月1日0点开始算起,但不会唤醒cpu。RTC_WAKEUP同样表示让定时任务的触发时间从1970年1月1日0点开始算起,但会唤醒cpu。使用SystemClock.elapsedRealtime()方法可以获取到系统开机至今所历经的毫秒数,使用System.currentTimeMillis()方法可以获取到1970年1月1日0点至今所经历时间的毫秒数。
- 第二个参数就是定时任务触发的时间,以毫秒为单位。如果第一个参数使用的是ELAPSED_REALTIME或ELAPSED_REALTIME_WAKEUP则这里传入开机至今的时间在加上延迟执行的时间。如果第一个参数使用的是RTC或RTC_WAKEUP,则这里传入1970年1月1日0点至今的时间再加上延迟执行的时间。
- 第三个参数是一个PendingIntent,对于它应该不会陌生了 吧。这里我们一般会调用getBroadcast()方法来获取一个能够执行广播的PendingIntent。这样当定时任务被触发的时候,广播接收器的onReceive()方法就可以得到执行。
了解了 set()方法的每个参数之后,你应该能想到,设定一个任务在10秒后执行还可以写成:
1 long triggerAtTime=System.curentTimeMillis()+10*1000; 2 manager.set(AlarmManager.RTC_WAKEUP,triggerAtTime,pendingIntent);
现在已经掌握了Alarm机制的基本用法,下面我们就来创建一个可以长期在后台执行定时任务的服务。
- 创建一个ServiceBestPractice项目,
- 然后新增一个LongRunningService类,在onStartCommand()方法中开启了一个子线程,然后在子线程里就可以执行具体的逻辑操作了,这里简单的,只是打印了当前的时间。
1 public class LongRunningService extends Service { 2 3 @Override 4 public IBinder onBind(Intent intent) { 5 // TODO Auto-generated method stub 6 return null; 7 } 8 9 @Override 10 public int onStartCommand(Intent intent, int flags, int startId) { 11 // TODO Auto-generated method stub 12 new Thread(new Runnable(){ 13 14 @Override 15 public void run() { 16 // TODO Auto-generated method stub 17 Log.d(LongRunningService,executed at +new Date().toString()); 18 } 20 }).start();
21 AlarmManager manager=(AlarmManager) getSystemService(ALARM_SERVICE); 22 int anHour=10*1000; 23 long triggerAtTime=SystemClock.elapsedRealtime()+anHour; 24 Intent i=new Intent(this,AlarmReceiver.class); 25 PendingIntent pi=PendingIntent.getBroadcast(this, 0, i, 0); 26 manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 27 triggerAtTime, pi); 28 29 return super.onStartCommand(intent, flags, startId); 30 } 31 32 } - 创建线程之后的代码就是上面讲解的Alarm机制的用法,先是获取到了AlarmManager的实例,然后定义任务的触发时间为10秒,在使用PendingIntent指定处理定时任务的广播接收器为AlarmReceiver,最后调用set()方法完成设定。显然,AlarmReceiver不存在,我们就创建一个,onReceiver()方法里的代码非常简单,就是构建出了一个Intent对象,然后去启动LongRunningService这个服务代码如下所示:
1 public class AlarmReceiver extends BroadcastReceiver { 2 3 @Override 4 public void onReceive(Context context, Intent intent) { 5 // TODO Auto-generated method stub 6 7 Intent i=new Intent(context,LongRunningService.class); 8 context.startService(i); 9 } 10 }
这就已经将一个长期服务在后台定时运行的服务完成了。因为一旦启动了LongRunningService,就会在onStartCommand()方法里设定一个定时任务,这样10秒后AlarmReceiver的onReceive()方法就将得到执行了,然后我们在这里再次启动LongRunningService,这样就形成了一个永久的循环,保证LongRunningService可以每隔10秒就会启动一次,这个长期在后台运行的服务就完成了。
- 接下来,我们需要在打开程序的时候启动一次LongRunningService,之后LongRunningService就可以一直运行了。修改MainActivity中的代码,如下所示:
1 public class MainActivity extends Activity { 2 3 @Override 4 protected void onCreate(Bundle savedInstanceState) { 5 super.onCreate(savedInstanceState); 6 setContentView(R.layout.activity_main); 7 Intent intent=new Intent(this,LongRunningService.class); 8 startService(intent); 10 } 19 }
- 最后别忘了要注册服务和广播就OK了。
另外需要注意的是,从android4.4版开始,Alarm任务的触发时间将会变得不准确,有可能会延迟一段时间后任务才能得到执行。这并不是bug,而是系统在耗电方面进行的优化。系统会自动检测目前有多少Alarm任务存在,然后将触发时间将近的几个任务存放在一起执行,这就可以大幅度减少cpu被唤醒的次数,从而有效延长电池的使用时间。当然,如果要求Alarm任务的执行时间必须准确无误,android仍然提供l解决方案。使用AlarmManager的setExact()方法来替代set()方法,就可以保证任务准时执行了。