客户端 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::vectorstd::string manufactures, std::vectorstd::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::vectorbindtids, int timeoutms, int callertid, int64_t timestamp);
其中,bindtids,需要绑定到大核的线程 id 数组。参考实现,部分厂商会直接把请求线程所在进程的所有线程同时绑定到大核。
int cancelCpuCoreForThread(std::vectorbindtids, 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::vectorbindtids, int gpulevel, int timeoutms, int callertid, int64_t timestamp);
取消的时候使用cancelUnifyCpuIOThreadCoreGpu方法。
int cancelUnifyCpuIOThreadCoreGpu(int cancelcpu, int cancelio, int cancelthread, std::vectorbindtids, int cancelgpu, int callertid, int64_t timestamp);
Hardcoder 接入指南
接入步骤
- 下载 Hardcoder 工程编译 aar;
- 项目 build.gradle 引入 Hardcoder aar;
- 进程启动时调用 initHardCoder 建立 socket
连接(一般进程启动时需要请求资源,因而推荐在进程启动时调用)。每个进程都是独立的,都需要调用 initHardCoder 建立 socket 连接,建立连接后每个进程维持一个 socket,进程退出时 socket 也会断开; - initHardCoder 回调成功后调用 checkPermission,传入 APP 已申请的各个厂商鉴权值;
- 在需要请求资源的场景调用 startPerformance,传入请求资源的参数。若场景位于进程启动阶段,比如 APP 启动,需要在initHardCoder 的回调成功以后再调用 startPerformance,确保连接已成功建立,或者判断HardCoderJNI 的 isConnect() 检查 socket 是否已连接。
- 场景结束时主动调用 stopPerformance,传入对应场景 startPerformance 时的返回值 hashCode作为参数,停止本次请求。
- 测试性能,APP 可对打开/关闭 Hardcoder 的情况做对比实验,测试性能数据。
编译 Hardcoder aar
Hardcoder项目给开发环境为 AndroidStudio,采用 gradle 构建,其中 Native 部分代码使用 CMAKE 编译。编译aar的步骤如下:
- 根目录下运行命令行
gradlew assembleDebug
触发子工程 libapp2sys 编译,编译成功后生成 Hardcoder aar, 输出目录为hardcoder/libapp2sys/build/outputs/aar/libapp2sys-debug.aar; - 若需要把 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
写在最后
本次我的分享也接近尾声了,感谢你们在百忙中花上一下午来这里聆听我的宣讲,希望在接下来的日子,我们共同成长,一起进步!!!
最后放上一个大概的Android学习方向及思路(详细的内容太多了~),提供给大家:
对于程序员来说,要学习的知识内容、技术有太多太多,这里就先放上一部分,其他的内容有机会在后面的文章向大家呈现出来,不过我自己所有的学习资料都整理成了一个文档,一直在不断学习,希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!
为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能点个小赞和关注下我,以后还会更新技术干货,谢谢您的支持!
Android架构师之路很漫长,一起共勉吧!
如果你觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言,一定会认真查询,修正不足,谢谢。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
还会更新技术干货,谢谢您的支持!**
Android架构师之路很漫长,一起共勉吧!
如果你觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言,一定会认真查询,修正不足,谢谢。
[外链图片转存中…(img-vQFXmjqf-1714685870732)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!