Xposed开发之微信赞赏

Author Avatar
发表:10月 10, 2018 更新:4月 29, 2019
字数统计:1.1k 字 阅读时长:4 分

个人开发者开发出来的 App,经常会见到微信赞赏(或者捐赠)入口。这个是如何实现的呢?

一般有以下方案:

  1. 一般的个人 App 是不会接入微信/支付宝 SDK 的,因为 SDK 嵌入成本高,也会增加不小的 APK 体积,应用市场审核一般也是通过不了的。
  2. 而微信对通过直接使用相应的 URI 传参跳转至微信相应捐赠页面是不可行的。
  3. 众多独立 App 开发者就采取迂回策略,通过 App 内嵌收款二维码 或者 赞赏码图片,把图片放到相册中,并跳转至微信扫一扫界面,通过引导用户从相册中选取相应的二维码图片,达到曲线救国的目的。

以上方案中,只有方案 3 可行,看起来挺麻烦的,但不失为一种有效的方案。

那么,如果让强大的 Xposed 来做,会不会有其他的方式呢?因为的解决方案中有很致命的点在于,需要引导用户自己去选取二维码图片从而实现迂回跳转到捐赠/转账页面,这一系列操作中交互很多,其实是很不方便的。 Xposed 能越过这些麻烦的操作的同时也能达到最终目的吗?

原理分析

从上面可行的 方案3 中,以微信赞赏为例,赞赏时的界面如下:

微信赞赏界面微信赞赏界面

其 Activity 为 QrRewardSelectMoneyUI,因为该 Activity 没有暴露给其他第三方 App 进行调用,所以上面的 方案2 是不可行的,而是需要通过微信内部其他 Activity 实现跳转。

实质上就是微信内部 Activity 带着相关的参数跳转到赞赏界面,其本质就是通过 Intent 携带相关参数,启动目标赞赏 Activity。

那么 Xposed 实现微信捐赠的思路就来了:

  1. 用户点击 App 中的微信赞赏时,跳转至微信的主界面 com.tencent.mm.ui.LauncherUI
  2. Hook 主界面的 onCreate 方法,在 Intent 中添加相关参数,跳转至捐赠界面 com.tencent.mm.plugin.collect.reward.ui.QrRewardSelectMoneyUI,并销毁主界面 Activity。
  3. 为了以防万一,Hook QrRewardSelectMoneyUIonCreate 方法,已确保相关参数正确传递。完工。

那么,启动 QrRewardSelectMoneyUI 时到底需要传递哪些参数呢? 这时就可以通过反编译查看源码,或者通过 Xposed Hook 的方式,进行相关参数的获取。

技术实现

常量类 Const

public interface Const {

    String WECHAT_PACKAGE_NAME = "com.tencent.mm";
    String WECHAT_LAUNCHER_UI = WECHAT_PACKAGE_NAME + ".ui.LauncherUI";
    String WECHAT_QR_REWARD_SELECT_MONEY_UI = WECHAT_PACKAGE_NAME + ".plugin.collect.reward.ui.QrRewardSelectMoneyUI";
    String WECHAT_KEY_EXTRA_DONATE = "TianmaDonate";
    // 核心参数,可以通过 Xposed Hook 的方式获取参数的值。
    String WECHAT_QRCODE_URL = "m01pPa@:hEyGJ5P*a1@$xPI";

}

核心Hook类 DonateWechatHook

public class DonateWechatHook implements IXposedHookLoadPackage {

    private static final String KEY_SCENE = "key_scene";
    private static final String KEY_QRCODE_URL = "key_qrcode_url";
    private static final String KEY_CHANNEL = "key_channel";

    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
        if (Const.WECHAT_PACKAGE_NAME.equals(lpparam.packageName)) {
            try {
                hookLauncherUIOnCreate(lpparam);
                hookQrRewardSelectMoneyUI(lpparam);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Hook com.tencent.mm.ui.LauncherUI#onCreate();
     */
    private void hookLauncherUIOnCreate(XC_LoadPackage.LoadPackageParam lpparam) {
        XposedHelpers.findAndHookMethod(Const.WECHAT_LAUNCHER_UI,
                lpparam.classLoader,
                "onCreate",
                Bundle.class,
                new LauncherUIOnCreateHook());
    }

    private class LauncherUIOnCreateHook extends XC_MethodHook {

        @Override
        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
            Activity activity = (Activity) param.thisObject;
            if (activity != null) {
                Intent intent = activity.getIntent();
                if (intent != null) {
                    ComponentName cn = intent.getComponent();
                    String className = cn == null ? null : cn.getClassName();
                    // 自定义的参数,用于区分正常启动微信主界面
                    boolean hasDonateExtra = intent.hasExtra(Const.WECHAT_KEY_EXTRA_DONATE);
                    if (Const.WECHAT_LAUNCHER_UI.equals(className) && hasDonateExtra) {
                        // 参数校验成功,则跳转至微信赞赏界面,并销毁主Activity
                        intent.removeExtra(Const.WECHAT_KEY_EXTRA_DONATE);

                        Intent donateIntent = new Intent();
                        donateIntent.setClassName(activity, Const.WECHAT_QR_REWARD_SELECT_MONEY_UI);
                        donateIntent.putExtra(KEY_SCENE, 2);
                        donateIntent.putExtra(KEY_QRCODE_URL, Const.WECHAT_QRCODE_URL);
                        donateIntent.putExtra(KEY_CHANNEL, 13);
                        donateIntent.putExtra(Const.WECHAT_KEY_EXTRA_DONATE, true);
                        activity.startActivity(donateIntent);
                        activity.finish();
                    }

                }
            }
        }
    }

    /**
     * Hook com.tencent.mm.plugin.collect.reward.ui.QrRewardSelectMoneyUI#onCreate();
     */
    private void hookQrRewardSelectMoneyUI(XC_LoadPackage.LoadPackageParam lpparam) {
        XposedHelpers.findAndHookMethod(Const.WECHAT_QR_REWARD_SELECT_MONEY_UI,
                lpparam.classLoader,
                "onCreate",
                Bundle.class,
                new QrRewardSelectMoneyUIOnCreateHook());
    }

    private class QrRewardSelectMoneyUIOnCreateHook extends XC_MethodHook {
        @Override
        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
            Activity activity = (Activity) param.thisObject;
            if (activity != null) {
                Intent intent = activity.getIntent();
                if (intent != null) {
                    // 确保参数合法
                    boolean hasDonateExtra = intent.hasExtra(Const.WECHAT_KEY_EXTRA_DONATE);
                    if (hasDonateExtra) {
                        String qrCodeUrl = intent.getStringExtra(KEY_QRCODE_URL);
                        if (TextUtils.isEmpty(qrCodeUrl)) {
                            intent.putExtra(KEY_QRCODE_URL, Const.WECHAT_QRCODE_URL);
                        }
                        intent.removeExtra(Const.WECHAT_KEY_EXTRA_DONATE);
                    }
                }
            }
        }
    }
}

调用则是正常启动微信主界面,并传递特定参数即可:

public void donateByWechat() {
    Intent intent = new Intent();
    intent.setClassName(Const.WECHAT_PACKAGE_NAME, Const.WECHAT_LAUNCHER_UI);
    intent.putExtra(Const.WECHAT_KEY_EXTRA_DONATE, true);
    startActivity(intent);
}

源码: DonateWechatHook

参考

WechatLuckeyMoney