手把手教你如何用AIDL實現程序通訊
1.前言
在研究DroidPlugin外掛和service程序與主程序需要互動時都會用到AIDL來實現程序通訊,今天回頭整理資料的時候,發現AIDL有些模糊了,所以有些比較重要的知識點還是有必要把它記錄下來。
2.緒論
AIDL,全稱是Android interface definition language,Android介面描述語言。我們知道,每一個程序都有自己的Dalvik VM例項,都有自己的一塊獨立的記憶體,互不影響。那我們要實現程序間通訊,該怎麼做呢?這個時候AIDL就起作用了,下面就帶大家一步一步實現程序通訊。
3.實現程序通訊
(由於介面排版問題,下面是以圖片的形式講解,在最後面會將原始碼貼出來)
1.在Android工程中,新建一個AIDL檔案,此時as會自動生成AIDL目錄,跟java目錄並列。如圖:
2.編寫AIDL介面方法,就以登入功能為例。寫完後,一定要build下工程,讓系統生成對應的java檔案。
3.建立一個服務程序,實現剛寫的AIDL介面,提供檢查登入輸入的使用者名稱和密碼是否正確的功能。並在onBind方法中返回AIDL介面物件。
4.在清單檔案裡註冊該service,並以程序形式存在。android:process=”:checkLogin”:表示新開程序
5.現在我們就可以在主程序呼叫該介面實現程序通訊了,先上程式碼
程式碼還是很清晰,第一塊是建立serviceConnection回撥物件,在連線成功後會回撥onServiceConnected()方法,然後通過IMyAidl.Stub.asInterface()來獲取IMyAidl介面物件。第二塊是繫結檢查登入服務,繫結時將serviceConnection回撥物件傳入。第三塊是當點選登入按鈕時通過IMyAidl物件呼叫checkLogin方法去判斷登入是否成功。此時就是程序通訊了。第四塊是當activity被銷燬時,將此服務解綁。
6.編譯生成apk後,執行效果:
程序通訊基本上結束了,但是,有些需求是需要程序間傳遞自定義資料結構,這個怎麼實現呢?那我們也來一起試試吧,畢竟這個需求也是很大的。
4.程序通訊之自定義資料結構
1.新建一個數據結構LoginBean類,要實現在程序間傳遞的話就必須實現Parcleable介面,為啥不能用Serializable ? 因為 第一,Parcleable序列化和反序列化的效率均比Serializable介面高;第二,Parcelable介面是Android所特有的,AIDL進行在程序間通訊(IPC),都是需要實現Parcelable介面。實現Parcleable介面就需要實現writeToParcel、describeContents函式以及靜態的CREATOR變數,請看圖:
這個類最好是放在AIDL包下,方便移植,如果說我們需要與別的應用進行通訊時,那麼在別的應用裡也需要放一個AIDL包,而且包名和內容都要一樣。
2.在AIDL包下新建一個LoginBean.aidl檔案,表示引入了一個序列化物件 LoginBean 供其他的AIDL檔案使用,如果不做這一步就會編譯不通過;
3.在IMyAidl接口裡新增login方法,在接口裡引用了LoginBean類。
此時編譯肯定不通過。第一,沒有導包;就是上面第一個紅框。記得LoginBean這個類一定要導包,雖然在同一個包下;第二,as是使用 Gradle 來構建 Android 專案的, 預設是將 java 程式碼的訪問路徑設定在 java 包下的,而LoginBean類是在AIDL包下的,所以不認識這個類。我們可以在build檔案中配置下就可以解決問題。
4.在服務端實現這個介面方法。
5.在主程序呼叫這個介面。
6.編譯生成apk,執行效果也是一樣。
5.總結
其實AIDL沒有想象的那麼難,只是有些繁瑣和需要多些細心就好了,下面是我總結需要注意的幾個點:
在有些文章上看到介面方法裡用到了“in”“out”“inout”,它們什麼意思呢?
in 表示資料只能由客戶端流向服務端, out 表示資料只能由服務端流向客戶端,而 inout 則表示資料可在服務端與客戶端之間雙向流通。inout不要亂用,用的不好會加大系統開銷。
如 boolean login(in LoginBean data); 服務端將收到這個物件的完整資料,但客戶端的那個物件不會因為服務端對傳參的修改而發生變動。
boolean login(out LoginBean data); 服務端將收到個空物件,但在服務端對接收到的空物件有任何修改之後客戶端將會同步變動。
boolean login(inout LoginBean data); 服務端將會接收到客戶端傳來物件的完整資訊,並且客戶端將會同步服務端對該物件的任何變動。當程序通訊用到自定義資料結構時,如LoginBean.java,此時就必須要生成對應的AIDL檔案,不然導致編譯不成功。
如:// LoginBean.aidl的內容
package com.dalongtech.textapk;
parcelable LoginBean;//注意parcelable是小寫當第二步做完後,發現還是編譯不通過,原因是LoginBean.java在AIDL檔案中不認識,解決方法:
在build檔案中新增:
sourceSets {
main {
java.srcDirs = [‘src/main/java’, ‘src/main/aidl’]
}
}可序列化資料結構需要實現Parcelable介面
到此呢,程序通訊講解就真正結束了!如果屏前的你有一定的收穫的話,那麼請您給筆者點個贊或者留個言吧~
謝謝!
核心程式碼:
IMyAidl.aidl
package com.dalongtech.textapk;
import com.dalongtech.textapk.LoginBean;
// Declare any non-default types here with import statements
interface IMyAidl {
boolean checkLogin(String name,String psw);
boolean login(in LoginBean data);
}
LoginBean.aidl
package com.dalongtech.textapk;
parcelable LoginBean;//注意parcelable是小寫
LoginBean.java
package com.dalongtech.textapk;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by sunmoon on 2017/7/6 0006.
*/
public class LoginBean implements Parcelable {
private String loginName;
private String loginPsw;
private int loginSucc;
public LoginBean(){}
protected LoginBean(Parcel in) {
loginName = in.readString();
loginPsw = in.readString();
loginSucc = in.readInt();
}
public static final Creator<LoginBean> CREATOR = new Creator<LoginBean>() {
@Override
public LoginBean createFromParcel(Parcel in) {
return new LoginBean(in);
}
@Override
public LoginBean[] newArray(int size) {
return new LoginBean[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(loginName);
dest.writeString(loginPsw);
dest.writeInt(loginSucc);
}
public void readFromParcel(Parcel dest) {
//注意,此處的讀值順序應當是和writeToParcel()方法中一致的
loginName = dest.readString();
loginPsw = dest.readString();
loginSucc = dest.readInt();
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public String getLoginPsw() {
return loginPsw;
}
public void setLoginPsw(String loginPsw) {
this.loginPsw = loginPsw;
}
public int getLoginSucc() {
return loginSucc;
}
public void setLoginSucc(int loginSucc) {
this.loginSucc = loginSucc;
}
}
CheckLoginService.java
package com.dalongtech.textapk;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
/**
*
* Created by sunmoon on 2017/7/6 0005.
*/
public class CheckLoginService extends Service {
private static final String UserName = "sunmoon";
private static final String UserPsw = "sunmoon001";
private IMyAidl.Stub mBinder = new IMyAidl.Stub() {
@Override
public boolean checkLogin(String name, String psw) throws RemoteException {
return UserName.equals(name) && UserPsw.equals(psw);
}
@Override
public boolean login(LoginBean data) throws RemoteException {
return UserName.equals(data.getLoginName())&&UserPsw.equals(data.getLoginPsw());
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
MainActivity.java
package com.dalongtech.textapk;
import android.app.Service;
import android.content.ComponentName;
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.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private EditText mUserNameEt;
private EditText mUserPswEt;
private IMyAidl myAidl;
//與service連線
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myAidl = IMyAidl.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
myAidl = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.loginAct_toolbar);
toolbar.setTitle("登入頁面");
mUserNameEt = (EditText)findViewById(R.id.loginAct_userName);
mUserPswEt = (EditText)findViewById(R.id.loginAct_userPsw);
//繫結檢查登入服務
Intent intent = new Intent(this,CheckLoginService.class);
bindService(intent,mConnection, Service.BIND_AUTO_CREATE);
findViewById(R.id.loginAct_loginBtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
// boolean loginSucc = myAidl.checkLogin(mUserNameEt.getText().toString(),
// mUserPswEt.getText().toString());
LoginBean bean = new LoginBean();
bean.setLoginName(mUserNameEt.getText().toString());
bean.setLoginPsw(mUserPswEt.getText().toString());
boolean loginSucc = myAidl.login(bean);
if(loginSucc) {
startActivity(new Intent(MainActivity.this,SecondActivity.class));
return;
}
} catch (Exception e) {}
Toast.makeText(MainActivity.this,"使用者名稱或密碼錯誤",Toast.LENGTH_LONG).show();
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
}
}