作者: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
源代碼:下載