AIDL(Android Interface Definition Language)技术的作用主要是用来在Android中进行进程间通信的。
我们的需求是这样的:
第一、我们知道在AndroId中如果需要进行音乐播放,最方便的方法就是使用自带的MediaPlayer对象,如果我们在Activity中控制MediaPlayer对象进行播放,那么一旦你打开了另外一个程序譬如浏览器,那么歌声就会立刻停止,这当然不是我们需要的结果。 我们需要的是在做其他事情的同时能够在后台听歌,于是我们就需要把对MediaPlayer对象的操作放在后台Service中去。
第二、我们已经把对MediaPlayer的操作转移到Service中去了,按照我们以前的做法,我们在Activity中发送一个Intent对象给Service对象,在Intent中传送播放啊、暂停啊一类的信息给Service,这样Service就知道该怎么做了。这一切看起来很美好,可是现在出了一个新问题,那就是我想在Activity中显示一个进度条,这个进度条要跟着Service中的MediaPlayer中的歌曲进度同步向前走,而且如果我点击进度条中的某一个位置,还想让歌曲跳转到新的时间点继续播放,这个,该怎么实现?
第三、我们需要在Activity中操作Service中的MediaPlayer对象,就好像这个对象是自己的一样。我们可以采用Android接口定义语言 AIDL(Android Interface Definition Language)技术
下面就详细讲解实现方法
谷歌官方说明地址:http://developer.android.com/guide/components/aidl.html
定义一个AIDL接口一共有三步:
- Create the .aidl file
This file defines the programming interface with method signatures.
- Implement the interface
The Android SDK tools generate an interface in the Java programming language, based on your
.aidl
file. This interface has an inner abstract class namedStub
that extendsBinder
and implements methods from your AIDL interface. You must extend theStub
class and implement the methods. - Expose the interface to clients
Implement a
Service
and overrideonBind()
to return your implementation of theStub
class.
1.创建.aidl文件:这个文件定义了程序接口,里面包含方法的声明。例如
// IRemoteService.aidl
package com.example.android;
// Declare any non-default types here with import statements
/** Example service interface */
interface IRemoteService {
/** Request the process ID of this service, to do evil things with it. */
int getPid();
/** Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
IRemoteService就是AIDL接口的名字,getPid(),basicTypes(....)就是方法的声明。这个文件编译完成后,ADT会为我们在gen目录下自动生成相同包名的(此处为com.example.android)IRemoteService.java的文件
2.实现接口
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
public int getPid(){
return Process.myPid();
}
public void basicTypes(int anInt, long aLong, boolean aBoolean,
float aFloat, double aDouble, String aString) {
// Does nothing
}
};
3.交给客户端使用该接口
IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
// Called when the connection with the service is established
public void onServiceConnected(ComponentName className, IBinder service) {
// Following the example above for an AIDL interface,
// this gets an instance of the IRemoteInterface, which we can use to call on the service
mIRemoteService = IRemoteService.Stub.asInterface(service);
}
// Called when the connection with the service disconnects unexpectedly
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "Service has unexpectedly disconnected");
mIRemoteService = null;
}
};
这样,我们就可以在客户端调用Service中的方法了,如,mIRemoteService.basicTypes(...)
package com.mjook007.aidl;
interface ServicePlayer{
void play();
void pause();
void stop();
int getDuration();//歌曲总时间
int getCurrentTime();//当前播放时间
void setCurrent(int cur);//快进,快退。。
boolean isPlaying();//是否处于播放状态
}
然后创建一个PlayService的类,该类继承自Service,并重写了onBind方法
package com.mjook007.service;
import java.io.File;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Environment;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.mjook007.aidl.ServicePlayer;
import com.mjook007.model.Mp3Info;
public class PlayService extends Service {
private Mp3Info info = null;
MediaPlayer mediaPlayer = null;
ServicePlayer.Stub stub = new ServicePlayer.Stub() {
@Override
public void stop() throws RemoteException {
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
Log.v("PlayService", "停止");
}
}
@Override
public void setCurrent(int cur) throws RemoteException {
if (mediaPlayer != null) {
mediaPlayer.seekTo(cur);
}
}
@Override
public void play() throws RemoteException {
if (mediaPlayer == null) {
String path = getMp3Path(info);
mediaPlayer = MediaPlayer.create(PlayService.this,
Uri.parse("file://" + path));
}
if (!isPlaying()) {
mediaPlayer.setLooping(false);
mediaPlayer.start();
Log.v("PlayService", "播放");
}
}
@Override
public void pause() throws RemoteException {
if (mediaPlayer != null) {
if (isPlaying()) {
mediaPlayer.pause();
Log.v("PlayService", "暂停");
} else {
mediaPlayer.start();
Log.v("PlayService", "播放");
}
}
}
@Override
public boolean isPlaying() throws RemoteException {
boolean b = false;
if (mediaPlayer != null) {
b = mediaPlayer.isPlaying();
}
return b;
}
@Override
public int getDuration() throws RemoteException {
int i = 0;
if (mediaPlayer != null) {
i = mediaPlayer.getDuration();
}
return i;
}
@Override
public int getCurrentTime() throws RemoteException {
int cur = 0;
if (mediaPlayer != null) {
cur = mediaPlayer.getCurrentPosition();
}
return cur;
}
};
@Override
public IBinder onBind(Intent intent) {
Log.v("onBind", "!!!!!!!!!!!!!!!!!!!!");
info = (Mp3Info) intent.getSerializableExtra("mp3Info");
String path = getMp3Path(info);
Log.v("service--path", path);
mediaPlayer = MediaPlayer.create(this, Uri.parse("file://" + path));
return stub;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 此处返回值可控制服务在被KILL后是否重启
return super.onStartCommand(intent, flags, startId);
}
private String getMp3Path(Mp3Info info) {
String SDCardRoot = Environment.getExternalStorageDirectory()
.getAbsolutePath();
String path = SDCardRoot + File.separator + "mp3" + File.separator
+ info.getMp3Name();
return path;
}
}
这里需要注意的是,我们Stub内部类中要用到的mediaPlayer对象一定要在onBind之前的生命周期里进行初始化!
package com.mjook007.mp3player;
import com.mjook007.aidl.ServicePlayer;
import com.mjook007.model.Mp3Info;
import com.mjook007.service.PlayService;
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.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.TextView;
public class Mp3PlayerActivity extends Activity implements OnClickListener {
private Button startBt;
private Button pauseBt;
private Button stopBt;
private SeekBar seekBar;
private TextView cur;// 当前播放时间
private TextView dur;// 歌曲总长度
// MediaPlayer mediaPlayer = null;
Mp3Info info = null;
boolean isPlaying = false;
private static Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// cur.setText("当前播放时间" + msg.arg1);
};
};
private ServicePlayer sPlayer;
private ServiceConnection sConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
sPlayer = ServicePlayer.Stub.asInterface(service);
Log.v("onServiceConnected", "connected");
}
};
private Runnable updateThread = new Runnable() {
@Override
public void run() {
if (sPlayer != null) {// 不执行null Check 会出现NullPointException
// 执行了sPlayer的null Check 但依然会出现空指针错误,why??????????
try {
int seconds = sPlayer.getCurrentTime() / 1000;
int minutes = seconds / 60;
seconds = seconds % 60;
cur.setText("当前播放时间" + minutes + ":" + seconds + "播放状态"
+ sPlayer.isPlaying());
dur.setText("歌曲总长度:" + sPlayer.getDuration());
// 更新进度条
seekBar.setMax(sPlayer.getDuration());
seekBar.setProgress(sPlayer.getCurrentTime());
} catch (RemoteException e) {
Log.e("Error", "Unable to get Time------------->>");
}
}
mHandler.postDelayed(updateThread, 500);// 将线程延迟加入handler处理
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.player);
Intent intent = getIntent();
info = (Mp3Info) intent.getSerializableExtra("mp3Info");
startBt = (Button) findViewById(R.id.start);
pauseBt = (Button) findViewById(R.id.pause);
stopBt = (Button) findViewById(R.id.stop);
cur = (TextView) findViewById(R.id.cur);
dur = (TextView) findViewById(R.id.dur);
seekBar = (SeekBar) findViewById(R.id.seekbar01);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
Log.v("SeekBar", "停止调节");
try {
if (sPlayer != null) {
sPlayer.setCurrent(seekBar.getProgress());
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
Log.v("SeekBar", "开始调节");
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
}
});
startBt.setOnClickListener(this);
pauseBt.setOnClickListener(this);
stopBt.setOnClickListener(this);
intent = new Intent(Mp3PlayerActivity.this, PlayService.class);
intent.putExtra("mp3Info", info);
bindService(intent, sConnection, Context.BIND_AUTO_CREATE);
startService(intent);
mHandler.post(updateThread);
}
@Override
protected void onDestroy() {
super.onDestroy();
doUnbindService();
}
void doUnbindService() {
unbindService(sConnection);
}
@Override
public void onClick(View v) {
// Intent intent = new Intent();
// intent.setClass(this, PlayService.class);
if (sPlayer != null) {
try {
isPlaying = sPlayer.isPlaying();
} catch (RemoteException e) {
e.printStackTrace();
}
}
switch (v.getId()) {
case R.id.start:
try {
sPlayer.play();
} catch (RemoteException e) {
e.printStackTrace();
}
break;
case R.id.pause:
try {
sPlayer.pause();
} catch (RemoteException e) {
e.printStackTrace();
}
break;
case R.id.stop:
try {
sPlayer.stop();
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
}
}
别忘了,Activity和Service都需要在AndroidManifest中进行声明