HarmonyOS实践--Hook
一、Hook是什么?
最近工作中,旁边的同事给我提了一个需求,让我给鸿蒙应用中的所有点击事件添加个音效。我说道这还不简单Ctrl+Shift+R全局替换不就好了,立马抢过他的键盘一顿操作猛如虎,然后点击运行,的确点击生效出来了。但是呢,只是替换了部分onClick,并没有把所有的都给替换掉,不知道这是DevEco Studio的Bug呢还是什么原因。这时同事说正常思路应该是通过hook
onClick方法再对其进行增强。
二、为什么要进行Hook?
Hook
操作其实就是对现有的函数进行调整,就比如说我刚刚提到的给所有点击事件添加个音效,这时就需要AOP思想直接对onClick增强。除此之外Hook操作还不会影响已有的业务逻辑,因为我们的操作并对原有代码进行任何修改!并且Hook还可以将核心的业务逻辑和日志、验证、事务管理这种辅助功能进行解耦,使得维护也更简单。
三、Hook onClick()
1. 系统的Hook
定义一个 MyClass
类,包含实例属性 msg
和实例方法 foo
,还有静态属性 data
和静态方法 bar
,并且通过实例属性和方法
class MyClass {
msg: string = 'msg000';
foo(arg: string): string {
console.info('foo arg is ' + arg);
return this.msg;
}
static data: string = 'data000';
static bar(arg: string): string {
console.info('bar arg is ' + arg);
return MyClass.data;
}
}
/**
* 访问实例属性和实例方法
*/
let asp = new MyClass();
let result = asp.foo('123');
// 输出结果:foo arg is 123
console.info('result is ' + result);
// 输出结果:result is msg000
console.info('asp.msg is ' + asp.msg);
// 输出结果:asp.msg is msg000
/**
* 访问静态属性和静态方法
*/
let res = MyClass.bar('456');
// 输出结果:bar arg is 456
console.info('res is ' + res);
// 输出结果:res is data000
console.info('MyClass.data is ' + MyClass.data);
// 输出结果:MyClass.data is data000
导入系统的util模块,在util模块的Aspect类中提供了addBefore
、addAfter
和replace
三个方法来Hook类方法
/**
* 对实例方法进行hook
*/
util.Aspect.addBefore(MyClass, 'foo', false, (instance: MyClass, arg: string) => {
console.info('arg is ' + arg);
instance.msg = 'msg111';
console.info('msg is changed to ' + instance.msg)
});
result = asp.foo('123');
// 输出:arg is 123
// 输出:msg is changed to msg111
// 输出:foo arg is 123
console.info('result is ' + result);
// 输出:result is msg111
console.info('asp.msg is ' + asp.msg);
// 输出:asp.msg is msg111
/**
* 对静态方法进行hook
*/
util.Aspect.addBefore(MyClass, 'bar', true, (target: Object, arg: string) => {
console.info('arg is ' + arg);
let newVal = 'data111';
Reflect.set(target, 'data', newVal);
console.info('data is changed to ' + newVal);
});
res = MyClass.bar('456');
// 输出:arg is 456
// 输出:data is changed to data111
// 输出:bar arg is 456
console.info('res is ' + res);
// 输出:res is data111
console.info('MyClass.data is ' + MyClass.data);
// 输出:MyClass.data is data111
上面是官方的案例系统的hook是不是看起来很简单,然而就在我打算对onClick进行hook的时候发现好像hook不了 0_0
util.Aspect.addBefore(Button, 'onClick', true, () => {
hilog.info(0x000000, 'tagtag', '11111');
});
然后我通过调试,发现当点击的时候执行的是传入给onClick
的匿名函数,而不是onClick
,所以应该对匿名函数hook,但是这个匿名函数又没有所属的类,应该怎么hook呢?
2. 三方库的hook
经过我查阅发现OpenHarmony三方库中心仓有一个三方库可以hook onClick,使用方法如下:
引入依赖
ohpm i @huolala/aspectpro
在工程级的 hvigor/hvigor-config.json5文件中配置
"dependencies": {
"aspect-pro-plugin": "0.0.7"
}
在entry或其他需要使用插件模块的 hvigorfile.ts文件中添加
import { aspectProPlugin } from 'aspect-pro-plugin';
export default {
system: appTasks,
plugins: [aspectProPlugin()]
}
创建插件配置文件 aspectProPluginConfig.txt 和上一步目录保持一致即可
# 配置规则
-hook path | file : 配置需要被hook的文件/文件夹 <相对路径>
-keep path | file : 配置需要keep的文件/文件夹 <相对路径>
-replace pattern replacement [import xxx import xxx] : 配置需要替换的代码,花括号是配置自动导包
# 示例:
-hook ./src/main/ets/
-keep ./src/main/ets/hook/
-replace router.pushUrl this.getUIContext().getRouter().pushUrl
#-replace router.pushUrl this.getUIContext().getRouter().pushUrl [import { Logger } from '@huolala/logger';]
#支持三方库
-hook ./oh_modules/@hll-wp/foundation/src/main/com.wp.foundation/utils/WPFUtil.js
-replace IdUtils.next IdUtils.uuid
使用Aspect Pro
import AspectPro from '@huolala/aspectpro';
/**
* hook onclick
*/
@Entry
@Component
struct Index {
aboutToAppear(): void {
AspectPro.addBefore(Button, "onClick", () => {
console.info('hook onClick ');
}, true)
}
build() {
Column() {
Button('点击')
.onClick((event: ClickEvent) => {
})
}
.height('100%')
.width('100%')
.justifyContent(FlexAlign.Center)
}
}
嘿嘿,完美解决!
四、系统Aspect与三方库AspectPro的异同
按照常识来说Apple 16 与Apple 16 Pro这两个有异同,我们这两个自然也有异同。系统自带的Aspect用起来方便,不需要额外引入依赖,也不需要配置hvigor任务,相对三方库的AspectPro来说比较简单这是他们之间最直观的区别,最重要的就是系统的Aspect不能hook系统自带的方法,而AspectPro可以,此外Aspect需要区分是否为静态方法,而AspectPro无需关心。但是AspectPro的使用方法与Aspect保持了高度相似,其背后的原理也大同小异。选择使用Aspect还是AspectPro得根据具体的开发场景和需求来判断。
五、总结
当我们在需要对某一方法需要进行增强、监控、调试、拦截和控制程序行为时,我们就可以采用hook
在不修改原始代码的情况下来进行处理。而不是通过Ctrl+Shift+R全局替换直接修改原有代码,修改原有代码可能引入新的错误或不一致。当遇到问题一定要多思考,研究是否有新的方法来解决现有的问题,实在没有再采取原来的解决办法。