一、Binder上層原理
1、Binder是啥?
從不同的角度了解有不同的說法
(1)Binder是Android中的一個類,它實作了IBinder接口
public class Binder implements IBinder {
...
}
(2)從IPC角度來說,Binder是Android中特有的一種跨程序通信方式。Aidl、Messenger、ContentProvider 底層就是基于Binder實作的。
(3)Binder還可以了解為一種虛拟的實體裝置,它的裝置驅動是/dev/binder,該通信方式在Linux中沒有。
(4)從AndroidFramework角度來說,Binder是ServiceManager連接配接各種Manager(如ActivityManager、WindowManager,等等)和相應ManagerService的橋梁。
(5)從Android應用層來說,Binder是用戶端和服務端進行通信的媒介。
當bindService的時候,服務端會傳回一個包含了服務端業務調用的Binder對象,通過這個Binder對象,用戶端就可以擷取服務端提供的服務或者資料。
2、簡單的AIDL栗子
Sever端App提供服務:
const val tag = "MyService"
class MyService : Service() {
override fun onBind(intent: Intent): IBinder {
return MyBinder()
}
class MyBinder : MusicManager.Stub() {
override fun playMusic() {
Log.i(tag,"playMusic()")
}
override fun stopMusic() {
Log.i(tag,"stopMusic()")
}
override fun pauseMusic() {
Log.i(tag,"pauseMusic()")
}
}
}
Client App調用服務功能
public class MainActivity extends AppCompatActivity {
MusicManager musicManager;
private ServiceConnection conn;
private Intent intent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//隐式啟動
intent = new Intent();
intent.setAction("com.sunnyday.administrator.servicedemo.services.MusicService");
intent.setPackage("com.sunnyday.administrator.servicedemo");
conn = new MyServiceConnection();
}
// 綁定按鈕
public void bindRemoteService(View view) {
bindService(intent, conn, BIND_AUTO_CREATE);
}
// 播放按鈕
public void PlayMusic(View view) {
try {
if (null != musicManager) {
Log.i("233", "test: ");
musicManager.playMusic();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 獲得執行個體
musicManager = MusicManager.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}
3、AIDl底層Binder實作
項目工程buidl檔案下找AIDL 檔案對應的java檔案進行分析
(1)MusicManager接口很簡單。
1、它繼承了IInterface這個接口,同時它自己也還是個接口,所有可以在Binder中傳輸的接口都需要繼承IInterface接口。
2、聲明了幾個接口方法,這幾個方法都是我們在Aidl檔案中聲明過的。
3、定義了個内部類Stub作為接口成員變量。
public interface MusicManager extends android.os.IInterface {
public static abstract class Stub extends android.os.Binder implements com.example.myservice.MusicManager {
...
}
public void playMusic() throws android.os.RemoteException;
public void stopMusic() throws android.os.RemoteException;
public void pauseMusic() throws android.os.RemoteException;
}
擴充
public interface IInterface{
public IBinder asBinder();
}
public class Binder implements IBinder {
...
}
(2)MusicManager#Stub 内部類
//1、
public static abstract class Stub extends android.os.Binder implements com.example.myservice.MusicManager {
// 2、三個int 常量值:辨別TRANSACTION (trænˈzækʃn)過程中用戶端請求的是哪個方法。
static final int TRANSACTION_playMusic = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_stopMusic = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_pauseMusic = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
//3、DESCRIPTOR Binder的唯一辨別。
//一般用目前類的全限定名辨別(包名+類名)
private static final java.lang.String DESCRIPTOR = "com.example.myservice.MusicManager";
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/*
4、将服務端的IBnid對象轉換為用戶端所需要的aidl接口對象。
這個方法區分程序:
1、用戶端、服務端同一程序:此方法反回服務端Stub對象本身。
2、用戶端、服務端不同程序:此方法傳回系統封裝後的Stub.Proxy對象。
*/
public static com.example.myservice.MusicManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.example.myservice.MusicManager))) {
return ((com.example.myservice.MusicManager) iin);
}
return new com.example.myservice.MusicManager.Stub.Proxy(obj);
}
//5、傳回目前Binder對象(Stub)
@Override
public android.os.IBinder asBinder() {
return this;
}
/*
6、onTransact
(1)、此方法運作在服務端的Binder線程池中
(2)、當用戶端發起誇程序請求時遠端請求會交給系統底層封裝後交由此方法處理。
方法參數:
code:服務端通過code可以知道用戶端要請求的方法是哪個
data:目标方法所需的參數(如果目标方法有參數的話)
reply:目标方法執行後的傳回值(如果目标方法有傳回值的話)
onTransact傳回值:傳回false用戶端請求會失敗,即無用戶端要執行的目标方法。
ps:是以我們可以利用這個特性來做權限驗證,畢竟我們也不希望随便一個程序都能
遠端調用我們的服務
*/
@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_playMusic: {
data.enforceInterface(descriptor);
this.playMusic();
reply.writeNoException();
return true;
}
case TRANSACTION_stopMusic: {
data.enforceInterface(descriptor);
this.stopMusic();
reply.writeNoException();
return true;
}
case TRANSACTION_pauseMusic: {
data.enforceInterface(descriptor);
this.pauseMusic();
reply.writeNoException();
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
//Stub的内部類,代理類。
private static class Proxy implements com.example.myservice.MusicManager {
...
}
}
}
1、這個Stub就是一個Binder類,當用戶端和服務端都位于同一個程序時,方法調用不會走跨程序的transact過程,而當兩者位于不同程序時,方法調用需要走transact過程,這個邏輯由Stub的内部代理類Proxy來完成.。
2、Stub類中聲明了一些int類型常量字段,這些字段用于辨別Aidl檔案對應的接口方法。
3、DESCRIPTOR:Binder的唯一辨別,一般用目前Binder的類名表示。
(3)Stub#Proxy 代理類
//1、代理類也實作了被代理類的MusicManager接口
private static class Proxy implements com.example.myservice.MusicManager {
//2、持有被代理類對象(Binder)
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;
}
/*
3、方法運作在用戶端,當用戶端遠端調用此方法時,具體的工作流程如下:
(1)、建立Parcel 類型輸入對象_data輸出對象_reply
(方法如有傳回值還會建立傳回值對象)
(2)、把方法資訊寫入_data
(3)、調用transact發起IPC(同時目前線程挂起)
(4)、接着服務端的onTransact會被調用直到IPC傳回資料。
(5)、服務端onTransact傳回資料後用戶端的_reply開始讀取RPC傳回結果
需要注意:
(1)、盡量不要在UI線程中發起IPC 因為發起IPC時會挂起目前線程,
且當遠端服務的方法有耗時的邏輯是UI線程會ANR
(2)、由于服務端的Binder方法運作在Binder線程池中。
是以Binder中的方法盡量采取同步實作(加鎖)。
因為他已經運作在一個線程中了。
*/
@Override
public void playMusic() 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_playMusic, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void stopMusic() 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_stopMusic, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void pauseMusic() 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_pauseMusic, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
(4)流程小結
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHLx0kaOdXV61ENNpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL2cTOkFDMiNjNmhTYyczN5kjZmRjYhZjZjRWN2MmY4Y2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
4、Binder死亡
Binder運作在服務端程序,如果服務端程序由于某種原因異常終止,這個時候我們到服務端的Binder連接配接斷裂稱之為Binder死亡。
Binder死亡會導緻我們的遠端調用失敗。如果我們不知道Binder連接配接已經斷裂,那麼用戶端的功能就會受到影響。
為了解決Binder死亡問題,Binder中提供了兩個配對的方法linkToDeath和unlinkToDeath,通過linkToDeath我們可以給Binder設定一個死亡代理,當Binder死亡時,我們就會收到通知,這個時候我們就可以重新發起連接配接請求進而恢複連接配接。
(1)做法
聲明一個DeathRecipient對象。DeathRecipient是一個接口,其内部隻有一個方法binderDied,我們需要實作這個方法,當Binder死亡的時候,系統就會回調binderDied方法,然後我們就可以移出之前綁定的binder代理并重新綁定遠端服務。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){
@Override
public void binderDied() {
if (musicManager==null)return;
//1、binder 死亡時 用戶端unlinkToDeath
musicManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
//置空處理
musicManager =null;
//2、todo 用戶端可請求重新遠端綁定服務
}
};
其次,在用戶端綁定遠端服務成功後,給binder設定死亡代理
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
musicManager = IMusicManager.Stub.asInterface(service)
// 設定死亡代理
service.linkToDeath(mDeathRecipient,0)
}
其中linkToDeath的第二個參數是個标記位,我們直接設為0即可。經過上面兩個步驟,就給我們的Binder設定了死亡代理,當Binder死亡的時候我們就可以收到通知了。另外,通過Binder的方法isBinderAlive也可以判斷Binder是否死亡。
二、Binder上層原理加深了解-手寫Binder
其實觀察上文的AIDl生成的檔案還是很有規律的,在上文中我的講解順序也是按照規律來講解的即結構上由外到内。
1、聲明個接口繼承IInterface
public interface MusicManager extends android.os.IInterface {
// 調整下DESCRIPTOR位置
public static final java.lang.String DESCRIPTOR = "com.example.myservice.MusicManager";
public void playMusic() throws android.os.RemoteException;
public void stopMusic() throws android.os.RemoteException;
public void pauseMusic() throws android.os.RemoteException;
}
2、仿寫Stub類
定義MusicManager 接口實作類,繼承Binder類
public static abstract class MusicManagerImpl extends android.os.Binder implements com.example.myservice.MusicManager {
static final int TRANSACTION_playMusic = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_stopMusic = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_pauseMusic = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
public MusicManagerImpl () {
this.attachInterface(this, DESCRIPTOR);
}
public static com.example.myservice.MusicManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.example.myservice.MusicManager))) {
return ((com.example.myservice.MusicManager) iin);
}
return new com.example.myservice.MusicManager.MusicManagerImpl .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_playMusic: {
data.enforceInterface(descriptor);
this.playMusic();
reply.writeNoException();
return true;
}
case TRANSACTION_stopMusic: {
data.enforceInterface(descriptor);
this.stopMusic();
reply.writeNoException();
return true;
}
case TRANSACTION_pauseMusic: {
data.enforceInterface(descriptor);
this.pauseMusic();
reply.writeNoException();
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
}
}
3、仿寫Stub代理類
private static class Proxy implements com.example.myservice.MusicManager {
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 playMusic() 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(MusicManagerImpl .TRANSACTION_playMusic, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void stopMusic() 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(MusicManagerImpl .TRANSACTION_stopMusic, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void pauseMusic() 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(MusicManagerImpl .TRANSACTION_pauseMusic, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
總結:
1、上述寫法和Aidl生成方式幾乎沒啥差別
2、可見實作IPC并不一定使用aidl,我們完全可以手寫。隻是使用aidl方式系統可快速幫我們生成相應檔案。
三、Binder連接配接池
1、不同的業務子產品都需要使用AIDL來進行程序間通信,那該怎麼處理呢?
随着項目的疊代,公司的項目越來越龐大了。現在有n個不同的業務子產品都需要使用AIDL來進行程序間通信,那我們該怎麼處理呢?也許你會說:“就按照AIDL的實作方式一個個來吧”,這是可以的。
如果用這種方法,首先我們需要建立n個Service,這好像有點多啊!如果有100個地方需要用到AIDL呢,先建立100個Service?好吧問題出來了:随着AIDL數量的增加,我們不能無限制地增加Service,Service是四大元件之一,本身就是一種系統資源。而且太多的Service會使得我們的應用看起來很重量級,因為正在運作的Service可以在應用詳情頁看到,當我們的應用詳情顯示有10個服務正在運作時,這看起來并不是什麼好事。
2、問題解決:Binder連接配接池的設計
我們需要減少Service的數量,将所有的AIDL放在同一個Service中去管理,基于這個思想該如何做呢?
(1)服務端每個業務子產品建立自己的AIDL接口并實作此接口,這個時候不同業務子產品之間是不能有耦合的,所有實作細節我們要單獨開來,然後提供自己的唯一辨別和其對應的Binder對象。
(2)服務端隻需要一個Service就可以了。但是服務端需額外提供一個queryBinder接口,這個接口能夠根據用戶端的不同請求傳回相應的Binder對象給它們,用戶端對應業務子產品塊拿到所需的Binder對象後就可以進行遠端方法調用了。
Binder連接配接池的主要作用就是将用戶端每個業務子產品的Binder請求統一轉發到遠端Service中去執行,進而避免了重複建立Service的過程。流程圖如下:
3、服務端實作
(1)概覽圖
(2)Aidl 接口
// IAudioManager.aidl
package com.sunnyday.binderpool;
// Declare any non-default types here with import statements
interface IAudioManager {
void playAudio();
}
// IMusicManager.aidl
package com.sunnyday.binderpool;
// Declare any non-default types here with import statements
interface IMusicManager {
void playMusic();
}
// IBinderPool.aidl
package com.sunnyday.binderpool;
// Declare any non-default types here with import statements
interface IBinderPool {
/**
* @param binderType: the unique token of specific Binder
* @return specific Binder who's token is binderType
*/
IBinder getBinderByType(int binderType);
}
(3)Service&Aidl對應實作類
class BinderPoolService : Service() {
companion object{
const val TAG = "MusicManagerImpl"
}
override fun onBind(intent: Intent): IBinder {
Log.i(TAG,"onBind")
return BinderPoolImpl()
}
}
/**
* Create by SunnyDay on 09:47 2021/08/07
*/
class AudioManagerImpl:IAudioManager.Stub() {
companion object{
const val TAG = "AudioManagerImpl"
}
override fun playAudio() {
Log.i(TAG,"playAudio")
}
}
/**
* Create by SunnyDay on 09:44 2021/08/07
*/
class MusicManagerImpl:IMusicManager.Stub() {
companion object{
const val TAG = "MusicManagerImpl"
}
override fun playMusic() {
Log.i(TAG,"playMusic")
}
}
/**
* Create by SunnyDay on 11:53 2021/08/07
*/
class BinderPoolImpl : IBinderPool.Stub() {
companion object {
const val TAG = "BinderPoolImpl"
const val BINDER_NONE = -1
const val BINDER_MUSIC_MANAGER = 0
const val BINDER_AUDIO_MANAGER = 1
}
override fun getBinderByType(binderType: Int): IBinder? {
return when (binderType) {
BINDER_MUSIC_MANAGER -> MusicManagerImpl()
BINDER_AUDIO_MANAGER -> AudioManagerImpl()
else -> null
}
}
}
4、用戶端實作
(1) 概覽圖
(2)連接配接池
package com.sunnyday.client;
/**
* Create by SunnyDay on 11:56 2021/08/07
*/
class BinderPool {
private static final String TAG = "BinderPool";
private static volatile BinderPool INSTANCE = null;
public static final int BINDER_NONE = -1;
public static final int BINDER_MUSIC_MANAGER = 0;
public static final int BINDER_AUDIO_MANAGER = 1;
private final Context mContext;
//使用BlockingQueue也可實作同樣功能
private CountDownLatch mCountDownLatch;
private IBinderPool mIBinderPool;
private BinderPool(Context context) {
//use application context avoid memory leak.
this.mContext = context.getApplicationContext();
connectBinderPoolService();
}
public static BinderPool getInstance(Context context) {
if (INSTANCE == null) {
synchronized (BinderPool.class) {
if (INSTANCE == null) {
INSTANCE = new BinderPool(context);
}
}
}
return INSTANCE;
}
private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
//死亡回調
@Override
public void binderDied() {
if (mIBinderPool!=null){
mIBinderPool.asBinder().unlinkToDeath(mDeathRecipient,0);
mIBinderPool=null;
connectBinderPoolService();
}
}
};
private final ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG,"onServiceConnected");
mIBinderPool = IBinderPool.Stub.asInterface(service);
try {
//設定死亡代理
mIBinderPool.asBinder().linkToDeath(mDeathRecipient,0);
} catch (RemoteException e) {
e.printStackTrace();
}
mCountDownLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG,"onServiceDisconnected");
mIBinderPool = null;
}
};
public IBinder getBinder(int bindType) {
Log.i(TAG, "getBinder");
IBinder binder = null;
try {
if (mIBinderPool != null) {
binder = mIBinderPool.getBinderByType(bindType);
}
} catch (RemoteException e) {
e.printStackTrace();
}
return binder;
}
private void connectBinderPoolService() {
Log.i(TAG, "connectBinderPoolService");
mCountDownLatch = new CountDownLatch(1);
Intent intent = new Intent();
intent.setAction("com.sunnyday.binderpool.BinderPoolService");
intent.setPackage("com.sunnyday.binderpool");
mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE);
try {
mCountDownLatch.await();
} catch (Exception e) {
e.printStackTrace();
}
}
}
(3)業務調用模拟
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// notice here ,in new Thread not main.
// if in main can lead to ANR.
Thread { test() }.start()
}
private fun test() {
Log.i("MainActivity","client:test")
// test get MusicBinder
val musicBinder = BinderPool.getInstance(this).getBinder(BinderPool.BINDER_MUSIC_MANAGER)
val mIMusicManager = IMusicManager.Stub.asInterface(musicBinder)
mIMusicManager.playMusic()
// test get AudioBinder
val audioBinder = BinderPool.getInstance(this).getBinder(BinderPool.BINDER_AUDIO_MANAGER)
val mAudioManager = IAudioManager.Stub.asInterface(audioBinder)
mAudioManager.playAudio()
}
}
5、小結
Binder 連接配接池的設計其實也沒牽涉額外多的知識點,主要如下
(1)單例大家都會吧emmmmm
(2)CountDownLatch 這個主要是解決服務綁定結果異步回調的弊端。也即結果不能立即獲得,我們又要使用相應對象這時導緻空指針。
使用CountDownLatch 可以等待相應線程執行完結果,目前線程在執行邏輯。其實BlockingQueue也可完成這個需求。大家可以自己實踐下。
服務端完整代碼
用戶端完整代碼
The end
參考1:Android 開發藝術探索微信讀書版
參考2
參考3