AIDL 是Android上提供的一套進程間通訊的機制,在多進程間的通信上他是比較常見的,那爲什麼要用aidl 呢,我們的四大組件都可以實現跨進程,我只能說使用的場景有所不同罷了。
1.activity 可以跨進程調用另一個進程 的activity 並且也可以攜帶參數,但是侷限性比較大,只能通過intent 在啓動時傳遞。
2.BroadcastReceiver 廣播接收器,他可以無限次的發送全局廣播,但是它的數據可能不是安全的,並且通信的效率也沒有aidl 高,並且維護成本也很高,不能處理耗時操作。
3.ContentProvider 是一種數據共享型組件,用於向其他組件乃至其他應用共享數據。在它內部維持着一份數據集合, 這個數據集合既可以通過數據庫來實現,雖然很強大,但是有點重了,實現起來比較麻煩,但是對於大量數據共享是非常有用的。
Service 我們叫它服務,他可以在後臺執行一些耗時任務。可以單獨啓動執行任務,也可以綁定activity交互數據,這種只能在本進程的數據通信。其次就是通過aidl 來實現進程的通信,當然也可以通過信使Messenger 進程間通信。
介紹完了上邊四大組件的跨進程通信方式,對跨進程通信的方式有了一個大概瞭解,下面來着重介紹aidl 的使用。
AIDL 是什麼能幹什麼?
AIDL是Android中IPC(Inter-Process Communication)方式中的一種,AIDL是Android Interface definition language的縮寫,可以綁定其他進程service 通過aidl 實現進程間數據的通信。
具體的使用步驟:
1.建立兩個module ,一個用於服務端app (aidlapplication),一個用於客戶端app(clientservice)
2.服務端新建aidl類定義aidl 接口和方法及自定義數據類,配置build.gradle (看情況配置)
3.服務端新建service類(MyService),並且新建內部類(MyBinder)繼承aidl 接口類的Stub抽象類
4.服務端 MyService 重寫onBind 函數 並返回內部類MyBinder 實例
5.服務端 manifest註冊service
6.客戶端 ,將服務端定義的aidl 及自定義數據類,複製到服務端,注意包名要完全一致
7.客戶端 ,通過包名和action 綁定服務端的service
8.客戶端 ,通過綁定成功後返回的service 獲取定義的aidl 接口
9.客戶端 ,通過接口和服務端通信了。
總共分爲9部,這裏分的比較細,下面按步驟來學習:
先來假定一個業務邏輯然後我們來實現,客戶端可以通過接口直接獲取服務端的基本數據類型,可以獲取服務端的自定義數據類型,可以修改添加服務端的自定義數據類型。
1.新建兩個app這個就不用說,掠過
2.新建aidl 類:
新建aidl 接口類IMyAidlInterface 及自定義數據類Person 類,IMyAidlInterface 新建完成後,會自動新建一個接口方法
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
他只是告訴我們,他支持的基本數據類型,這些基本數據類型不需要import 方式導包,沒啥用可以刪除了,其他自定義的數據類型以及其他類型數據都要使用import 方式來導包。
創建Person 類並且實現Parcelable 接口
public class Person implements Parcelable {
int age;
int height;
String name;
public Person(int age, int height, String name) {
this.age = age;
this.height = height;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person() {
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.age);
dest.writeInt(this.height);
dest.writeString(this.name);
}
protected Person(Parcel in) {
this.age = in.readInt();
this.height = in.readInt();
this.name = in.readString();
}
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel source) {
return new Person(source);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
@Override
public String toString() {
return "Person{" +
"age=" + age +
", height=" + height +
", name='" + name + '\'' +
'}';
}
}
添加IMyAidlInterface 接口的方法,定義我們的業務方法。
package com.example.aidlapplication;
// Declare any non-default types here with import statements
//導入Person 包路徑
import com.example.aidlapplication.Person;
import java.util.List;
//自定義數據類型必須parcelable 關鍵字標註否則會找不到類
parcelable Person;
interface IMyAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
//定義提供的接口 返回值是一個基本數據類型
String getName();
// 返回值是一個自定義的數據類型集合,用於獲取本進程內的數據 不在基本數據類型內的,都要導包
List<Person> getPerson();
// 提供遠程操作本進程內數據的接口方法
void addPerson(in Person person);
}
注意,此處有坑,先來看這些類的包路徑情況
1坑. 我們剛纔生成的類都在aidl 文件夾下,這時就會報找不Person 的錯誤,這時要配置下build.gradle文件,注意是module 的,添加如下
sourceSets {
main {
java.srcDirs = ["src/main/java", "src/main/aidl"]
}
}
但是如果放在java 目錄包下就不用配置了,因爲默認是去java下找類的。
2坑. 自定義數據類型一定要用 parcelable 關鍵字標註 如
parcelable Person;
其次必須要正確的導入Person 的包路徑,否則也會找不到類錯誤。
完成上面的操作後我們需要構建下項目,用於通過aidl 生成java 接口文件。
生成的位置位於:
3.服務端新建service類(MyService),並且新建內部類(MyBinder)繼承aidl 接口類的Stub抽象類,實現aidl 的所有接口,至於問什麼要繼承Stub 類,這要去看下構建生成的IMyAidlInterface 類,本篇只講使用。
public class MyService extends Service {
private static final String TAG = "ClientService";
List<Person> personList = new ArrayList<>();
public MyService() {
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
class MyBinder extends IMyAidlInterface.Stub {
@Override
public String getName() throws RemoteException {
Log.d(TAG, "getName: 遠程調用獲取基本類型數據");
return "幺妹子";
}
@Override
public List<Person> getPerson() throws RemoteException {
Log.d(TAG, "getPerson: 遠程調用獲取自定義類型數據");
return personList;
}
@Override
public void addPerson(Person person) throws RemoteException {
Log.d(TAG, "addPerson: " +"收到添加進來的person" + person.toString());
personList.add(person);
}
}
}
4.服務端 MyService 重寫onBind 函數 並返回內部類MyBinder 實例
MyBinder 類繼承自Stub抽象類,stub實現了IMyAidlInterface 接口並且繼承自
binder 類,binder實現了IBinder 接口。這裏返回MyBinder 實例,實際返回類型就是IBinder類,當綁定service時返回的就是這個MyBinder 實例。
public IBinder onBind(Intent intent) {
return new MyBinder();
}
5.服務端 manifest註冊service
這部非常關鍵,四大組件都必須在manifest 中註冊,並且要簡單配置下,以便可以遠程調用。
<service
android:name=".MyService"
android:enabled="true"
android:exported="true"
android:process=":remote">
<intent-filter>
<action android:name="com.example.service.aidl"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
enabled:是否這個service能被系統實例化,如果能則爲true,否則爲false。默認爲true。
exported :是否支持其它應用調用當前組件
process :創建本應用的私有的新進程
Action:Action屬性的值爲一個字符串,它代表了系統中已經定義了一系列常用的動作。
Category:Category屬性用於指定當前動作(Action)被執行的環境
通過Intent Filter 隱式啓動service
6.客戶端 ,將服務端定義的aidl 及自定義數據類,複製到服務端,注意包名要完全一致.
將服務端 aidl 文件夾複製客戶端
注意配置要和服務端一致。
7.客戶端 ,通過包名和action 綁定服務端的service
注意包名是服務端的包名,action 啓動標示 ,用於匹配intent-filter 啓動service
Intent intent = new Intent();
intent.setPackage("com.example.aidlapplication");
intent.setAction("com.example.service.aidl");
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}, BIND_AUTO_CREATE);
8.客戶端 ,通過綁定成功後返回的service 獲取定義的aidl 接口
綁定成功後onServiceConnected 方法會返回來兩個參數,其中IBinder就是我上邊說的onBind 返回的接口類,
通過IMyAidlInterface.Stub.asInterface方法轉爲我們定義的接口
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
9.客戶端 ,通過接口和服務端通信了
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
IMyAidlInterface iMyAidlInterface;
private TextView getAidlTest;
private TextView getAidlCustom;
private TextView getAidlAddCustom;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
intent.setPackage("com.example.aidlapplication");
intent.setAction("com.example.service.aidl");
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}, BIND_AUTO_CREATE);
initView();
}
private void initView() {
getAidlTest = findViewById(R.id.get_aidl_Test);
getAidlAddCustom = findViewById(R.id.get_aidl_add_custom);
getAidlCustom = findViewById(R.id.get_aidl_custom);
getAidlTest.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
exception();
try {
Toast.makeText(MainActivity.this, iMyAidlInterface.getName(), Toast.LENGTH_LONG).show();
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
getAidlCustom.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
exception();
try {
List<Person> personList = iMyAidlInterface.getPerson();
Iterator<Person> pe = personList.iterator();
for (int i = 0; i < personList.size(); ++i) {
Person person = pe.next();
Log.d(TAG, "onClick: " + person.toString());
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
getAidlAddCustom.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
exception();
try {
iMyAidlInterface.addPerson(new Person(34,56,"這是一條插入的人名" + System.currentTimeMillis() +""));
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
private void exception() {
if (iMyAidlInterface == null) {
try {
throw new IllegalAccessException("接口爲空");
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return;
}
}
}
xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="20dp"
android:orientation="vertical">
<TextView
android:id="@+id/get_aidl_Test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="獲取基本數據類型信息"
android:textSize="18sp" />
<TextView
android:id="@+id/get_aidl_custom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="獲取自定義數據類型"
android:textSize="18sp" />
<TextView
android:id="@+id/get_aidl_add_custom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="輸入自定義數據類型"
android:textSize="18sp" />
</LinearLayout>
</merge>
最後記得構建項目,然後測試的時候先啓動服務端,然後啓動客戶端。