騰訊Hardcoder Android通信框架簡介

Hardcoder簡介

Hardcoder是騰訊開源的一套Android APP 與系統間的通訊解決方案,Hardcoder有效的解決了 APP 只能調用系統標準 API,沒法直接調用系統底層硬件資源的問題,讓 Android APP 和系統可以實現實時通訊。html

按照官方的說法,APP 能充分調度系統資源如 CPU 頻率,大小核,GPU 頻率等來提高 APP 性能,系統可以從 APP 側獲取更多信息以便更合理提供各項系統資源。同時,對於 Android 缺少標準接口實現的功能,APP 和系統也能夠經過該框架實現機型適配和功能拓展。java

之因此要研發這一框架,是由於微信在不少的低端機型上,一些經常使用的優化手段都使用到了極致,因此微信的開發者一直在思考,該如何突破這個優化的極限?直到有一次與廠商的交流時候瞭解到,部分廠商會針對微信作一些小改動,其中比較典型的就是【暴力提頻】。系統在識別到微信啓動,頁面切換等場景時,會粗暴地提升 CPU 頻率,從而提高 APP 運行的性能。git

但因爲廠商沒法準確判斷微信場景,暴力提頻效果並不理想;而若是過多地提升 CPU 頻率,又對手機的功耗有影響。所以,微信的開發者但願在手機硬件的層面上挖掘更多的性能優化空間,因而 Hardcoder 框架應運而生。github

Hardcoder是什麼

廠商暴力提頻效果不理想是因爲在目前 Android 框架下,手機沒有辦法準確獲知 APP 須要資源的時機。若是咱們須要挖掘手機硬件層面的性能優化,就須要跳過 Android 操做系統的應用框架,在應用開發者和硬件之間打開一個通道,讓硬件能夠直接根據應用開發者的須要進行資源的調度。api

Hardcoder 構建了 APP 與系統(ROM)之間可靠的通訊框架,突破了 APP 只能調用系統標準 API,沒法直接調用系統底層硬件資源的問題,讓 Android APP 和系統能實時通訊,其架構圖以下所示。數組

在這裏插入圖片描述
利用 Hardcoder,APP 能充分調度系統資源如 CPU 頻率,大小核,GPU 頻率等來提高 APP 性能,系統可以從 APP 側獲取更多信息以便更合理提供各項系統資源。同時,對於 Android 缺少標準接口實現的功能,APP 和系統間也能夠經過該框架實現機型適配和功能拓展。安全

Hardcoder 通訊流程

和不少的通信框架同樣,Hardcoder 框架也分爲 Server 端和 Client 端。其中 Server 端在廠商系統側實現,Client 端以 aar 形式合入到 APP中,其通信的流程圖以下所示。
在這裏插入圖片描述
當APP 須要額外資源的時候,向 Hardcoder 的 Client 端發出請求。Hardcoder Client 端接收到請求後向 Hardcoder Server 端發出請求。Server 端接受到請求後會根據請求參數向硬件申請不一樣的資源,好比調整 CPU 頻率,把線程綁定到大核運行等,實現了 APP 到系統的通訊。性能優化

同時系統也可把當前系統的狀態經過 Hardcoder Client 在 Server 端註冊的接口回調通知到 Client 端,從而 APP 能夠獲取到系統狀態,實現系統到 APP 的通訊。微信

Hardcoder Client 端與 Server 端採用的是 LocalSocket 的通訊方式,關於LocalSocket,讀者能夠看這一篇文章的介紹:Android LocalSocket簡介。因爲 Hardcoder 採用 Native 實現,於是在 C 層使用 Linux 的 socket 接口實現了一套 LocalSocket 機制做爲 Client 端與 Server 端之間的通訊方式。網絡

在這裏插入圖片描述
能夠發現,Hardcoder有以下一些優勢或者說特性。

  • 系統服務爲 optional,實現上能夠徹底支持或者部分支持;
  • 框架實現不依賴於特定 Android 系統,如 API level 限制;
  • APP 的功能和業務特性不依賴於該框架。

總而言之,因爲Hardcoder是騰訊主導的,因此咱們不用太擔憂兼容性問題,騰訊會和手機廠商進行洽談並提供解決方案,而且目前已經支持Hardcoder框架的手機廠商有OPPO、vivo、華爲、小米、三星、魅族等。

Hardcoder 性能優化技術方案

Hardcoder 優化基礎

Hardcoder 在Android系統側主要優化的方法有提升 CPU 頻率、提升 IO 頻率, CPU 鎖核以及提升 GPU 頻率。

提升 CPU 頻率

通常來講,移動設備爲了下降功耗,會不一樣程度地抑制 cpu 頻率,同時內核會根據當前的負載動態調整 cpu 頻率。這就致使 APP 在執行一些須要資源的操做的時候,不能最大限度利用到 cpu 資源,可能出現卡頓等狀況。

在Android系統中,經過修改Android內核配置,就能夠達到提升 cpu 頻率的目標。咱們可使用下面的命令來查看當前系統的 cpu 核數。

ls -l  /sys/devices/system/cpu

若是要查看 cpu 支持的頻率,可使用下面的命令。

cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies

上面的命令是查看某個核(cpu0)支持的頻率。主流的 cpu 都會有大小核的區分,因此並不能保證每一個核的支持頻率是同樣的。通常來講,大核支持更高的頻率。要修改某個核的頻率,須要有 root 權限。如下操做就是直接 echo 指定頻率到 cpu0 上,以達到提頻的效果。

echo 1440000  > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq

通常來講,爲了方便測試,咱們會禁用某些核,甚至只給系統留下一個核,以方便作些基準測試,下面的命令可禁用某個指定的內核。

echo 0 > /sys/devices/system/cpu/cpu0/online

同理,咱們能夠把 0 改爲 1,便可啓用對應的內核。

提升 IO 頻率

就 Hardcoder 項目與部分廠商的溝經過程中,發現廠商並不把 IO 提頻看做重點,認爲 IO 提頻自己效果有限,且必須同時提升 cpu 頻率效果纔會明顯,而部分廠商好比 vivo 則把 IO 頻率長期鎖定爲高頻狀態。

cat /sys/class/mmc_host/mmc0/clk_scaling/enable

若是值爲1 , 則 emmc 運行在 50MHz 和 200MHz 之間變化; 若是值爲0 , 則 emmc 運行在 200MHz 上,也就是高頻上了,如使用下面的提頻命令便可。

echo 0 > /sys/class/mmc_host/mmc0/clk_scaling/enable

CPU 鎖核

鎖核操做實際上操做的是 cpu 親和度,設置某個線程的 cpu 親和度,便可變相把線程鎖定在某個大核上運行。實際操做中,線程可固定在某個核上運行,但不獨佔,也就是這個核一樣能夠執行其餘線程的指令。這樣就減小了內核切換帶來的性能開銷。

提升 GPU 頻率

GPU主要用於渲染圖像,部分廠商提供了調節 GPU 芯片參數的能力,提升 GPU 頻率,從而更好地支持對 GPU 需求較高的場景。

Hardcoder 通信方式 —— LocalSocket

Hardcoder 採用 LocalSocket 實現手機本地雙方 Native 層實時通訊機制,關於LocalSocket能夠自行查閱相關的資料。Android中的LocalSocket實際上是Linux中Socket進行了封裝,採用JNI方式調用,進而實現進程間通訊。與普通的Socket不一樣,LocalSocket實現的是Android和Framework直接的通訊。

LocalSocket 通訊框架

目前在 Android 上進程通訊的 IPC 機制主要有 Binder(Java INTENT/AIDL)、共享內存、Socket(TCP/UDP)等,同時在 Java 層 Android 提供了一套 LocalSocket 的API。LocalSocket 的本質是基於對 Linux 層 Socket 的封裝,用來進行進程間的通訊。LocalSocket 根據是否命名分爲兩種類型:非命名 LocalSocket 只能在父進程和子進程之間通訊(由於其餘進程沒法得到 Server 端 Socket 名,只有保存了未命名 Socket 的文件描述符的父子進程之間可使用);命名的 LocalSocket 則能夠在任意進程間進行通訊。

Android 中的 LocalSocket 在 Unix 域名空間建立一個 Socket 進行通訊。Unix 域名空間 Socket 是在 Socket 基礎上衍生的 IPC 通訊機制,所以 LocalSocket 是爲了解決同一臺主機上不一樣進程間的通訊問題,其對應接口和 TCP 等跨網絡 Socket 方式一致,可是無需實現複雜的 TCP/IP 協議棧,不須要打包拆包、計算校驗,只需通訊雙方協商好套接字地址(Android 上即爲文件描述符)便可。於是 LocketSocket 在 Android 系統中做爲 IPC 通訊手段被普遍使用,如常見的Binder、Ashmem和LocalSocket等。

IPC通訊機制 安全性 傳輸效率 實現及推廣難易度
Binder 支持UID鑑權安全性高 單次拷貝 缺乏C層公開API,實現難度大
Ashmem 需實現鑑權 無需拷貝,效率最高 需額外實現同步,實現難度大
Socket(TCP/UDP) 需實現鑑權 兩次拷貝 技術成熟,系統側接入難度小
LocalSocket 需實現鑑權 兩次拷貝,面向本地通訊,無需協議棧 技術成熟,面向Android設計,系統側接入難度小

因爲 Hardcoder 採用 Native 實現,天然沒法直接使用 Android SDK 提供的 Java 類的 LocalSocket 和 LocalSocketServer API。固然在 Hardcoder 的設計預期中,使用者無需關心通訊細節,因此項目在 C 層使用 Linux 的 Socket 接口實現了一套類 Java 類的 LocalSocket 機制。

Hardcoder 子項目 libapp2sys 中 localsocket.h 定義了C++類 Localsocket 用於實現對通訊流程的接口封裝和邏輯控制,工做流程以下所示。
在這裏插入圖片描述

基類 Localsocket

基類 Localsocket 定義了做爲 Socket 雙方通訊的基本行爲接口,主要包括建立接口、循環處理收發數據和回調函數。

CreateSocket() 建立接口

int createSocket(const char *localPath, const char *remotePath)

其中,CreateSocket() 建立套接字,傳入參數爲 Server 端的 Socket 名。

對 Server 端,傳入的 Socket 名爲空,則默認建立 Server 端。先新建套接字 new Socket(),而後 bind() 綁定地址,再調用 listen() 監聽端口,一系列系統調用成功後,則進入 loop() 循環等待處理數據。

對 Client 端,傳入的 Socket 名有效,則建立 Client 端。先新建套接字 new Socket(),而後 connect() 嘗試經過 Socket 名鏈接 Server 端。鏈接成功後,Client 端建立子線程進入 loop() 循環等待處理數據。

Loop() 循環處理收發數據

int loop(const int isServer)

Client 端和 Server 端分別維護一個發送隊列 sendQueue,當發送隊列不爲空則發送數據,不然調用 select() 判斷是否有待處理的接收數據,若是有則調用 read() 讀取數據而後處理接收數據。對 Server 端該函數還會調用 accept() 處理 Client 端的鏈接請求。

RecvEvent()回調函數

int recvEvent(Event event, int fd, uid_t uid, const char *path, uint8_t *data, int len)

RecvEvent() 爲虛函數,數據接收處理完後回調上層進行對應的業務處理,具體實現由各自派生類自行完成。

客戶端 LocalsocketClient

客戶端 LocalsocketClient 繼承自 LocalSocket,是 Client 端實現的 LocalSocket 類,實如今 client.h 文件。除了包括基類的基礎函數外,主要包括 start() 方法建立鏈接。LocalSocketClient 對應實例 Client 由客戶端代理類負責建立,主要方法包括初始化和啓動。

初始化 init()

int init(const char *remote, const int port, const char *local, IC2JavaCallback *callback)

上層 JNI 入口調用初始化建立 Client 端,當前版本 Localsocket 實現會忽略 port 和 local 兩個參數(UDP 歷史實現遺留), remote 爲約定的 Server 端 Socket 名,callback 爲 APP 端 Java 層監聽 server 端回調函數。

啓動函數 tryStartEngine()

int tryStartEngine()

啓動函數 tryStartEngine() 建立本地的 LocalSocketClient 引擎實例 clientEngine,並調用 LocalSocketClient 類 start() 方法建立鏈接並負責斷開超時重連邏輯。

服務端 LocalsocketServer

服務端 LocalsocketServer 繼承自 LocalSocket,是 Server 端實現的 LocalSocket 類,實如今 server.h 文件,對應實例由服務端代理類負責建立。其建立和 start() 方法由實現了相應服務能力接口的系統級別進程負責調用。

Native 層實現的 LocalSocket 流程

對 Server 端,建立 Socket 後,調用 bind() 綁定地址和 listen() 監聽端口,進入 loop() 循環。首先經過 accept() 處理來自 Client 端的鏈接請求,經過 recvEvent() 的回調通知 Client 端是否鏈接成功;而後檢查發送隊列 sendQueue 是否有待發送數據,如有則調用 send() 發送數據;再調用 select() 查找是否有待處理的接收數據,有則調用 read() 讀取數據進行相應處理;而後從新進行 loop() 循環。

對 Client 端,建立 Socket 後,調用 connect() 經過 Socket 名嘗試鏈接 Server 端,從 recvEvent() 回調獲取是否鏈接成功。若鏈接成功,則進入 loop() 循環,與 Server 端相似循環 send()、select()、read() 過程。

在這裏插入圖片描述

Hardcoder 數據格式 —— proto + JSON

Hardcoder 使用 LocalSocket 機制完成 C/S 雙向實時通訊,client 端到 server 端傳輸數據簡稱爲請求(request),server 端到 client 端數據簡稱爲響應(response)。雙向數據包均由包頭和包體兩部分組成,考慮到爲本地通訊且已經過 UID 方式實現鑑權認證,當前版本全部數據均無加密。

請求數據格式

請求的數據格式以下所示。

--------------------------------------------------------
| AMCReqHeaderV2 | body(業務請求結構體序列化數據)         |
--------------------------------------------------------

AMCReqHeaderV2 定義在 libapp2sys 子項目的 header.h 中。

const static uint16_t HEADER_PROTOCAL_VERSION_2 = 16;
const static uint32_t HEADER_BEGIN = 0x48444352;

typedef struct AMCReqHeaderV2 {
    uint32_t begin;     // 包頭其起始字段
    uint16_t version;   // 協議版本
    uint16_t funcid;    // 請求對應的function ID
    uint32_t bodylen;   // 包體序列化數據長度
    int64_t    requestid;  // 當前請求包ID
    uint32_t callertid; // 上層JNI調用者所在線程ID
    int64_t timestamp;  // 當前請求時間戳
    uint32_t headerlen; // 包頭數據長度(Ver2新增)
    uint32_t bodyformat; //包體數據序列化格式枚舉值(Ver2新增)

}__attribute__ ((packed)) AMCReqHeaderV2;

其中:

  • begin 字段固定標識 Hardcoder 通訊。
  • version 字段支持協議版本擴展。
  • funcid 定義在 protocol.h,代表本次客戶端請求對應的系統操做,例如申請 CPU 提頻或者線程鎖核等。
const static uint32_t FUNC_BASE = 1000;

const static uint32_t FUNC_CHECK_PERMISSION = FUNC_BASE + 1;

const static uint32_t FUNC_CPU_HIGH_FREQ = FUNC_BASE + 2;
const static uint32_t FUNC_CANCEL_CPU_HIGH_FREQ = FUNC_BASE  + 3;

const static uint32_t FUNC_CPU_CORE_FOR_THREAD = FUNC_BASE + 4;
const static uint32_t FUNC_CANCEL_CPU_CORE_FOR_THREAD = FUNC_BASE + 5;

const static uint32_t FUNC_HIGH_IO_FREQ = FUNC_BASE + 6;
const static uint32_t FUNC_CANCEL_HIGH_IO_FREQ = FUNC_BASE + 7;

const static uint32_t FUNC_SET_SCREEN_RESOLUTION = FUNC_BASE + 8;
const static uint32_t FUNC_RESET_SCREEN_RESOLUTION = FUNC_BASE + 9;

const static uint32_t FUNC_REG_ANR_CALLBACK = FUNC_BASE + 10;

const static uint32_t FUNC_REG_PRELOAD_BOOT_RESOURCE = FUNC_BASE + 11;

const static uint32_t FUNC_TERMINATE_APP = FUNC_BASE + 12;

const static uint32_t FUNC_UNIFY_CPU_IO_THREAD_CORE = FUNC_BASE + 13;
const static uint32_t FUNC_CANCEL_UNIFY_CPU_IO_THREAD_CORE = FUNC_BASE + 14;

static const uint32_t FUNC_REG_SYSTEM_EVENT_CALLBACK = FUNC_BASE + 15;

static const uint32_t FUNC_GPU_HIGH_FREQ = FUNC_BASE + 16;
static const uint32_t FUNC_CANCEL_GPU_HIGH_FREQ = FUNC_BASE + 17;

static const uint32_t FUNC_CONFIGURE = FUNC_BASE + 18;
static const uint32_t FUNC_GET_PARAMETERS = FUNC_BASE + 19;

其中,requestid 字段從 0 開始遞增保持惟一性。headerlen 字段爲包頭長度。bodyformat 爲包體序列化格式,當前取值爲:1-raw(byte數組)、2-protobuf 序列化字節流 、3-JSON 序列化字節流。

請求 body 部分若是使用 google protobuf 協議格式實現(C 庫),請參見項目amc.proto 文件中請求結構體定義,例如申請 CPU 提頻的業務請求結構體:

message RequestCPUHighFreq{
    required int32 scene = 1;
    required int32 level = 2;
    required int32 timeoutMs = 3;
    required int64 action = 4;
}

編譯後會自動在 gen/cpp/amc.pb.h 文件中生成對應的 C++ 類,主要公開成員函數爲序列化/反序列化接口,以及數據屬性的 get/set 訪問接口。

class RequestCPUHighFreq : public ::google::protobuf::MessageLite {

    RequestCPUHighFreq* New() const;
    void CheckTypeAndMergeFrom(const ::google::protobuf::MessageLite& from);
    void CopyFrom(const RequestCPUHighFreq& from);
    void MergeFrom(const RequestCPUHighFreq& from);

    // required int32 scene = 1;
    inline bool has_scene() const;
    inline void clear_scene();
    static const int kSceneFieldNumber = 1;
    inline ::google::protobuf::int32 scene() const;
    inline void set_scene(::google::protobuf::int32 value);
    
    //...

}

調用 header.h 的靜態函數 genReqPack 便可以完成請求數據包的完整封包邏輯。

static int64_t genReqPack(uint32_t funcid, uint8_t *data, int dataLen, uint8_t **outPack, uint32_t *outLen, uint32_t callertid, int64_t timestamp)

若是採用 JSON 格式,則使用 key-value 方式,其中 key 統一爲字符串,當前版本已有屬性定義以下:

  • "funcid" 和包頭中的 funcid 保持一致,int32 格式。
  • "scene" 場景值,int32 格式,表示 APP 具體業務場景,和 Ver1 已有定義保持一致。
  • "status" 狀態值,int32 格式,表示該操做是一個請求/置位(1)仍是取消/復位(2)操做。
  • "timouts" 超時值,int32 格式,表示該操做任務最長時間,單位 ms。
  • "cpulevel" 請求 cpu level 值,分爲 Level 0~3,具體定義請參見 protocol.h。
  • "iolevel" 請求 io level 值,分爲 Level 0~3,具體定義請參見 protocol.h。
  • "gpulevel" 請求 gpu level 值,分爲 Level 0和 Level 1,具體定義請參見 protocol.h。
  • "bindtids" 須要綁核的線程,int32格式數組。
  • "unbindtids" 須要解綁的線程,int32格式數組。

響應數據格式

響應數據的格式以下:

-------------------------------------------------
| AMCRespHeaderV2 |     payload               |

AMCRespHeaderV2 一樣定義在 libapp2sys 子項目的 header.h 中。

typedef struct AMCRespHeaderV2 {
    uint32_t begin;            // 包頭其起始字段
    uint16_t version;        // 協議版本
    uint16_t funcid;        // 響應請求對應的function ID 
        uint32_t retCode;        // 請求處理結果
    uint32_t bodylen;        // 包體序列化數據長度
    int64_t requestid;    // 響應對應的請求包ID
    int64_t timestamp;    // 當前響應時間戳
    uint32_t headerlen; // 包頭數據長度(Ver2新增)
        uint32_t bodyformat;//包體數據序列化格式枚舉值(Ver2新增)
}__attribute__ ((packed)) AMCRespHeaderV2;

begin、version、bodylen、timestamp、headerlen 和 bodyformat 等字段含義與 AMCReqHeader2 中各字段同樣用於標識響應包自身屬性,而 funcid、requestid 則表示其對應處理的請求包屬性,便於請求端確認;若是請求包在解包或者校驗方面不經過,則對應的 retCode 會返回響應的全局錯誤碼(負值,定義在 protocal.h),不然返回 0 值或者是具體業務處理結果。

const static int32_t RET_OK = 0;

//requestCpuHighFreq,requestHighIOFreq 直接返回level n
const static int32_t RET_LEVEL_1 = 1;
const static int32_t RET_LEVEL_2 = 2;
const static int32_t RET_LEVEL_3 = 3;

//預留返回值最後三位做爲level,倒數第四位表明cpu level,倒數第五位表明io level,新增值繼續左移
const static int32_t RET_CPU_HIGH_FREQ = 1 << 3;// 1000,即8
const static int32_t RET_HIGH_IO_FREQ = 1 << 4; // 10000,即16


//requestUnifyCpuIOThreadCore使用複合標識位
const static int32_t RET_CPU_HIGH_FREQ_LEVEL_1 = RET_CPU_HIGH_FREQ | RET_LEVEL_1;   //Unify接口返回cpu level 1,1000 | 01 = 1001
const static int32_t RET_CPU_HIGH_FREQ_LEVEL_2 = RET_CPU_HIGH_FREQ | RET_LEVEL_2;   //Unify接口返回cpu level 2,1000 | 10 = 1010
const static int32_t RET_CPU_HIGH_FREQ_LEVEL_3 = RET_CPU_HIGH_FREQ | RET_LEVEL_3;   //Unify接口返回cpu level 3,1000 | 11 = 1011

const static int32_t RET_HIGH_IO_FREQ_LEVEL_1 = RET_HIGH_IO_FREQ | RET_LEVEL_1;     //Unify接口返回io level 1,10000 | 01 = 10001
const static int32_t RET_HIGH_IO_FREQ_LEVEL_2 = RET_HIGH_IO_FREQ | RET_LEVEL_2;     //Unify接口返回io level 2,10000 | 10 = 10010
const static int32_t RET_HIGH_IO_FREQ_LEVEL_3 = RET_HIGH_IO_FREQ | RET_LEVEL_3;     //Unify接口返回io level 3,10000 | 11 = 10011


const static int32_t ERR_UNAUTHORIZED = -10001;
const static int32_t ERR_FUNCTION_NOT_SUPPORT = -10002;
const static int32_t ERR_SERVICE_UNAVAILABLE = -10003;
const static int32_t ERR_FAILED_DEPENDENCY = -10004;
const static int32_t ERR_PACKAGE_DECODE_FAILED = -10005;
const static int32_t ERR_PARAMETERS_WRONG = -10006;
const static int32_t ERR_CLIENT_UPGRADE_REQUIRED = -10007;

const static int32_t ERR_CLIENT_DISCONNECT = -20001;
const static int32_t ERR_CLIENT_RESPONSE = -20002;

請求 body 部分若是使用 protobuf 定義,請參見項目amc.proto 文件中響應結構體定義;若是採用 JSON 格式,屬性定義與 AMCReqHeaderV2 保持一致。調用 header.h 的靜態函數 genRespPack 便可以完成響應數據包的完整封包邏輯。

static int64_t genRespPack(uint32_t funcid, uint32_t retCode, uint64_t requestid, uint8_t *data, int dataLen, uint8_t **outPack, uint32_t *outLen)

Native 層實現的 LocalSocket 鑑權方式

因爲 Native 層實現的 LocalSocket 通訊方案爲本地進程間通訊,於是只須要在 Server 端接收到 Client 端請求時,經過調用 getsockopt() 方法獲取到 Client 端的 UID,而後經過 UID 反查出 Client 端對應的 APP 信息,進而完成響應的鑑權處理。

Hardcoder 廠商接入指南

Hardcoder 項目工程中提供了 Server 端的實現例子,代碼主要參見 server.h ,server.cpp 以及 protocol.h。

設置系統屬性

APP 判斷手機是否支持 Hardcoder 會讀取 persist.sys.hardcoder.name 的 property,若不爲空則手機取 property 字段做爲 server 端 socket name 請求創建 socket 鏈接。廠商側需設置 persist.sys.hardcoder.name 屬性爲系統 server 側 socket name。

實現主要接口函數

protocol.h 的 HardCoder 類定義了全部 Hardcoder 接口爲虛函數,廠商側需繼承 HardCoder 類實現相關接口。代碼例子中 server.h 的 ManufacturerCoder 繼承了 HardCoder 實現了相關接口,例子中爲空函數,具體實現須要廠商側編寫。此部分接口可同時參照 Hardcoder 接入指南中接口說明。

int getUidByAddress(const char *ip, int port);

獲取 APP UID,每次在 socket 鏈接中收到 APP 請求都會檢查 APP UID。因爲 UID 有惟一性,可做爲鑑權用。

bool checkPermission(std::vector<std::string> manufactures, std::vector<std::string> certs, int funcid, int uid, int callertid, int64_t timestamp);

checkPermission方法的做用是否容許 APP 使用 Hardcoder,容許返回 true,不然返回 false。若容許全部 APP 使用,直接返回 true 便可。

若須要限制 APP 接入,應實現對應的 checkPermission,其中 manufactures 爲廠商名數組,certs 爲鑑權值數組,可提供鑑權值給容許使用的 APP 做爲參數傳入。注意若限制應用使用,需告知 APP 開發者如何申請權限接入。

int requestCpuHighFreq(int scene, int64_t action, int level, int timeoutms, int callertid, int64_t timestamp);

requestCpuHighFreq的主要做用是提升CPU頻率。其中,scene參數爲APP 場景值;action參數爲APP 場景值擴展,爲保留字段;level,請求的 CPU level,定義在 protocol.h;目前共分爲三個 level,LEVEL 0 爲不變,LEVEL 1 最高,默認爲 CPU 最高頻率,LEVEL 2 次之,LEVEL 3 最低,但仍比當前頻率會提升,LEVEL 2 和 LEVEL 3 具體頻率可由廠商自行決定。timeoutms,從 timestamp 開始請求 timeoutms 時間的資源;callertid,請求線程 id;timestamp,請求時間戳;若是要取消提頻請求,可使用下面的函數。

int cancelCpuHighFreq(int callertid, int64_t timestamp);

若是要請求綁定指定線程到cpu大核,可使用下面的函數。

int requestCpuCoreForThread(int scene, int64_t action, std::vector<int>bindtids, int timeoutms, int callertid, int64_t timestamp);

其中,bindtids,須要綁定到大核的線程 id 數組。參考實現,部分廠商會直接把請求線程所在進程的全部線程同時綁定到大核。

int cancelCpuCoreForThread(std::vector<int>bindtids, int callertid, int64_t timestamp);

上面的方法用於取消綁定線程請求。

int requestHighIOFreq(int scene, int64_t action, int level, int timeoutms, int callertid, int64_t timestamp);

requestHighIOFreq方法用於提升 IO 頻率請求。固然,咱們還可使用混合請求,同時請求提升 CPU 頻率,提升 IO 頻率,提升 GPU 頻率以及線程綁核。

int requestUnifyCpuIOThreadCoreGpu(int scene, int64_t action, int cpulevel, int iolevel, std::vector<int>bindtids, int gpulevel, int timeoutms, int callertid, int64_t timestamp);

取消的時候使用cancelUnifyCpuIOThreadCoreGpu方法。

int cancelUnifyCpuIOThreadCoreGpu(int cancelcpu, int cancelio, int cancelthread, std::vector<int>bindtids, int cancelgpu, int callertid, int64_t timestamp);

Hardcoder 接入指南

接入步驟

  1. 下載 Hardcoder 工程編譯 aar;
  2. 項目 build.gradle 引入 Hardcoder aar;
  3. 進程啓動時調用 initHardCoder 創建 socket
    鏈接(通常進程啓動時須要請求資源,於是推薦在進程啓動時調用)。每一個進程都是獨立的,都須要調用 initHardCoder 創建 socket 鏈接,創建鏈接後每一個進程維持一個 socket,進程退出時 socket 也會斷開;
  4. initHardCoder 回調成功後調用 checkPermission,傳入 APP 已申請的各個廠商鑑權值;
  5. 在須要請求資源的場景調用 startPerformance,傳入請求資源的參數。若場景位於進程啓動階段,好比 APP 啓動,須要在initHardCoder 的回調成功之後再調用 startPerformance,確保鏈接已成功創建,或者判斷HardCoderJNI 的 isConnect() 檢查 socket 是否已鏈接。
  6. 場景結束時主動調用 stopPerformance,傳入對應場景 startPerformance 時的返回值 hashCode做爲參數,中止本次請求。
  7. 測試性能,APP 可對打開/關閉 Hardcoder 的狀況作對比實驗,測試性能數據。

編譯 Hardcoder aar

Hardcoder項目給開發環境爲 AndroidStudio,採用 gradle 構建,其中 Native 部分代碼使用 CMAKE 編譯。編譯aar的步驟以下:

  1. 根目錄下運行命令行 gradlew assembleDebug 觸發子工程 libapp2sys 編譯,編譯成功後生成 Hardcoder aar, 輸出目錄爲hardcoder/libapp2sys/build/outputs/aar/libapp2sys-debug.aar;
  2. 若須要把 aar publish 到本地 maven 庫,以便自身項目使用,在根目錄下運行以下命令行./gradlew publishToMavenLocal,輸出 aar 目錄爲 User
    目錄/.m2/repository/com/tencent/mm/hardcoder/app2sys/。

固然,也可使用Android Studio自帶的Gradle工具進行構建,構建完成後能夠在outputs/aar目錄下看到生成的aar。

App 引入 Hardcoder aar

Hardcoder 工程以 aar 方式引入,把 hardcoder:app2sys 添加到主工程目錄下的 build.gradle 文件的依賴庫中。

dependencies {
    api('com.tencent.mm.hardcoder:app2sys:1.0.0')
}

其中 1.0.0 爲當前 Hardcoder 版本號,當前版本號在 Hardcoder 主工程目錄下 gradle.properties 文件。

HC_VERSION_NAME=1.0.0

Hardcoder 方法

HardCoderJNI.java 中提供了 App 須要調用的接口。帶 native 關鍵字的函數爲 jni 接口,爲 Hardcoder Client 端與 Server 端約定的全部通訊接口。通常不建議直接使用 JNI 接口,HardCoderJNI.java 提供了封裝的 JAVA 層接口,其中比較常見的方法有以下一些。

初始化 initHardCoder
InitHardCoder 負責創建 socket 鏈接。調用 initHardCoder 前,請確保已正確設置了 hcEnable (上層接口是否開啓 Hardcoder 功能開關,默認爲 true)和 hcDebug (Hardcoder 組件是否打印 debug log,默認爲 false)標誌位。全部與系統間通訊都須要依賴 socket 鏈接,因此調用請求前確保已調用 initHardCoder 接口 初始化了InitHardCoder,以下所示。

public static int initHardCoder(String remote, int port, String local, HCPerfManagerThread hcPerfManagerThread, HardCoderCallback.ConnectStatusCallback callback)

InitHardCoder中參數的含義以下。

  • remote 爲 Hardcoder server 端 socket name,默認調用 HardCoderJNI.readServerAddr() 讀取;
  • port 爲 Hardcoder server 端端口值,可傳入任意 int 值;
  • local 爲 Hardcoder client 端 socket name,主要用於標識 client 端,可傳入任意非空字符串;
  • hcPerfManagerThread 爲運行 HCPerfManager 的線程,可傳入 null,默認由 Hardcoder 新建一個線程運行;若使用 APP 已有線程池中線程,請傳入接口HardCoderJNI.HCPerfManagerThread 的實現類;
  • callback 爲 initHardCoder 回調,獲取鏈接創建成功/失敗回調,不須要回調時可傳入 null。建議在回調成功創建 socket 後再發起請求,不然可能存在請求時 socket 未成功鏈接致使請求失敗的狀況。
  • InitHardCoder 爲異步執行,返回值不表明鏈接是否創建成功,調用 initHardCoder 成功返回 0,若返回ERROR_CODE_NOT_ENABLE = -3,表明 setHcEnable 未設置爲 true(默認爲 true,上層可用來控制 Hardcoder 是否打開);

InitHardCoder 鏈接是否創建成功經過 HardCoderCallback.ConnectStatusCallback 返回值獲取到,ConnectStatusCallback接口以下所示。

public interface ConnectStatusCallback {
    void onConnectStatus(boolean isConnect);
}

鑑權 checkPermission
CheckPermission 接口傳入廠商名與鑑權值,用於廠商鑑權 APP 是否有權限使用並接入了 Hardcoder 功能。

CheckPermission 必須在 initHardCoder 創建 socket 成功後調用。此接口用於傳輸鑑權值給廠商,對須要驗證鑑權值的廠商(好比 OPPO),必須調用 checkPermission 把鑑權值傳入纔可以使用 Hardcoder;對沒有實現 checkPermission 接口的廠商,系統對 checkPermission 請求沒有 callback,於是後續請求調用不該依賴於 checkPermission 的回調,若廠商不返回 checkPermission callback也能夠調用請求。

public static long checkPermission(String[] manufactures, String[] certs, HardCoderCallback.FuncRetCallback callback)

對於checkPermission鑑權接口,參數的含義以下。

  • manufactures 爲字符串數組,傳入廠商名;
  • certs 爲字符串數組,傳入對應 manufactures 中的廠商的鑑權值;
  • callback 爲 checkPermission 回調,以下所示
public interface FuncRetCallback {
    void onFuncRet(final int callbackType, final long requestId, final int retCode,
                   final int funcId, final int dataType, final byte[] buffer);
}

其中,retCode 表明鑑權是否成功,retCode 爲 0 表示成功,不然失敗。

請求資源 startPerformance
StartPerformance 用於向系統申請提升 cpu 頻率、io 頻率、gpu 頻率、線程綁核等操做,分別對應 JNI 接口requestCpuHighFreq、requestGpuHighFreq、requestCpuCoreForThread、requestHighIOFreq 以及混合接口 requestUnifyCpuIOThreadCoreGpu,爲方便調用,只須要調用統一接口 startPerformance 便可申請全部資源,對不一樣資源傳入不一樣參數便可。

StartPerformance 在須要申請資源處調用,返回值爲請求的 hashcode,上層可保存返回值,在調用stopPerformance 時傳入,指定中止這次請求的資源。

public static int startPerformance(final int delay, final int cpuLevel, final int ioLevel, final int gpuLevel, final int[] tids, final int timeout, final int scene, final long action, final int callerTid, final String tag);

delay 單位 ms,當前請求延遲多久調用,通常爲 0。當連續調用 initHardcoder 和 startPerformance 時,若Hardcoder 此時未鏈接成功,請求會被丟棄,此時可使用 delay 參數推遲調用。

cpuLevel 本次請求的 cpu 等級,分爲:

public static final int CPU_LEVEL_0 = 0;//不變
public static final int CPU_LEVEL_1 = 1;//最高頻,通常用於重度場景,會把cpu頻率提到廠商設置的最高值,適用於能明確知道結束時間的場景(好比啓動,進入某個界面,結束後會主動調用stopPerformance)
public static final int CPU_LEVEL_2 = 2;//次高頻,通常適用於不知道明確結束時間的場景,好比列表滑動等,不調用stopPerformance,經過手動設置timeout值設置結束時間
public static final int CPU_LEVEL_3 = 3;//也會比正常運行頻率要高

ioLevel 本次請求的 io 等級,分爲:

public static final int IO_LEVEL_0 = 0;//不變
public static final int IO_LEVEL_1 = 1;//最高級
public static final int IO_LEVEL_2 = 2;
public static final int IO_LEVEL_3 = 3;

gpuLevel 本次請求的 gpu 等級,分爲:

public static final int GPU_LEVEL_0 = 0;//不變
public static final int GPU_LEVEL_1 = 1;//最高級

參數的含義以下:

  • tids 須要綁定到大核上的線程,爲 int[] 數組,可傳入單個或多個線程線程號。部分廠商會直接把線程對應的進程的全部線程同時綁定到大核;
  • timeout 單位 ms,本次請求超時值。若請求未主動調用 stopPerformance,超過 timeout 時間後系統會主動釋放資源;若請求超過 timeout 時間仍未被執行(請求過多狀況下有可能出現),請求會被移出請求隊列;
  • scene int 值,APP 側定義的場景值;
  • action long 值,APP 側定義的動做值,擴展字段,APP 側若是隻須要使用到 scene 值能夠任意傳入 action 值;
  • callerTid 本次請求方的線程號;
  • tag String 類型,APP 側定義的 tag,主要用於日誌。

結束請求 stopPerformance
場景結束時調用 stopPerformance 方法釋放本次請求資源的調用,以下所示。

public static int stopPerformance(int hashCode);

hashCode 對應 startPerformance 的返回值,聲明結束本次請求的資源;返回值表示這次調用是否成功,有ERROR_CODE_SUCCESS 和 ERROR_CODE_FAILED。

參考:騰訊Hardcoder

相關文章
相關標籤/搜索