Android AIDL應用間交互


Android Service介紹中我們對長時間運行的服務、應用內交互的服務進行了相關介紹,本文主要介紹 使用Service進行應用間的交互示例APK見:TrineaAndroidDemo.apk
 
1、介紹
Android使用AIDL來完成進程間通信(IPC),AIDL全程為Android Interface Definition Language。 在服務需要接受不同應用多線程的請求時才需要使用AIDL,如果是同一個應用內的請求使用Binder實現即可,見應用內交互的服務;如果只是應用間通信而不是多線程處理的話使用Messenger,當然這兩種情況也可以使用AIDL。本地進程和遠程進程使用AIDL有所不同,本地進程內調用時會都在調用的線程內執行,遠程進程使用是通過Service進程內一個由系統維護的線程池發出調用,所以可能是未知線程同時調用,需要注意 線程安全問題
 
2、示例
在被調用應用內(后面稱服務端)的Service內有個屬性count,需要在調用者(后面稱客戶端)內對這個屬性進行操作。下面將介紹服務器端和客戶端的主要代碼
服務器端(代碼見ServiceDemo):

(1) 服務器端新建AIDL文件定義接口

在服務器端的src目錄下新建以.aidl為后綴的文件,在這個文件中定義一個接口,聲明服務器端和客戶端交互的api,語法跟普通java接口聲明一樣,可以添加中英文注釋。不同的是

a. 除了Java基本數據類型 (int, long, char, boolean等)、String、CharSequence、List、Map外,其他復雜類型都需要顯式import(包括其他AIDL定義的接口),即便復雜類型在同一個包內定義。

b. 支持泛型實例化的List,如List<String>;不支持泛型實例化的Map,如Map<String, String>。對於List為參數接收者接收到的始終是ArrayList;對於Map為參數接收者接收到的始終是HashMap。

c. interface和函數都不能帶訪問權限修飾符。

d. 接口內只允許定義方法,不允許定義靜態屬性。

我們定義MyAIDLInterface.aidl文件如下

package com.trinea.android.demo;

interface MyAIDLInterface {

     int getCount();

     void setCount(int count);
}

聲明兩個接口在后面供客戶端調用。保存文件,編譯后,Sdk工具在gen目錄下相同子目錄中自動生成同名的以.java為后綴的文件,這里文件名為MyAIDLInterface.java。

 

(2) 自動生成的同名java文件解析

MyAIDLInterface.java為Sdk工具自動生成,內容如下

根據AIDL文件自動生成的Java文件
/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: D:\\Eclipse\\AndroidDemo\\MapDemo\\src\\com\\trinea\\android\\demo\\MyAIDLInterface.aidl
 */
package com.trinea.android.demo;

public interface MyAIDLInterface extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.trinea.android.demo.MyAIDLInterface
{
private static final java.lang.String DESCRIPTOR = "com.trinea.android.demo.MyAIDLInterface";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
 * Cast an IBinder object into an com.trinea.android.demo.MyAIDLInterface interface,
 * generating a proxy if needed.
 */
public static com.trinea.android.demo.MyAIDLInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.trinea.android.demo.MyAIDLInterface))) {
return ((com.trinea.android.demo.MyAIDLInterface)iin);
}
return new com.trinea.android.demo.MyAIDLInterface.Stub.Proxy(obj);
}
public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getCount:
{
data.enforceInterface(DESCRIPTOR);
int _result = this.getCount();
reply.writeNoException();
reply.writeInt(_result);
return true;
}
case TRANSACTION_setCount:
{
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
this.setCount(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.trinea.android.demo.MyAIDLInterface
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
public int getCount() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getCount, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public void setCount(int count) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(count);
mRemote.transact(Stub.TRANSACTION_setCount, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getCount = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_setCount = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public int getCount() throws android.os.RemoteException;
public void setCount(int count) throws android.os.RemoteException;
}

其中有幾個主要的部分:

a. 抽象類Stub,繼承Binder實現自定義接口,作用同進程內通信服務中自定義的Binder,客戶端通過它對服務進行調用。

b. 靜態類Proxy,實現自定義接口,代理模式接收對Stub的調用。

 

(3) 服務器端定義服務

跟一般的服務定義類似,新建個上面自動生成的MyAIDLInterface.java文件中的Stub的對象,在onBind中返回,代碼如下:
服務器端Service代碼
package com.trinea.android.demo;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.widget.Toast;

public class MyAIDLService extends Service {

    private int                  mCount;
    private MyAIDLInterface.Stub myBinder = new MyAIDLInterface.Stub() {

                                              @Override
                                              public void setCount(int count) throws RemoteException {
                                                  mCount = count;
                                              }

                                              @Override
                                              public int getCount() throws RemoteException {
                                                  return mCount;
                                              }
                                          };

    @Override
    public void onCreate() {
        mCount = 0;
        Toast.makeText(this, "Service onCreate", Toast.LENGTH_SHORT).show();
        super.onCreate();
    }

    @Override
    public void onDestroy() {
        Toast.makeText(this, "Service onDestroy", Toast.LENGTH_SHORT).show();
        super.onDestroy();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Toast.makeText(this, Integer.toString(mCount), Toast.LENGTH_SHORT).show();
        return super.onStartCommand(intent, flags, startId);
    }

    public int getCount() {
        return mCount;
    }

    /**
     * 服務被綁定時調用
     * 返回值用於讓調用者和服務通信,傳入ServiceConnection的public void onServiceConnected(ComponentName name, IBinder service)函數第二個參數
     */
    @Override
    public IBinder onBind(Intent intent) {
        return myBinder;
    }
}

AndroidManifest.xml中注冊服務

<service android:name=".MyAIDLService">
    <intent-filter>
        <action android:name="com.trinea.android.demo.remote.MyAIDLServiceAction" />
    </intent-filter>
</service>

其中的action name用於后面客戶端調用時匹配

 

客戶端(代碼見AndroidAidlClient@GoogleCode):

(1) 拷貝aidl文件
將服務器端的aidl文件拷貝到客戶端,客戶端也會在gen目錄下自動生成同名的java文件。服務器端和客戶端文件目錄如下:

服務器端目錄結構:,客戶端目錄結構:

PS:在運行過程中發現一個小問題,即服務器端服務已經啟動,而客戶端始終無法BindService成功,報錯

ERROR/AndroidRuntime(2104): java.lang.SecurityException: Binder invocation to an incorrect interface。后發現是因為服務器端和客戶端aidl文件的包名不同導致生成的java文件不同。

 

(2) 服務綁定
跟一般的服務綁定類似,只是獲取服務的接口不同而已,這里我們通過Stub.asInterface函數獲得,代碼如下
客戶端調用遠程服務代碼
private MyAIDLInterface   binder = null;
private Intent            myAIDLServiceIntent;
private ServiceConnection con    = new ServiceConnection() {

                                     @Override
                                     public void onServiceDisconnected(ComponentName name) {

                                     }

                                     @Override
                                     public void onServiceConnected(ComponentName name, IBinder service) {
                                         binder = MyAIDLInterface.Stub.asInterface(service);
                                     }
                                 };

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.client);
    
    boundAIDLServiceBtn = (Button)findViewById(R.id.boundAIDLService);
    myAIDLServiceIntent = new Intent("com.trinea.android.demo.remote.MyAIDLServiceAction");
    boundAIDLServiceBtn.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
            boolean result = bindService(myAIDLServiceIntent, con, Context.BIND_AUTO_CREATE);
            if (!result) {
                binder = null;
                Toast.makeText(getApplicationContext(), "服務綁定失敗。", Toast.LENGTH_SHORT).show();
            }
        }
    });

    getBoundAIDLServiceProBtn = (Button)findViewById(R.id.getBoundAIDLServicePro);
    getBoundAIDLServiceProBtn.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
            try {
                if (binder != null) {
                    Toast.makeText(getApplicationContext(), "Service count:" + binder.getCount(),
                                   Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(getApplicationContext(), "請先綁定服務。", Toast.LENGTH_SHORT).show();
                }
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    });

    unboundAIDLServiceBtn = (Button)findViewById(R.id.unboundAIDLService);
    unboundAIDLServiceBtn.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
            if (binder != null) {
                unbindService(con);
                binder = null;
            }
        }
    });
}

上面的三個按鈕boundAIDLServiceBtn、getBoundAIDLServiceProBtn、unboundAIDLServiceBtn分別用於綁定服務、得到服務中屬性值、解除綁定服務。

從上面可以看出我們先定義ServiceConnection對象用於和服務器通信,在onServiceConnected函數中通過MyAIDLInterface.Stub.asInterface(service)返回用戶定義的接口對象,通過此對象調用遠程接口api。在boundAIDLServiceBtn的onClick函數中綁定服務,在getBoundAIDLServiceProBtn的onClick函數中調用服務的api,在boundAIDLServiceBtn的onClick函數中取消綁定服務,

現在我們運行兩個項目即可,在客戶端綁定服務再獲取服務屬性顯示。

 

3、生命周期

通過bindService綁定服務,若服務未啟動,會先執行Service的onCreate函數,再執行onBind函數,最后執行ServiceConnection對象的onServiceConnected函數,客戶端可以自動啟動服務。若服務已啟動但尚未綁定,先執行onBind函數,再執行ServiceConnection對象的onServiceConnected函數。若服務已綁定成功,則直接返回。這里不會自動調用onStartCommand函數。

通過unbindService解除綁定服務,若已綁定成功,會先執行Service的onUnbind函數,再執行onDestroy函數,注意這里不會執行ServiceConnection對象的onServiceDisconnected函數,因為該函數是在服務所在進程被kill或是crash時被調用。

 
參考:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM