由于android系统中应用程序之间不能共享内存。因此,在不同应用程序之间交互数据(跨进程通讯)就稍微麻烦一些。
在android SDK中提供了4种用于跨进程通讯的方式。这4种方式正好对应于android系统中4种应用程序组件:Activity、Content Provider、Broadcast和Service。
AIDL (Android Interface Definition Language) 是一种IDL 语言,用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。
AIDL IPC机制是面向接口的,像COM或Corba一样,但是更加轻量级。它是使用代理类在客户端和实现端传递数据。
由于每个应用程序都运行在自己的进程空间,并且可以从应用程序UI运行另一个服务进程,而且经常会在不同的进程间传递对象。在Android平台,一个进程通常不能访问另一个进程的内存空间,所以要想对话,需要将对象分解成操作系统可以理解的基本单元,并且有序的通过进程边界。
通过代码来实现这个数据传输过程是冗长乏味的,Android提供了AIDL工具来处理这项工作。
官方文档特别提醒我们何时使用AIDL是必要的:只有你允许客户端从不同的应用程序为了进程间的通信而去访问你的service,以及想在你的service处理多线程。
如果不需要进行不同应用程序间的并发通信(IPC),you should create your interface by
implementing a Binder;或者你想进行IPC,但不需要处理多线程的,则implement your interface
using a Messenger。无论如何,在使用AIDL前,必须要理解如何绑定service——bindService。
Android Service是分为两种:
本地服务(Local Service): 同一个apk内被调用
远程服务(Remote Service):被另一个apk调用
远程服务需要借助AIDL来完成。
下面学习一下 作者:Panda Fang 的例子 链接
这是篇不错的AIDL学习例子
下面用一个客户端Activity操作服务端Service播放音乐的实例演示AIDL的使用。
新建一个Android工程,命名为AIDLPlayerserver。 包名为com.ylbf.aidlplayerserver
在res下的raw文件夹里面放入一个音乐文件,我这里放入的是 TheFatRat - Monody (feat. Laura Brehm).mp3
的一个片段。如果不存在raw这个文件夹就自己新建一个,命名为raw。这个文件夹在raw文件夹下,与layout文件夹平级。raw中的文件遵守标识符的命名规则,不要出现中文和空格,多个单词可以用下划线连接,我这里改为monody.mp3
。
新建一个IRemoteServiice.aidl 文件,加入如下代码
package com.ylbf.aidlplayerserver; interface IRemoteService {
void play();
void stop();
}
可见aidl文件的代码跟java的interface一样,但是aidl中不能加public等修饰符。Ctrl + S 保存后 ADT 会根据这个IRemoteService.aidl
文件自动生成IRemoteService.java文件。如同R.java文件一样在gen/包名
下,代码是自动生成的,不要手动修改。
接下来就是bound service
的知识了。IRemoteService.java 中有一个Stub静态抽象类extends Binder implements IRemoteService
。自己动手写一个PlayerService
用来播放音乐,播放音乐需要使用android.media.MediaPlayer
类。
/** * @category 播放音乐的服务 * @author ylbf * @version 2016-02-18 14:35:48 */
public class PlayerService extends Service {
public static final String TAG = "PlayerService";
private MediaPlayer mPlayer;
private IBinder mBinder = new IRemoteServiice.Stub() {
@Override
public void stop() throws RemoteException {
try {
if (mPlayer.isPlaying()) {
mPlayer.stop();
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void play() throws RemoteException {
try {
if (mPlayer.isPlaying()) {
return;
}
// start之前需要prepare。
// 如果前面实例化mplayer时使用方法一,则第一次play的时候直接start,不用prepare。
// 但是stop一次之后,再次play就需要在start之前prepare了。
// 前面使用方法二 这里就简便了, 不用判断各种状况
mPlayer.prepare();
mPlayer.start();
} catch (Exception e) {
e.printStackTrace();
}
}
};
@Override
public IBinder onBind(Intent arg0) {
Log.i(TAG, "service onbind");
if (mPlayer == null) {
// 方法一说明
// 此方法实例化播放器的同时指定音乐数据源 ,若用此方法在,mPlayer.start()
// 之前不需再调用mplayer.prepare()
// 官方文档有说明 :On success, prepare() will already have been called and
// must not be called again.
// 译文:一旦create成功,prepare已被调用,勿再调用 。查看源代码可知create方法内部已经调用prepare方法。
// 方法一开始
// mPlayer = MediaPlayer.create(this, R.raw.monody);
// 方法一结束
// 方法二说明
// 若用此方法,在mplayer.start() 之前需要调用mplayer.prepare()
// 方法二开始
mPlayer = new MediaPlayer();
try {
FileDescriptor fd = getResources().openRawResourceFd(R.raw.monody).getFileDescriptor(); // 获取音乐数据源
mPlayer.setDataSource(fd); // 设置数据源
mPlayer.setLooping(true); // 设为循环播放
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 方法二结束
Log.i(TAG, "player created");
}
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
if (mPlayer != null) {
mPlayer.release();
}
Log.i(TAG, "service onUnbind");
return super.onUnbind(intent);
}
}
服务编写好以后,按照惯例在AndroidManifest.xml中加入声明,代码如下
需要加入的只是…那段,要注意的是 android:process=”:remote” 和 intent-filter 。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.ylbf.aidlplayerserver" android:versionCode="1" android:versionName="1.0" >
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" />
<application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" >
<service android:name=".PlayerService" android:process=":remote" >
<intent-filter>
<action android:name="com.ylbf.aidlplayerserver.PlayerService" />
</intent-filter>
</service>
</application>
</manifest>
安装运行服务端到设备上,准备给客户端调用
[2016-02-18 15:25:46 - AIDLPlayerserver] ------------------------------
[2016-02-18 15:25:46 - AIDLPlayerserver] Android Launch!
[2016-02-18 15:25:46 - AIDLPlayerserver] adb is running normally.
[2016-02-18 15:25:46 - AIDLPlayerserver] No Launcher activity found!
[2016-02-18 15:25:46 - AIDLPlayerserver] The launch will only sync the application package on the device!
[2016-02-18 15:25:46 - AIDLPlayerserver] Performing sync
[2016-02-18 15:25:46 - AIDLPlayerserver] Automatic Target Mode: Unable to detect device compatibility. Please select a target device.
[2016-02-18 15:25:50 - AIDLPlayerserver] Uploading AIDLPlayerserver.apk onto device 'xxxxxxxxxxxx'
[2016-02-18 15:25:50 - AIDLPlayerserver] Installing AIDLPlayerserver.apk...
[2016-02-18 15:25:53 - AIDLPlayerserver] Success!
[2016-02-18 15:25:53 - AIDLPlayerserver] \AIDLPlayerserver\bin\AIDLPlayerserver.apk installed on device
[2016-02-18 15:25:53 - AIDLPlayerserver] Done!
新建一个Android工程,命名为AIDLPlayerClient,包名为com.ylbf.aidlplayerclient
。将服务端放有aidl文件的包直接copy到客户端src目录下,保留包中的aidl文件,其他删除。
编写MainActivity.java 代码如下
/** * 客户端控制界面 * * @author ylbf * @version 2016-02-18 15:44:45 */
public class MainActivity extends Activity implements OnClickListener {
public static final String TAG = "MainActivity";
/** * 服务端 AndroidManifest.xml中的intent-filter action声明的字符串 */
public static final String ACTION = "com.ylbf.aidlplayerserver.PlayerService";
private IRemoteServiice mService;
private boolean isBinded = false;
private Button playbtn, stopbtn;
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
isBinded = false;
mService = null;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = IRemoteServiice.Stub.asInterface(service);
isBinded = true;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
doBind();
initViews();
}
private void initViews() {
playbtn = (Button) findViewById(R.id.button1);
stopbtn = (Button) findViewById(R.id.button2);
playbtn.setOnClickListener(this);
stopbtn.setOnClickListener(this);
}
@Override
protected void onDestroy() {
doUnbind();
super.onDestroy();
}
/** * 绑定服务 */
public void doBind() {
Intent intent = new Intent(ACTION);
bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
/** * 解绑服务 */
public void doUnbind() {
if (isBinded) {
unbindService(conn);
mService = null;
isBinded = false;
}
}
@Override
public void onClick(View v) {
if (v.getId() == playbtn.getId()) {
// play
Log.i(TAG, "play button clicked");
try {
mService.play();
} catch (RemoteException e) {
e.printStackTrace();
}
} else {
// stop
Log.i(TAG, "stop button clicked");
try {
mService.stop();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
运行客户端到设备,按下按钮可以播放/停止 就可以了
项目源代码
Android Service学习之AIDL, Parcelable和远程服务 2011-04-07;