9.服务

服务Service

  • 运行于后台的一个组件,用来运行适合运行在后台的代码,服务是没有前台界面,可以视为没有界面的activity
  • 启动不了服务,在清单文件中写全包名 电话监听器
    • 电话状态:空闲、响铃、接听
    • 此代码在服务里运行,activity是很容易被杀死的
    • 录音机
      • 音频文件的编码和格式不是一一对应的
    • 获取电话管理器,设置侦听 TelephonyManager tm =(TelephonyManager) getSystemService(TELEPHONY_SERVICE); tm.listen(newMyPhoneStateListener(),PhoneStateListener.LISTEN_CALL_STATE);
    • 侦听对象的实现 classMyPhoneStateListenerextendsPhoneStateListener{ //当电话状态改变时,此方法调用 @Override publicvoid onCallStateChanged(int state,String incomingNumber){ // TODO Auto-generated method stub super.onCallStateChanged(state, incomingNumber); switch(state){ caseTelephonyManager.CALL_STATE_IDLE://空闲 if(recorder !=null){ recorder.stop(); recorder.release(); } break; caseTelephonyManager.CALL_STATE_OFFHOOK://摘机 if(recorder !=null){ recorder.start(); } break; caseTelephonyManager.CALL_STATE_RINGING://响铃 recorder =newMediaRecorder();//单词:媒体录音机 //设置声音来源 recorder.setAudioSource(MediaRecorder.AudioSource.MIC); //设置音频文件格式 recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);//3gp recorder.setOutputFile("sdcard/haha.3gp"); //设置音频文件编码 recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); try{ recorder.prepare(); }catch(IllegalStateException e){ // TODO Auto-generated catch block e.printStackTrace(); }catch(IOException e){ // TODO Auto-generated catch block e.printStackTrace(); } break; } } }
  1. publicclassBootReceiverextendsBroadcastReceiver{
  2. @Override
  3. publicvoid onReceive(Context context,Intent intent){
  4. //开机自启广播,启动录音机服务,在mainactivity中也启动服务
  5. Intent it =newIntent(context,RecorderService.class);
  6. context.startService(it);
  7. }
  8. }

开启方式

  • startService
    • 该方法启动的服务所在的进程属于服务进程
    • Activity一旦启动服务,服务就跟Activity一毛钱关系也没有了
  • bindService
    • 该方法启动的服务所在进程不属于服务进程
    • Activity与服务建立连接,Activity一旦死亡,服务也会死亡,跟启动它的组件同生共死
    • 绑定服务和解绑服务的生命周期方法:onCreate->onBind->onUnbind->onDestroy

找领导办证

  • 把服务看成一个领导,服务中有一个banZheng方法,如何才能访问?
  • 绑定服务时,会触发服务的onBind方法,此方法会返回一个Ibinder的对象给MainActivity,通过这个对象访问服务中的方法
    1. publicclassLeaderServiceextendsService{
    2. @Override
    3. publicIBinder onBind(Intent intent){
    4. // 返回一个Binder对象,这个对象就是中间人对象
    5. returnnewZhouMi();
    6. }
    7. //在服务中定义一个类实现Ibinder接口,以在onBind方法中返回
    8. classZhouMiextendsBinderimplementsPublicBusiness{
    9. publicvoidQianXian(){
    10. banZheng();
    11. }
    12. publicvoid daMaJiang(){
    13. System.out.println("陪李处打麻将");
    14. }
    15. }
    16. publicvoid banZheng(){
    17. System.out.println("李处帮你来办证");
    18. }
    19. }

    把QianXian方法抽取到接口PublicBusiness中定义,因为周密的如果有自己的方法(打麻将)返回onbind去,那在mainactivity中能访问这个方法了。 可以把想对外提供的方法定义一个接口,然后实现它。在mainactivity中可以强转成接口,提供对外的方法。pb.damajiang就不能调用了 当然也可以把打麻将方法弄成私有,不过一般都不那样写

    1. publicinterfacePublicBusiness{
    2. voidQianXian();
    3. }
    1. publicclassMainActivityextendsActivity{
    2. privateIntent intent;
    3. privateMyServiceConn conn;
    4. PublicBusiness pb;
    5. @Override
    6. protectedvoid onCreate(Bundle savedInstanceState){
    7. super.onCreate(savedInstanceState);
    8. setContentView(R.layout.activity_main);
    9. intent =newIntent(this,LeaderService.class);
    10. conn =newMyServiceConn();
    11. //绑定领导服务
    12. bindService(intent, conn, BIND_AUTO_CREATE);
    13. }
    14. publicvoid click(View v){
    15. //调用服务的办证方法
    16. pb.QianXian();
    17. }
    18. //绑定服务时要求必须传入一个ServiceConnection实现类的对象
    19. classMyServiceConnimplementsServiceConnection{
    20. //连接服务成功,此方法调用
    21. @Override
    22. publicvoid onServiceConnected(ComponentName name,IBinder service){
    23. // TODO Auto-generated method stub
    24. pb =(PublicBusiness) service;
    25. }
    26. //服务失去连接时,此方法调用
    27. @Override
    28. publicvoid onServiceDisconnected(ComponentName name){
    29. // TODO Auto-generated method stub
    30. }
    31. }
    32. }

    LeaderService.ZhouMi zm = ( LeaderService.ZhouMi) service;书中是这样转的 书中的方法

    1. publicclassMyServiceextendsService{
    2. privateDownloadBinder mBinder =newDownloadBinder();
    3. classDownloadBinderextendsBinder{
    4. publicvoid startDownload(){
    5. Log.d("MyService","startDownload executed");
    6. }
    7. publicint getProgress(){
    8. Log.d("MyService","getProgress executed");
    9. return0;
    10. }
    11. }
    12. @Override
    13. publicIBinder onBind(Intent intent){
    14. return mBinder;
    15. }
    16. ……
    17. }
    18. privateMyService.DownloadBinder downloadBinder;
    19. privateServiceConnection connection =newServiceConnection(){
    20. @Override
    21. publicvoid onServiceDisconnected(ComponentName name){
    22. }
    23. @Override
    24. publicvoid onServiceConnected(ComponentName name,IBinder service){
    25. downloadBinder =(MyService.DownloadBinder) service;
    26. downloadBinder.startDownload();
    27. downloadBinder.getProgress();
    28. }
    29. };
    30. @Override
    31. protectedvoid onCreate(Bundle savedInstanceState){
    32. super.onCreate(savedInstanceState);
    33. setContentView(R.layout.activity_main);
    34. ……
    35. bindService =(Button) findViewById(R.id.bind_service);
    36. unbindService =(Button) findViewById(R.id.unbind_service);
    37. bindService.setOnClickListener(this);
    38. unbindService.setOnClickListener(this);
    39. }

两种启动方法混合使用

  • 用服务实现音乐播放时,因为音乐播放必须运行在服务进程中,可是音乐服务中的方法,需要被前台Activity所调用,所以需要混合启动音乐服务
  • 先start,再bind,销毁时先unbind,在stop。先开始、再绑定,先解绑、再停止
  1. publicclassMusicServiceextendsService{
  2. @Override
  3. publicIBinder onBind(Intent intent){
  4. // TODO Auto-generated method stub
  5. returnnewMusicController();
  6. }
  7. //必须继承binder,才能作为中间人对象返回
  8. classMusicControllerextendsBinderimplementsMusicInterface{
  9. publicvoid play(){//不写MusicService.this就成了死循环了,自己调用自己
  10. MusicService.this.play();
  11. }
  12. publicvoid pause(){
  13. MusicService.this.pause();
  14. }
  15. }
  16. publicvoid play(){
  17. System.out.println("播放音乐");
  18. }
  19. publicvoid pause(){
  20. System.out.println("暂停播放");
  21. }
  22. }
  23. publicinterfaceMusicInterface{
  24. void play();
  25. void pause();
  26. }
  27. publicclassMainActivityextendsActivity{
  28. MusicInterface mi;
  29. @Override
  30. protectedvoid onCreate(Bundle savedInstanceState){
  31. super.onCreate(savedInstanceState);
  32. setContentView(R.layout.activity_main);
  33. Intent intent =newIntent(this,MusicService.class);
  34. //混合调用
  35. //为了把服务所在进程变成服务进程
  36. startService(intent);
  37. //为了拿到中间人对象
  38. bindService(intent,newMusicServiceConn(), BIND_AUTO_CREATE);
  39. }
  40. classMusicServiceConnimplementsServiceConnection{
  41. @Override
  42. publicvoid onServiceConnected(ComponentName name,IBinder service){
  43. // TODO Auto-generated method stub
  44. mi =(MusicInterface) service;
  45. }
  46. @Override
  47. publicvoid onServiceDisconnected(ComponentName name){
  48. // TODO Auto-generated method stub
  49. }
  50. }
  51. //开始播放按钮(an)
  52. publicvoid play(View v){
  53. mi.play();
  54. }
  55. //暂停播放按钮
  56. publicvoid pause(View v){
  57. mi.pause();
  58. }
  59. }

服务的生命周期

1.一旦在项目的任何位置调用了Context的startService()方法,相应的服务就会启动起来,并回调onStartCommand()方法。如果这个服务之前还没有创建过,onCreate()方法会先于onStartCommand()方法执行。

2.服务启动了之后会一直保持运行状态,直到 stopService()或stopSelf()方法被调用。注意虽然每调用一次startService()方法,onStartCommand()就会执行一次,但实际上每个服务都只会存在一个实例。所以不管调用了多少次startService()方法,只需调用一次stopService()或stopSelf()方法,服务就会停止下来了。

3.还可以调用Context的bindService()来获取一个服务的持久连接,这时就会回调服务中的onBind()方法。类似地,如果这个服务之前还没有创建过,onCreate()方法会先于onBind()方法执行。之后,调用方可以获取到onBind()方法里返回的IBinder对象的实例,这样就能自由地和服务进行通信了。只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态。

4.当调用了startService()方法后,又去调用 stopService()方法,这时服务中的 onDestroy()方法就会执行,表示服务已经销毁了。类似地,当调用了 bindService()方法后,又去调用unbindService()方法,onDestroy()方法也会执行,这两种情况都很好理解。但是需要注意,我们是完全有可能对一个服务既调用了startService()方法,又调用了bindService()方法的,这种情况下该如何才能让服务销毁掉呢?根据Android系统的机制,一个服务只要被启动或者被绑定了之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被销毁。所以,这种情况下要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行这样就把服务的生命周期完整地走了一遍。

服务的分类

  • 本地服务:指的是服务和启动服务的activity在同一个进程中
  • 远程服务:指的是服务和启动服务的activity不在同一个进程中

远程服务只能隐式启动,类似隐式启动Activity,在清单文件中配置Service标签时,必须配置intent-filter子节点,并指定action子节点

AIDL

  • Android interface definition language安卓接口定义语言
  • 作用:跨进程通信
  • 应用场景:远程服务中的中间人对象,其他应用是拿不到的,那么在通过绑定服务获取中间人对象时,就无法强制转换,使用aidl,就可以在其他应用中拿到中间人类所实现的接口

支付宝远程服务

  1. 定义支付宝的服务,在服务中定义pay方法
  2. 定义中间人对象,把pay方法抽取成接口
  3. 把抽取出来的接口后缀名改成aidl
  4. 在自动生成的PublicBusiness.java文件中,有一个静态抽象类Stub,它已经继承了binder类,实现了publicBusiness接口,这个类就是新的中间人
  5. 中间人对象直接继承Stub对象,然后在onbind方法中把它返回出去
  6. 注册这个支付宝服务,定义它的intent-Filter,这样其他应用才可以访问。

需要支付的应用

  1. 把刚才定义好的aidl文件拷贝过来,注意aidl文件所在的包名必须跟原包名一致
  2. 远程绑定支付宝的服务,通过onServiceConnected方法我们可以拿到中间人对象
  3. 把中间人对象通过Stub.asInterface方法强转成定义了pay方法的接口
  4. 调用中间人的pay方法
  5. publicclassMainActivityextendsActivity{
  6. privateMyserviceConn conn;
  7. PublicBusiness pb;
  8. @Override
  9. protectedvoid onCreate(Bundle savedInstanceState){
  10. super.onCreate(savedInstanceState);
  11. setContentView(R.layout.activity_main);
  12. conn =newMyserviceConn();
  13. }
  14. publicvoid click(View v){
  15. //启动远程服务
  16. Intent intent =newIntent();
  17. intent.setAction("com.itheima.remote");
  18. startService(intent);
  19. }
  20. publicvoid click2(View v){
  21. //停止远程服务
  22. Intent intent =newIntent();
  23. intent.setAction("com.itheima.remote");
  24. stopService(intent);
  25. }
  26. publicvoid click3(View v){
  27. Intent intent =newIntent();
  28. intent.setAction("com.itheima.remote");
  29. bindService(intent, conn, BIND_AUTO_CREATE);
  30. }
  31. publicvoid click4(View v){
  32. unbindService(conn);
  33. }
  34. classMyserviceConnimplementsServiceConnection{
  35. @Override
  36. publicvoid onServiceConnected(ComponentName name,IBinder service){
  37. //把Ibinder中间人对象强转成publicbusiness
  38. pb =Stub.asInterface(service);
  39. }
  40. @Override
  41. publicvoid onServiceDisconnected(ComponentName name){
  42. // TODO Auto-generated method stub
  43. }
  44. }
  45. publicvoid click5(View v){
  46. try{
  47. pb.qianXian();
  48. }catch(RemoteException e){
  49. // TODO Auto-generated catch block
  50. e.printStackTrace();
  51. }
  52. }
  53. }

不是非得调用startservice,bindService也可以,能访问支付宝服务里的方法就行,上面只是演示

  1. publicclassMainActivityextendsActivity{
  2. PayInterface pi;
  3. @Override
  4. protectedvoid onCreate(Bundle savedInstanceState){
  5. super.onCreate(savedInstanceState);
  6. setContentView(R.layout.activity_main);
  7. Intent intent =newIntent();
  8. intent.setAction("com.itheima.pangzhi");
  9. bindService(intent,newServiceConnection(){
  10. @Override
  11. publicvoid onServiceDisconnected(ComponentName name){
  12. // TODO Auto-generated method stub
  13. }
  14. @Override
  15. publicvoid onServiceConnected(ComponentName name,IBinder service){
  16. // 使用aidl中自动生成的方法来强转
  17. pi =Stub.asInterface(service);
  18. }
  19. }, BIND_AUTO_CREATE);
  20. }
  21. publicvoid click(View v){
  22. //调用远程服务的支付方法
  23. try{
  24. pi.pay();
  25. }catch(RemoteException e){
  26. // TODO Auto-generated catch block
  27. e.printStackTrace();
  28. }
  29. }
  30. }

  • 进程优先级
    • 前台进程
      • 拥有一个正在与用户交互的activity(onResume调用)的进程
      • 拥有一个与正在和用户交互的activity绑定的服务的进程
      • 拥有一个正在“运行于前台”的服务——服务的startForeground方法调用
      • 拥有一个正在执行以下三个生命周期方法中任意一个的服务(onCreate(), onStart(), or onDestroy())
      • 拥有一个正在执行onReceive方法的广播接收者的进程
    • 可见进程
      • 拥有一个不在前台,但是对用户依然可见的activity(onPause方法调用)的进程
      • 拥有一个与可见(或前台)activity绑定的服务的进程
    • 服务进程:拥有一个通过startService方法启动的服务
    • 后台进程:拥有一个不可见的Activity(onStop方法被调用)的进程
    • 空进程:没有拥有任何活动的应用组件的进程

使用服务注册广播接收者

  • Android四大组件都要在清单文件中注册
  • 广播接收者比较特殊,既可以在清单文件中注册,也可以直接使用代码注册
  • 有的广播接收者,必须代码注册,清单注册无效,因为这俩个发生的太平常。因为比如屏幕解锁改变不需要 一直就是他的广播,只在发生改变时接收就行了;电量改变在运行你的程序时接收就行了
    • 电量改变(不是低电广播)
    • 屏幕锁屏和解锁
    1. publicclassMainActivityextendsActivity{
    2. privateIntent intent;
    3. @Override
    4. protectedvoid onCreate(Bundle savedInstanceState){
    5. super.onCreate(savedInstanceState);
    6. setContentView(R.layout.activity_main);
    7. intent =newIntent(this,RegisterService.class);
    8. }
    9. publicvoid start(View v){
    10. startService(intent);
    11. }
    12. publicvoid stop(View v){
    13. stopService(intent);
    14. }
    15. }
    16. publicclassRegisterServiceextendsService{
    17. privateScreenReceiver receiver;
    18. @Override
    19. publicIBinder onBind(Intent intent){
    20. // TODO Auto-generated method stub
    21. returnnull;
    22. }
    23. @Override
    24. publicvoid onCreate(){
    25. super.onCreate();
    26. //1.创建广播接收者对象
    27. receiver =newScreenReceiver();
    28. //2.创建intent-filter对象
    29. IntentFilter filter =newIntentFilter();
    30. filter.addAction(Intent.ACTION_SCREEN_OFF);
    31. filter.addAction(Intent.ACTION_SCREEN_ON);
    32. //3.注册广播接收者
    33. registerReceiver(receiver, filter);
    34. }
    35. @Override
    36. publicvoid onDestroy(){
    37. super.onDestroy();
    38. //解除注册
    39. unregisterReceiver(receiver);
    40. }
    41. }
    42. publicclassScreenReceiverextendsBroadcastReceiver{
    43. @Override
    44. publicvoid onReceive(Context context,Intent intent){
    45. // TODO Auto-generated method stub
    46. String action = intent.getAction();
    47. if(Intent.ACTION_SCREEN_OFF.equals(action)){
    48. System.out.println("屏幕关闭");
    49. }
    50. elseif(Intent.ACTION_SCREEN_ON.equals(action)){
    51. System.out.println("屏幕打开");
    52. }
    53. }
    54. }

使用前台服务

  • 服务几乎都是在后台运行的,一直以来它都是默默地做着辛苦的工作。但是服务的系统优先级还是比较低的,当系统出现内存不足的情况时,就有可能会回收掉正在后台运行的服务。如果你希望服务可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台服务。前台服务和普通服务最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏可以看到更加详细的信息,非常类似于通知的效果。当然有时候你也可能不仅仅是为了防止服务被回收掉才使用前台服务的,有些项目由于特殊的需求会要求必须使用前台服务,比如说墨迹天气,它的服务在后台更新天气数据的同时,还会在系统状态栏一直显示当前的天气信息
    1. //和创建通知的方法类似。只不过这次在构建出Notification对象后并没有使用NotificationManager来将通知显示出来,而是调用了startForeground()方法。这个方法接收两个参数,第一个参数是通知的id,类似于notify()方法的第一个参数,第二个参数则是构建出的Notification对象。调用startForeground()方法后就会让MyService变成一个前台服务,并在系统状态栏显示出来。现在重新运行一下程序,并点击StartService或BindService按钮,MyService就会以前台服务的模式启动了,并且在系统状态栏会显示一个通知图标,下拉状态栏后可以看到该通知的详细内容
    2. Notification notification=newNotification(R.drawable.ic_launcher,"notification comes",System.currentTimeMillis());
    3. Intent notificationIntent=newIntent(this,MainActivity.class);
    4. PendingIntent pendingIntent=PendingIntent.getActivity(this,0,notificationIntent,0);
    5. notification.setLatestEventInfo(this,"This is title","This iscontent", pendingIntent);
    6. startForeground(1, notification);
    7. Log.d("MyService","onCreate executed");

后台定时任务

  • Android中的定时任务一般有两种实现方式,一种是使用 Java API里提供的 Timer类,一种是使用 Android的 Alarm机制。 这两种方式在多数情况下都能实现类似的效果, 但 Timer有一个明显的短板,它并不太适用于那些需要长期在后台运行的定时任务。我们都知道,为了能让电池更加耐用,每种手机都会有自己的休眠策略,Android手机就会在长时间不操作的情况下自动让 CPU进入到睡眠状态,这就有可能导致 Timer中的定时任务无法正常运行。
  • 而 Alarm机制则不存在这种情况,它具有唤醒 CPU的功能,即可以保证每次需要执行定时任务的时候 CPU都能正常工作。 需要注意, 这里唤醒 CPU和唤醒屏幕完全不是同一个概念,千万不要产生混淆。
  • 那么首先来看一下 Alarm 机制的用法吧,其实并不复杂,主要就是借助了AlarmManager类来实现的。这个类和NotificationManager有点类似,都是通过调用Context的getSystemService()方法来获取实例的, 只是这里需要传入的参数是Context.ALARM_SERVICE。因此,获取一个 AlarmManager的实例就可以写成:

AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

  • 接下来调用 AlarmManager的 set()方法就可以设置一个定时任务了,比如说想要设定一个任务在 10秒钟后执行,就可以写成:

long triggerAtTime = SystemClock.elapsedRealtime() + 10 * 1000;

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秒钟后执行还可以写成:

long triggerAtTime = System.currentTimeMillis() + 10 * 1000;

manager.set(AlarmManager.RTC_WAKEUP, triggerAtTime, pendingIntent);

 创建一个 ServiceBestPractice项目, 然后新增一个 LongRunningService类,代码如下所示:

  1. publicclassLongRunningServiceextendsService{
  2. @Override
  3. publicIBinder onBind(Intent intent){
  4. returnnull;
  5. }
  6. @Override
  7. publicint onStartCommand(Intent intent,int flags,int startId){
  8. newThread(newRunnable(){
  9. @Override
  10. publicvoid run(){
  11. Log.d("LongRunningService",
  12. "executed at "+newDate().toString());
  13. }
  14. }).start();
  15. AlarmManager manager =(AlarmManager) getSystemService(ALARM_SERVICE);
  16. int anHour =60*60*1000;// 这是一小时的毫秒数
  17. long triggerAtTime =SystemClock.elapsedRealtime()+ anHour;
  18. Intent i =newIntent(this,AlarmReceiver.class);
  19. PendingIntent pi =PendingIntent.getBroadcast(this,0, i,0);
  20. manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);
  21. returnsuper.onStartCommand(intent, flags, startId);
  22. }
  23. }

下一步是要新建一个 AlarmReceiver类,并让它继承自 BroadcastReceiver,代码如下所示:

  1. publicclassAlarmReceiverextendsBroadcastReceiver{
  2. @Override
  3. publicvoid onReceive(Context context,Intent intent){
  4. Intent i =newIntent(context,LongRunningService.class);
  5. context.startService(i);
  6. }
  7. }

这就已经将一个长期在后台定时运行的服务完成了。因为一旦启动 LongRunningService,就会在onStartCommand()方法里设定一个定时任务,这样一小时后 AlarmReceiver的 onReceive()方法就将得到执行,然后在这里再次启动 LongRunningService,这样就形成了一个永久的循环,保证 LongRunningService可以每隔一小时就会启动一次,一个长期在后台定时运行的服务自然也就完成了。

接下来需要在打开程序的时候启动一次LongRunningService,之后 LongRunningService就可以一直运行了。修改 MainActivity中的代码,如下所示:

  1. publicclassMainActivityextendsActivity{
  2. @Override
  3. protectedvoid onCreate(Bundle savedInstanceState){
  4. super.onCreate(savedInstanceState);
  5. Intent intent =newIntent(this,LongRunningService.class);
  6. startService(intent);
  7. }
  8. }
  9. <service android:name=".LongRunningService">
  10. </service>
  11. <receiver android:name=".AlarmReceiver">
  12. </receiver>

使用 IntentService

服务中的代码都是默认运行在主线程当中的,如果直接在服务里去处理一些耗时的逻辑,就很容易出现 ANR(Application NotResponding)的情况。所以这个时候就需要用到Android多线程编程的技术,应该在服务的每个具体的方法里开启一个子线程,然后在这里去处理那些耗时的逻辑,但是,这种服务一旦启动之后,就会一直处于运行状态,必须调用 stopService()或者stopSelf()方法才能让服务停止下来。所以,如果想要实现让一个服务在执行完毕后自动停止的功能,就可以这样写:

  1. publicint onStartCommand(Intent intent,int flags,int startId){
  2. returnsuper.onStartCommand(intent, flags, startId);
  3. newThread(newRunnable(){
  4. @Override
  5. publicvoid run(){
  6. // 处理具体的逻辑
  7. stopSelf();
  8. }
  9. }).start
  10. }
  11. @Override
  12. publicvoid onDestroy(){
  13. super.onDestroy();
  14. }
  15. }

虽说这种写法并不复杂,但是总会有一些程序员忘记开启线程,或者忘记调用stopSelf()方法。为了可以简单地创建一个异步的、会自动停止的服务,Android专门提供了一个IntentService类,这个类就很好地解决了前面所提到的两种尴尬,下面就来看一下它的用法。新建MyIntentService类继承自IntentService

  1. publicclassMyIntentServiceextendsIntentService
  2. {
  3. //服务里开启子线程
  4. //1.提供一个无参的构造函数,并且必须在其内部调用父类的有参构造函数
  5. publicMyIntentService(){
  6. super("MyIntentService");//调用父类的有参构造函数
  7. }
  8. //2.在子类中去实现onHandleIntent()这个抽象方法,在这个方法中可以去处理一些具体的逻辑,而且不用担心ANR的问题,因为这个方法已经是在子线程中运行的了。这里为了证实一下在onHandleIntent()方法中打印了当前线程的id
  9. @Override
  10. protectedvoid onHandleIntent(Intent p1)
  11. {
  12. // TODO: Implement this method
  13. // 打印当前线程的id
  14. Log.d("MyIntentService","Thread id is "+Thread.currentThread().getId());
  15. }
  16. //3.根据IntentService的特性,这个服务在运行结束后应该是会自动停止的,所以重写onDestroy()方法,在这里也打印了一行日志,以证实服务是不是停止掉了。
  17. publicvoid onDestroy(){
  18. super.onDestroy();
  19. Log.d("MyIntentService","onDestroy executed");
  20. }
  21. //4.加入一个用于启动MyIntentService这个服务的按钮
  22. //5.在StartIntentService按钮的点击事件里面去启动MyIntentService这个服务, 并在这里打印了一下主线程的id,稍后用于和IntentService进行比对,其实IntentService的用法和普通的服务没什么两样。
  23. //6.不要忘记,服务都是需要在AndroidManifest.xml里注册的点击Start IntentService按钮后,观察LogCat中的打印日志可以看到,不仅MyIntentService和MainActivity所在的线程id不一样,而且onDestroy()方法也得到了执行,说明MyIntentService在运行完毕后确实自动停止了。集开启线程和自动停止于一身,IntentService还是博得了不少程序员的喜爱
  24. }

遇到的错误:

//这是在服务里,接收activity传递过来的数据,每次用户点击ListActivity当中的一个条目时,就会服务里的该方法

@Override

publicint onStartCommand(Intent intent,int flags,int startId){

// TODO Auto-generated method stub

//从Intent对象当中将Mp3Info对象取出

Mp3Info mp3Info =(Mp3Info)intent.getSerializableExtra("mp3Info");

//生成一个下载线程,并将Mp3Info对象作为参数传递到线程对象当中

DownloadThread downloadThread =newDownloadThread(mp3Info);

//启动新线程

Thread thread =newThread(downloadThread);

thread.start();

returnsuper.onStartCommand(intent, flags, startId);

}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏玄魂工作室

Scrapy爬虫入门

快两周了,还没缓过来劲,python 黑帽的系列教程今天才开始捡起来。不过工作又要忙了,晚上照顾玄小魂,白天敲代码,抽时间写文章,真的有点心力交瘁。不过没关系,...

3267
来自专栏我就是马云飞

RxJava2 实战知识梳理(3) - 优化搜索联想功能

应用场景 几乎每个应用程序都提供了搜索功能,某些应用还提供了搜索联想。对于一个搜索联想功能,最基本的实现流程为:客户端通过EditText的addTextCha...

2647
来自专栏Kubernetes

原 荐 深度解析Kubernetes Pod

Author: xidianwangtao@gmail.com PDB的应用场景 大概在Kubernetes 1.4新增了PodDisruptionBudge...

1.2K13
来自专栏项勇

笔记16 | 解析和练习AsyncTask

1716
来自专栏小樱的经验随笔

【批处理学习笔记】第十四课:常用DOS命令(4)

系统管理 at 安排在特定日期和时间运行命令和程序 shutdown立即或定时关机或重启 taskkill结束进程(WinXPHome版中无该命令) taskl...

2513
来自专栏向治洪

Android ORM 框架之 greenDAO

前言 我相信,在平时的开发过程中,大家一定会或多或少地接触到 SQLite。然而在使用它时,我们往往需要做许多额外的工作,像编写 SQL 语句与解析查询结果等。...

2176
来自专栏肖蕾的博客

关于AndroidStudio混淆打包 proguard-rules.pro 的配置关于AndroidStudio混淆打包 proguard-rules.pro 的配置

1732
来自专栏Android先生

RxJava2 实战知识梳理(3) - 优化搜索联想功能

几乎每个应用程序都提供了搜索功能,某些应用还提供了搜索联想。对于一个搜索联想功能,最基本的实现流程为:客户端通过EditText的addTextChan...

771
来自专栏项勇

笔记37 | Android App优化之ANR详解

2376
来自专栏Android机动车

Android实现异步的几种方式——从简单的图片加载说起

说到异步,脑海中立马浮现的就是多线程开发,Thread、Handler啥的一一涌上心头…

1515

扫码关注云+社区

领取腾讯云代金券