android跨進程通信AIDL使用

AIDL(Android Interface Definition Language,Android接口定義語言):如果在一個進程中要調用另一個進程中對象的方法,可使用AIDL生成可序列化的參數,AIDL會生成一個服務端對象的代理類,通過它客戶端實現間接調用服務端對象的方法。

AIDL的本質是系統提供了一套可快速實現Binder的工具。

關鍵類和方法:

  • AIDL接口:繼承IInterface。
  • Stub類:Binder的實現類,服務端通過這個類來提供服務。
  • Proxy類:服務器的本地代理,客戶端通過這個類調用服務器的方法。
  • asInterface():客戶端調用,將服務端的返回的Binder對象,轉換成客戶端所需要的AIDL接口類型對象。
    返回對象:
    若客戶端和服務端位于同一進程,則直接返回Stub對象本身;
    若客戶端和服務端位于不同一進程,返回的是系統封裝后的Stub.proxy對象。
  • asBinder():根據當前調用情況返回代理Proxy的Binder對象。
  • onTransact():運行服務端的Binder線程池中,當客戶端發起跨進程請求時,遠程請求會通過系統底層封裝后交由此方法來處理。
  • transact():運行在客戶端,當客戶端發起遠程請求的同時將當前線程掛起。之后調用服務端的onTransact()直到遠程請求返回,當前線程才繼續執行。


如何使用AIDL

1.先建立兩個android項目
創建一個BinderA項目和一個BinderB工程,BinderA項目調用BinderB中提供的登錄接口,在BinderB中點擊登錄后,登錄之后不管是否成功,BinderB都會調用BinderA中登錄返回接口,這樣就實現了一個雙向的通信。



2、創建一個包名用來存放aidl文件
創建一個包名用來存放aidl文件,比如com.migill.binder,在里面新建ILoginInterface.aidl文件,如果需要訪問自定義對象,還需要建立對象的aidl文件,這里我們由于使用了自定義對象LoginUser,所以,還需要創建LoginUser.aidl和LoginUser.java。注意,這三個文件,需要都放在com.migill.binder包里。而且ILoginInterface.aidl,LoginUser.aidl,LoginUser.java這三個文件在BinderA與BinderB項目中是一樣的。下面描述如何寫這三個文件。
客戶端中的aidl文件與服務端中的aidl文件的包名要一樣,在創建客戶端AIDL文件的時候是不需要把服務端提供的接口都添加進來,只需要添加需要的接口就好了

// ILoginInterface.aidl
package com.migill.binder;
import com.migill.binder.LoginUser;
interface ILoginInterface {
    // 登錄
    void login();
    // 登錄返回
    void loginCallback(boolean loginStatus, inout LoginUser loginUser);
}

說明:
aidl中支持的參數類型為:

  • 基本類型(ibyte,int,long,float,double,boolean,char),String類型,CharSequence類型,List,Map。
  • 其他類型必須使用import導入,即使它們可能在同一個包里,比如這里的LoginUser,盡管它和ILoginInterface在同一個包中,但是還是需要顯示的import進來。另外,接口中的參數除了aidl支持的類型,其他類型必須標識其方向:到底是輸入還是輸出抑或兩者兼之,用in,out或者inout來表示,上面的代碼我們用inout標記,其實用in就可以,因為它是輸入型參數(我的理解是BinderB現在是客戶端,BinderA是服務端,BinderB中的數據流入BinderA中,所以是入型參數)。

在gen下面可以看到,AS為我們自動生成了一個代理類public static abstract class Stub extends android.os.Binder implements com.migill.binder.ILoginInterface可見這個Stub類就是一個普通的Binder,只不過它實現了我們定義的aidl接口。
它還有一個靜態方法public static com.migill.binder.ILoginInterface asInterface(android.os.IBinder obj)這個方法很有用,通過它,我們就可以在客戶端中得到MyService的實例,進而通過實例來調用其方法。

// LoginUser.aidl
package com.migill.binder;
parcelable LoginUser;
package com.migill.binder;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Locale;
public final class LoginUser implements Parcelable {
    public String userName;
    public String userPassWord;
    public LoginUser() {
    }
    protected LoginUser(Parcel in) {
        readFromParcel(in);
    }
    public void readFromParcel(Parcel in) {
        userName = in.readString();
        userPassWord = in.readString();
    }
    public static final Creator<LoginUser> CREATOR = new Creator<LoginUser>() {
        @Override
        public LoginUser createFromParcel(Parcel in) {
            return new LoginUser(in);
        }
        @Override
        public LoginUser[] newArray(int size) {
            return new LoginUser[size];
        }
    };
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getUserPassWord() {
        return userPassWord;
    }
    public void setUserPassWord(String userPassWord) {
        this.userPassWord = userPassWord;
    }
    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(userName);
        dest.writeString(userPassWord);
    }
    @Override
    public String toString() {
        return String.format(Locale.ENGLISH, "[ %s, %s ]", userName, userPassWord);
    }
}

3.BinderA項目
MyService.java中的loginCallback()中發送了一個廣播,廣播在LoginActivity中接收。
有個問題就是有可能你的service只想讓某個特定的apk使用,而不是所有apk都能使用,這個時候,你需要重寫Stub中的onTransact方法,根據調用者的uid來獲得其信息,然后做權限認證,如果返回true,則調用成功,否則調用會失敗。對于其他apk,你只要在onTransact中返回false就可以讓其無法調用MyService中的方法,這樣就可以解決這個問題了。

public class MyService extends Service {
    private static String TAG = "migill_A";
    private static final String PACKAGE_SAYHI = "com.migill.binder.b";
    @Override
    public IBinder onBind(Intent intent) {
        return new ILoginInterface.Stub() {
            @Override
            public void login() throws RemoteException {
            }
            @Override
            public void loginCallback(boolean loginStatus, LoginUser loginUser) throws RemoteException {
                Log.e(TAG, "loginStatus: " + loginStatus + " / LoginUser: " + loginUser.toString());
                sendLoginCallBackBrodcast(loginStatus, loginUser);
            }
            //在這里可以做權限認證,return false意味著客戶端的調用就會失敗,比如下面,只允許包名為com.migill.binder.a的客戶端通過,
            //其他apk將無法完成調用過程
            public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                    throws RemoteException {
                String packageName = null;
                String[] packages = MyService.this.getPackageManager().
                        getPackagesForUid(getCallingUid());
                if (packages != null && packages.length > 0) {
                    packageName = packages[0];
                }
                Log.e(TAG, "onTransact: " + packageName);
                if (!PACKAGE_SAYHI.equals(packageName)) {
                    return false;
                }
                return super.onTransact(code, data, reply, flags);
            }
        };
    }

    private void sendLoginCallBackBrodcast(boolean loginStatus, LoginUser loginUser) {
        Intent intent = new Intent("com.migill.binder.a.LOGINCAKKBACK_STATE");
        intent.putExtra("loginStatus", loginStatus);
        intent.putExtra("loginUser", loginUser);
        sendBroadcast(intent);
    }
}

AndroidManifest.xml中添加service標簽

     <!--
                 代表在應用程序里,當需要該service時,會自動創建新的進程。
                 android:process=":remote"
                 是否可以被系統實例化
                 android:enabled="true"
                 代表是否能被其他應用隱式調用
                 android:exported="true"
        -->
        <service
            android:name=".service.MyService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote">
            <intent-filter>
                <!-- 激活 MyService 唯一name,不能重名 -->
                <action android:name="BinderA_Action" />
            </intent-filter>
        </service>

LoginActivity綁定BinderB提供的服務,在點擊QQ按鈕的時候,調用BinderB中提供的登錄接口。在LoginActivity定義了一個廣播接受者,用于接收BinderB返回的登錄結果

public class LoginActivity extends Activity {
    private static String TAG = "migill_A";
    private boolean isStartRemote; // 是否開啟跨進程通信
    private ILoginInterface iLogin; // AIDL定義接口
    LoginCallbackReceiver loginCallbackReceiver;
    TextView tvLogincallback;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        requestWindowFeature(Window.FEATURE_NO_TITLE); // 隱藏標題
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN); // 設置全屏

        setContentView(R.layout.activity_login);
        tvLogincallback = findViewById(R.id.tv_logincallback);
        initBindService();//初始化
        RegisterBroadcast();
    }
    public void RegisterBroadcast() {
        IntentFilter filter = new IntentFilter();
        filter.addAction("com.migill.binder.a.LOGINCAKKBACK_STATE");
        loginCallbackReceiver = new LoginCallbackReceiver();
        registerReceiver(loginCallbackReceiver, filter);
    }
    // 點擊事件
    public void startQQLoginAction(View view) {
        if (iLogin != null) {
            try {
                // 調用Server方法
                iLogin.login();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        } else {
            Toast.makeText(this, "請安裝QQ應用...", Toast.LENGTH_SHORT).show();
        }
    }
    public void initBindService() {
        Intent intent = new Intent();
        // 設置Server應用Action
        intent.setAction("BinderB_Action");
        // 設置Server應用包名(5.1+要求)
        intent.setPackage("com.migill.binder.b");
        // 開始綁定服務
        bindService(intent, conn, BIND_AUTO_CREATE);
        // 標識跨進程綁定
        isStartRemote = true;
    }
    // 服務連接
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iLogin = ILoginInterface.Stub.asInterface(service);
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (isStartRemote) {
            // 解綁服務,一定要記得解綁服務,否則可能會報異常(服務連接資源異常)
            unbindService(conn);
        }
        unregisterReceiver(loginCallbackReceiver);
    }
    private void LoginCallback(boolean loginStatus, LoginUser loginUser) {
        Log.e(TAG, "LoginCallback loginStatus: " + loginStatus + " / LoginUser: " + loginUser.toString());
        tvLogincallback.setText("登錄結果: " + (loginStatus==true?"登錄成功":"登錄失敗") + "\n" +
                "LoginUser: " + loginUser.toString());
    }
    private final class LoginCallbackReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            boolean loginStatus = intent.getBooleanExtra("loginStatus", false);
            LoginUser loginUser = (LoginUser) intent.getParcelableExtra("loginUser");
            LoginCallback(loginStatus, loginUser);
        }
    }
}

4.BinderB項目
MyService.java中在login()中啟動了MainActivity。

public class MyService extends Service {
    private static String TAG = "migill_B";
    private static final String PACKAGE_SAYHI = "com.migill.binder.a";
    @Override
    public IBinder onBind(Intent intent) {
        return new ILoginInterface.Stub() {
            @Override
            public void login() throws RemoteException {
                Log.e(TAG, "BinderB_MyService");
                // 單項通信有問題,真實項目雙向通信,雙服務綁定
                serviceStartActivity();
            }
            @Override
            public void loginCallback(boolean loginStatus, LoginUser loginUser) throws RemoteException {
            }
            //在這里可以做權限認證,return false意味著客戶端的調用就會失敗,比如下面,只允許包名為com.migill.binder.a的客戶端通過,
            //其他apk將無法完成調用過程
            public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                    throws RemoteException {
                String packageName = null;
                String[] packages = MyService.this.getPackageManager().
                        getPackagesForUid(getCallingUid());
                if (packages != null && packages.length > 0) {
                    packageName = packages[0];
                }
                Log.e(TAG, "onTransact: " + packageName);
                if (!PACKAGE_SAYHI.equals(packageName)) {
                    return false;
                }
                return super.onTransact(code, data, reply, flags);
            }

        };
    }
    /**
     * 在Service啟動Activity,需要配置:.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
     */
    private void serviceStartActivity() {
        Intent intent = new Intent(this, MainActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }
}

AndroidManifest.xml中添加service標簽

   <!--
                 代表在應用程序里,當需要該service時,會自動創建新的進程。
                 android:process=":remote"
                 是否可以被系統實例化
                 android:enabled="true"
                 代表是否能被其他應用隱式調用
                 android:exported="true"
        -->
        <service
            android:name="com.migill.binder.b.service.MyService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote_service">
            <intent-filter>
                <!-- 激活 MyService 唯一name,不能重名-->
                <action android:name="BinderB_Action" />
            </intent-filter>
        </service>

BinderA調用了BinderB提供的登錄接口,BinderB的登錄接口執行啟動MainActivity,在BinderB項目的MainActivity中綁定了BinderA中提供的服務。在點擊登錄的時候,在調用BidnerA中登錄返回接口。

public class MainActivity extends Activity {
    private static String TAG = "migill_B";
    // 模擬用戶名和密碼 的值
    private final static String NAME = "migill";
    private final static String PWD = "123";
    private EditText nameEt;
    private EditText pwdEt;
    private boolean isStartRemote; // 是否開啟跨進程通信
    private ILoginInterface iLogin; // AIDL定義接口

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        requestWindowFeature(Window.FEATURE_NO_TITLE);  // 隱藏標題
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);    // 設置全屏
        setContentView(R.layout.activity_main);
        nameEt = findViewById(R.id.nameEt);
        pwdEt = findViewById(R.id.pwdEt);
        initBindService();
    }

    public void initBindService() {
        Intent intent = new Intent();
        // 設置Server應用Action
        intent.setAction("BinderA_Action");
        // 設置Server應用包名(5.1+要求)
        intent.setPackage("com.migill.binder.a");
        // 開始綁定服務
        bindService(intent, conn, BIND_AUTO_CREATE);
        // 標識跨進程綁定
        isStartRemote = true;
    }

    // 服務連接
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //將服務端的返回的Binder對象,轉換成客戶端所需要的AIDL接口類型對象。
            //返回對象:
            //若客戶端和服務端位于同一進程,則直接返回Stub對象本身;
            //若客戶端和服務端位于不同一進程,返回的是系統封裝后的Stub.proxy對象。
            iLogin = ILoginInterface.Stub.asInterface(service);
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    // 點擊事件
    public void startLogin(View view) {
        final String name = nameEt.getText().toString();
        final String pwd = pwdEt.getText().toString();
        if (TextUtils.isEmpty(name) || TextUtils.isEmpty(pwd)) {
            showToast("請輸入用戶名或密碼...");
            return;
        }
        // 模擬登錄狀態加載...
        final ProgressDialog progressDialog = new ProgressDialog(this);
        progressDialog.setTitle("登錄");
        progressDialog.setMessage("正在登錄中...");
        progressDialog.show();
        new Thread(new Runnable() {
            @Override
            public void run() {

                SystemClock.sleep(1000);

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            boolean isLoginSuccess = false;
                            if (NAME.equals(name) && PWD.equals(pwd)) {
                                showToast("QQ登錄成功!");
                                isLoginSuccess = true;
                                finish();
                            } else {
                                showToast("QQ登錄失敗!");
                                progressDialog.dismiss();
                            }
                            LoginUser loginUser = new LoginUser();
                            loginUser.userName = name;
                            loginUser.userPassWord = pwd;
                            iLogin.loginCallback(isLoginSuccess, loginUser);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        }).start();

    }
    private void showToast(String text) {
        Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (isStartRemote) {
            // 解綁服務,一定要記得解綁服務,否則可能會報異常(服務連接資源異常)
            unbindService(conn);
        }
    }
}

總結:我們要先安裝BinderB項目,這樣BinderB就會在ServiceManager中進行注冊。BinderA就可以在ServiceManager中通過名字查找,獲取Binder對象。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容