標籤:des android class http java tar
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 檔案,加入如下代碼:
package pandafang.demo.playerserver; 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類。代碼如下:
package pandafang.demo.playerserver; import java.io.FileDescriptor; import java.io.IOException; import android.app.Service; import android.content.Intent; import android.media.MediaPlayer; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; /** * 播放音樂的服務 * @author Panda Fang * @date 2012-10-22 10:15:33 */ public class PlayerService extends Service { public static final String TAG = "PlayerService"; private MediaPlayer mplayer; // 實現aidl檔案中定義的介面 private IBinder mBinder = new IRemoteService.Stub() { @Override public void stop() throws RemoteException { try { if (mplayer.isPlaying()) { mplayer.stop(); } } catch (Exception e) { // TODO: handle exception 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) { // TODO: handle exception e.printStackTrace(); } } }; @Override public IBinder onBind(Intent intent) { 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.lost); // 方法一結束 // 方法二說明 // 若用此方法,在mplayer.start() 之前需要調用mplayer.prepare() // 方法二開始 mplayer = new MediaPlayer(); try { FileDescriptor fd = getResources().openRawResourceFd(R.raw.lost).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 。
運行服務端到裝置上,準備給用戶端調用。
用戶端
建立一個android application project,命名為PlayerClient。將服務端放有aidl檔案的包直接copy到用戶端src目錄下,保留包中的aidl檔案,其他刪除。
編輯 layout 下的 activity_main.xml 布局檔案,加入兩個按鈕,代碼如下:
編寫MainActivity.java 代碼如下:
package pandafang.demo.playerclient; import pandafang.demo.playerserver.IRemoteService; 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.IBinder; import android.os.RemoteException; import android.util.Log; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; /** * 用戶端控制介面 * @author Panda Fang * @date 2012-10-22 10:36:44 */ public class MainActivity extends Activity { public static final String TAG = "MainActivity"; // 服務端 AndroidManifest.xml中的intent-filter action聲明的字串 public static final String ACTION = "com.example.playerserver.PlayerService"; private Button playbtn, stopbtn; private IRemoteService mService; private boolean isBinded = false; private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { isBinded = false; mService = null; } @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = IRemoteService.Stub.asInterface(service); isBinded = true; } }; @Override public 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(clickListener); stopbtn.setOnClickListener(clickListener); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_main, menu); return true; } @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; } } private OnClickListener clickListener = new OnClickListener() { @Override public void onClick(View v) { if (v.getId() == playbtn.getId()) { // play Log.i(TAG,"play button clicked"); try { mService.play(); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else { // stop Log.i(TAG,"stop button clicked"); try { mService.stop(); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }; }
MainActivity是根據嚮導自動產生的,不需要在AndroidManifest.xml中註冊聲明了。
運行用戶端到裝置,按下按鈕可以播放/停止 效果