[摸鱼篇]记录一次Hook差劲的英语app拦截听力音频URL并重构播放操作
139

此app疑似完全外包开发,logcat不堪入目,仿佛看到这位程序员在挠自己的满头秀发:

3923180683aefdb3b4c8eb765cdfda51.jpgfcf0303a8965407fcd3d1a678c84a736.jpg

虽然看起来草率,实际用起来也是苦不堪言。作文输入框的长按菜单被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第一时间看到效果。

Screenshot_2025-10-09-00-31-21-63_de76c92ef1a6a18.jpg

[摸鱼篇]记录一次Hook差劲的英语app拦截听力音频URL并重构播放操作
https://sunight.cn/archives/B6JNnD6G
作者
Sunight
发布于
更新于
许可