- 服务的生命周期
- 服务的两种启动方式
- 本地服务通信
- 远程服务通信(调用其它应用的服务)
服务(Service)和Activity一样,是Android中的四大组件之一,不同的是服务没有界面,是一个长期运行在后台的组件,即使启动服务的应用程序被切换掉,服务仍可以在后台正常运行。因此服务经常被用来处理一些耗时的任务,例如进行网络传输或者播放音乐等。
一、服务的创建
1. 创建服务
服务的创建方式与创建Activity类似,只需要继承Service类即可。
示例代码:
public class MyService extends Service{
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
2. 在清单文件中配置
由于服务是Android四大组件中的一个,因此需要在Androidmanifest.xml文件中进行注册,否则服务是不生效的。
示例代码:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="learning.android.it.org.demo0801">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!--注册服务-->
<service android:name=".MyService"/>
</application>
</manifest>
二、服务的生命周期
与其他组件不同,Service不能自己主动运行,需要调用相应的方法来启动。启动服务的方法有两种,分别是Context.startService()和Context.bindService()。如图8-1所示,使用不同的方法启动服务,服务的生命周期也会不同。
图 8-1
1. startService方式开启服务的生命周期
当其他组件调用startService()方法时,服务会先执行onCreate()方法,接着执行onStartCommand()方法,此时服务处于运行状态,直到自身调用stopSelf()方法或者其他组件调用stopService()方法时服务停止,最终被系统销毁。
这种方式开启的服务会长期在后台运行,并且服务的状态与开启者的状态无关。
2. bindService方式开启服务的生命周期
当其他组件调用bindService()方法时,服务被创建,接着客户端通过Ibinder接口与服务通信。客户端通过unbindService()方法关闭连接,多个客户端能绑定到同一个服务上,并且当它们都解绑时,系统将直接销毁服务(服务不需要被停止)。
这种方法开启的服务与开启者的状态有关,当调用者销毁了,服务也会被销毁。
3. 服务声明周期中的重要方法
- onCreate() 服务被创建时执行的方法。
- onDestroy() 服务被销毁时执行的方法。
- onStartCommand() 客户端通过startService()显式启动服务时执行该方法。
- onBind() 客户端通过调用bindService()方法启动服务时执行该方法。
- onUnbind() 客户端调用unBindService()方法断开服务绑定时执行该方法。
三、服务的启动方式
前面讲过,启动服务有两种方式,分别是通过startService()方法启动服务和bindService()方法启动服务。
1. start方式启动服务
使用Context的startService()和stopService()方法来启动、关闭服务,Intent对象用于指定要启动或关闭的服务。
Intent intent=new Intent(this, MyService.class);
startService(intent); //开启服务
stopService(intent); //关闭服务
onCreate()方法只有在服务创建时执行,而onStartCommand()方法则是在每次启动服务时调用。
需要注意的是,如果不调用stopService()或stopSelf()方法,这种方式开启的服务会长期运行在后台,除非用户强制停止服务。
案例:Demo0801,start方式启动服务
2. bind方式启动服务
当程序使用startService()和stopService()启动、关闭服务时,服务与调用者之间基本不存在太多的关联,也无法与访问者进行通信、数据交互等。
如果服务需要与调用者进行方法调用和数据交互时,应该使用bindService()和unbindService()方法启动、关闭服务。
使用bindService()方式启动服务的标准步骤:
// 1. 创建MyConn类,用于实现连接服务
private MyService.MyBinder myBinder;
private class MyConn implements ServiceConnection{
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myBinder= (MyService.MyBinder) service;
Log.i("MainActivity", "服务成功绑定,内存地址为"+myBinder.toString());
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
MyConn myConn;
// 2. 绑定服务
Intent intent=new Intent(this, MyService.class);
bindService(intent, myConn, BIND_AUTO_CREATE);
// 3. 利用myBinder调用服务中的方法
myBinder.callMethodInService();
// 4. 解绑服务
unbindService(myConn);
bindService()方法的完整方法名为public boolean bindService(Intent service, ServiceConnection conn, int flags),该方法的参数解释如下:
- Intent对象用于指定要启动的Service
- ServiceConnection对象用于监听调用者与Service之间的连接状态。
- 当调用者与Service连接成功时,将回调该对象的public void onServiceConnected(ComponentName name, IBinder service)方法;
- 当调用者与Service断开连接时,将回调该对象的public void onServiceDisconnected(ComponentName name)方法。
- flags指定绑定时是否自动创建Service(如果Service还未创建)。该参数可指定为0即不自动创建,也可指定为BIND_AUTO_CREATE_即自动创建。
注意:ServiceConnection中的onServiceConnected()方法有一个参数IBinder service,这个参数是在服务的onBind()方法中返回的。
public class MyService extends Service{
// 创建服务的代理,调用服务中的方法
class MyBinder extends Binder{
public void callMethodInService(){
methodInService();
}
}
public void methodInService(){
Log.i("BindService", "自定义方法methodInService");
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i("BindService", "onBind");
return new MyBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.i("BindService", "onUnbind");
return super.onUnbind(intent);
}
@Override
public void onCreate() {
super.onCreate();
Log.i("BindService", "onCreate");
}
}
案例:Demo0802,bind方式启动服务
四、服务通信
在Android系统中,服务的通信方式有两种:一种是本地服务通信,另一种是远程服务通信。本地服务通信是指应用程序内部的通信,而远程服务通信是指两个应用程序之间的通信。使用这两种方式进行通信时必须满足一个前提,就是服务必须以绑定方式开启。
1. 本地服务通信
在使用服务进行本地通信时,首先需要开发一个Service类,该类会提供一个IBinder onBind(Intent intent)方法,onBind()方法返回的IBinder对象会作为参数传递给ServiceConnection类中onServiceConnected(ComponentName name, IBinder service)方法,这样访问者就可以通过IBinder对象与Service进行通信。
使用Ibinder对象进行本地服务通信:
图 8-2
如图8-2所示,服务在进行通信时实际上使用的就是IBinder对象,在ServiceConnection类中得到IBinder对象,通过该对象就可以获取到服务中定义的方法,执行具体的操作。
案例:Demo0803,音乐播放器,演示如何使用服务进行通信
(1) MediaPlayer
在Android中,播放音频文件一般都是使用MediaPlayer类来实现的。
- setAudioStreamType() 指定音频文件的类型必须在prepare()方法之前调用
- setDataSource() 设置要播放的音频文件的位置
- prepare() 在开始播放之前调用这个方法完成准备工作
- start() 开始或继续播放音频
- pause() 暂停播放音频
- reset() 将MediaPlayer对象重置到刚刚创建的状态
- seekTo() 从指定的位置开始播放音频
- stop() 停止播放音频,调用该方法后MediaPlayer对象就无法再播放音频
- release() 释放掉与MediaPlayer对象相关的资源
- isPlaying() 判断当前MediaPlayer是否正在播放音频
- getDuration() 获取载入音频文件的时长
- getCurrentPosition() 获取当前播放音频文件的位置
(2) SeekBar
SeekBar与ProgressBar十分类似,它是通过滑块的位置来表示数值的。SeekBar允许用户拖动滑块改变SeekBar的值,例如手机的音量调节,同时SeekBar还允许用户改变滑块外观。
SeekBar的常用方法属性:
- 属性 android:thumb 指定一个Drawable对象,该对象将作为自定义滑块
- 监听器OnSeekBarChangeListener 监听滑块位置的改变
- 方法setProgress() 用来设置SeekBar的当前值
- 方法setMax() 设置SeekBar的最大值
2. 远程通信服务
在Android系统中,各个应用程序都运行在自己的进程中,进程之间一般无法直接进行通信,如果想要完成不同进程之间的通信,就需要使用远程服务通信。
远程服务通信是通过AIDL(Android Interface Definition Language)实现的,它是一种接口定义语言,其语法格式简单,与Java中接口定义类似,但存在几点差异:
- AIDL定义接口的源代码文件必须以.aidl结尾
- AIDL接口中用到的数据类型,除了基本数据类型、String、List、Map、CharSequence
之外,其他类型全部都需要导入包,即使它们在同一个包中。
开发人员定义的AIDL接口只是定义了进程之间的通信接口,服务端、客户端都需要使用Android SDK安装目录下的platform-tools子目录下的aidl.exe工具为该接口提供实现。
远程服务调用的实例:
应用A
(1) 定义AIDL接口
package learning.android.it.org.demo0804;
interface IService {
void callAlipayService();
}
AIDL接口定义的方式与Java接口的定义方式类似,都需要包名、接口名以及自定义的方法。不同的是,AIDL没有类型修饰符,在编写ADIL文件时,不能加上类型修饰符,因为它本身就是公有的,例如写为public interface IService是不正确的,正确的写法应该为interface IService。定义好AIDL接口之后,aidl.exe会为IService自动生成实现类IService.Stub。
(2) 创建Service
接着需要在应用程序中创建Service的子类,该Service的onBind()方法所返回的IBinder对象应该是aidl.exe所生成的IService.Stub的子类对象。
public class AlipayService extends Service {
private class MyBinder extends IService.Stub {
@Override
public void callAlipayService() throws RemoteException {
methodInService();
}
}
private void methodInService() {
Log.v(TAG, "开始付费,购买装备");
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.v(TAG, "绑定支付宝,准备付费");
return new MyBinder();
}
}
(3) 注册Service
由于该Service需要被其他应用调用,因此需要在注册服务时设置一个action,当其他应用使用隐式Intent调用该服务时,就可以通过这个action匹配上该服务。
<service android:name=".AlipayService" >
<intent-filter>
<action android:name="www.baidu.com" />
</intent-filter>
</service>
应用B
(1) 复制应用A的AIDL接口文件
(2) 绑定Service
现在在另一个应用绑定上面应用的服务。注意在调用bindService()方法绑定服务时,使用的Intent设置的action需要和上面应用中服务注册的action一致。
private MyConn conn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//这里Action需要与Demo04在清单文件中服务注册的Action要一致
service = new Intent();
service.setAction("www.baidu.com");
}
public void bind(View view) {
conn = new MyConn();
bindService(service, conn, BIND_AUTO_CREATE);
}
private class MyConn implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iService = IService.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
(3) 调用应用A的服务
最后在应用B中调用第一个应用的服务提供的方法
public void call(View view) {
try {
if (iService != null) {
iService.callAlipayService();
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
注意:在调用本地方法时,在onServiceConnected()方法中是通过强制转换将service转换为MyBinder的;但在远程服务调用中,需要通过IService.Stub.asInterface(service)方法转换。IService就是应用A中定义的AIDL接口,需要将这个接口文件复制到应用B相同目录下。
案例:Demo0804与Demo0805,模拟小游戏Demo0805远程调用支付宝Demo0804,演示如何绑定并使用远程服务
注意:两个应用程序中的ADIL文件所在的包名要一致。