AIDL示例

背景

最近在考慮項目重構的時候,考慮將項目拆分紅兩個APK,一個用於數據服務,一個用於UI展現。
數據服務APK向本身編寫APK提供數據,同時也能夠向第三方提供數據。考慮使用這樣的方式代替向第三方提供jar形式的sdk包。
若是拆分紅多個APK,不得不考慮 進程間通訊(IPC)的問題。Android提供了一種IPC的實現,就是AIDL.java

在學習AIDL時編寫示例造成本文。放在Github的demo項目中。能夠在下面的地址下載到源代碼
github: https://github.com/vir56k/demo/tree/master/aidlDemoandroid

什麼是AIDL

AIDL (Android Interface Definition Language, Android接口定義語言)
在不一樣的進程(應用)之間進行數據交換,就要約定 之間的通訊接口。git

從面向對象的角度來看,接口設計要考慮狀態和行爲。通常來講,接口定義的內容分爲:
1.方法操做(描述行爲)
2.參數(描述狀態,數據的類型,數據的載體/實體)github

AIDL是一種IDL,它有特有的語法描述。咱們須要編寫一個AIDL文件做爲約定。它的語法很是相似java語法。
它支持基礎數據類型,好比 int,String,float等。
它支持實體類,必須是實現了Parcelable接口,支持序列化。多線程

AIDL經過服務綁定的方式來使用。你須要定義一個service,傳遞一個 IBinder對象。這個 IBinder對象具備咱們須要的方法。
拿到這個對象後執行具體方法。app

AIDL分爲 服務端和客戶端
服務端即服務提供着,提供可操做的方法和數據。
客戶端即調用者,使用方法和數據。async

何時適合使用AIDL:
官方文檔建議只有你容許客戶端從不一樣的應用程序爲了進程間的通訊而去訪問你的service,以及想在你的service處理多線程。ide

步驟說明

服務端開發步驟以下:

1.定義一個AIDL文件
2.實現描述的接口,編寫service
3.若是有實體類,須要提供實體類(jar包形式)學習

客戶端

1.拿到AIDL文件
2.綁定服務,得到接口持有對象。ui

示例

服務端開發

1.聲明AIDL文件

Android提供的特殊的文件夾來放置AIDL文件,位於 src/mian/aidl 文件夾下。
因爲java類/接口是有 package(命名空間)的。咱們須要定義命名空間,通常和文件位置一致。
在這裏,咱們在 src/mian/aidl 文件夾下,建立package,名稱爲:com.example.myserver。
對應文件夾路徑爲src/mian/aidl/com/example/myserver,咱們在這個文件下創建咱們的aidl文件,內容以下:

IRemoteService.aidl

package com.example.myserver;
    import com.example.myserver.Entity;
    import com.example.myserver.IMyCallback;
    
    // Declare any non-default types here with import statements
    
    interface IRemoteService {
    
        void doSomeThing(int anInt,String aString);
    
        void addEntity(in Entity entity);
    
        List<Entity> getEntity();
    
    }

Entity.aidl,這個是實體類 ,它還須要對應一個java class文件

// Entity.aidl
package com.example.myserver;

parcelable Entity;

2.實現接口,編寫service

在src/java文件夾寫下 MyService class,集成服務Service類.在mainifest文件中註冊這個服務類。
若是你的aidl描述文件編寫無誤的話,android studio 會自動幫你生成一些輔助類,你能夠在下面的目錄找到:

build/generated/source/debug

在這個文件夾下回自動生成有 IRemoteService類,和它的子類 IRemoteService.Stub類及其餘。感興趣的同窗能夠讀讀。

IRemoteService.Stub是一個根文件,它是一個抽象類。下面代碼演示了,一個 IRemoteService.Stub 的匿名類的實現。
在這個服務類的 public IBinder onBind(Intent intent) 方法中,咱們return 一個 IRemoteService.Stub 的匿名類實現。

在客戶綁定到這個服務的時候,將能夠得到到這個實現的一個實例,調用它的方法。

代碼以下

package com.example.myserver;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by zhangyunfei on 16/10/12.
 */
public class MyService extends Service {
    public static final String TAG = "MyService";

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, String.format("on bind,intent = %s", intent.toString()));
        return binder;
    }


    private final IRemoteService.Stub binder = new IRemoteService.Stub() {
        public static final String TAG = "IRemoteService.Stub";
        private List<Entity> data = new ArrayList<Entity>();

        @Override
        public void doSomeThing(int anInt, String aString) throws RemoteException {
            Log.d(TAG, String.format("收到:%s, %s", anInt, aString));
        }

        @Override
        public void addEntity(Entity entity) throws RemoteException {
            Log.d(TAG, String.format("收到:entity = %s", entity));
            data.add(entity);
        }

        @Override
        public List<Entity> getEntity() throws RemoteException {
            return data;
        }


    };
}

3.編寫實體類

咱們上面提到,接口的參數能夠是實體類。咱們在前面定義了一個entity.aidl,它裏面寫了一句

parcelable Entity;

這麼一句話指明它須要關聯到一個具體的實體類。咱們須要在src/java文件夾編寫這麼一個類的實現,必須實現parcelable接口。
注意咱們要先創建package,這個 package要和aidl接口聲明裏的一致。
android studio爲咱們方便的提供自動生成parcelable實現的快捷鍵,在mac下是 command+空格。實現後的代碼以下:

package com.example.myserver;

import android.os.Parcel;
import android.os.Parcelable;

/**
 * Created by zhangyunfei on 16/10/12.
 */
public class Entity implements Parcelable {
    int age;
    String name;

    public Entity() {
    }

    public Entity(int age, String name) {
        this.age = age;
        this.name = name;
    }

    protected Entity(Parcel in) {
        age = in.readInt();
        name = in.readString();
    }

    public static final Creator<Entity> CREATOR = new Creator<Entity>() {
        @Override
        public Entity createFromParcel(Parcel in) {
            return new Entity(in);
        }

        @Override
        public Entity[] newArray(int size) {
            return new Entity[size];
        }
    };

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(age);
        dest.writeString(name);
    }

    @Override
    public String toString() {
        return String.format("age=%s, name=%s", age, name);
    }
}

客戶端開發 - 調用AIDL接口

再開始以前,咱們能夠新建一個app來作演示.步驟以下:

1.得到AIDL,放到項目中

咱們先拿到AIDL描述文件才用使用,將AIDL文件放到aidl文件夾下。android studio 自動生成根文件類。
得到實體類Entity.class 放入到項目中。

2.在activity中調用

在它的 MainActivity 下綁定服務

Intent intent = new Intent();
        intent.setAction("com.example.REMOTE.myserver");
        intent.setPackage("com.example.myserver");
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);

指定服務的名稱,bindService方法中須要傳入一個 ServiceConnection對象。
咱們寫一個ServiceConnection的匿名類,在它的onServiceConnected方法中,得到 aidl定義的接口持有類。

iRemoteService = IRemoteService.Stub.asInterface(service);

還記得剛剛編寫服務類返回的 binder嗎,在這裏得到的就是那個binder示例。咱們能夠經過對這個示例進行 轉型 後的對象來調用 接口定義的方法。

3.調用接口方法

經過 iRemoteService.addEntity(entity) 方法,咱們能夠操做具體的實體,傳入實體類做爲參數。

if (!mBound) {
                    alert("未鏈接到遠程服務");
                    return;
                }
                try {
                    Entity entity = new Entity(1, "zhang");
                    if (iRemoteService != null)
                        iRemoteService.addEntity(entity);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }

完整代碼以下:

package com.example.zhangyunfei.myapplication;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import com.example.myserver.Entity;
import com.example.myserver.IMyCallback;
import com.example.myserver.IRemoteService;

import java.util.List;

public class MainActivity extends AppCompatActivity {

    private boolean mBound = false;
    private IRemoteService iRemoteService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.btnAdd).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!mBound) {
                    alert("未鏈接到遠程服務");
                    return;
                }
                try {
                    Entity entity = new Entity(1, "zhang");
                    if (iRemoteService != null)
                        iRemoteService.addEntity(entity);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }


        });

        findViewById(R.id.btnList).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!mBound) {
                    alert("未鏈接到遠程服務");
                    return;
                }
                if (iRemoteService != null) {
                    try {
                        List<Entity> entityList = iRemoteService.getEntity();

                        StringBuilder sb = new StringBuilder("當前數量:" + entityList.size() + "\r\n");
                        for (int i = 0; i < entityList.size(); i++) {
                            sb.append(i + ": ");
                            sb.append(entityList.get(i) == null ? "" : entityList.get(i).toString());
                            sb.append("\n");
                        }
                        alert(sb.toString());
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        findViewById(R.id.btnCallback).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!mBound) {
                    alert("未鏈接到遠程服務");
                    return;
                }
                try {
                    if (iRemoteService != null) {
                        final String para = "canshu";
                        iRemoteService.asyncCallSomeone(para, new IMyCallback.Stub() {
                            @Override
                            public void onSuccess(String aString) throws RemoteException {
                                alert(String.format("發送: %s, 回調: %s", para, aString));
                            }
                        });
                    }
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private void alert(String str) {
        Toast.makeText(this, str, 0).show();
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (!mBound) {
            attemptToBindService();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mBound) {
            unbindService(mServiceConnection);
            mBound = false;
        }
    }


    /**
     * 嘗試與服務端創建鏈接
     */
    private void attemptToBindService() {
        Intent intent = new Intent();
        intent.setAction("com.example.REMOTE.myserver");
        intent.setPackage("com.example.myserver");
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }


    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(getLocalClassName(), "service connected");
            iRemoteService = IRemoteService.Stub.asInterface(service);
            mBound = true;

            if (iRemoteService != null) {
                try {
                    iRemoteService.doSomeThing(0, "anything string");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(getLocalClassName(), "service disconnected");
            mBound = false;
        }
    };
}

回調

在AIDL中,有時候須要實現回調,傳入一個回調callbak,或者listener類。如何實現呢?

1.編寫回調類aidl文件

IMyCallback類具備一個 onSuccess回調方法
IMyCallback.aidl,這個文件裏描述一個回調接口

// IMyCallback.aidl
package com.example.myserver;

// Declare any non-default types here with import statements

interface IMyCallback {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void onSuccess(String aString);
}

2.聲明方法,以回調類做爲參數,示例:

IRemoteService.aidl

package com.example.myserver;
    import com.example.myserver.Entity;
    import com.example.myserver.IMyCallback;
    
    // Declare any non-default types here with import statements
    
    interface IRemoteService {
   
        void asyncCallSomeone( String para, IMyCallback callback);
    }

3.實現方法,發起回調通知

發起回調有點相似廣播的方式,示例:

@Override
        public void asyncCallSomeone(String para, IMyCallback callback) throws RemoteException {
            RemoteCallbackList<IMyCallback> remoteCallbackList = new RemoteCallbackList<>();
            remoteCallbackList.register(callback);
            final int len = remoteCallbackList.beginBroadcast();
            for (int i = 0; i < len; i++) {
                remoteCallbackList.getBroadcastItem(i).onSuccess(para + "_callbck");
            }
            remoteCallbackList.finishBroadcast();
        }

咱們須要一個 RemoteCallbackList 集合類,把 要回調的類的示例callback示例放到這集合內。調用這個集合類RemoteCallbackList的下面兩個方法:
beginBroadcast 開始廣播,finishBroadcast 結束廣播,配合使用。

4.客戶端調用示例:

客戶端在得到接口操做對象後,傳入回調類,示例:

try {
                if (iRemoteService != null) {
                    final String para = "canshu";
                    iRemoteService.asyncCallSomeone(para, new IMyCallback.Stub() {
                        @Override
                        public void onSuccess(String aString) throws RemoteException {
                            alert(String.format("發送: %s, 回調: %s", para, aString));
                        }
                    });
                }
            } catch (RemoteException e) {
                e.printStackTrace();
            }

參考

谷歌官方文檔

相關文章
相關標籤/搜索