孔明详细讲解了一下Intent,这时的刘备才恍然大悟,捶胸顿足,后悔道:“早知听完军师讲解再去曹操那儿了,这下被曹操那些手下各种看不起呀,不行,我得回去再跟他们好好讲讲Intent是啥!”
关羽一把拉住刘备,劝道:“算了吧,大哥,你现在去谁还会听你说呀,不要再去被鄙视了,这回咱们只能认栽了。”
刘备叹道:“我就是咽不下这口气呀,那个面试的气死我了,长得贼眉鼠眼的,装作道貌岸然的样子,我看他才啥也不会才是!想起我就气不打一处来啊!”
关羽拉拉刘备,说:“大哥,面试你的那个貌似就是曹孟德……”
刘备气说:“曹什么孟德,我还刘玄德呢,姓曹了不起呀,还是有孟了不起呀!”
关羽又拉拉刘备,说:“大哥……曹孟德就是曹操。”
刘备这才反应过来,说:“啊?他就是传说中的小曹?唉,早知道管他要个签名再走了。”
关羽说:“大哥,你好没节操……”
那边孔明听他们越扯越远,越扯越没边,问道:“云长,你为啥也悲剧了呢?按理说你学的不错,开发功底也比主公要好,咋会被鄙视呢?问你的又是什么题目呀?”
关羽立马趴到地上,抱住孔明的大腿,哭道:“军师啊,说起我那更是惨上加惨呀!”
“二哥,你也好没节操啊……”张飞不知从哪里拿了根鸡腿出来,一边啃一边说道。
关羽捂着心口,一把胡子耷拉到地上,说:“正因为我比大哥有着更加深厚的基础,被问崩的时候才更伤心啊。试想我写代码这么长时间,还没被这么鄙视过!”
张飞一边咬鸡骨头一边问道:“是什么面试官如此伤害二哥?”
关羽咬牙道:“我这辈子都不会忘了他的嘴脸,他竟然还蒙住一只眼睛,不知道装的是什么酷,别人都管他叫‘惇哥’,不知道是不是也跟军师似的特别喜欢蹲厕所?”
孔明用脚踹了踹趴在地上的关羽,“骂他归骂他,不要讽刺我的兴趣爱好。废话少说,他到底问的你啥啊?”
关羽答道:“问的我Service相关概念。”
刘备也一脸迷茫道:“军师,这Service是怎么个思想感情啊?”
1.1. Service简介
前面我们一起学习了Android的常用控件、Dialog、PopupWindow、Activity等知识。这些都是看得见摸得到的内容,它们共同构建起了Android应用的界面。而这一回要介绍的Service却是一种看不见的服务。虽然看不见,但Service是Android非常重要的组件之一。
1.1.1. 什么是Service?
由于手机屏幕的限制,通常情况下在同一时间内仅有一个应用程序处于激活状态,并且只能显示该应用程序的某个界面,因此,应用程序往往需要一种能够长期在后台运行的机制,在没有用户界面的情况下,实现应用程序的特定功能,并能够处理事件或更新数据。这种机制就是Service。
Service适用于无需用户干预,且需要长期运行的后台程序。Service没有用户界面,有利于降低系统资源。Service比Activity具有更高的优先级,因此在系统资源紧张的时候,Service不会轻易被Android系统终止。即使Service被系统终止了,在系统资源恢复后Service也将自动恢复运行状态,因此可以认为Service是在系统中永久运行的组件。Service除了实现后台服务功能,还可以解决两个不同Activity应用程序进程之间的调用和通信问题。
Service与进程、线程之间的关系
l 一个进程可以包含多个Service。一般情况下,Service与所属程序是在同一个进程中运行的。例如音乐播放器的播放Service,与音乐播放器是在同一个进程中的。
l 一个Service可以包含多个线程。Service是运行在主线程上的,而不是运行在另一个线程中,如果想在Service中处理很占时间的操作,可以在Service中开一个新的线程,这样可以降低Activity没有响应的风险。
Service和Activity的相同点与不同点
l 不同点:Activity是与用户交互的组件,用户可以直观的看到。而Service是后台运行的,执行应用程序的后台操作。
l 相同点:Acivitiy和Service都属于Android四大组件。在使用这两者时,都需要在配置文件中添加标签进行声明。
Service的分类
l 本地服务(Local Service):用于应用程序内部。
l 远程服务(Remote Service):用于Android系统内部的应用程序之间的进程通信。
1.1.2. Service生命周期
Service是不能被自己启动的,只有通过Context对象调用startService()和bindService()方法来启动。这两种方法的Service生命周期如图10-1所示:
图 10-1 Service的生命周期
如上图10-1所示,Service有两种启动方式:
startService()方法启动
此时启动的Service与调用者Activity之间没有关联,即Activity已经退出,Service仍然可以继续运行,而且调用者和Service之间无法进行数据交换和通信。如果需要停止Service的运行,只能调用Context类的stopService()方法,或者由Service本身调用其stopSelf()方法。当第一次启动Service时,系统会调用onCreate()、onStart()两个方法;当停止Service服务时,系统会调用onDestory()方法;如果Service已经启动了,再启动同一个服务时,系统就只调用 onStart() 这个方法了。
bindService()方法启动
此方法调用Service时,调用者Activity与Service绑定在一起,如果Activity退出,则Service也随之退出,而且调用者Activity和Service之间可以进行数据交换或通信。
1.2.音乐播放服务实例
关羽听完军师介绍的Service,不觉手指发痒,马上开始一个简单音乐播放实例的开发,来详细的学习Service的使用。关羽新建一个Android工程,创建一个MainActivity,代码如下所示:
MainActivity.java代码清单10-1:
/**
* @author 关羽:大哥最近身体偶感不适,让贤弟为你高歌一曲,疏通经脉:“我是女生,可爱的
女生……”
张飞:大哥!冷静!
刘备:不要拦我!
*/
public classMainActivity extends Activity {
// author 关羽 我要证明自己!
//LocalServiceDemo Activity
private static String TAG ="service";
private Button startButton;
private Button stopButton;
private Button bindButton;
private Button unbindButton;
private Button timeButton;
private MyBinder binder = null;
@Override
public void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
initViews();
}
//定义ServiceConnection用于在Activity和Service中传递数据
final ServiceConnection conn = newServiceConnection() {
@Override
public voidonServiceConnected(ComponentName name, IBinder service) {
// MyService中的onBinder()方法的返回值实际上是一个MyBinder对象
//因此可以使用强制转换
//MyBinder继承于IBinder在代码清单10-2中定义
binder = (MyBinder)service;
Log.e(TAG, "MainActivityonSeviceConnected");
}
@Override
public voidonServiceDisconnected(ComponentName name) {
// 注意该方法只在Service非正常中断时调用
binder = null;
Log.e(TAG, "MainActivityonSeviceDisconnected");
}
};
private void initViews() {
startButton =(Button)findViewById(R.id.start_button);
stopButton =(Button)findViewById(R.id.stop_button);
bindButton =(Button)findViewById(R.id.bind_button);
unbindButton =(Button)findViewById(R.id.unbind_button);
timeButton = (Button)findViewById(R.id.time_button);
// 定义按钮监听器
OnClickListener ocl = newOnClickListener() {
@Override
public void onClick(View v) {
Intent i = newIntent(MainActivity.this, MusicPlayService.class);
switch (v.getId()) {
case R.id.start_button:
// 开始播放按钮响应事件
startService(i);
break;
case R.id.stop_button:
// 停止播放按钮响应事件
stopService(i);
break;
case R.id.bind_button:
// 绑定按钮响应事件
bindService(i, conn,Context.BIND_AUTO_CREATE);
break;
case R.id.unbind_button:
// 解除绑定按钮响应事件
unbindService(conn);
binder = null;
break;
case R.id.time_button:
// 查看播放时间按钮响应事件
if (binder != null) {
Toast.makeText(MainActivity.this, "播放了" + binder.getRunTime() + "毫秒",Toast.LENGTH_LONG).show();
} else {
Toast.makeText(MainActivity.this, "没有绑定服务,无法查询播放时间", Toast.LENGTH_LONG).show();
}
break;
}
}
};
// 设置按钮监听器
startButton.setOnClickListener(ocl);
stopButton.setOnClickListener(ocl);
bindButton.setOnClickListener(ocl);
unbindButton.setOnClickListener(ocl);
timeButton.setOnClickListener(ocl);
}
}
该音乐播放器界面包含5个按钮分别用于:开启音乐播放服务、停止音乐播放服务、绑定音乐播放服务、解除绑定、查看播放时间。
l 开启音乐播放服务:调用startService()方法启动MusicPlayService服务。
l 停止音乐播放服务:调用stopService()方法来停止服务。
l 绑定音乐服务:调用bindService(Intentservice, ServiceConnection conn, int flags)方法来绑定Activity与Service。参数Intent service是指向需要绑定服务的Intent;ServiceConnection是一个接口,重写其中的onServiceConnected()的方法可以通过Binder类来达到Activity与Service之间传递数据的目的;flags用来标明绑定中的操作,不想指定时设为0即可,通常设置为BIND_AUTO_CREATE,这样就会在service不存在时创建一个。上段代码中就利用onServiceConnected()方法得到了service中的Binder数据。
孔明:需要注意的是,ServiceConnection的onServiceDisconnected()方法仅在服务非正常中断时调用,如果正常解除Service绑定不会调用该语句。 |
l 解除绑定服务:调用unbindService(ServiceConnectionconn)方法解除服务绑定,服务绑定解除后服务停止。
l 查看播放时间:通过绑定服务返回的Binder类来获取所需的服务运行时间,如果Binder为空,则提示需要绑定服务。
运行程序,结果如图10-2所示:
图 10-2 音乐播放服务主界面
除了MainActivity.java之外,本程序还包含一个继承自Service的MusicPlayService类。该类包含一个MediaPlayer和一个long成员变量,用于播放音乐和标记播放开始时间。
MusicPlayService.java代码清单10-2-0:
/**
* @author 孔明:小羽羽昨天去精神病院给人唱歌,病人好了,大夫疯了。
*/
public class MusicPlayService extends Service {
// 用来播放音乐的Service类
private MediaPlayermPlayer;
private static StringTAG = "service";
private MyBinder binder= null;
// Log TAG
private long startTime;
// 标记播放开始时间;
@Override
public void onCreate() {
Log.e(TAG,"Service onCreate");
// 初始化播放器,并设置为循环播放
mPlayer =MediaPlayer.create(this, R.raw.bgmusic);
mPlayer.setLooping(true);
super.onCreate();
}
@Override
public void onDestroy(){
// 服务终止
Log.e(TAG,"Service onDestory");
mPlayer.stop();
super.onDestroy();
}
@Override
public voidonStart(Intent intent, int startId) {
// 服务启动
Log.e(TAG,"Service onStart");
mPlayer.start();
super.onStart(intent, startId);
}
@Override
public IBinderonBind(Intent intent) {
// 绑定服务
Log.e(TAG,"Service onBind");
mPlayer.start();
startTime =System.currentTimeMillis();
binder = newMyBinder();
return binder;
}
@Override
public booleanonUnbind(Intent intent) {
// 解除绑定
Log.e(TAG,"Service onUnbind");
mPlayer.stop();
return super.onUnbind(intent);
}
public class MyBinderextends Binder {
// 获取Service运行时间
public longgetRunTime() {
return(System.currentTimeMillis() - startTime);
}
}
}
关羽你以为这样就完成了整个程序的开发了吗?还没有!如同Activity一样,记得在AndroidManifest.xml中对Service进行注册,在<application>标签下,添加如下语句:
<service android:name=".MusicPlayService" />
这样,整个音乐播放服务程序才算完成。
在MusicPlayService类中,重写了Service的onCreate()、onDestroy()、onStart(Intent intent, int startId)、onBind(Intentintent)、onUnbind(Intent intent)五个方法。当服务被创建时会调用onCreate()方法,在onCreate()方法中初始化播放器,并设置循环播放。这里需要将mp3文件放到工程资源文件夹res/raw下,再在初始化时指定资源ID;当服务被终止时会调用onDestroy()方法,在onDestroy()方法中添加mPlayer.stop()用于停止音乐播放;当服务开始播放时会调用onStart()方法,在该方法中添加mPlayer.start()以开启音乐播放;当服务被绑定至某Activity时会调用onBind()方法,在该方法中添加mPlayer.start()以开启音乐播放,在onBind()方法中关羽创建了一个MyBinder类继承自Binder类, MyBinder类的作用是传递Service的运行时间,onBind()方法将MyBinder对象返回,在MainActivity中的onServiceConnected()方法得到传递过去的Binder类;当服务解除绑定时会调用onUnbind(Intentintent)方法,在该方法中添加mPlayer.stop()以停止音乐播放。
运行程序,绑定音乐服务后,点击“查看播放时间”按钮,如图10-3所示:
图 10-3 查询播放时间
当关羽点击点击“开启音乐播放服务”按钮时,发现无法查看播放时间。经过研究,发现这是因为服务并没有与Activity进行绑定,只有在绑定之后服务运行时间才能通过Binder传递给了Activity,并在界面上显示。
1.3.玄德有话说
刘备:原来Service是这么回事!貌似还是很重要的呀!
孔明:那必须的!Service可是Android的四大组件之一,跟歌坛四大天王的地位差不多的。
刘备:刚开始听你说这Service分本地的和远程的,这远程的又是什么思想感情呢?
孔明:Android系统的进程之间不能共享内存,当传递对象时,需要把对象转化为操作系统可以识别的形式。在Android中,可以采用AIDL来公开服务的接口,采用远程过程调用(Remote Procedure Call,RPC)和代理模式来实现跨进程通信。AIDL(Android InterfaceDefinition Language)即Android接口描述语言,ADT会根据AIDL文件在gen目录下生成对应的Java接口文件。我们需要手工创建一个Service的子类并实现生成的Java接口,然后在AndroidManifest.xml文件中进行配置。远程服务可以为多个客户端服务,由于涉及到数据通信,一般采用bindService的方式。
刘备:老“猪”,怎么这么专业!
张飞:老“猪”啊,我也有个问题。
孔明:拜托,我姓诸葛……
张飞:嗨,差不多啦。拿我们这个音乐播放服务来说,如果我先start音乐播放服务,再bind音乐播放服务。会怎么样呢?
孔明:这样的话会在start时调用onCreate()、onStart()方法播放音乐,bind时不会调用onCreate(),而会直接调用onBind()方法。需要注意的是,如果此时unBind()解除绑定服务不会调用onDestory()方法,仅调用了onUnbind()方法,Service不会停止。如果要真正的停止服务,需要再调用stop()方法触发onDestory()销毁服务。
张飞:老“猪”,你说的我好迷茫。
孔明:简而言之两种方法混合使用时,Service启动和退出服务的调用顺序为onCreate()→onStart()→onBind()→onUnbind→onStop()→onDestory()或onCreate()→onBind()→onStart()→onStop()→onUnbind→onDestory()。如果以这两种顺序调用,Service将不能正常退出。
张飞:了解!