Binder機制

Binder概述

Binder在咱們大Android中是無處不在的,不管是調用媒體服務,傳感器,還有咱們常常在用的startActivity ,startService等等都在使用着Bindder來完成相應的功能。整個Android系統就能夠當作一個基於Binder的C/S架構,binder英文意思是粘合劑,Binder就是這個粘合劑,把各個組件系統粘合在一塊兒。Binder這麼重要,做爲Android開發者咱們也更有必要搞懂它。java

下面開始學習Binder之旅吧。android

Binder是用來作進程間通訊的,Android系統是基於Linux的,在Linux中已經有一些進程間通訊的解決方案了,好比管道,共享內存,socket等,爲啥Android又弄了個Binder呢?那咱們就須要瞭解一下他們的優缺點了c++

管道緩存

就好比A到B之間有一個管道,A把數據拷貝到管道中,B從管道中讀取數據,這個過程須要創建管道並須要兩次數據的拷貝安全

並且管道是半雙工的也就是數據只能往一個方向流動,若是想要雙方通訊,就須要創建兩個管道bash

因此管道比較耗費性能服務器

共享內存微信

多個進程之間共享一塊內存區域,這個過程當中無需拷貝,效率很是高,可是因爲這塊內存對全部進程均可見,很差管理並且安全方面也很差網絡

Socket架構

Socket是一個通用的接口,主要用來進行網絡之間的通訊,雖然能夠實現進程間通訊,就是殺雞用牛刀了,傳輸效率低,開銷大,也須要兩次的拷貝。

Binder

只須要一次數據拷貝,性能上僅次於共享內存。穩定性上Binder基於C/S架構模式,客戶端有什麼去求就丟給服務端去作,架構清晰職責明確。

安全方面,傳統的進程間通訊都沒有作這一塊,一個安卓系統中有那麼多的APP存在,每一個APP都運行在一個獨立的進程中,咱們不但願別的進程可以獲取咱們應用的信息。

Android爲每一個新安裝的應用都分配了本身的UID,PID,這是通訊時鑑別身份的重要憑證。

Binder中有4個比較重要的角色:

  • Server
  • Client
  • ServiceManager
  • Binder驅動

如上圖所畫

  1. 服務端經過Binder驅動在ServiceManager中註冊咱們的服務
  2. 客戶端經過Bindr驅動查詢ServiceManager中註冊的服務
  3. ServiceManager經過Binder驅動返回服務端的代理對象
  4. 客戶端拿到服務器端的代理對象就能夠進行進程間通信了。

Client和Server是開發者本身來實現,Binder驅動和ServiceManager是系統提供的。

Binder核心原理

  • Client端發送數據到內核緩存區也就是拷貝
  • 這個內核緩存區和Binder驅動中的數據緩存區存在映射關係
  • 服務端和Bindr的數據緩存區有直接的內存映射關係。
  • 這樣就至關於把數據直接拷貝到了服務端

Binder源碼(9.0)

下面的這些代碼我本身也都是系統代碼,我本身也雲裏霧裏,不過咱們也不須要深刻了解,只須要經過這些地方來強化對其原理的理解就行了

一、打開Binder設備

源碼位置:/frameworks/native/cmds/servicemanager/service_manager.c

在該文件中的main方法中有一句話 driver = "/dev/binder"; 這裏就打開binder驅動設備

二、建立buffer用於進程間傳遞數據,開闢內存映射(128k)

第一步打開Binder驅動以後,緊接着一句代碼bs = binder_open(driver, 128*1024);這裏就是打開一個128k的內存映射

內存映射命令是mmap(),它在 /frameworks/native/cmds/servicemanager/binder.c文件中,進入能夠看到 bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);

它是在系統啓動的時候就會調用,在9.0系統源碼/device/google/cuttlefish_kernel/4.4-x86_64/System.map文件中的25306行能夠看到下面的指令 ffffffff815dbf50 t binder_mmap來開啓映射

三、ServiceManager啓動

在系統源碼位置 /system/core/rootdir/init.rc 文件中,能夠找到start servicemanager指令

四、打包到Parcel中,數據寫入binder設備copy_from_user

在系統源碼:/frameworks/native/libs/binder/IServiceManager.cpp中能夠看到parcel打包過程

virtual status_t addService(const String16& name, const sp<IBinder>& service, bool allowIsolated, int dumpsysPriority) {
        Parcel data, reply;
        data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
        data.writeString16(name);
        data.writeStrongBinder(service);
        data.writeInt32(allowIsolated ? 1 : 0);
        data.writeInt32(dumpsysPriority);
        status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
        return err == NO_ERROR ? reply.readExceptionCode() : err;
    }
複製代碼

在系統文件 /frameworks/native/libs/binder/IPCThreadState.cpp中,找到 err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);

將parcel中的信息封裝成結構體而且寫入到mOut中,並等待返回

五、服務註冊,添加到鏈表svclist中

server向ServiceManager中註冊

在系統代碼:/frameworks/native/cmds/servicemanager/service_manager.c文件中

if (!svc_can_register(s, len, spid, uid)) {
        ALOGE("add_service('%s',%x) uid=%d - PERMISSION DENIED\n",
             str8(s, len), handle, uid);
        return -1;
    }
    //去鏈表鏈表svclist中查找,看服務是否註冊過
    si = find_svc(s, len);
    ....
複製代碼

定義主線程中的線程池

在系統源碼/frameworks/native/libs/binder/IPCThreadState.cpp文件中能夠找到joinThreadPool方法。

這裏就是定義一個主線程的線程池,,不停的讀寫

循環從mln和mOut中取出讀寫請求 mIn.setDataCapacity(256); mOut.setDataCapacity(256);他們默認是256字節的大小。

在talkWithDriver方法中,判斷是否能夠讀寫,最終發送到binder設備中。

這些代碼真是看的雲裏霧裏,只須要經過他們加深對Binder的執行原理就好了。

手擼AIDL

直接操做Binder是比較麻煩的,Andorid中經過AIDL來簡化咱們使用Binder。

AIDL四個重要對象

  • IBinder: 一個接口,表明了一個跨進程通信的能力
  • IInterance: 服務端進程有什麼能力,能夠提供哪些方法
  • Binder: Binder的本地對象 繼承自IBinder
  • Stub: 繼承自Binder 實現了IInterance,本地實現的服務端的能力

例子:使用AIDL實現一個第三方的登陸,如今有一個A應用和一個B應用,A應用調用B應用來實現登陸。

最終效果以下圖:

A調用B的登陸服務,B是服務端,咱們先從B工程中建立一個aidl,直接在工程的main文件夾上右擊鼠標建立便可,也能夠建立到別的文件夾。

package com.chs.binderb;

interface ILoginInterface {
    void login();

    void loginCallBack(boolean isSuccess,String user);
}

複製代碼

建立兩個方法一個登陸方法,一個登陸回調。

而後把這個AIDL的完整包名和文件都複製到A工程的相同位置。必須如出一轍直接複製。

在B工程中建立一個LoginService來監聽A工程發來的消息,跳轉到第三方登陸界面,注意跳轉的時候須要設置Intent.FLAG_ACTIVITY_NEW_TASK這個flag

public class LoginService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new ILoginInterface.Stub() {
            @Override
            public void login() throws RemoteException {
                Intent intent = new Intent(getApplicationContext(), MainActivity.class);
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(intent);
            }

            @Override
            public void loginCallBack(boolean isSuccess, String user) throws RemoteException {

            }
        };
    }
}
複製代碼

而後在AndroidMainfest.xml文件中註冊服務

<service android:name=".service.LoginService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote_server"
            >
            <intent-filter>
                <action android:name="BinderB_Action"></action>
            </intent-filter>
</service>
複製代碼
  • android:enabled="true" 表示能夠被實例化
  • android:exported="true" 表示能夠被別的應用隱式調用
  • android:process=":remote_server" 表示開啓一個新進程,進程名字是remote_server
  • action中的名字在 A應用隱式調用的時候使用

下面去A工程中寫調用的方法

public class MainActivity extends AppCompatActivity {
    /** * 是否綁定了遠程服務 */
    private boolean isStartRemote;
    private ILoginInterface mILoginInterface;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initBinderService();
    }

    /** * 經過隱示意圖綁定B應用的service */
    private void initBinderService() {
        Intent intent = new Intent();
        //設置action
        intent.setAction("BinderB_Action");
        //設置B應用的包名
        intent.setPackage("com.chs.binderb");
        //綁定服務
        bindService(intent,cnn,BIND_AUTO_CREATE);
        isStartRemote = true;
    }

    ServiceConnection cnn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //綁定成功,可使用服務端的方法了
            mILoginInterface = ILoginInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    public void startWeiXinLogin(View view) {
        if(mILoginInterface!=null){
            try {
                mILoginInterface.login();
            } catch (RemoteException e) {
                e.printStackTrace();
                Toast.makeText(this,"請先安裝微信",Toast.LENGTH_SHORT).show();
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(isStartRemote){
            unbindService(cnn);
        }
    }
}
複製代碼

佈局樣式就是前面gif圖中的樣式,微信圖標的點擊方法是startWeiXinLogin方法。裏面調用了ILoginInterface的login方法

先說一下ILoginInterface

當咱們建立好AIDL文件,從新Rebuild一下工程以後,系統會給咱們生成一個ILoginInterface文件,位置在 app\build\generated\aidl_source_output_dir\debug\compileDebugAidl\out\com\chs\binderb\ILoginInterface.java

/* * This file is auto-generated. DO NOT MODIFY. * Original file: D:\\android\\A1\\BinderA\\app\\src\\main\\aidl\\com\\chs\\binderb\\ILoginInterface.aidl */
package com.chs.binderb;

public interface ILoginInterface extends android.os.IInterface {
    /** * Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements com.chs.binderb.ILoginInterface {
        private static final java.lang.String DESCRIPTOR = "com.chs.binderb.ILoginInterface";

        /** * Construct the stub at attach it to the interface. */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /** * Cast an IBinder object into an com.chs.binderb.ILoginInterface interface, * generating a proxy if needed. */
        public static com.chs.binderb.ILoginInterface asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.chs.binderb.ILoginInterface))) {
                return ((com.chs.binderb.ILoginInterface) iin);
            }
            return new com.chs.binderb.ILoginInterface.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_login: {
                    data.enforceInterface(descriptor);
                    this.login();
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_loginCallBack: {
                    data.enforceInterface(descriptor);
                    boolean _arg0;
                    _arg0 = (0 != data.readInt());
                    java.lang.String _arg1;
                    _arg1 = data.readString();
                    this.loginCallBack(_arg0, _arg1);
                    reply.writeNoException();
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        private static class Proxy implements com.chs.binderb.ILoginInterface {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public void login() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_login, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public void loginCallBack(boolean isSuccess, java.lang.String user) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(((isSuccess) ? (1) : (0)));
                    _data.writeString(user);
                    mRemote.transact(Stub.TRANSACTION_loginCallBack, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_login = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_loginCallBack = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    public void login() throws android.os.RemoteException;

    public void loginCallBack(boolean isSuccess, java.lang.String user) throws android.os.RemoteException;
}

複製代碼

它繼承了IInterface接口,全部能夠在Binder中傳輸的接口都須要繼承IInterface接口。同時它本身也是一個接口。

它聲明瞭兩個方法login()和loginCallBack就是咱們在AIDL文件中寫的兩個方法。同時聲明瞭兩個整形id:TRANSACTION_login和TRANSACTION_loginCallBack來標識這兩個方法。在onTransact方法中經過這兩個id來識別客戶端請求的是哪一個方法

它內部有一個內部類Stub,這個就是一個Binder,跨進程通訊的過程就由它的內部代理Proxy完成,它裏面有幾個重要的方法

asInterface

用於將服務端的Binder對象轉化成客戶端可使用的AIDL接口類型的對象。這個轉化是分進程的,若是客戶端和服務端在同一個進程中就返回Stub自己,若是是在不一樣的進程中,就返回Stub.Proxy(obj)代理對象

asBinder

返回當前的Binder對象

onTransact

這個方法時重寫的Binder類中的onTransact方法。它運行在服務端的Binder線程池中,遠程客戶端發起請求時,請求會通過系統包裝後交給該方法來處理。它經過不一樣的code來判斷調用哪一個方法。而後執行方法並寫入返回值

Proxy#login

這個方法運行在客戶端,前面的MainActivity中咱們調用asInterface方法其實就是拿到了這個Proxy對象,因此咱們就能調用它的login方法,當客戶端調用該方法的時候建立輸入的Parcel對象_data和輸出的Parcel對象 _reply,而後調用transact方法來發起遠程調用請求,而後當前線程掛起,以後服務端的onTransact方法會被調用,直到完成並返回結果

Proxy#loginCallBack

和上面的login方法同樣。

OK 如今回到MainActivity中,在onCreate方法中經過隱式的調用綁定B應用中的服務。

這樣點擊按鈕的時候,B應用中的LoginService的onBind方法就會調用,而後就會打開登陸的Activity。

到這裏其實A到B的跨進程通訊就已經完成了,可是咱們在B應用中點擊輸入用戶名和密碼若是成功或者失敗,應該反饋給A應用啊,怎麼反饋呢。

方法就是跟A找B通訊時同樣的道理,在A中也寫一個Service,讓B去綁定A中的Service,執行完登陸以後,調用A的遠程方法。

代碼以下

public class LoginService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new ILoginInterface.Stub() {
            @Override
            public void login() throws RemoteException {

            }

            @Override
            public void loginCallBack(boolean isSuccess, String user) throws RemoteException {
                Log.i("登陸狀況","狀態:"+isSuccess+">>>>>user:"+user);
            }
        };
    }
}
複製代碼

A中也寫一個LoginService,在回調方法中打印一下回調狀態和用戶名,並在AndroidMasfet.xml中註冊

B中模擬登陸並調用A中服務的方法

public class MainActivity extends AppCompatActivity {
    private static final String NAME = "chs";
    private static final String PWD = "123";


    private EditText etName;
    private EditText etPwd;
    /** * 是否綁定了遠程服務 */
    private boolean isStartRemote;
    private ILoginInterface mILoginInterface;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        etName = findViewById(R.id.et_name);
        etPwd = findViewById(R.id.et_pwd);

        initBinderService();

    }

    /** * 經過隱示意圖綁定A應用的service */
    private void initBinderService() {
        Intent intent = new Intent();
        //設置action
        intent.setAction("BinderA_Action");
        //設置B應用的包名
        intent.setPackage("com.chs.bindera");
        //綁定服務
        bindService(intent,cnn,BIND_AUTO_CREATE);
        isStartRemote = true;
    }
    ServiceConnection cnn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //綁定成功,可使用服務端的方法了
            mILoginInterface = ILoginInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    public void qqLogin(View view) {
        final String name = etName.getText().toString();
        final String pwd = etPwd.getText().toString();
        ProgressDialog dialog = new ProgressDialog(this);
        dialog.setTitle("正在登陸");
        new Thread(){
            @Override
            public void run() {
                super.run();
                SystemClock.sleep(1000);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        boolean isSuccess = false;
                        if(name.equals(NAME)&&pwd.equals(PWD)){
                            isSuccess = true;
                            showToast("登陸成功");
                            finish();
                        }else {
                            showToast("登陸失敗");
                        }
                        try {
                            mILoginInterface.loginCallBack(isSuccess,name);
                        } 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(cnn);
        }
    }
}
複製代碼

OK代碼完成,運行以後就是前面gif中的效果了。A中LoginService中的回調打印以下。

2019-07-10 21:51:27.225 10173-10191/com.chs.bindera:remote_a I/登陸狀況: 狀態:false>>>>>user:
2019-07-10 21:51:35.343 10173-10191/com.chs.bindera:remote_a I/登陸狀況: 狀態:true>>>>>user:chs
複製代碼
相關文章
相關標籤/搜索