在Android框架中,Service是比较难以理解的一部分,而网上的大多数资料最多就是讲述了如何去用Service,并没有对Service做一 个深入的、系统的讲解。现在傻蛋将做一个系列文章,将对Service做一个由浅入深的梳理,帮助大家深入的掌握Android Service。
首先我们先来看看怎么使用Service,然后再谈Android Service的内部机制。
在Android中Service的启动方式有两种,今天先发第一种。
Service的启动方式一:
启动:Context.startService(new Intent(context,xxx.class));
停止:Context.stopService() ;
我画了一个Service启动的流程图,相信大家一看就懂。Activity通过 Intent启动Service,如果Service还没有运 行,则android先调用onCreate()然后调用onStart();如果 Service已经运行,则只调用onStart(),所以一个 Service的onStart方法可能会重复调用多次。 调用stopService就会触发Service的onDestroy()方法。
这一节里面傻蛋做了一个示例程序是一个音乐播放器,界面如下,功能很简单:播放、暂停、停止音乐、关闭Activity(这时Service仍然运 行,继续播放音乐)、退出程序(停止音乐退出Activity)。
由于要控制Service的动作,傻蛋写了一个播放的通用类,这样在以后的课程中还能用上,代码如下:
/**
* MyMediaController.java
* com.androidtest.service.mediaplayer
*
* Function: TODO
*
*verdateauthor
* ──────────────────────────────────
*2011-5-16Leon
*
* Copyright (c) 2011, 最牛网 All Rights Reserved.
*/
package com.zuiniuwang;
import java.io.Serializable;
import android.media.MediaPlayer;
/**
* ClassName:MyMediaController
* Function: Mediaplayer 的一个控制类,控制播放器的播放 暂停 停止 等动作
* REASON
*
* @author Leon
* @version
* @since Ver 1.1
* @Date 2011-5-16
*/
public enum MyMediaController implements Serializable {
play {
@Override
public void execute() {
if (mediaPlayer != null && !mediaPlayer.isPlaying())
mediaPlayer.start();
// TODO Auto-generated method stub
}
},
pause {
@Override
public void execute() {
// TODO Auto-generated method stub
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
}
},
stop {
@Override
public void execute() {
// TODO Auto-generated method stub
if (mediaPlayer != null) {
mediaPlayer.stop();
try {
// 在stop后如果要重新Start需要prepare一下
mediaPlayer.prepare();
// 从头播放
mediaPlayer.seekTo(0);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
public static MediaPlayer mediaPlayer;
public abstract void execute();
}
然后是控制播放的Activity:
/**
* MusicPlayer.java
* com.androidtest.activity
*
* Function: TODO
*
*verdateauthor
* ──────────────────────────────────
*2011-5-15Leon
*
* Copyright (c) 2011, 最牛网 All Rights Reserved.
*/
package com.androidtest.activity.musicplayer;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import com.androidtest.MyImageButton;
import com.androidtest.R;
import com.androidtest.service.mediaplayer.MyMediaController;
import com.androidtest.service.mediaplayer.NormalMusicService;
import com.androidtest.sharedpreferences.TestSharePreferences;
/**
* ClassName:MusicPlayer Function: TODO ADD FUNCTION Reason: TODO ADD REASON
*
* @author Leon
* @version
* @since Ver 1.1
* @Date 2011-5-15
*/
public class NormalMusicPlayerActivity extends Activity implements OnClickListener {
private static final String TAG = NormalMusicPlayerActivity.class.getSimpleName();
private Intent intent ;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
this.setContentView(R.layout.music_player_layout);
Button playButton = (Button) this.findViewById(R.id.play);
playButton.setOnClickListener(this);
Button pauseButton =(Button) this.findViewById(R.id.pause);
pauseButton.setOnClickListener(this);
Button stopButton =(Button)this.findViewById(R.id.stop);
stopButton.setOnClickListener(this);
Button closeActivityButton =(Button)this.findViewById(R.id.close);
closeActivityButton.setOnClickListener(this);
Button exitActivityButton =(Button)this.findViewById(R.id.exit);
exitActivityButton.setOnClickListener(this);
intent = new Intent("com.androidtest.service.mediaplayer.NormalMusicService");
TestSharePreferences testSharePreferences=(TestSharePreferences)this.getApplication();
Log.v(TAG , ""+testSharePreferences.getSharedInteger());
}
private void playAction(MyMediaController playType) {
Bundle bundle = new Bundle();
bundle.putSerializable(NormalMusicService.INTENT_KEY, playType);
intent.putExtras(bundle);
NormalMusicPlayerActivity.this.startService(intent);
}
@Override
public void onClick(View view) {
// TODO Auto-generated method stub
switch (view.getId()) {
case R.id.play:
Log.d(TAG, "play.......");
playAction(MyMediaController.play);
break;
case R.id.pause:
Log.d(TAG, "pause.......");
playAction(MyMediaController.pause);
break;
case R.id.stop:
Log.d(TAG, "stop.......");
playAction(MyMediaController.stop);
break;
case R.id.close:
Log.d(TAG, "close.......");
this.finish();
break;
case R.id.exit:
Log.d(TAG, "exit.......");
stopService(intent);
this.finish();
}
}
}
最后是Service类
/**
* MusicService.java
* com.androidtest.service
*
* Function: TODO
*
*verdateauthor
* ──────────────────────────────────
*2011-5-15Leon
*
* Copyright (c) 2011, 最牛网 All Rights Reserved.
*/
package com.zuiniuwang.service;
import java.io.Serializable;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;
import android.util.Log;
import com.zuiniuwang.*;
/**
* ClassName:MusicService
* Function: TODO ADD FUNCTION
* Reason:TODO ADD REASON
*
* @authorLeon
* @version
* @sinceVer 1.1
* @Date2011-5-15
*/
public class NormalMusicService extends Service{
privateStringTAG = NormalMusicService.class.getSimpleName();
privateMediaPlayer myMediaPlayer ;
publicstatic finalString INTENT_KEY= "action" ;
@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public void onCreate() {
// TODO Auto-generated method stub
Log.v(TAG , TAG+ " onCreate()");
super.onCreate();
if(myMediaPlayer==null){
myMediaPlayer=MediaPlayer.create(this, R.raw.test) ;
myMediaPlayer.setLooping(false);
}
}
@Override
public void onStart(Intent intent, int startId) {
// TODO Auto-generated method stub
Log.v(TAG , TAG + " onStart()");
super.onStart(intent, startId);
if(intent!=null){
MyMediaController mediaControl =(MyMediaController)intent.getSerializableExtra(NormalMusicService.INTENT_KEY);
mediaControl.mediaPlayer=myMediaPlayer;
mediaControl.execute();
}
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
Log.v(TAG , " onDestroy");
if(myMediaPlayer!=null){
myMediaPlayer.stop();
myMediaPlayer.release();
}
}
}
第二种是通过绑定的方式来启动Service。先看流程图。
点击 查看大图
绑定的方式和第一节的方式最大的不同就是在于,Activity可以和Service实现关联,当被关联的Activity结束后,相应的 Service 也会停止,同时在绑定了的Activity中我们还可以回调我们在Service中定义的方法。在这里我们使用了 this.bindService(intent, myServiceConnection, Context.BIND_AUTO_CREATE); 来启动Service,当Service创建了同时绑定了Activity之后,会回调我们定义的ServiceConnection(),从而传回 IBinder接口,我们就能够调用Service中的方法了。这时候Activity就和 Service实现了绑定,Activity退出了Service就相应的退出了。Service的申明如下,intent filter 是对接收Service的过滤。
<service android:enabled="true" android:name=".service.mediaplayer.BindMusicService">
<intent-filter>
<action android:name="com.androidtest.service.mediaplayer.BindMusicService" />
</intent-filter>
</service>
前两节中可以看到Activity和Service,context.startService对应着Service中的onStart()方 法,context.onBindService对应的是Service中的onBind()方法。当我们继想绑定一个Service又想 在 Activity停止时,Service不会停止,我们可以先StartService,然后再BindService()。这时候的流程图如下所 示:
点击查看 大图
此时需要注意一个问题,当Activity退出的时候,Sercvice并不会停止,此时我们可以再进入Activity重新绑定,当这时 候 Service就会调用onRebind()方法,但是调用onRebind()方法的前提是先前的onUnbind()方法执行成功,但是使 用 super.onUnbind(intent)是执行不成功的,这时候我们要手动的使其返回true,再次绑定时Rebind()就会执行。否则,如 果退出时不显示的指定onUnbind()为成功的话(为false),那么重新启动此Activity来绑定服务时,Service的onBind() 方法和onReBind都不会执行,但是ServiceConnection方法确一定会回调了。这说明在Service中的onBind()方法不同 于 onStart()方法不能被重复调用。
本节代码和上节代码大致相同,只不过是在bind之前先start service ,大家可以在DDMS中看 onUnbind() onRebind()的调用情况。
在Android平台中,一个进程通常不能访问其他进程中的内存区域的。但是,我们可以使用IDL语言来把对象伪装成操作系统能理解的简单形式,以 便伪装成对象跨越边界访问。
如果想在应用程序中调用其他进程中的Service,则需要用到AIDL,AIDL(android接口描述语言)是一种IDL语言,它可以生成一 段代码,可以使在一个android设备上运行的两个进程使用内部通信进程进行交互。如果你需要在一个进程中(例如:在一个Activity中)访问另一 个进程中(例如:一个Service)某个对象的方法,你就可以使用AIDL来生成这样的代码来伪装传递各种参数。
使用AIDL的方法如下:
1.首先要编写一个 IMusicService.aidl的服务接口,ADT会根据这个接口文件帮我们自动生成一个 Stub类,这个类继承了Binder类,同时继承了IMusicService这个接口,还可以看到其中包含了一个Proxy代理类,以实现远程代理, 访问不同的进程。(aidl和Stub类如下所示)。
/**
* IMusicService.aidl
* com.androidtest.service.mediaplayer
*
* Function: TODO
*
*verdateauthor
* ──────────────────────────────────
*2011-5-19Leon
*
* Copyright (c) 2011, TNT All Rights Reserved.
*/
package com.zuiniuwang.service;
/**
* ClassName:IMusicService
* Function: TODO ADD FUNCTION
* Reason:TODO ADD REASON
*
* @authorLeon
* @version
* @sinceVer 1.1
* @Date2011-5-19
*/
interface IMusicService{
void play();
void pause();
void stop();
}
2. 生成的Stub类如下,我们暂不做详细讲解,后面的课程中我们会尝试自己来写一个类似的类,完成不同进程的访问。
/*
* This file is auto-generated.DO NOT MODIFY.
* Original file: E:\\myworkspace\\musicservice4\\src\\com\\zuiniuwang\\service\\IMusicService.aidl
*/
package com.zuiniuwang.service;
/**
* ClassName:IMusicService
* Function: TODO ADD FUNCTION
* Reason:TODO ADD REASON
*
* @authorLeon
* @version
* @sinceVer 1.1
* @Date2011-5-19
*/
public interface IMusicService extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.zuiniuwang.service.IMusicService
{
private static final java.lang.String DESCRIPTOR = "com.zuiniuwang.service.IMusicService";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.zuiniuwang.service.IMusicService interface,
* generating a proxy if needed.
*/
public static com.zuiniuwang.service.IMusicService asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.zuiniuwang.service.IMusicService))) {
return ((com.zuiniuwang.service.IMusicService)iin);
}
return new com.zuiniuwang.service.IMusicService.Stub.Proxy(obj);
}
public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_play:
{
data.enforceInterface(DESCRIPTOR);
this.play();
reply.writeNoException();
return true;
}
case TRANSACTION_pause:
{
data.enforceInterface(DESCRIPTOR);
this.pause();
reply.writeNoException();
return true;
}
case TRANSACTION_stop:
{
data.enforceInterface(DESCRIPTOR);
this.stop();
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.zuiniuwang.service.IMusicService
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
public void play() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_play, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
public void pause() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_pause, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
public void stop() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_stop, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_play = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_pause = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_stop = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
}
public void play() throws android.os.RemoteException;
public void pause() throws android.os.RemoteException;
public void stop() throws android.os.RemoteException;
}
3. 在Activity中得到Binder的方式,是通过Stub类的IMusicService.Stub.asInterface(binder)方法 (这一点和以前不同)。相应的代码如下:
/**
* RemoteMusicPlayerActivity.java
* com.androidtest.activity.musicplayer
*
* Function: TODO
*
*verdateauthor
* ──────────────────────────────────
*2011-5-20Leon
*
* Copyright (c) 2011, TNT All Rights Reserved.
*/
package com.zuiniuwang.playeractivity;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import com.zuiniuwang.R;
import com.zuiniuwang.service.IMusicService;
/**
* ClassName:RemoteMusicPlayerActivity Function: TODO ADD FUNCTION Reason: TODO
* ADD REASON
*
* @author Leon
* @version
* @since Ver 1.1
* @Date 2011-5-20
*/
public class RemoteMusicPlayerActivity extends Activity implements
OnClickListener {
private static final String TAG = RemoteMusicPlayerActivity.class
.getSimpleName();
private Button playButton, pauseButton, stopButton, closeActivityButton,
exitActivityButton;
private IMusicService musicServiceInterface;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
this.setContentView(R.layout.music_player_layout);
findViews();
bindViews();
connection();
}
private void findViews() {
playButton = (Button) this.findViewById(R.id.play);
pauseButton = (Button) this.findViewById(R.id.pause);
stopButton = (Button) this.findViewById(R.id.stop);
closeActivityButton = (Button) this.findViewById(R.id.close);
exitActivityButton = (Button) this.findViewById(R.id.exit);
}
private void bindViews() {
playButton.setOnClickListener(this);
pauseButton.setOnClickListener(this);
stopButton.setOnClickListener(this);
closeActivityButton.setOnClickListener(this);
exitActivityButton.setOnClickListener(this);
}
private void connection() {
Intent intent = new Intent(
"com.androidtest.service.mediaplayer.RemoteMusicService");
this.startService(intent);
this.bindService(intent, myServiceConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection myServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
musicServiceInterface = IMusicService.Stub.asInterface(binder);
Log.d(TAG, " onServiceConnected");
}
@Override
public void onServiceDisconnected(ComponentName name) {
musicServiceInterface = null;
Log.d(TAG, " onServiceDisconnected");
}
};
@Override
public void onClick(View view) {
// TODO Auto-generated method stub
try {
switch (view.getId()) {
case R.id.play:
Log.d(TAG, "play.......");
musicServiceInterface.play();
break;
case R.id.pause:
Log.d(TAG, "pause.......");
musicServiceInterface.pause();
break;
case R.id.stop:
Log.d(TAG, "stop.......");
musicServiceInterface.stop();
break;
case R.id.close:
//Activity退出之前要解除绑定,不然会报错
this.unbindService(myServiceConnection);
Log.d(TAG, "close.......");
this.finish();
break;
case R.id.exit:
Log.d(TAG, "exit.......");
this.unbindService(myServiceConnection);
this.stopService(new Intent("com.androidtest.service.mediaplayer.RemoteMusicService"));
this.finish();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
4. 最后在此Service注册的时候我们需要指定它是在一个不同的进程中运行的,本例子指定的是remote进程。注意 process参数。
<!-- 注册Service -->
<service android:enabled="true"
android:name=".service.RemoteMusicService"android:process=":remote">
<intent-filter>
<action android:name="com.androidtest.service.mediaplayer.RemoteMusicService" />
</intent-filter>
</service>
在远程的Service调用中,Activity和Service到底是怎么沟通的?对于Service的远程调用,一般会在不同的工程中也就是两 个不同的进程,那么进程的沟通机制是什么?傻蛋画了一个图来说明。
点击查看 大图
Android进程在进行远程通讯时会:
1.产生一个主线程。
2. 产生Looper对象
3.产生一个消息队列。
4.产生一个虚拟机对象来实现Java和C++之间的沟通。
5.通过C/C++层的IPC来实现远程通讯。
所谓的 进程间通讯:Android通过IBinder接口来实现进程间的通讯,MyActivity会调用IBinder的transact() 函数通过IPC来调用远程的onTransact()函数。注意: 在默认情况下,如果Service和 Activity、 BroadcastReceiver在同一个工程里面,那么这些组件都会在同一个进程中执行,并且由主线程负责执行,当然也可以通过 配置让其在不同的组件里面执行,比如上一节我们就让Service在Remote进程中运行。