天天看点

Hardcoder框架的集成与调试

作者:闪念基因

「Hardcoder是什么」

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

我们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:s0
coolhcserver.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:s0
file.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端和底层硬件的通信、实际应用场景下对性能的提升等等,大家有兴趣的话可以深入学习一下,以后有机会我们再细聊!

作者:MountainHigh

来源-微信公众号:酷派技术团队

出处:https://mp.weixin.qq.com/s/ysbt9YiuXlMy9Zlr9kKVFw