- Service的基本概念
- 为什么要有Service
- Service的基本用法
- 电话窃听器的小案例
- Service和Activity通信
- Service和Thread的关系
向光明而行!
Service的基本概念
Service是Android的四大组件之一,在每一个应用程序中都扮演者非常重要的角色。
它主要用于在后台处理一些耗时的逻辑,或者去执行某些需要长期运行的任务。必要的时候,我们甚至可以在程序退出的情况下,让Service在后台继续保持运行状态。
既然都是被用于处理耗时的操作,那么我们什么时候使用Service,什么时候使用Thread呢?这就涉及到进程与线程之间的关系了,我们进入到下一节。
为什么要有Service
首先我们要知道,当一个应用开启后,并且没有任何其他的组件运行时,Android操作系统就给我们的应用开启了一个新的Linux进程并在里面创建了一个独立的线程。默认的,应用的所有组件都会运行在相同进程和线程(这个线程被称为”main”主线程)中运行。
进程创建后,就有了生命周期。Android操作系统会尽可能长时间的去维系一个应用进程保持运行。当然,如果为了创建一个新的或者更为重要的进程,Android操作系统也会移除旧的进程去释放内存。
根据进程中运行的组件和组件的状态,Andriod系统维护着一个”优先级关系“,来决定哪个进程被保持运行或者被杀死,最不重要的进程最先被杀死。
下面是优先级关系的列表(第一个进程最重要也是最后被杀死)
- 前台进程(Foreground process)
简单来说,当用户和应用程序交互时就是前台进程,此时会调用Activity的onResume()
方法。 - 可视进程
当一个应用程序不可以和用户交互,但是仍然可以被用户看到时就是可是进程,此时会调用Activity的onPause()
方法。 - 服务进程
当使用startService()
开启一个服务时,这个服务就处于服务进程中。 - 后台进程
当应用的Activity对当前用户都不可见时就是后台进程,此时会调用Activity的onStop()
方法。 - 空进程
应用进程中没有任何活动的组件时,就是空进程。当应用中没有服务,并且一直按返回键直到回到桌面时,这个应用就编程了空进程了。
当我们的应用开启了子线程并执行逻辑时,被用户按返回键变成了空进程时。如果有更重要的应用被启动,那么空进程可能会被Android操作系统杀死,我们的子线程中的逻辑也就会被中断,这样逻辑就不安全了。这是这个原因,Android才为我们提供了更稳定的Service方法。
Service的基本用法
Service最基本的用法自然就是如何启动一个Service了,启动Service的方法和启动Activity很类似,都需要借助Intent来实现,下面我们就通过一个具体的例子来看一下。
- 首先要明确,创建一个简单服务的基本步骤:
- ①写一个类继承
Service
,并重写其中的onCreate()
、onStartCommand()
、onDestory()
等生命周期方法。其中onBind()
方法是服务和Activity通信使用的,后面在讲解。 - ②Service是四大组件之一,需要在清单文件中声明。
- ③使用
startService(intent)
启动服务和使用stopService(intent)
停止服务。
接下来我们就创建一个简单的工程,画一个界面来操作一下。
首先,写一个类继承Service
,并重写其中的onCreate()
、onStartCommand()
、onDestory()
等生命周期方法,并在其中打印一些Log。代码如下:
public class MyService extends Service{ @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); System.out.println("服务第一次被创建时调用"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { System.out.println("服务启动了"); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); System.out.println("服务销毁了"); }}
其次,在清单文件中声明服务。
<application ... > ... <service android:name="com.bzh.service01.MyService" > </service></application>
最后,做好以上这些操作后,我们就可以启动服务了。我们通过如下代码来启动和停止服务。
public void start(View v) { // 准备好开启服务的意图 Intent service = new Intent(this, MyService.class); // 开启服务 startService(service);}public void stop(View v) { Intent service = new Intent(this, MyService.class); // 停止服务 stopService(service);}
值得说明的是,当启动一个Service的时候,会调用该Service中的onCreate()
和onStartCommand()
方法。当我们再次点击启动服务按钮时,就只有onStartCommand()
方法执行了,onCreate()
方法并没有执行。
之所以这样是由于onCreate()
方法只会在Service第一次被创建的时候调用,如果当前Service已经被创建过了,不管怎样调用启动服务的方法,onCreate()
方法都不会再执行。
而当我们点击停止服务是,onDesctory()
方法会被调用。请看一下,调用的测试图。
当我们启动服务后,我们可以在后台查看正在运行的服务。
电话窃听器的小案例
接下来,再通过一个小案例巩固一下服务、TelephonyManager
和录音设备的使用。
- 开发的基本步骤是:
- ①创建
PhoneService
类并继承Service
;在AndroidManifest.xml
文件中声明服务。 - ②获取到电话管理服务器(
TelephonyManager
),监听电话到来的状态; - ③判断电话的三种状态(空闲、响铃、接通),并在相应的方法中获取录音设备(
MediaRecorder
)、配置参数、录制、释放资源; - ④在应用启动时启动服务,退出时销毁服务。
第一步,创建PhoneService
类并继承Service
;在AndroidManifest.xml
文件中声明服务。请看下面的简略代码;
public class PhoneService extends Service { @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); System.out.println("服务第一次被创建"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { System.out.println("服务启动"); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); System.out.println("服务销毁"); }}
在清单文件中声明服务:
<service android:name="com.bzh.phonelistener.PhoneService" ></service>
第二步,获取到电话管理服务器(TelephonyManager
),监听电话到来的状态;
我们在PhoneService
类的onCreate()
方法中,拿到电话管理者的实例,并监听电话状态的改变。
@Overridepublic void onCreate() { super.onCreate(); System.out.println("服务第一次被创建"); // 拿到电话服务的管理者实例 TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); // 去监听电话状态的改变 telephonyManager.listen(new MyPhoneListener(), PhoneStateListener.LISTEN_CALL_STATE);}
值得注意的是,我们去监听电话状态影响到了用户的隐私,所以需要添加权限。
<!-- 监听电话状态所需的权限 --><uses-permission android:name="android.permission.READ_PHONE_STATE" />
第三步,判断电话的三种状态(空闲、响铃、接通),并在相应的方法中获取录音设备(MediaRecorder
)、配置参数、录制、释放资源;
在上一步中TelephonyManager
对象中listen(PhoneStateListener listener, int events)
方法,可以注册一个监听对象去接收指定电话设备状态的通知,那么我们下面的重点就在这个监听对象上了。
我们创建一个内容类,继承PhoneStateListener
,并重写onCallStateChanged(int state, String incomingNumber)
方法,接收改变的事件。事件分为三种:空闲(TelephonyManager.CALL_STATE_IDLE
)、响铃(TelephonyManager.CALL_STATE_RINGING
)、接通(TelephonyManager.CALL_STATE_OFFHOOK
)。
另外,当电话到来时我们需要用到MediaRecorder
类去记录音频数据。分别在空闲、响铃、接通状态中做初始化、配置、准备录音、结束录音等操作。请看代码。
private class MyPhoneListener extends PhoneStateListener { private MediaRecorder recorder; @Override public void onCallStateChanged(int state, String incomingNumber) { super.onCallStateChanged(state, incomingNumber); switch (state) { case TelephonyManager.CALL_STATE_IDLE: // 电话处于空闲状态 System.out.println("电话处于空闲状态"); if (recorder != null) { // 停止录音并释放资源 recorder.stop(); recorder.reset(); recorder.release(); System.out.println("录音成功"); } break; case TelephonyManager.CALL_STATE_RINGING:// 电话处于响铃状态 System.out.println("电话处于响铃状态"); // 拿到录音机 recorder = new MediaRecorder(); // 设置录音的声音来源;MIC是麦克风;VOICE_CALL是双方通话的声音,但是好多手机不支持; recorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 音频输出格式为3GP recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); // 音频编码格式 recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); // 音频的输出目录 recorder.setOutputFile("/mnt/sdcard/" + UUID.randomUUID().toString() + ".3gp"); try { recorder.prepare(); } catch (Exception e) { e.printStackTrace(); } break; case TelephonyManager.CALL_STATE_OFFHOOK:// 电话接通 System.out.println("电话接通"); // 开始录制 recorder.start(); // Recording is now started System.out.println("开始录制"); break; default: break; } }}
此时,当我们执行,会包一个异常,是由于未加权限引起的。
下面是录音所加的权限。
<!-- 录音和保存录音所需的权限 --><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.RECORD_AUDIO" />
最后一步,在应用启动时启动服务,退出时销毁服务。这里的代码就更简单了。
@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 开启窃听服务 Intent service = new Intent(this, PhoneService.class); startService(service);}@Overrideprotected void onDestroy() { super.onDestroy(); Intent service = new Intent(this, PhoneService.class); // 销毁服务 stopService(service);}
至此,整个小案例就结束了。通过这个案例,练习了服务的基本使用方法,和电话管理者对电话状态的监听,以及如何录音。
Service和Activity通信
上面我们学习了Service的基本用法,启动Service之后,就可以在onCreate()
或onStartCommand()
方法里去执行一些具体的逻辑了。
不过这样的话Service和Activity的关系并不大,只是Activity通知了Service一下:“你可以启动了。”然后Service就去忙自己的事情了。那么有没有什么办法能让它们俩的关联更多一些呢?比如说在Activity中可以调用Service中的方法。
当然可以,只需要让Activity和Service建立关联就好了,这其中就用到了ContextWrapper.bindService()
方法和IBinder
接口了。
通过查看Api可以发现IBinder
接口描述了抽象的协议如何与远程对象进行交互的。而Google又为我们提供了默认的实现Binder
类。帮助我们在Activity和Service之间进行通信。
我们再来看一下ContextWrapper.bindService()
的API注释Connect to an application service, creating it if needed.
,简单来说就是,通过这个方法去连接到Service,如果Service还没有创建,并且需要去创建,那么就创建它。
此外,有了绑定服务的方法,那么也会有解除绑定的unbindService()
方法。
这样说来说去可能大家都晕了,还是通过一个工程来讲解吧。
创建一个工程,界面如下,每个Button都对应着自己的方法。
界面布局非常简单,这里为了节省篇幅就不贴了。
那么我们先看一下MainActivity里面都做了些什么吧。
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 实例化Activity与Service的连接对象 conn = new MyServiceConnection(); } ServiceConnection conn; // 启动服务 public void start(View v) { Intent service = new Intent(this, MyService.class); startService(service); } // 绑定服务 public void bind(View v) { Intent service = new Intent(this, MyService.class); // 参数1:Activity要绑定的Service // 参数2:Activity与Service连接所需的连接对象 // 参数3:连接时,如果服务还没有创建就自动创建 bindService(service, conn, Context.BIND_AUTO_CREATE); } // 解绑服务 public void unbind(View v) { // 解除连接 unbindService(conn); } // 停止服务 public void stop(View v) { Intent service = new Intent(this, MyService.class); stopService(service); } // 监视服务的状态 private class MyServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { System.out.println("MainActivity和MyService已经连接成功"); MyBinder myBinder = (MyBinder) service; myBinder.callMethod1(); myBinder.callMethod2(); } @Override public void onServiceDisconnected(ComponentName name) { } }}
除了Binder
,Activity与Service建立连接还需要一个实现ServiceConnection
接口的对象。
可以看到,这里我们创建了一个实现ServiecConnection
接口的内部类,在里面重写了onServiceConnected()
方法和onServiceDisconnected()
方法,这两个方法分别会在Activity与Service建立关联和解除关联的时候调用。
在onServiceConnected()
方法中,我们又通过向下转型得到了MyBinder
的实例,有了这个实例,Activity和Service之间的关系就变得非常紧密了。
现在我们可以在Activity中根据具体的场景,并借助MyBinder
中的任何public方法去调用服务中的任何方法了,即实现了Activity指挥Service干什么Service就去干什么的功能。
再看一下MyService中的方法。
public class MyService extends Service { @Override public IBinder onBind(Intent intent) { // 返回自定的Binder,帮助Activity与Service之间的通信 return new MyBinder(); } @Override public void onCreate() { super.onCreate(); System.out.println("onCreate()"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { System.out.println("onStartCommand()"); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { System.out.println("onDestroy()"); super.onDestroy(); } public void method1() { System.out.println("我是服务中的方法1"); } public void method2() { System.out.println("我是服务中的方法2"); } class MyBinder extends Binder { public void callMethod1() { method1(); } public void callMethod2() { method2(); } }}
MyService中的逻辑也比较简单,其中Activity与Service建立关联的核心方法就是onBind()
方法,在其中我们返回了继承了Binder
类MyBinder的实例。这样在MainActivity的MyServiceConnection内部类中拿到IBinder并向下转型后,就可以随意的操纵服务中的方法了。
另外非常值得注意的一点:
在bindService(service, conn, Context.BIND_AUTO_CREATE);
中可以看到,当去连接时,如果服务还没有创建,便会通过Context.BIND_AUTO_CREATE
标识位来自动帮助我们创建服务。
但是这样你会发现startService()
、bindService()
、unbindService()
、stopService()
它们之间如果乱序的调用,会非常容易出现异常。
而Google也给出了建议的调用顺序,我们只需要遵守就可以了:
- 如果需要长期运行的服务,那么就使用
startService()
来开启。 - 如果需要Activity与Service进行交互,那么先使用
startService()
来开启服务,再使用bindService()
方法绑定服务、建立连接。 - 如果需要终止Activity与Service之间的交互,那么调用
unbindService()
方法解除它们之间的绑定和连接。 - 如果需要之中服务,那么首先看是否有Activity与Service建立了连接,如果有就先调用
unbindService
再去调用stopService()
去停止服务。如果没有,那么直接调用stopService()
方法去停止服务。
Service和Thread的关系
这里再说下Service和Thread的关系,如果不说清楚,可能会弄混。
Service和Thread到底有什么关系呢?什么时候应该用Service,什么时候又应该用Thread?答案可能会有点让你吃惊,因为Service和Thread之间没有任何关系!
之所以有不少人会把它们联系起来,主要就是因为Service的后台概念。Thread我们大家都知道,是用于开启一个子线程,在这里去执行一些耗时操作就不会阻塞主线程的运行。
而Service我们最初理解的时候,总会觉得它是用来处理一些后台任务的,一些比较耗时的操作也可以放在这里运行,这就会让人产生混淆了。但是,如果我告诉你Service其实是运行在主线程里的,你还会觉得它和Thread有什么关系吗?
在MainActivity的onCreate()方法里加入一行打印当前线程id的语句:
Log.i("MyService", "MainActivity thread id is " + Thread.currentThread().getId());
然后在MyService的onCreate()方法里也加入一行打印当前线程id的语句:
Log.i("MyService", "MyService thread id is " + Thread.currentThread().getId());
请看测试结果:
可以看到,它们的线程id完全是一样的,由此证实了Service确实是运行在主线程里的,也就是说如果你在Service里编写了非常耗时的代码,程序必定会出现ANR的。
你可能会惊呼,这不是坑爹么!?那我要Service又有何用呢?其实大家不要把后台和子线程联系在一起就行了,这是两个完全不同的概念。
Android的后台就是指,它的运行是完全不依赖UI的。即使Activity被销毁,或者程序被关闭,只要进程还在,Service就可以继续运行。
比如说一些应用程序,始终需要与服务器之间始终保持着心跳连接,就可以使用Service来实现。你可能又会问,前面不是刚刚验证过Service是运行在主线程里的么?在这里一直执行着心跳连接,难道就不会阻塞主线程的运行吗?当然会,但是我们可以在Service中再创建一个子线程,然后在这里去处理耗时逻辑就没问题了。
既然在Service里也要创建一个子线程,那为什么不直接在Activity里创建呢?这是因为Activity很难对Thread进行控制,当Activity被销毁之后,就没有任何其它的办法可以再重新获取到之前创建的子线程的实例。
而且在一个Activity中创建的子线程,另一个Activity无法对其进行操作。但是Service就不同了,所有的Activity都可以与Service进行关联,然后可以很方便地操作其中的方法,即使Activity被销毁了,之后只要重新与Service建立关联,就又能够获取到原有的Service中Binder的实例。
因此,使用Service来处理后台任务,Activity就可以放心地finish,完全不需要担心无法对后台任务进行控制的情况。
一个比较标准的Service就可以写成:
@Override public int onStartCommand(Intent intent, int flags, int startId) { new Thread(new Runnable() { @Override public void run() { // 开始执行后台任务 } }).start(); return super.onStartCommand(intent, flags, startId); } class MyBinder extends Binder { public void startDownload() { new Thread(new Runnable() { @Override public void run() { // 执行具体的下载任务 } }).start(); } }