Android進階:Binder機制、AIDL進程通信學習(模擬支付寶支付案例)

目錄

  • Binder是什么?
  • 從面向對象的思想看 Binder IPC
  • 進程空間的劃分
  • Binder機制是如何跨進程通信的
  • Binder 到底是什么
  • 理解Java層的Binder 代碼
  • Android中的AIDL
  • android開發AIDL使用模擬支付寶支付

Binder是什么?

Binder是Android 系統中最重要的特性之一:稱之為"粘合劑",粘合了兩個不同的進程,是系統間各個組件通信的橋梁,是一種跨進程通信機制。Binder基于Client-Server通信模式。

從面向對象的思想看 Binder IPC?

Binder使用Client-Server通信方式:

  • 對Binder而言,Binder 可以看成 Server 提供的實現某個特定服務的訪問接入點, Client 通過這個‘地址’向Server 發送請求來使用該服務;對 Client 而言,Binder 可以看成是通向 Server 的管道入口,要想和某個Server 通信首先必須建立這個管道并獲得管道入口。

  • Binder 是一個實體位于 Server 中的對象,該對象提供了一套方法用以實現對服務的請求,就像類的成員函數方法。遍布于 Client 中的入口 可以看成指向這個 Binder對象的 引用, 一旦獲得了這個引用就可以調用該對象的方法訪問 Server 。在 Client 看來通過 Binder引用調用 Server 提供的方法 和通過引用調用本地對象的方法并無區別,盡管這個Binder 引用的實體位于 Server 端,從通信角度中 Client 中的Binder 引用也可以看作是 Server Binder 的 "代理" ,在本地代表遠端 Server 為 Client 提供的服務。

  • 面向對象思想的引入將進程間通信轉化為通過對某個Binder對象的引用調用該對象的方法,而其獨特之處在于Binder對象是一個可以跨進程引用的對象,它的實體位于一個進程中,而它的引用卻遍布于系統的各個進程之中。最誘人的是,這個引用和java里引用一樣既可以是強類型,也可以是弱類型,而且可以從一個進程傳給其它進程,讓大家都能訪問同一Server,就象將一個對象或引用賦值給另一個引用一樣。Binder模糊了進程邊界,淡化了進程間通信過程,整個系統仿佛運行于同一個面向對象的程序之中。形形色色的Binder對象以及星羅棋布的引用仿佛粘接各個應用程序的膠水,這也是Binder在英文里的原意。

摘錄:Android Bander設計與實現 - 設計篇

進程空間的劃分

Android系統基于Linux內核 ,進程都有隔離機制

  • 一個進程空間分為 用戶空間 & 內核空間(Kernel),即把進程內 用戶 & 內核 隔離開來
  • 二者區別:
    1. 進程間,用戶空間的數據不可共享,所以用戶空間 = 不可共享空間
    2. 進程間,內核空間的數據可共享,所以內核空間 = 可共享空間

進程隔離是為保護操作系統中進程互不干擾而設計的一組不同硬件和軟件的技術。這個技術是為了避免進程A寫入進程B的情況發生。 進程的隔離實現,使用了虛擬地址空間。進程A的虛擬地址和進程B的虛擬地址不同,這樣就防止進程A將數據信息寫入進程B。

不同進程之間,數據不共享;而需要進程間通信,則需要某種機制才能完成。Linux內核擁有著非常多的跨進程通信機制,比如管道,System V,Socket等;

而Android 為什么要用 Binder ?主要從性能和安全上

性能:

  • 在移動設備上,廣泛地使用跨進程通信肯定對通信機制本身提出了嚴格的要求;Binder相對出傳統的Socket方式,更加高效;

安全:

  • 另外,傳統的進程通信方式對于通信雙方的身份并沒有做出嚴格的驗證,只有在上層協議上進行架設;比如Socket通信ip地址是客戶端手動填入的,都可以進行偽造;而Binder機制從協議本身就支持對通信雙方做身份校檢,傳輸過程只需一次拷貝,為發送發添加UID/PID身份,既支持實名Binder也支持匿名Binder,因而大大提升了安全性。這個也是Android權限模型的基礎

Binder機制是如何跨進程通信的?

通信過程的四個角色模型:

a. Client(客戶端), 使用服務的進程

b. Server(服務端),提供服務的進程

c. ServiceManager(SM 類似于通信錄或路由器),管理 Service注冊與查詢(將字符形式的Binder 名字轉化成 Client中對該 Binder的引用 )

d. driver(binder驅動),連接 Server 進程、Client進程 和 ServiceManage的橋梁;作用為:1傳遞進程間數據,通過內存映射;2 實現線程控制,采用BInder 的線程池,由Binder驅動自身進行管理

Server,Client,ServiceManager運行于【用戶空間】;驅動運行于【內核空間】。

通信步驟:

  1. Server 進程向 SM 注冊,并告知自己有什么能力,我叫zhangsan 有一個 Object 對象,里面有 add() 方法。
  2. Client 向SM 查詢,我要找 zhangsan 需要使用 Object 對象中的 add() 方法;這時驅動在數據流動的時候,它并不會給 Client 進程返回一個真正的 Object 對象,而是返回一個和Object 一樣的代理對象 objectProxy,這個objectProxy 也有 add()方法,這個代理對象把參數包裝后交給驅動。
  3. 驅動收到這個消息,發現是objectProxy ; 驅動通知 Server 調用真正的 Object 的add() 方法,然后把結果發給驅動,驅動再把結果返回給 Client ,于是整個過程就完成了。

實際上,由于SM與Server通常不在一個進程,Server進程向SM注冊的過程也是跨進程通信,驅動也會對這個過程進行暗箱操作:SM中存在的Server端的對象實際上也是代理對象,后面Client向SM查詢的時候,驅動會給Client返回另外一個代理對象。Sever進程的本地對象僅有一個,其他進程所擁有的全部都是它的代理。

Binder 跨進程并不是真把一個對象傳遞到另一個進程,是通過代理對象經過 Binder 驅動來查找并調用真正對象的方法和屬性。

事實上 Client 進程只不過持有了 Server 端的代理,代理對象協助驅動完成了跨進程的通信。

而相同進程下直接通過驅動返回真實的對象。

在這里插入圖片描述

Binder 到底是什么?

  • 通常意義下,Binder指的是一種通信機制;我們說AIDL使用Binder進行通信,指的就是Binder這種IPC機制。在Android 中實現跨進程通信
  • Binder是一種虛擬的物理設備驅動,即 Binder 驅動;作用于連接 Server 進程、Client進程 和 ServiceManage進程
  • 從Framework層角度看,Binder是ServiceManager連接各種Manager和相應的ManagerService的橋梁
  • 從Android應用層來說,Binder是客戶端和服務端進行通信的媒介,當bindService的時候,服務端會返回一個包含了服務端業務調用的Binder對象,通過這個Binder對象,客戶端就可以獲取服務端提供的服務或者數據,這里的服務包括普通服務和基于AIDL的服務。
  • 在Android開發中,Binder主要用在Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及進程間通信,較為簡單;而Messenger的底層其實是AIDL,正是Binder的核心工作機制。

理解Java層的Binder 代碼

IBinder/IInterface/Binder/BinderProxy/Stub

我們使用AIDL接口的時候,經常會接觸到這些類,那么這每個類代表的是什么呢?

  • IBinder是一個接口,它代表了一種跨進程傳輸的能力;只要實現了這個接口,就能將這個對象進行跨進程傳遞;這是驅動底層支持的;在跨進程數據流經驅動的時候,驅動會識別IBinder類型的數據,從而自動完成不同進程Binder本地對象以及Binder代理對象的轉換。
  • IBinder負責數據傳輸,那么client與server端的調用契約(這里不用接口避免混淆)呢?這里的IInterface代表的就是遠程server對象具有什么能力。具體來說,就是aidl里面的接口。
  • Java層的Binder類,代表的其實就是Binder本地對象。BinderProxy類是Binder類的一個內部類,它代表遠程進程的Binder對象的本地代理;這兩個類都繼承自IBinder, 因而都具有跨進程傳輸的能力;實際上,在跨越進程的時候,Binder驅動會自動完成這兩個對象的轉換。
  • 在使用AIDL的時候,編譯工具會給我們生成一個Stub的靜態內部類;這個類繼承了Binder, 說明它是一個Binder本地對象,它實現了IInterface接口,表明它具有遠程Server承諾給Client的能力;Stub是一個抽象類,具體的IInterface的相關實現需要我們手動完成,這里使用了策略模式。

Android中的AIDL

1. AIDL的簡介

AIDL (Android Interface Definition Language) 是一種接口定義語言,用于生成可以在Android設備上兩個進程之間進行進程間通信(interprocess communication, IPC)的代碼。如果在一個進程中(例如Activity)要調用另一個進程中(例如Service)對象的操作,就可以使用AIDL生成可序列化的參數,來完成進程間通信。

簡言之,AIDL能夠實現進程間通信,其內部是通過Binder機制來實現的

2. AIDL的具體使用

AIDL的實現一共分為三部分,一部分是客戶端,調用遠程服務。一部分是服務端,提供服務。最后一部分,也是最關鍵的是AIDL接口,用來傳遞的參數,提供進程間通信。

大致流程:首先建一個Service和一個AIDL接口,接著創建一個類繼承自AIDL接口中的Stub類并實現Stub類中的抽象方法,在Service的onBind方法中返回這個類的對象,然后客戶端就可以綁定服務端Service,建立連接后就可以訪問遠程服務端的方法了。

  1. AIDL支持的數據類型:基本數據類型、StringCharSequenceArrayListHashMapParcelable以及AIDL

  2. 某些類即使和AIDL文件在同一個包中也要顯式import進來;

  3. AIDL中除了基本數據類,其他類型的參數都要標上方向:inout或者inout

    稱之為定向Tag:

    InOut 輸入輸出類型

    In 類型 輸入類型

    Out類型 輸出類型

  4. AIDL接口中支持方法,不支持聲明靜態變量;

  5. 為了方便AIDL的開發,建議把所有和AIDL相關的類和文件全部放入同一個包中,這樣做的好處是,當客戶端是另一個應用的時候,可以直接把整個包復制到客戶端工程中。

  6. RemoteCallbackList是系統專門提供的用于刪除跨進程Listener的接口。RemoteCallbackList是一個泛型,支持管理任意的AIDL接口,因為所有的AIDL接口都繼承自IInterface`接口。


android開發AIDL使用模擬支付寶支付

alipayMp4 (3).gif
模擬實現功能

模擬支付寶支付,客戶端進行充值調用 支付寶的服務,支付寶處理充值請求,返回充值成功或失敗的結果給客戶端

創建兩個應用項目,AlipayServiceDemo 和 AlipayClientDemo 端

Service流程:

1、定義一個 Service 服務 ,并在 manifest中注冊服務,定義意圖 并 設置 android:exported="true" 可被外部應用使用該服務;

2、 服務端 編寫 AIDL 文件 ,定義發起支付接口,以及回調,ReBuild Project 生成對應的 .java 文件;

3、發起支付請求,通過在Service 的onBind 中根據 action 的不同, 返回一個指定了支付寶 action 的 IBinder 對象 ,繼承自 AIDL 構建的 xxx.Stub 對象 ,(是根據AIDL 自動生成的.java文件 )。繼承Stub 并實現接口定義的方法;

requestPay(); //實現,打開一個支付界面

paySuccess();//回調告訴第三方應用支付成功

payFailed();//回調第三方應用支付失敗

4、 在打開的頁面回綁該服務 并在服務中實現支付寶的支付服務邏輯動作

Client流程:

1、創建 AIDL文件夾 ,將 Service 端的 AIDL 文件包 復制過來 Client 中進行替換 (需要一模一樣),進行 Rebuild Project 生成.java 文件

2、綁定支付寶的服務,之后在onServiceConnected() 連接中 并使用 :.Stub.asInterface(service); 轉換成接口的實現

3、使用該接口方法,判空并 catch 異常


AlipayServiceDemo 服務端實現

AndroidMenifest.xml
  <application
      ....>
        <service
            android:name=".PayService"
            android:exported="true">
                <!--定義可被其它應用使用該服務,定義意圖-->
            <intent-filter>
                <action android:name="com.alibaba.alipay.THIRD_PART_PAY" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>

        </service>
    </application>
2個AIDL文件的創建:
package com.alibaba.alipay;
//使用要導入完整包名!!
import com.alibaba.alipay.ThirdPartPayResult;

interface ThirdPartPayAction {
    /*
    發起支付請求 接口
    */
    void requestPay( String orderInfo, float payMoney,ThirdPartPayResult callBack);
}
----------------------------------------------------------------------------------------
package com.alibaba.alipay;
interface ThirdPartPayResult {
    /*
    支付成功的回調
    */
    void onPaySuccess();
         /*
    支付失敗的回調
    */
    void onPayFaild(in int errorCode , in String msg);

}


PayService.java
package com.alibaba.alipay;

/**
 * @author:thisfeng
 * @time 2019/4/18 22:41
 * 支付服務流程:
 * 1、首先接收到第三方應用的綁定,請求支付。
 * <p>
 * 2、然后拉起我們的支付頁面Activity ,拉起之后回綁服務,服務內定義并返回支付的動作類 給 PayActivity,
 * <p>
 * 3、通過用戶輸入的密碼判斷是否正確, 從服務連接中拿到 IBinder 也是就是這個支付的動作類, 進行服務的動作回調
 */
public class PayService extends Service {


    private static final String TAG = "PayService";

    private ThirdPartPayImpl thirdPartPay;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {

        String action = intent.getAction();
        Log.d(TAG, "action --->" + action);

        if (!TextUtils.isEmpty(action)) {
            if ("com.alibaba.alipay.THIRD_PART_PAY".equals(action)) {
                //通過 action 說明這是第三方要求我們支付寶進行支付
                thirdPartPay = new ThirdPartPayImpl();
                //提取指全局供外部交互時調用
                return thirdPartPay;
            }
        }

        //PayActivity 回綁服務 返回的對象 進行交互,無指定action 默認返回此對象
        return new PayAction();
    }

    /**
     * 定義支付寶的支付 服務邏輯動作
     */
    public class PayAction extends Binder {

        /**
         * 實際的支付是比較復雜的,比如說加密,向服務器發起請求,等待服務器的結果,多次握手等
         * <p>
         * 支付方法
         */
        public void pay(float payMoney) {
            Log.d(TAG, "pay money is --->" + payMoney);

            if (thirdPartPay != null) {
                //回調告訴遠程第三方 支付成功
                thirdPartPay.onPaySuccess();
            }
        }

        /**
         * 用戶點擊界面上的取消/退出
         */
        public void onUserCancel() {
            if (thirdPartPay != null) {
                //回調告訴遠程 支付失敗
                thirdPartPay.onPayFaild(-1, "user cancel pay...");
            }
        }
    }

    /**
     * 第三方調用起 跨進程 進行支付
     */
    private class ThirdPartPayImpl extends ThirdPartPayAction.Stub {


        private ThirdPartPayResult callBack;

        @Override
        public void requestPay(String orderInfo, float payMoney, ThirdPartPayResult callBack) throws RemoteException {
            this.callBack = callBack;

            Log.d(TAG, "requestPay --->orderInfo:" + orderInfo + " payMoney:" + payMoney);
          
            //第三方應用發起請求,拉起 打開一個支付頁面
            Intent intent = new Intent();
            intent.setClass(PayService.this, PayActivity.class);
            intent.putExtra(Const.KEY_BILL_INFO, orderInfo);
            intent.putExtra(Const.KEY_PAY_MONEY, payMoney);
            //新的 task 中打開
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(intent);
        }

        /**
         * 定義相同的方法,進行回調 ,給外部調用
         */
        public void onPaySuccess() {
            try {
                callBack.onPaySuccess();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        public void onPayFaild(int errorCode, String errorMsg) {
            try {
                callBack.onPayFaild(errorCode, errorMsg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
}
PayActivity.java
package com.alibaba.alipay;

/**
 * @author:thisfeng
 * @time 2019/4/18 22:52
 * 支付頁面
 */

public class PayActivity extends AppCompatActivity {

    private final String TAG = "PayActivity";

    private boolean isBind;

    private PayService.PayAction payAction;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pay);
      
        doBindService();
        initView();
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        if (payAction != null) {
            payAction.onUserCancel();
        }
    }

    private void initView() {
        Intent intent = getIntent();
        String billInfo = intent.getStringExtra(Const.KEY_BILL_INFO);
        final float payMoney = intent.getFloatExtra(Const.KEY_PAY_MONEY, 0);

        TextView tvPayInfo = findViewById(R.id.tvPayInfo);
        TextView tvPayMoney = findViewById(R.id.tvPayMoney);
        final EditText edtPayPwd = findViewById(R.id.edtPayPwd);

        tvPayInfo.setText("支付賬單:" + billInfo);
        tvPayMoney.setText("支付金額:¥" + payMoney);

        findViewById(R.id.btnCommit).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                String password = edtPayPwd.getText().toString().trim();

                if ("123456".equals(password) && payAction != null) {
                    //模擬如果密碼輸入成功就去調用支付,實際上應該請求后端進行加密驗證
                    payAction.pay(payMoney);
                    Toast.makeText(PayActivity.this, "支付成功", Toast.LENGTH_SHORT).show();
                    finish();
                } else {
                    Toast.makeText(PayActivity.this, "支付密碼錯誤!", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    /**
     * 支付頁面回綁服務,等待獲取服務中的支付結果
     * 因為我們的Activity 也要跟 服務 進行通訊,告訴服務通訊結果,所以也要綁定服務
     * <p>
     * 綁定服務
     */
    private void doBindService() {

        Intent intent = new Intent(this, PayService.class);
//        intent.setAction("com.alibaba.alipay.THIRD_PART_PAY");//回綁頁面不需要指定這個了,這里不指定設置的話就會默認返回 了 PayAction 這個對象
//        intent.addCategory(Intent.CATEGORY_DEFAULT);
//        intent.setPackage(this.getPackageName());
        isBind = bindService(intent, connection, BIND_AUTO_CREATE);

        Log.d(TAG, "Bind Pay Service..");
    }

    private ServiceConnection connection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

            //從服務連接中拿到 支付動作
            payAction = (PayService.PayAction) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };


    @Override
    protected void onDestroy() {
        super.onDestroy();

        if (isBind && connection != null) {
            unbindService(connection);
            Log.d(TAG, "unBind Pay Service..");
            connection = null;
            isBind = false;
        }

    }
}


AlipayClientDemo 客戶端實現

MainActivity.java

拷貝服務端的 AIDL 整個文件到本地后定義一個充值頁面。

package com.thisfeng.alipayclientdemo;


public class MainActivity extends AppCompatActivity {

    private static final String TAG = "Client MainActivity";
  
    private AlipayConnection alipayConnection;
    private boolean isBind;
    private ThirdPartPayAction thirdPartPayAction;
    private TextView tvTitle;
    private TextView tvMoney;

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

    /**
     * 綁定支付寶的服務,在現在開發中,其實這部分動作是由支付寶的 SDK 完成
     */
    private void bindAlipayService() {

        Intent intent = new Intent();
        intent.setAction("com.alibaba.alipay.THIRD_PART_PAY");//復制服務端的action 和包名,建議提取至全局
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        intent.setPackage("com.alibaba.alipay");

        alipayConnection = new AlipayConnection();

        isBind = bindService(intent, alipayConnection, BIND_AUTO_CREATE);

        Log.d(TAG, "Client bind service ....");
    }

    private void initView() {

        tvTitle = findViewById(R.id.tvTitle);
        tvMoney = findViewById(R.id.tvMoney);

        findViewById(R.id.btnRecharge).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                if (thirdPartPayAction != null) {
                    //進行充值
                    try {
                        thirdPartPayAction.requestPay("充值100QB", 100, new PayCallBack());
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }

                }

            }
        });

    }

    private class PayCallBack extends ThirdPartPayResult.Stub {

        @Override
        public void onPaySuccess() throws RemoteException {
            //支付成功,修改 UI內容
            //實際上是取修改數據庫,其實支付寶是通過回調URL地址,直接通知我們的后臺服務器,后臺返回我們客戶端進行通知
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    tvMoney.setText("100");

                    Log.d(TAG, "Client onPaySuccess() -----充值成功 !");

                    Toast.makeText(MainActivity.this, "充值成功!", Toast.LENGTH_SHORT).show();
                }
            });
        }

        @Override
        public void onPayFaild(int errorCode, String msg) throws RemoteException {

            Log.d(TAG, "Client onPayFaild() ----- errorCode:" + errorCode + "---msg:" + msg);

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(MainActivity.this, "充值失敗!", Toast.LENGTH_SHORT).show();
                }
            });
        }
    }

    private class AlipayConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

            //獲取支付寶的服務
            thirdPartPayAction = ThirdPartPayAction.Stub.asInterface(service);
            Log.d(TAG, "Client onServiceConnected----->" + name);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "Client onServiceDisconnected----->" + name);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (isBind && alipayConnection != null) {
            unbindService(alipayConnection);
            Log.d(TAG, "Client unbind service ....");
            alipayConnection = null;
            isBind = false;
        }
    }
}

以上為Service 和 Client 的 全部代碼。便于理解請務必手動敲一遍實例加深印象。

完整案例代碼

學習參考:

Android Bander設計與實現 - 設計篇

Binder學習指南

Android跨進程通信:圖文詳解 Binder機制 原理

Android藝術探索

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

推薦閱讀更多精彩內容