标签:android 后台 service 通信 activity
向光明而行!
Service是Android的四大组件之一,在每一个应用程序中都扮演者非常重要的角色。
它主要用于在后台处理一些耗时的逻辑,或者去执行某些需要长期运行的任务。必要的时候,我们甚至可以在程序退出的情况下,让Service在后台继续保持运行状态。
既然都是被用于处理耗时的操作,那么我们什么时候使用Service,什么时候使用Thread呢?这就涉及到进程与线程之间的关系了,我们进入到下一节。
首先我们要知道,当一个应用开启后,并且没有任何其他的组件运行时,Android操作系统就给我们的应用开启了一个新的Linux进程并在里面创建了一个独立的线程。默认的,应用的所有组件都会运行在相同进程和线程(这个线程被称为”main”主线程)中运行。
进程创建后,就有了生命周期。Android操作系统会尽可能长时间的去维系一个应用进程保持运行。当然,如果为了创建一个新的或者更为重要的进程,Android操作系统也会移除旧的进程去释放内存。
根据进程中运行的组件和组件的状态,Andriod系统维护着一个”优先级关系“,来决定哪个进程被保持运行或者被杀死,最不重要的进程最先被杀死。
下面是优先级关系的列表(第一个进程最重要也是最后被杀死)
onResume()
方法。onPause()
方法。startService()
开启一个服务时,这个服务就处于服务进程中。onStop()
方法。当我们的应用开启了子线程并执行逻辑时,被用户按返回键变成了空进程时。如果有更重要的应用被启动,那么空进程可能会被Android操作系统杀死,我们的子线程中的逻辑也就会被中断,这样逻辑就不安全了。这是这个原因,Android才为我们提供了更稳定的Service方法。
Service最基本的用法自然就是如何启动一个Service了,启动Service的方法和启动Activity很类似,都需要借助Intent来实现,下面我们就通过一个具体的例子来看一下。
Service
,并重写其中的onCreate()
、onStartCommand()
、onDestory()
等生命周期方法。其中onBind()
方法是服务和Activity通信使用的,后面在讲解。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()
方法中,拿到电话管理者的实例,并监听电话状态的改变。
@Override
public 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" />
最后一步,在应用启动时启动服务,退出时销毁服务。这里的代码就更简单了。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 开启窃听服务
Intent service = new Intent(this, PhoneService.class);
startService(service);
}
@Override
protected void onDestroy() {
super.onDestroy();
Intent service = new Intent(this, PhoneService.class);
// 销毁服务
stopService(service);
}
至此,整个小案例就结束了。通过这个案例,练习了服务的基本使用方法,和电话管理者对电话状态的监听,以及如何录音。
上面我们学习了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()
来开启。startService()
来开启服务,再使用bindService()
方法绑定服务、建立连接。unbindService()
方法解除它们之间的绑定和连接。unbindService
再去调用stopService()
去停止服务。如果没有,那么直接调用stopService()
方法去停止服务。这里再说下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();
}
}
Android基础笔记(十一)- Service基础和注意事项以及Activity与Service的通信
标签:android 后台 service 通信 activity
原文地址:http://blog.csdn.net/biezhihua/article/details/44281029