Android Binder機制詳解:手寫IPC通信

来源:http://www.cnblogs.com/DoNetCoder/archive/2017/08/03/7280481.html
-Advertisement-
Play Games

想要掌握一樣東西,最好的方式就是閱讀理解它的源碼。想要掌握Android Binder,最好的方式就是寫一個AIDL文件,然後查看其生成的代碼。本文的思路也是來自於此。 ...


想要掌握一樣東西,最好的方式就是閱讀理解它的源碼。想要掌握Android Binder,最好的方式就是寫一個AIDL文件,然後查看其生成的代碼。本文的思路也是來自於此。

簡介

Binder是Android常用的一種進程間通信方式。當然,不使用Binder,你還可以使用Socket甚至文件來進行通信。

通常Android上的進程間通信,指的就是遠程Service的調用。

開始

新建測試工程

打開Android Studio新建IPCClient和IPCServer兩個app工程。

假設我們要做這樣一件事情:

  1. Client向Server發起一個請求:請告訴我1+2等於多少

  2. Server將答案返回給Client

創建遠程Service

IPCServer新建ManualCalculatorService作為遠程Service。

遠程Server需要重寫onBind。

public class ManualCalculatorService extends Service
{
    @Nullable
    @Override
    public IBinder onBind(Intent intent)
    {
        return new Binder()
        {
            @Override
            protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException
            {
                return super.onTransact(code, data, reply, flags);
            }
        };
    }
}

然後在AndroidManifest中註冊這個Service。

<service android:name=".ManualCalculatorService"
         android:exported="true"
         android:process=":manualremote"/>

android:exported="true"表示這個Service對外是暴露的。

android:process=":manualremote"表示這個Service的運行進程的名稱

一個Service要作為遠程Service被其他Client調用,上面兩個缺一不可。

創建Client

Client調用bindService即可和遠程Service建立聯繫。

Intent intent = new Intent();
intent.setComponent(new ComponentName("cn.zmy.ipcserver", "cn.zmy.ipcserver.ManualCalculatorService"));
bindService(intent, new ServiceConnection()
{
    @Override
    public void onServiceConnected(ComponentName name, IBinder service)
    {
    
    }

    @Override
    public void onServiceDisconnected(ComponentName name)
    {

    }
}, Context.BIND_AUTO_CREATE);

至此,兩個項目大體代碼結構已經完成。

Client調用Server

Client可以通過onServiceConnected中的IBinder類型的service參數來調用遠程Service。

Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();

data.writeInt(1);
data.writeInt(2);
try
{
    service.transact(1000, data, reply, 0);
}
catch (RemoteException e)
{
    e.printStackTrace();
}

int result = reply.readInt();
data.recycle();
reply.recycle();
Toast.makeText(MainActivity.this, "" + result, Toast.LENGTH_SHORT).show();

代碼很簡單,最關鍵的是這一句:

service.transact(1000, data, reply, 0);

第一個參數,1000。這是我隨便寫的個數字,你可以寫2000,3000都沒得問題。(實際項目中通常使用常量定義,這裡主要為了方便演示)

第二個參數,data。表示我想要傳遞給Server的數據。

第三個參數,reply。Server會把結果寫入這個參數。

第四個參數,0。這個參數只有兩個可選值:0和IBinder.FLAG_ONEWAY

0表示這是一個雙向的IPC調用,也就是Client向Server發起請求後,Server也會答覆Client。
IBinder.FLAG_ONEWA表示這是一個單向IPC調用,也就是Client向Server發起請求後,會直接返回,不接受Server的答覆。

Server處理Client請求

Client通過transact請求Server之後,Server可以在onTransact接收到Client的請求。

@Nullable
@Override
public IBinder onBind(Intent intent)
{
    return new Binder()
    {
        @Override
        protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException
        {
            switch (code)
            {
                case 1000:
                {
                    int num1 = data.readInt();
                    int num2 = data.readInt();
                    reply.writeInt(num1 + num2);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
    };
}

data中讀出數據,然後將結果寫入reply中。整個過程就這樣。

運行

先後安裝Server和Client程式,Client中就可以看到結果。

Demo

項目代碼:

https://github.com/a3349384/IPCDemo

原理分析

所謂原理分析就是追本溯源,接下來我們看一下Client的請求是如何一步步到達Server的

## IBinder

回到Client調用Server的代碼:

bindService(intent, new ServiceConnection(){
      @Override
      public void onServiceConnected(ComponentName name, IBinder service)
      {
            ...
      }

      @Override
      public void onServiceDisconnected(ComponentName name)
      {

      }
}, Context.BIND_AUTO_CREATE);

關鍵在於這個IBinder,Client是通過IBinder.transact()將請求發給Server的。

這裡的IBinder實際上是個BinderProxy對象。(我怎麼知道的?打斷點,打日誌啊。。。)

BinderProxy處於{framework}/core/java/android/os/Binder.java中。

final class BinderProxy implements IBinder {
    private long mObject;
    
    public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        ...
        return transactNative(code, data, reply, flags);
    }
    
    public native boolean transactNative(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException;
    ...
}

Client調用BindProxy類的transact方法,實際邏輯還是交給transactNative方法處理的。

接下來找到transactNative的代碼。

代碼在{framework}/core/jni/android_util_Binder.cpp中

static const JNINativeMethod gBinderProxyMethods[] = {
    ...
    {"transactNative",      "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z", (void*)android_os_BinderProxy_transact}
    ...
};

可以看的transactNative是動態註冊的。找到android_os_BinderProxy_transact方法,看看它的代碼。

JNI方法註冊分為靜態註冊和動態註冊,感興趣的朋友可以自行搜索瞭解。

static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
        jint code, jobject dataObj, jobject replyObj, jint flags)
{
    IBinder* target = (IBinder*)
        env->GetLongField(obj, gBinderProxyOffsets.mObject);
 
    status_t err = target->transact(code, *data, reply, flags);

    if (err == NO_ERROR) {
        return JNI_TRUE;
    } else if (err == UNKNOWN_TRANSACTION) {
        return JNI_FALSE;
    }
}

可以看到,裡面又調用了target的transact方法,將請求發送出去。

target是通過反射獲取BinderProxy類的mObject對象得到的。

final class BinderProxy implements IBinder {    
    private long mObject;
}

long是怎麼被強轉為IBinder的?

實際上這裡的long mObject保存的是IBinder的指針。指針的大小和long的大小都是一樣的,都是4個位元組。

而名為target的這個IBinder實際上就是Server中onBind返回的這個Binder:

public class ManualCalculatorService extends Service
{
    @Nullable
    @Override
    public IBinder onBind(Intent intent)
    {
        return new Binder()
        {
            ...
        };
    }
}

到這裡,我們就差不多明白了。BinderProxy之所以叫BinderProxy,它代理的就是Server中onBind返回的Binder。

而Client經過一層層的調用,最終調用了Server中返回的Binder對象的transact方法。
我們看一下這個方法:

public final boolean transact(int code, Parcel data, Parcel reply,
        int flags) throws RemoteException {
    ...
    boolean r = onTransact(code, data, reply, flags);
    ...
    return r;
}

這個方法實際上調用了onTransact方法進行具體的邏輯處理。這也是為什麼我們可以在onTransact中處理Client請求的原因。

結尾

關於target是怎麼來的?

target是通過反射獲取BinderProxy類的mObject對象得到的。

mObject保存了server中IBinder的指針。

那麼這個指針又是哪裡來的?

這裡不得不提到另外一個類:ServiceManager

該類在{framework}/core/java/android/os/ServiceManager.java中

感興趣的朋友可以閱讀它的代碼。

這裡簡單說一下:ServiceManager通過map保存了Service和IBinder的關係。也就是通過Service的名稱就可以獲取到這個Service的IBinder。

參考鏈接:https://www.zhoumingyao.cn/Android%E7%AC%94%E8%AE%B0/Android-Binder%E6%9C%BA%E5%88%B6%E8%AF%A6%E8%A7%A3%EF%BC%9A%E6%89%8B%E5%86%99IPC%E9%80%9A%E4%BF%A1/


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這三個事件只在手機上生效 touchstart,手指開始觸屏 touchmove,手指移動 touchend,手指觸屏結束 這個事件在手機上跟在pc端都生效 scroll事件 addeventlistener(name,callback,optional,useCapture) useCapture ...
  • 所有方法基於這個數組: var arr=[1,3,4,5,6,7,8]; 1.length屬性: arr.length;//7 動態獲取數組長度. 2.shift: arr.shift();//1 刪除數組中第一個元素,返回刪除的那個值,並將長度減1;如:console.log(arr.shift( ...
  • 介紹一下 impress.js是一個非常炫酷的幻燈片展示框架,依靠CSS3技術。 impress.js使用起來非常簡單,下麵就來簡單介紹一下其用法。 Start 首先,當然要引入impress.js。 在div標簽設置id為impress(不要求一定是div),然後在你想進行展示的地方加上calss ...
  • 我們開發軟體中應用各種模式,主要是為了 1. 職責劃分:一個類只做一件事 2. 易用,可維護,方便擴展 3. 解耦,相互獨立,可單獨測試 各種設計模式其實都是在解決上面的問題,讓我們對比看看吧。 一、如何理解MVC設計模式 在通常的定義中,MVC 是下圖的結構 但是在 cocoa 體系中,蘋果建議的 ...
  • TextView實現圖文混合編排 一、簡介 在這裡實現圖文混合編排使用的是:TextView中預定義的類似Html的標簽 二、方法 * 1、設置好html標簽的文本 String html="<font>圖片1</font><img src='image1'/>"; html+="<font>圖片2 ...
  • TextView兩種顯示link的方法 一、簡介 也是TextView顯示文本控制項兩種方法 也是顯示豐富的文本 二、方法 TextView兩種顯示link的方法 1)通過TextView裡面的類html標簽 * 1、設置好html標簽的文本 String text1="<font color='re ...
  • View繪製的三部曲,測量,佈局,繪畫現在我們分析佈局部分測量部分在上篇文章中已經分析過了。不瞭解的可以去我的博客里找一下View的佈局和測量一樣,都是從ViewRootImpl中發起,ViewRootImpl先通過measure來初始化整個的view樹之後會調用onLayout方法來佈局,View ...
  • 1.UIButton UIButton的類是一個UIControl子類,它實現了在觸摸屏上的按鈕。觸摸一個按鈕攔截事件和動作消息發送到目標對象時,它的挖掘。設定的目標和行動方法都繼承自UIControl。這個類提供了方法來設置標題,圖像,按鈕等外觀屬性。通過使用set方法,你可以指定一個不同的外觀為 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...