cover_image

Hardcoder框架的集成与调试

MountainHigh 酷派技术团队
2022年09月23日 07:47

「Hardcoder是什么」

Hardcoder 是一套 Android APP 与系统间的通信解决方案,突破了 APP 只能调用系统标准 API,无法直接调用系统底层硬件资源的问题,让 Android APP 和系统能实时通信。APP 能充分调度系统资源如 CPU 频率,大小核,GPU 频率等来提升 APP 性能,系统能够从 APP 侧获取更多信息以便更合理提供各项系统资源。同时,对于 Android 缺乏标准接口实现的功能,APP 和系统也可以通过该框架实现机型适配和功能拓展。

图片

Hardcoder 在微信的启动、发送视频、小程序启动等重度场景平均优化效果达 10%-30%;在手机 QQ 的启动、打开聊天界面、发送图片等场景的平均优化效果达 10%-50%。

简单地说,Hardcoder就是能让APP直接调度底层资源的一个框架,它能让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的Server端集成到Android系统中,并进行简单的调试。


「集成前测试」

在将HardCoder源码集成到系统之前,我们可以先用手动的方式对整个框架进行简单的测试。
  • Android Studio 源码编译

Hardcoder Github仓库地址:https://github.com/Tencent/Hardcoder.git

我们clone源码并通过AS编译后,可以得到如下几个关键的模块:

-测试apk

Path:  Hardcoder_master\testapp\build\outputs\apk\debug\testapp-debug.apk

-Server端bin

Path:  Hardcoder_master\libapp2sys\build\intermediates\cmake\debug\obj\arm64-v8a\server

-依赖so库

Path:  Hardcoder_master\libapp2sys\build\intermediates\cmake\debug\obj\arm64-v8a\libc++_shared.so

  • 测试前置条件

APP 判断手机是否支持 Hardcoder 会读取 persist.sys.hardcoder.name 的 property,若不为空则手机取 property 字段作为 server 端 socket name 请求建立 socket 连接。

所以为了让系统支持Hardcoder,我们需要手动设置persist.sys.hardcoder.name属性,并将属性值设置为socket name,这个会在之后的socket连接中用到。

这里暂定属性值为"coolsocket",我们通过setprop手动设置属性值:

  adb shell setprop persist.sys.hardcoder.name coolsocket

设置后注意通过getprop看是否设置成功。

  • Hardcoder初始化

InitHardCoder 

主要验证 Hardcoder 与系统间通信连接是否正常,每次启动应用后都需要点击 initHardCoder 建立 socket 连接再进行请求,否则 Hardcoder 不生效。 

验证测试机是否支持 Hardcoder 方法:点击 initHardCoder 按钮,弹出 Toast 中若 server socket name 显示不为空,则此测试机支持 Hardcoder,若为空,则此测试机未支持 Hardcoder。

我们首先安装testapp-debug.apk,点击“initHardCoder” Button,抓取日志如下:

  09-28 11:42:23.937 13995 18151 I Hardcoder.HardCoderJNI: readServerAddr, serverprop[persist.sys.hardcoder.name] result[coolsocket]  09-28 11:42:23.937 13995 18151 D HARDCODER: [com_tencent_mm_hardcoder_HardCoderJNI.cpp,Java_com_tencent_mm_hardcoder_HardCoderJNI_initHardCoder:146]"initHardCoder, start."  09-28 11:42:23.937 13995 18151 D HARDCODER: [client.h,init:476]"init m_remote:coolsocket, HEADER_VERSION:4"  09-28 11:42:23.937 13995 18151 D HARDCODER: [client.h,tryStartEngine:370]"tryStartEngine, TimeDiff:592504, remote:coolsocket, running:-1"  09-28 11:42:23.938 13995 18151 W HARDCODER: [localsocket.h,uninit:215]"uninit, pipe:[0,0], m_fd:0, queue:0, flag:1"  09-28 11:42:23.938 13995 18151 D HARDCODER: [localsocket.h,createSocket:249]"createSocket, localPath: .hardcoder.client.sock, remotePath: coolsocket"  09-28 11:42:23.938 13995 18151 W HARDCODER: [localsocket.h,createSocket:310]"createSocket, connect socket, ret:-1, local:.hardcoder.client.sock, remote:coolsocket"  09-28 11:42:23.938 13995 18151 E HARDCODER: [localsocket.h,createSocket:333]"createSocket, connect failed ret:-1 fd:93 path:coolsocket"  09-28 11:42:23.938 13995 18151 W HARDCODER: [localsocket.h,uninit:215]"uninit, pipe:[87,88], m_fd:0, queue:0, flag:0"  09-28 11:42:23.938 13995 18151 D HARDCODER: [com_tencent_mm_hardcoder_HardCoderJNI.cpp,Java_com_tencent_mm_hardcoder_HardCoderJNI_initHardCoder:151]"initHardCoder, end."  09-28 11:42:23.939 13995 18151 I Hardcoder.TestAPIs: initHardCoder, server socket name:coolsocket  09-28 11:42:23.950  586 1344 I BufferQueueProducer: [com.tencent.mm.hardcoder.testapp/com.tencent.mm.hardcoder.testapp.TestAPIs#0](this:0xb40000724d44dc58,id:220,api:1,p:13995,c:586) queueBuffer: fps=21.13 dur=1324.90 max=882.75 min=10.10

这里connect socket的时候返回值为-1,连接失败,显然是Server端未启动,socket建立连接失败。

  • Server端服务启动

这里会用到之前AS编译生成的server和libc++_shared.so

1.将server bin push到system/bin目录下并添加执行权限

2.将libc++_shared.so 依赖库push到system/lib64目录下并添加权限

3.最后手动运行server服务,记得加上socketname作为参数(这里是coolsocket)

 adb shell server coolsocket


我们再次点击“initHardCoder” Button,抓取日志如下:

  09-28 17:57:59.511   582  1814 I BufferQueueProducer: [com.tencent.mm.hardcoder.testapp/com.tencent.mm.hardcoder.testapp.QuickStart#0](this:0xb400007785b18258,id:64,api:1,p:25978,c:582) queueBuffer: f  ps=2.16 dur=8332.40 max=8059.14 min=10.83  09-28 17:57:59.531   582   582 I SurfaceFlinger: operator()(), mtkRenderCntDebug 757, screenshot (com.tencent.mm.hardcoder.testapp/com.tencent.mm.hardcoder.testapp.QuickStart#0)  09-28 17:57:59.550 25978 26043 I Hardcoder.HardCoderJNI: readServerAddr, serverprop[persist.sys.hardcoder.name] result[coolsocket]  09-28 17:57:59.551 25978 26044 I Hardcoder.HCPerfStatThread: HCPerfStatThread start to run.  09-28 17:57:59.552 25978 26043 I Hardcoder.HCPerfManager: HCPerfManager new thread[Thread[HCPerfManager,5,main]]  09-28 17:57:59.552 25978 26045 I Hardcoder.HCPerfManager: HCPerfManager thread run start, id:439, name:HCPerfManager  09-28 17:57:59.552 25978 26043 D HARDCODER: [com_tencent_mm_hardcoder_HardCoderJNI.cpp,Java_com_tencent_mm_hardcoder_HardCoderJNI_initHardCoder:146]"initHardCoder, start."  09-28 17:57:59.553 25978 26043 D HARDCODER: [client.h,init:476]"init m_remote:coolsocket, HEADER_VERSION:4"  09-28 17:57:59.553 25978 26043 D HARDCODER: [client.h,tryStartEngine:370]"tryStartEngine, TimeDiff:735507073, remote:coolsocket, running:-1"  09-28 17:57:59.553 25978 26043 W HARDCODER: [localsocket.h,uninit:215]"uninit, pipe:[0,0], m_fd:0, queue:0, flag:1"  09-28 17:57:59.553 25978 26043 D HARDCODER: [localsocket.h,createSocket:249]"createSocket, localPath: .hardcoder.client.sock, remotePath: coolsocket"  09-28 17:57:59.553 25978 26043 W HARDCODER: [localsocket.h,createSocket:310]"createSocket, connect socket, ret:0, local:.hardcoder.client.sock, remote:coolsocket"  09-28 17:57:59.553 25978 26043 W HARDCODER: [localsocket.h,createSocket:353]"create ClientProtocal socket:103 local:.hardcoder.client.sock remote:coolsocket"  09-28 17:57:59.554  7384  7384 D HardCoderServer: [localsocket.h,socketAccept:91]"socketAccept getsockopt pid:25978, uid:10151, gid:10151, len:12"  09-28 17:57:59.554  7384  7384 W HardCoderServer: [localsocket.h,socketAccept:94]"socketAccept fd:7, acceptFd:8, uid:10151, path:?"  09-28 17:57:59.554  7384  7384 W HardCoderServer: [server.h,recvEvecom.android.documentsuint:49]"Connect By uid:10151  fd:8"  09-28 17:57:59.554 25978 26043 D HARDCODER: [com_tencent_mm_hardcoder_HardCoderJNI.cpp,Java_com_tencent_mm_hardcoder_HardCoderJNI_initHardCoder:151]"initHardCoder, end."  09-28 17:57:59.554  7384  7384 D HardCoderServer: [localsocket.h,loop:193]"loop, select success, socketAccept, acceptfd:8, maxfd:8"  09-28 17:57:59.554 25978 26047 D HARDCODER: [localsocket.h,loop:126]"loop, m_fd:103, maxfd:103, isServer:0"  09-28 17:57:59.554 25978 26043 I Hardcoder.QuickStart: initHardCoder, server socket name:coolsocket  09-28 17:57:59.555 25978 26045 D Hardcoder.HardCoderReporter: process:[44,15]  09-28 17:57:59.555 25978 26046 I Hardcoder.HardCoderJNI: onData callbackType:1, requestId:0, timestamp:0, retCode:-20000, funcId:0, dataType:0  09-28 17:57:59.555 25978 26046 I Hardcoder.QuickStart: initHardCoder callback, isConnectSuccess:true

这里connect socket的时候返回值为0,并且日志打印initHardCoder callback, isConnectSuccess:true,表示Hardcoder初始化成功,APP之后就可以和Server端成功通信了。

  • JNI接口测试

APP统一通过StartPerformance接口来进行JNI接口的调用。
StartPerformance 用于向系统申请提高 cpu 频率、io 频率、gpu 频率、线程绑核等操作,分别对应 JNI 接口requestCpuHighFreq、requestGpuHighFreq、requestCpuCoreForThread、requestHighIOFreq 以及混合接口 requestUnifyCpuIOThreadCoreGpu,为方便调用,只需要调用统一接口 startPerformance 即可申请所有资源,对不同资源传入不同参数即可。

这里以requestCpuHighFreq接口为例:

int requestCpuHighFreq(int scene, int64_t action, int level, int timeoutms, int callertid, int64_t timestamp) {  pdbg("ManufacturerCoder requestCpuHighFreq scene:%d action:%d level:%d timeoutms:%d callertid:%d timestamp:%d", scene, TOINT(action), level, timeoutms, callertid, TOINT(timestamp/1000000L));  return RET_OK;}

在成功对Hardcoder初始化后,我们在测试apk中点击“requestCpuHighFreq” Button,抓取日志如下:

  09-28 18:05:28.779   582  1814 I BufferQueueProducer: [com.tencent.mm.hardcoder.testapp/com.tencent.mm.hardcoder.testapp.TestAPIs#0](this:0xb4000078072dcc58,id:68,api:1,p:25978,c:582) queueBuffer: fps  =9.37 dur=5867.87 max=4994.89 min=11.54  09-28 18:05:28.801 25978 26936 D HARDCODER: [header.h,genReqPack:46]"getReqPack funcid:1002, dataLen:9 callertid:26936 timestamp:9888930"  09-28 18:05:28.801 25978 26936 D HARDCODER: [header.h,genReqPack:111]"genReqPack pb header len:32 requestid:1632823528801268"  09-28 18:05:28.801 25978 26936 D HARDCODER: [protocol.h,requestCpuHighFreq:654]"requestCpuHighFreq requestid:1632823528801268, scene:101, action:1, level:1, timeoutms:10000, tid:26936, timestamp:9888930"  09-28 18:05:28.801 25978 26936 D HARDCODER: [com_tencent_mm_hardcoder_HardCoderJNI.cpp,Java_com_tencent_mm_hardcoder_HardCoderJNI_requestCpuHighFreq:202]"requestCpuHighFreq, requestId:1632823528801268, scene:101, action:1, level:1, timeoutms:10000, tid:26936, timestamp:" PRId64  09-28 18:05:28.801 25978 26047 D HARDCODER: [localsocket.h,loop:185]"loop, select success, read pipe ret:4"  09-28 18:05:28.801 25978 26047 D HARDCODER: [localsocket.h,checkCanWrite:65]"checkCanWrite FD_ISSET(fd, &fdset):1, fd:103"  09-28 18:05:28.801 25978 26047 D HARDCODER: [localsocket.h,loop:158]"loop, send sendPack finish, ret:41, fd:103, len:41, offset:41, queue:0, costtime:0 "  09-28 18:05:28.802  7384  7384 D HardCoderServer: [localsocket.h,loop:203]"loop, select success, recv fd:8, ret:41"  09-28 18:05:28.802  7384  7384 D HardCoderServer: [server.cpp,requestCpuHighFreq:32]"ManufacturerCoder requestCpuHighFreq scene:101 action:1 level:1 timeoutms:10000 callertid:26936 timestamp:9888930"  09-28 18:05:28.802  7384  7384 D HardCoderServer: [protocol.h,processReceive:402]"FUNC_CPU_HIGH_FREQ parse:1 [101,1,1,10000] resp:0"

可以看到,requestCpuHighFreq接口中的日志被成功打印,表明测试apk已经可以调用JNI接口,即可以通过接口申请硬件资源。

至此,我们完成了简单的集成前测试,从源码的编译,到prop的设置,再到服务的手动拉起和接口的调试,这个测试的目的在于证明方案的可行性。


「系统的集成」

前面我们通过Android Studio的编译,二进制push的方式进行集成前的测试,证明了方案的可行性。接下来我们需要将Hardcoder以源码编译的方式集成到系统中,方便后续驱动端的修改和调试。
  • Hardcoder源码编译

Hardcoder Server端源码除了server.cpp入口之外,其它主要包括localsocket和一些依赖的库,例如数据处理相关的库libcJSON和libamc_proto。cJSON大家应该比较熟悉,在这里主要用于数据的序列化,其实protobuf同样是用于数据的序列化,只不过比JSON的性能更好。
Protocol buffers通常称为Protobuf,是Google开发的一种协议,允许对结构化数据进行序列化和反序列化。谷歌开发它的目的是提供一种比XML更好的方式来进行系统间通信。因此,他们专注于使其比XML更简单,更小,更快,更易于维护。该协议甚至超越了JSON,具有更好的性能,更好的可维护性和更小的尺寸。

编译的时候发生了一个小插曲,protobuf本来是准备以库的形式编译,但是Android编译系统一直提示找不到这个库,在折腾了许久之后,最终笔者妥协了,将protobuf改成源码的方式和依赖的源文件一起编译,才解决了这个问题。

这里要注意的是,由于接口的兼容性问题,Hardcoder对protobuf的版本是有要求的,虽然Android源码本身已经包含了protobuf,但是由于兼容性问题,最终笔者选择了自行集成3.1.0的版本,而没有引用Android源码中的protobuf。

Hardcoder Server端最终的bp编译文件如下(部分省略):

cc_defaults {  name: "hcdefaults",  ...}
IGNORED_WARNINGS = [ "-Wno-sign-compare", "-Wno-unused-parameter", "-Wno-sign-promo", "-Wno-error=return-type",]
cc_defaults { name: "protobuf-cflags-defaults-3.1.0", ...}
cc_defaults { name: "libprotobuf-cpp-lite-defaults-3.1.0", defaults: ["protobuf-cflags-defaults-3.1.0"], local_include_dirs: [ "protobuf-3.1.0/android", "protobuf-3.1.0/src", ], export_include_dirs: ["protobuf-3.1.0/src"], cflags: IGNORED_WARNINGS,}
cc_prebuilt_library_shared { name: "libc++_shared", arch: { arm64: { srcs: ["libs/arm64-v8a/libc++_shared.so"], },  }, compile_multilib: "64",}
cc_library_static { name: "libcJSON", defaults: ["hcdefaults"], srcs: ["src/main/cpp/cjson/cJSON.c"], export_include_dirs: ["src/main/cpp/cjson"],}
cc_library_static { name: "libamc_proto", host_supported: true, defaults: ["hcdefaults","libprotobuf-cpp-lite-defaults-3.1.0"], srcs: ["build/generated/source/proto/cpp/amc.pb.cc",    ... ], export_include_dirs: ["build/generated/source/proto/cpp","protobuf-3.1.0/src"],}
cc_binary { name: "coolhcserver", defaults: ["hcdefaults","libprotobuf-cpp-lite-defaults-3.1.0"], shared_libs: [ "liblog", "libc++_shared", ], static_libs: ["libcJSON","libamc_proto"], srcs: [ "src/main/cpp/server.cpp", ], local_include_dirs: ["build/generated/source/proto/cpp","protobuf-3.1.0/src"], init_rc: [ "rc/init.coolhcserver.rc", ],}


  • 其它集成相关修改

  • persist.sys.hardcoder.name
添加Hardcoder persist属性值,作为是否支持Hardcoder的标识,同时作为socket通信的桥梁。

在产品目录的Makefile中追加PRODUCT_PROPERTY_OVERRIDES属性

PRODUCT_PROPERTY_OVERRIDES += \  persist.sys.hardcoder.name=coolsocket
  • init.coolhcserver.rc

我们新建Hardcoder自己的rc,通过rc让coolhcserver自启动
service coolhcd /system/bin/coolhcserver  class main  user root  socket coolsocket stream 660 root system

注意这里需要为service分配socket资源,同时请在系统init.rc中import这个单独的rc文件,否则不会生效。

  • server.cpp的修改

server bin默认可以通过命令行传参,将socket name当做参数传入。

这里我们需要适当修改源码,在源码中获取socket name并当做实参传入:

int main(int argc, char *argv[]) {  setTag("HardCoderServer");  if (argc == 2) {    LocalSocketServer localsocket;    ManufacturerCoder *hc = new ManufacturerCoder(static_cast<IHardCoderDataCallback *>(&localsocket));    localsocket.start(argv[1], hc);  } else if (argc > 2) {    UdpServer udp;    ManufacturerCoder *hc = new ManufacturerCoder(static_cast<IHardCoderDataCallback *>(&udp));    udp.start(argv[1], atoi(argv[2]), hc);  }  else {    //fprintf(stderr, "usage:\n\t%s {ip} {port} (usage udp server)\nOR\n\t%s {path} (usage localsocket server)\n\n", argv[0], argv[0]);    LocalSocketServer localsocket;    ManufacturerCoder *hc = new ManufacturerCoder(static_cast<IHardCoderDataCallback *>(&localsocket));    localsocket.start(coolHardcoderSocketname, hc);  }  return 0;}

其中coolHardcoderSocketname可以通过property_get()来获取并赋值。

  • selinux权限

在添加coolhcserver自启动的rc后,我们会发现服务依然无法成功启动,这个时候我们需要添加service相关的selinux权限
file_contexts  /system/bin/coolhcserver     u:object_r:coolhcserver_exec:s0coolhcserver.te  type coolhcserver, domain, coredomain, mlstrustedsubject;  type coolhcserver_exec, exec_type, file_type, system_file_type;  init_daemon_domain(coolhcserver);


同时socket通信也需要添加相关的selinux权限

file_contexts  /dev/socket/coolsocket          u:object_r:coolhcd_socket:s0file.te  type coolhcd_socket, file_type, coredomain_socket;


之后服务便可以在开机后自启并成功进行socket通信

root  16612  1  11888  3340  do_select 0  S  coolhcserver


「写在最后」

本文主要介绍分享了Hardcoder Server端在Android系统中的编译集成方式,只是Hardcoder框架的其中一环。Hardcoder还有很多需要学习和实践的部分,例如APP侧的接入和编译、Server端和底层硬件的通信、实际应用场景下对性能的提升等等,大家有兴趣的话可以深入学习一下,以后有机会我们再细聊!


Android · 目录
上一篇“View背景模糊”的四种写法
继续滑动看下一个
酷派技术团队
向上滑动看下一个