[摸鱼篇]记录一次Hook差劲的英语app拦截听力音频URL并重构播放操作
139
此app疑似完全外包开发,logcat不堪入目,仿佛看到这位程序员在挠自己的满头秀发:


虽然看起来草率,实际用起来也是苦不堪言。作文输入框的长按菜单被block了,竟是要在一个简陋的自定义键盘一个个字母手敲作文。完美的防止粘贴方案我认为是使用TextWatcher,起码还能获得单词补全提高效率,也不用去敲那个shit。虽然通过无障碍服务去检索屏幕上的EditText控件并使用设置文本的办法可以免注入绕过此限制(TextWatcher也能够阻止此方法),但一般没人会去下个Autojs之类去运行自动化脚本,只能去敲那个{{github第10亿个仓库名}}。
在一套CET6任务里,作文写完了,接下来就是听力了,这个app对听力音频的播放也是教科书级别的一放到底,人一下子就清醒了,仿佛此刻就坐在CET6的考场上,不出意外的就出意外了。笔者的高考英语分全扣在了作文上,面对这个也是无力吐槽。于是在力所能及的范围内,去进行优化,允许中途重新播放,继续优化,允许往回拉进度照顾一下年迈的大脑,甚至能够调整倍速。
核心思路:劫持音频URL,展示我们自己的功能强大的播放器view(AI生成几分钟即可搞定)去负责交互,为了提高开发效率,选择xposed api,分发时,使用lspatch打包。
以下为核心代码:
public class HookInit implements IXposedHookLoadPackage {
public String globalAudioUrl;
@Override
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
XposedHelpers.findAndHookMethod("com.lancoo.answer.widget.audioPlayView.TopicAudioView$Config",
lpparam.classLoader,
"access$700",
lpparam.classLoader.loadClass("com.lancoo.answer.widget.audioPlayView.TopicAudioView$Config"),
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
Object configInstance = param.args[0];
if (configInstance != null) {
// 获取所有字段
Field[] fields = configInstance.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
try {
Object value = field.get(configInstance);
if (field.getName().equals("audioUrl")) {
globalAudioUrl = (String) value;
}
} catch (Exception e) {
XposedBridge.log("无法读取字段: " + field.getName());
}
}
}
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
}
});
XposedHelpers.findAndHookMethod("com.lancoo.answer.widget.audioPlayView.TopicAudioView", lpparam.classLoader, "play", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
android.widget.Toast.makeText(context, "successfully hijacked✓", android.widget.Toast.LENGTH_SHORT).show();
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
// 获取被hook的对象实例
Object topicAudioView = param.thisObject;
XposedHelpers.callMethod(topicAudioView, "pause");
resetPlayer();
}
});
XposedHelpers.findAndHookMethod("android.app.Activity", lpparam.classLoader,
"onCreate", android.os.Bundle.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
activity = (android.app.Activity) param.thisObject;
context = activity;
final android.view.View rootView = activity.getWindow().getDecorView();
// 创建主容器
android.widget.LinearLayout container = createPlayerContainer(context);
// 创建播放器UI组件
createPlayerComponents(context, container);
// 初始化PopupWindow
initPopupWindow(context, container);
// 初始化Handler和Runnable,用于定时更新播放进度
initHandler();
// 设置view的拖拽功能
setupDragListener(container);
// 显示播放器
showPlayer(rootView);
}
});
}
}页面是add到Activity的根布局的,因此无需悬浮窗权限,至于为什么选择注入到Activity大类,我们可以在模块生效后,打开app第一时间看到效果。
