Android IPC 之獲取服務(IBinder)

前言

IPC 系列文章:
建議按順序閱讀。

Android IPC 之Service 還可以這么理解
Android IPC 之Binder基礎
Android IPC 之Binder應用
Android IPC 之AIDL應用(上)
Android IPC 之AIDL應用(下)
Android IPC 之Messenger 原理及應用
Android IPC 之服務端回調
Android IPC 之獲取服務(IBinder)
Android Binder 原理換個姿勢就頓悟了(圖文版)

通過前面的文章我們知道,要進行進程通信的核心是能拿到另一個進程暴露出來的IBiner引用。本篇將重點分析獲取IBinder的方式及其原理。
通過本篇文章,你將了解到:

1、獲取系統服務
2、獲取自定義服務
3、兩者區別與聯系

本篇文章,系統服務、自定義服務里的服務并非單純是指Service,而是提供某一類功能的"服務"。

1、獲取系統服務

簡單例子

以手機振動為例:

        Vibrator vibrator = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
        vibrator.vibrate(1000);

調用Context 方法getSystemService(xx),xx表示服務名字,最終返回Vibrator。
拿到Vibrator 引用后就可以調用相應的方法讓手機振動。
繼續沿著方法調用分析:

#ContextImpl.java
    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }

#SystemServiceRegistry
    public static Object getSystemService(ContextImpl ctx, String name) {
        //從map 里獲取鍵值
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }

這個map從哪里來呢?在SystemServiceRegistry 靜態代碼塊里注冊的:

#SystemServiceRegistry.java
    static {
        ...
        registerService(Context.VIBRATOR_SERVICE, Vibrator.class,
                new CachedServiceFetcher<Vibrator>() {
                    @Override
                    public Vibrator createService(ContextImpl ctx) {
                        return new SystemVibrator(ctx);
                    }});
        ...
    }

可以看出返回了SystemVibrator,它是Vibrator(抽象類)的子類。
Vibrator.vibrate(xx)最終調用了如下方法:

#SystemVibrator.java
    private final IVibratorService mService;

    public SystemVibrator(Context context) {
        super(context);
        //獲取服務端提供的接口
        mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
    }
    
    public void vibrate(int uid, String opPkg, VibrationEffect effect,
                        String reason, AudioAttributes attributes) {
        if (mService == null) {
            Log.w(TAG, "Failed to vibrate; no vibrator service.");
            return;
        }
        try {
            //真正調用之處
            mService.vibrate(uid, opPkg, effect, usageForAttributes(attributes), reason, mToken);
        } catch (RemoteException e) {
            Log.w(TAG, "Failed to vibrate.", e);
        }
    }

了解過AIDL的同學都會知道,熟悉的套路:

  • mService 為服務端提供的接口,客戶端調用其提供的方法即可實現相應的功能。
  • 客戶端為當前待使用振動服務的App進程,服務端為提供振動服務的進程。
image.png

獲取IBinder

振動服務的IBinder是通過:

ServiceManager.getService("vibrator")

獲取的。

#ServiceManager.java
    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                //獲取IBinder
                return Binder.allowBlocking(rawGetService(name));
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }

    private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }

        //獲取服務端的ServiceManager
        sServiceManager = ServiceManagerNative
                .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
        return sServiceManager;
    }


    private static IBinder rawGetService(String name) throws RemoteException {
        ...
        final IBinder binder = getIServiceManager().getService(name);
        ...
        return binder;
    }

又是熟悉的套路,IServiceManager 為ServiceManager服務端提供的接口,通過該接口獲取振動服務的IBinder引用。
其中BinderInternal.getContextObject()) 獲取ServiceManager的IBinder。
此處需要說明一下:

Client 需要從ServiceManager獲取震動服務的IBinder,而Client本身需要和ServiceManager通信,要通信那么得有IBinder吧。BinderInternal.getContextObject())就是為了獲取ServiceManager的IBinder,該方法從Binder驅動獲取了IBinder引用。

注冊服務

ServiceManager是如何找到振動服務的呢?
Android 系統啟動后,會開啟system_server進程,該進程里開啟了很多系統服務,包括AMS、WMS、振動服務等。

#SystemServer.java
    private void startOtherServices() {
        ...
        VibratorService vibrator = null;
        ...
        vibrator = new VibratorService(context);
        //向ServiceManager注冊振動服務
        ServiceManager.addService("vibrator", vibrator);
        ...
    }

繼續來看addService(xx):

#ServiceManager.java
    public static void addService(String name, IBinder service) {
        addService(name, service, false, IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT);
    }

    public static void addService(String name, IBinder service, boolean allowIsolated,
                                  int dumpPriority) {
        try {
            //IPC 調用注冊服務
            getIServiceManager().addService(name, service, allowIsolated, dumpPriority);
        } catch (RemoteException e) {
            Log.e(TAG, "error in addService", e);
        }
    }

調用ServiceManager接口添加服務到ServiceManager里。

小結

好了,現在從頭到尾再捋一下。

1、ServiceManager 進程啟動
2、system_server 進程啟動,并將各個服務(包括振動服務)添加到ServiceManager里
3、客戶端從ServiceManager里獲取振動服務

用圖表示:


image.png

其中 Client、ServiceManager、SystemServer 分別運行于三個不同的進程,三者之間通過Binder進行IPC。實線為其調用目的,虛線為其調用手段。

1、SystemServer 通過IPC1 向ServiceManager注冊服務的IBinder引用
2、Client想要使用服務(如振動服務),先通過IPC2 向ServiceManager獲取
3、Client拿到服務IBinder后,調用服務接口(IPC3),使用服務提供的具體功能

為了減少多次無用IPC調用,因此Client會將拿到的各種服務緩存到數組里,當要查詢的服務已經存在,則不用進行IPC2,直接使用IPC3。

系統提供的服務如AMS、WMS、PMS等都將IBinder封裝在xxManager(如WindowManager等)里,通過xxManager就可以進行IPC使用具體的服務。

2、獲取自定義服務

上面說了系統提供的服務需要注冊到ServiceManager里,以便后來者查詢使用之。那么我們自己定義的服務該如何使用呢?

Service 的綁定流程

先來看看典型的綁定流程:
服務端代碼:

    IStudentServer iStudentServer = new IStudentServer.Stub() {
        @Override
        public void say(String world) throws RemoteException {
            Log.d(TAG, "hello " + world);
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return iStudentServer.asBinder();
    }

客戶端代碼:

    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //重點在service 類型
            IStudentServer iStudentServer = IStudentServer.Stub.asInterface(service);
            try {
                iStudentServer.say("hello");   
            } catch (Exception e) {
                
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    private void bindService() {
        Intent intent = new Intent(MainActivity.this, MyService.class);
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }

大致闡述上述流程:

1、Service 構造Binder對象,并將IBinder在onBind(xx)傳遞出去
2、客戶端在綁定Service成功后會收到服務端傳遞過來的IBinder
3、通過該IBinder獲取關聯的接口操作服務端

可以看出,我們在Service里定義業務邏輯(Server端),并開放了接口,通過Service的綁定功能將接IBinder傳遞給客戶端,這和獲取系統服務的邏輯是一樣的,核心都是IBinder的傳遞,接下來從源頭入手查看IBinder的傳遞。

從Context.bindService(xx)開始

由于涉及到的代碼較多,此處就不貼完整源碼了,重點關注關鍵之處和IPC 流程,多用圖示之。
綁定流程圖:


image.png

大致解釋上圖元素構成:
最頂上方框為類名。
紅色表示它們都運行在同一進程,暫且稱之為客戶端進程。
綠色表示它們都運行在同一進程,暫且稱之為系統服務進程。
黃色表示它們都運行在同一進程,暫且稱之為服務端進程。

紅色箭頭表示該調用為進程間調用,用IPC 表示之。其余為本進程內的對象調用。

分別來分析重點1、2、3。
重點1
客戶端發起綁定操作,傳入ServiceConnection 引用,該引用在ContextImpl.bindServiceCommon(xx)里被封裝在ServiceDispatcher里,而ServiceDispatcher又持有InnerConnection引用,InnerConnection 繼承自IServiceConnection.Stub 可以跨進程調用。
也就是說,客戶端進程留下了一個"樁",等待別的進程調用。

重點2
AMS 收到客戶端的綁定指令后,發起綁定操作,通過IPC 調用服務端接口。
最終調用到服務端的onBind(xx)方法,該方法里返回服務端的IBinder引用。

重點3
服務端返回IBinder引用后,委托AMS 發布這個IBinder,IBinder找到對應的客戶端進程。而在重點1里客戶端已經留下了"樁",此時AMS 順勢找到這個"樁"直接調用ServiceConnection的onServiceConnected(xx),就能將IBinder傳遞給客戶端。

可能比較繞,我們從進程的角度再簡化一下:


image.png

可以看出,以上發生了四次IPC 操作(當然里面還涉及到其它的IPC,此處忽略)。IBinder傳遞要經過兩次IPC。

IBinder 傳遞

上面分析了通過綁定流程返回服務端的IBinder引用。
但是運行的過程中卻發現問題:
服務端返回的IBinder是:IStudentServer
而客戶端收到的IBinder是:BinderProxy
這個是怎么回事呢?
既然IBinder是通過進程間傳遞的,看看其是否是支持序列化。

    public interface IBinder {
        ...
    }

    public class Binder implements android.os.IBinder {
        ...
    }

發現它們都沒有實現Parcelable 接口。它是怎么支持序列化的呢?
那只能從Parcel本身分析了。
Parcel 除了支持

readInt()
writeInt()
...

等基本數據類型外,還支持

    public final IBinder readStrongBinder() {
        return nativeReadStrongBinder(mNativePtr);
    }

    public final void writeStrongBinder(IBinder val) {
        nativeWriteStrongBinder(mNativePtr, val);
    }

顧名思義,應該是專門讀寫IBinder的方法,也就是說雖然沒有實現Parcelable,但是Parcel 內置支持了IBinder。
接著繼續查看其native方法,看看有何奧妙之處。

static jobject android_os_Parcel_readStrongBinder(JNIEnv* env, jclass clazz, jlong nativePtr)
{
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    if (parcel != NULL) {
        return javaObjectForIBinder(env, parcel->readStrongBinder());
    }
    return NULL;
}

static void android_os_Parcel_writeStrongBinder(JNIEnv* env, jclass clazz, jlong nativePtr, jobject object)
{
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    if (parcel != NULL) {
        const status_t err = parcel->writeStrongBinder(ibinderForJavaObject(env, object));
        if (err != NO_ERROR) {
            signalExceptionForError(env, clazz, err);
        }
    }
}

注:方法在/frameworks/core/jni/android_os_Parcel.cpp

先分析寫入IBinder的情況:
parcel->writeStrongBinder(xx) 調用了Parcel.cpp里的writeStrongBinder(xx)進而調用flatten_binder(xx)函數

    status_t flatten_binder(const sp<ProcessState>& /*proc*/,
    const sp<IBinder>& binder, Parcel* out)
    {
        flat_binder_object obj;
        ...
        if (binder != NULL) {
            IBinder *local = binder->localBinder();
            if (!local) {
                //本地引用不存在
                BpBinder *proxy = binder->remoteBinder();
                if (proxy == NULL) {
                    ALOGE("null proxy");
                }
            const int32_t handle = proxy ? proxy->handle() : 0;
                //type 標記為非本地Binder
                obj.hdr.type = BINDER_TYPE_HANDLE;
                obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */
                obj.handle = handle;
                obj.cookie = 0;
            } else {
                //IBinder為本地的Binder引用,也就是和Server處在同一進程
                //type 標記為本地Binder
                obj.hdr.type = BINDER_TYPE_BINDER;
                obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());
                obj.cookie = reinterpret_cast<uintptr_t>(local);
            }
        } else {
            ...
        }

        return finish_flatten_binder(binder, obj, out);
    }

可以看出,根據傳入的IBinder是不是本地Binder然后打上type標記。
再來看看讀取IBinder的情況
parcel->readStrongBinder()里最終調用了:

    status_t unflatten_binder(const sp<ProcessState>& proc,
    const Parcel& in, sp<IBinder>* out)
    {
    const flat_binder_object* flat = in.readObject(false);

        if (flat) {
            //根據Type 標記判斷
            switch (flat->hdr.type) {
                case BINDER_TYPE_BINDER:
                    //本地引用
                *out = reinterpret_cast<IBinder*>(flat->cookie);
                    return finish_unflatten_binder(NULL, *flat, in);
                case BINDER_TYPE_HANDLE:
                    //非本地引用,獲取代理對象
                *out = proc->getStrongProxyForHandle(flat->handle);
                    return finish_unflatten_binder(
                            static_cast<BpBinder*>(out->get()), *flat, in);
            }
        }
        return BAD_TYPE;
    }

由此可見,如果是Server端的IBinder與Client端不在同一進程,則會轉換為Proxy對象,最終體現在Java層的就是BinderProxy類型。
注:函數在/frameworks/native/libs/binder/Parcel.cpp

綜上所述,IBinder跨進程傳遞時:

  • 如果客戶端、服務端同一進程,則服務端回傳的IBinder為當前引用
  • 如果客戶端、服務端處在不同進程,則服務端回傳的IBinder為BinderProxy

3、兩者區別與聯系

獲取系統服務
系統服務會往ServiceManager注冊,ServiceManager運行在單獨的進程里,客戶端進程需要先向ServiceManager里請求IBinder,再使用IBinder獲取關聯接口進而使用系統服務。
獲取自己定義的服務
服務端進程開啟后,暴露出IBinder。客戶端通過綁定服務端進程里的Service,將IBinder跨進程傳遞至客戶端,客戶端再使用IBinder獲取關聯接口進而使用自定義服務。此過程沒有借助于ServiceManager。

不論是哪種方式,核心都需要獲得IBinder,IBinder的獲取需要IPC。

至此,Android IPC 系列文章已經分析完畢

本文基于Android 10.0。

您若喜歡,請點贊、關注,您的鼓勵是我前進的動力

持續更新中,和我一起步步為營系統、深入學習Android

1、Android各種Context的前世今生
2、Android DecorView 必知必會
3、Window/WindowManager 不可不知之事
4、View Measure/Layout/Draw 真明白了
5、Android事件分發全套服務
6、Android invalidate/postInvalidate/requestLayout 徹底厘清
7、Android Window 如何確定大小/onMeasure()多次執行原因
8、Android事件驅動Handler-Message-Looper解析
9、Android 鍵盤一招搞定
10、Android 各種坐標徹底明了
11、Android Activity/Window/View 的background
12、Android Activity創建到View的顯示過
13、Android IPC 系列
14、Android 存儲系列
15、Java 并發系列不再疑惑
16、Java 線程池系列

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

推薦閱讀更多精彩內容