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個比較重要的角色:
如上圖所畫
Client和Server是開發者本身來實現,Binder驅動和ServiceManager是系統提供的。
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的執行原理就好了。
直接操做Binder是比較麻煩的,Andorid中經過AIDL來簡化咱們使用Binder。
AIDL四個重要對象
例子:使用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>
複製代碼
下面去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
複製代碼