1. 程式人生 > >手把手教你如何用AIDL實現程序通訊

手把手教你如何用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沒有想象的那麼難,只是有些繁瑣和需要多些細心就好了,下面是我總結需要注意的幾個點:

  1. 在有些文章上看到介面方法裡用到了“in”“out”“inout”,它們什麼意思呢?
    in 表示資料只能由客戶端流向服務端, out 表示資料只能由服務端流向客戶端,而 inout 則表示資料可在服務端與客戶端之間雙向流通。inout不要亂用,用的不好會加大系統開銷。
    如 boolean login(in LoginBean data); 服務端將收到這個物件的完整資料,但客戶端的那個物件不會因為服務端對傳參的修改而發生變動。
    boolean login(out LoginBean data); 服務端將收到個空物件,但在服務端對接收到的空物件有任何修改之後客戶端將會同步變動。
    boolean login(inout LoginBean data); 服務端將會接收到客戶端傳來物件的完整資訊,並且客戶端將會同步服務端對該物件的任何變動。

  2. 當程序通訊用到自定義資料結構時,如LoginBean.java,此時就必須要生成對應的AIDL檔案,不然導致編譯不成功。
    如:// LoginBean.aidl的內容
    package com.dalongtech.textapk;
    parcelable LoginBean;//注意parcelable是小寫

  3. 當第二步做完後,發現還是編譯不通過,原因是LoginBean.java在AIDL檔案中不認識,解決方法:
    在build檔案中新增:
    sourceSets {
    main {
    java.srcDirs = [‘src/main/java’, ‘src/main/aidl’]
    }
    }

  4. 可序列化資料結構需要實現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);
    }
}