Android Service AIDL 远程调用服务之简单音乐播放实例
作者:Panda Fang
出处:http://www.cnblogs.com/lonkiss/archive/2012/10/23/2735548.html
原创文章,转载请注明作者和出处,未经允许不可用于商业营利活动
Android Service是分为两种:
本地服务(Local Service): 同一个apk内被调用
远程服务(Remote Service):被另一个apk调用
远程服务需要借助AIDL来完成。
AIDL 是什么AIDL (Android Interface Definition Language) 是一种IDL 语言,用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。
AIDL IPC机制是面向接口的,像COM或Corba一样,但是更加轻量级。它是使用代理类在客户端和实现端传递数据。AIDL 的作用由于每个应用程序都运行在自己的进程空间,并且可以从应用程序UI运行另一个服务进程,而且经常会在不同的进程间传递对象。在Android平台,一个进程通常不能访问另一个进程的内存空间,所以要想对话,需要将对象分解成操作系统可以理解的基本单元,并且有序的通过进程边界。通过代码来实现这个数据传输过程是冗长乏味的,Android提供了AIDL工具来处理这项工作。选择AIDL的使用场合官方文档特别提醒我们何时使用AIDL是必要的:只有你允许客户端从不同的应用程序为了进程间的通信而去访问你的service,以及想在你的service处理多线程。如果不需要进行不同应用程序间的并发通信(IPC),you should create your interface by implementing a Binder;或者你想进行IPC,但不需要处理多线程的,则implement your interface using a Messenger。无论如何,在使用AIDL前,必须要理解如何绑定service——bindService。
下面用一个客户端Activity操作服务端Service播放音乐的实例演示AIDL的使用。
开发工具: eclipse 3.7(indigo)+ android sdk 4.1+ adt 20.0.2
服务端代码结构(左) 客户端代码结构(右)
被标记的就是需要动手的。
服务端
新建一个android application project,命名为PlayerServer。 在res下的raw文件夹里面放入一个音乐文件,我这里放入的是Delta Goodrem的《Lost Without You》片段。如果不存在raw这个文件夹就自己新建一个,命名为raw。这个文件夹在raw文件夹下,与layout文件夹平级。raw中的文件遵守标识符的命名规则,不要出现中文和空格,多个单词可以用下划线连接。
新建一个IRemoteServiice.aidl 文件,加入如下代码
1 package pandafang.demo.playerserver; 2 interface IRemoteService { 3 void play(); 4 void stop(); 5 }
可见aidl文件的代码跟java的interface一样,但是aidl中不能加public等修饰符。Ctrl + S 保存后 ADT 会根据这个IRemoteService.aidl文件自动生成IRemoteService.java文件。如同R.java文件一样在“gen/包名”下,代码是自动生成的,不要手动修改。
接下来就是bound service(参考1:官方文档,参考2:好博客)的知识了。IRemoteService.java 中有一个Stub静态抽象类extends Binder implements IRemoteService。自己动手写一个PlayerService 用来播放音乐,播放音乐需要使用android.media.MediaPlayer类。代码如下
1 package pandafang.demo.playerserver; 2 3 import java.io.FileDescriptor; 4 import java.io.IOException; 5 import android.app.Service; 6 import android.content.Intent; 7 import android.media.MediaPlayer; 8 import android.os.IBinder; 9 import android.os.RemoteException; 10 import android.util.Log; 11 12 /** 13 * 播放音乐的服务 14 * @author Panda Fang 15 * @date 2012-10-22 10:15:33 16 */ 17 public class PlayerService extends Service { 18 19 public static final String TAG = "PlayerService"; 20 21 private MediaPlayer mplayer; 22 23 @Override 24 public IBinder onBind(Intent intent) { 25 Log.i(TAG,"service onbind"); 26 if(mplayer==null){ 27 // 方法一说明 28 // 此方法实例化播放器的同时指定音乐数据源 ,若用此方法在,mplayer.start() 之前不需再调用mplayer.prepare() 29 // 官方文档有说明 :On success, prepare() will already have been called and must not be called again. 30 // 译文:一旦create成功,prepare已被调用,勿再调用 。查看源代码可知create方法内部已经调用prepare方法。 31 // 方法一开始 32 // mplayer = MediaPlayer.create(this, R.raw.lost); 33 // 方法一结束 34 35 // 方法二说明 36 // 若用此方法,在mplayer.start() 之前需要调用mplayer.prepare() 37 // 方法二开始 38 mplayer = new MediaPlayer(); 39 try { 40 FileDescriptor fd = getResources().openRawResourceFd(R.raw.lost).getFileDescriptor(); // 获取音乐数据源 41 mplayer.setDataSource(fd); // 设置数据源 42 mplayer.setLooping(true); // 设为循环播放 43 } catch (IOException e) { 44 // TODO Auto-generated catch block 45 e.printStackTrace(); 46 } 47 // 方法二结束 48 Log.i(TAG,"player created"); 49 } 50 return mBinder; 51 } 52 53 // 实现aidl文件中定义的接口 54 private IBinder mBinder = new IRemoteService.Stub() { 55 56 @Override 57 public void stop() throws RemoteException { 58 try { 59 if (mplayer.isPlaying()) { 60 mplayer.stop(); 61 } 62 } catch (Exception e) { 63 // TODO: handle exception 64 e.printStackTrace(); 65 } 66 } 67 68 @Override 69 public void play() throws RemoteException { 70 try { 71 if (mplayer.isPlaying()) { 72 return; 73 } 74 // start之前需要prepare。 75 // 如果前面实例化mplayer时使用方法一,则第一次play的时候直接start,不用prepare。 76 // 但是stop一次之后,再次play就需要在start之前prepare了。 77 // 前面使用方法二 这里就简便了, 不用判断各种状况 78 mplayer.prepare(); 79 mplayer.start(); 80 } catch (Exception e) { 81 // TODO: handle exception 82 e.printStackTrace(); 83 } 84 } 85 }; 86 87 @Override 88 public boolean onUnbind(Intent intent) { 89 if (mplayer != null) { 90 mplayer.release(); 91 } 92 Log.i(TAG,"service onUnbind"); 93 return super.onUnbind(intent); 94 } 95 96 }
服务编写好以后,按照惯例在AndroidManifest.xml中加入声明,代码如下
1 <manifest xmlns:android="http://schemas.android.com/apk/res/android" 2 package="pandafang.demo.playerserver" 3 android:versionCode="1" 4 android:versionName="1.0" > 5 6 <uses-sdk 7 android:minSdkVersion="8" 8 android:targetSdkVersion="15" /> 9 10 <application 11 android:icon="@drawable/ic_launcher" 12 android:label="@string/app_name" 13 android:theme="@style/AppTheme" > 14 <service android:name=".PlayerService" android:process=":remote"> 15 <intent-filter > 16 <action android:name="com.example.playerserver.PlayerService"/> 17 </intent-filter> 18 </service> 19 </application> 20 21 </manifest>
需要加入的只是<service>...</service>那段,要注意的是 android:process=":remote" 和 intent-filter 。
运行服务端到设备上,准备给客户端调用
客户端
新建一个android application project,命名为PlayerClient。将服务端放有aidl文件的包直接copy到客户端src目录下,保留包中的aidl文件,其他删除。
编辑 layout 下的 activity_main.xml 布局文件,加入两个按钮,代码如下
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:gravity="center_vertical"> 6 7 <Button 8 android:id="@+id/button1" 9 android:layout_width="match_parent" 10 android:layout_height="wrap_content" 11 android:text="play" /> 12 13 <Button 14 android:id="@+id/button2" 15 android:layout_width="match_parent" 16 android:layout_height="wrap_content" 17 android:layout_below="@id/button1" 18 android:layout_marginTop="20dp" 19 android:text="stop" /> 20 21 </RelativeLayout>
编写MainActivity.java 代码如下
1 package pandafang.demo.playerclient; 2 3 import pandafang.demo.playerserver.IRemoteService; 4 import android.app.Activity; 5 import android.content.ComponentName; 6 import android.content.Context; 7 import android.content.Intent; 8 import android.content.ServiceConnection; 9 import android.os.Bundle; 10 import android.os.IBinder; 11 import android.os.RemoteException; 12 import android.util.Log; 13 import android.view.Menu; 14 import android.view.View; 15 import android.view.View.OnClickListener; 16 import android.widget.Button; 17 18 /** 19 * 客户端控制界面 20 * @author Panda Fang 21 * @date 2012-10-22 10:36:44 22 */ 23 public class MainActivity extends Activity { 24 25 public static final String TAG = "MainActivity"; 26 27 // 服务端 AndroidManifest.xml中的intent-filter action声明的字符串 28 public static final String ACTION = "com.example.playerserver.PlayerService"; 29 30 private Button playbtn, stopbtn; 31 32 private IRemoteService mService; 33 34 private boolean isBinded = false; 35 36 private ServiceConnection conn = new ServiceConnection() { 37 38 @Override 39 public void onServiceDisconnected(ComponentName name) { 40 isBinded = false; 41 mService = null; 42 } 43 44 @Override 45 public void onServiceConnected(ComponentName name, IBinder service) { 46 mService = IRemoteService.Stub.asInterface(service); 47 isBinded = true; 48 } 49 }; 50 51 @Override 52 public void onCreate(Bundle savedInstanceState) { 53 super.onCreate(savedInstanceState); 54 setContentView(R.layout.activity_main); 55 56 doBind(); 57 initViews(); 58 } 59 60 private void initViews() { 61 playbtn = (Button) findViewById(R.id.button1); 62 stopbtn = (Button) findViewById(R.id.button2); 63 playbtn.setOnClickListener(clickListener); 64 stopbtn.setOnClickListener(clickListener); 65 } 66 @Override 67 public boolean onCreateOptionsMenu(Menu menu) { 68 getMenuInflater().inflate(R.menu.activity_main, menu); 69 return true; 70 } 71 72 @Override 73 protected void onDestroy() { 74 doUnbind(); 75 super.onDestroy(); 76 } 77 78 public void doBind() { 79 Intent intent = new Intent(ACTION); 80 bindService(intent, conn, Context.BIND_AUTO_CREATE); 81 } 82 83 public void doUnbind() { 84 if (isBinded) { 85 unbindService(conn); 86 mService = null; 87 isBinded = false; 88 } 89 90 } 91 private OnClickListener clickListener = new OnClickListener() { 92 93 @Override 94 public void onClick(View v) { 95 if (v.getId() == playbtn.getId()) { 96 // play 97 Log.i(TAG,"play button clicked"); 98 try { 99 mService.play(); 100 } catch (RemoteException e) { 101 // TODO Auto-generated catch block 102 e.printStackTrace(); 103 } 104 } else { 105 // stop 106 Log.i(TAG,"stop button clicked"); 107 try { 108 mService.stop(); 109 } catch (RemoteException e) { 110 // TODO Auto-generated catch block 111 e.printStackTrace(); 112 } 113 } 114 } 115 }; 116 117 }
MainActivity是根据向导自动生成的,不需要在AndroidManifest.xml中注册声明了
运行客户端到设备,按下按钮可以播放/停止 效果如图
扩展阅读:
要想了解 AIDL 远程调用如何传输对象数据:http://blog.csdn.net/android_tutor/article/details/6427680
源代码:下载