Android运用AIDL技术实现实时更新MP3播放器的播放进度

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接口一共有三步:

  1. Create the .aidl file

    This file defines the programming interface with method signatures.

  2. 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 named Stub that extends Binder and implements methods from your AIDL interface. You must extend the Stub class and implement the methods.

  3. Expose the interface to clients

    Implement a Service and override onBind() to return your implementation of the Stub 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.实现接口

SDK会根据你的.aidl文件来生成一个接口(既IRemoteService.java),这个接口中含有一个继承自Binder的内部抽象类叫Stub,Stub还实现了.aidl中声明的方法,使用时必须继承Stub类并实现其中的方法。例如:
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.交给客户端使用该接口

要想让客户端使用该接口,你必须把mBinder放到一个继承了Service的服务类中,实现onBind方法,并返回mBinder。在客户端(例如一个Activity)声明一个ServiceConnection变量,并使用内部类重写onServiceConnected和onServiceDisconnected方法,在声明一个IRemoteService变量mIRemoteService,在onServiceConnected(ComponentName c,IBinder service)方法中对该变量进行赋值,其值为IRemoteService.Stub.asInterface(service),例如
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(...)

关于如何在进程之间通过IPC机制来传递Java类,我会在下篇博客中讲解!!!

===========================================================================================
下面我们就运用AIDL技术来实现我们上面提到的需求

首先创建一个ServicePlayer.aidl文件
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之前的生命周期里进行初始化!

最后,我们在Activity中进行使用
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中进行声明

源码下载(只包含了AIDL相关的类):http://download.csdn.net/detail/mjook007/5376437

你可能感兴趣的:(android,aidl,mediaplayer)