Hardcoder 是一套 Android APP 与系统间的通信解决方案,突破了 APP 只能调用系统标准 API,无法直接调用系统底层硬件资源的问题,让 Android APP 和系统能实时通信。APP 能充分调度系统资源如 CPU 频率,大小核,GPU 频率等来提升 APP 性能,系统能够从 APP 侧获取更多信息以便更合理提供各项系统资源。同时,对于 Android 缺乏标准接口实现的功能,APP 和系统也可以通过该框架实现机型适配和功能拓展。
Hardcoder 在微信的启动、发送视频、小程序启动等重度场景平均优化效果达 10%-30%;在手机 QQ 的启动、打开聊天界面、发送图片等场景的平均优化效果达 10%-50%。
简单地说,Hardcoder就是能让APP直接调度底层资源的一个框架,它能让APP根据自身各个使用场景,更加高效、有针对性地申请硬件资源,提升使用体验。
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 Github仓库地址:https://github.com/Tencent/Hardcoder.git
-测试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看是否设置成功。
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建立连接失败。
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接口测试
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的设置,再到服务的手动拉起和接口的调试,这个测试的目的在于证明方案的可行性。
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",
],
}
在产品目录的Makefile中追加PRODUCT_PROPERTY_OVERRIDES属性:
PRODUCT_PROPERTY_OVERRIDES += \
persist.sys.hardcoder.name=coolsocket
init.coolhcserver.rc
service coolhcd /system/bin/coolhcserver
class main
user root
socket coolsocket stream 660 root system
注意这里需要为service分配socket资源,同时请在系统init.rc中import这个单独的rc文件,否则不会生效。
server.cpp的修改
这里我们需要适当修改源码,在源码中获取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权限
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