个人开发者开发出来的 App,经常会见到微信赞赏(或者捐赠)入口。这个是如何实现的呢?
一般有以下方案:
- 一般的个人 App 是不会接入微信/支付宝 SDK 的,因为 SDK 嵌入成本高,也会增加不小的 APK 体积,应用市场审核一般也是通过不了的。
- 而微信对通过直接使用相应的 URI 传参跳转至微信相应捐赠页面是不可行的。
- 众多独立 App 开发者就采取迂回策略,通过 App 内嵌收款二维码 或者 赞赏码图片,把图片放到相册中,并跳转至微信扫一扫界面,通过引导用户从相册中选取相应的二维码图片,达到曲线救国的目的。
以上方案中,只有方案 3 可行,看起来挺麻烦的,但不失为一种有效的方案。
那么,如果让强大的 Xposed 来做,会不会有其他的方式呢?因为的解决方案中有很致命的点在于,需要引导用户自己去选取二维码图片从而实现迂回跳转到捐赠/转账页面,这一系列操作中交互很多,其实是很不方便的。 Xposed 能越过这些麻烦的操作的同时也能达到最终目的吗?
原理分析
从上面可行的 方案3 中,以微信赞赏为例,赞赏时的界面如下:
其 Activity 为 QrRewardSelectMoneyUI
,因为该 Activity 没有暴露给其他第三方 App 进行调用,所以上面的 方案2 是不可行的,而是需要通过微信内部其他 Activity 实现跳转。
实质上就是微信内部 Activity 带着相关的参数跳转到赞赏界面,其本质就是通过 Intent 携带相关参数,启动目标赞赏 Activity。
那么 Xposed 实现微信捐赠的思路就来了:
- 用户点击 App 中的微信赞赏时,跳转至微信的主界面
com.tencent.mm.ui.LauncherUI
- Hook 主界面的
onCreate
方法,在 Intent
中添加相关参数,跳转至捐赠界面 com.tencent.mm.plugin.collect.reward.ui.QrRewardSelectMoneyUI
,并销毁主界面 Activity。
- 为了以防万一,Hook
QrRewardSelectMoneyUI
的 onCreate
方法,已确保相关参数正确传递。完工。
那么,启动 QrRewardSelectMoneyUI
时到底需要传递哪些参数呢? 这时就可以通过反编译查看源码,或者通过 Xposed Hook 的方式,进行相关参数的获取。
技术实现
常量类 Const
:
1 2 3 4 5 6 7 8 9 10
| 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"; String WECHAT_QRCODE_URL = "m01pPa@:hEyGJ5P*a1@$xPI";
}
|
核心Hook类 DonateWechatHook
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| 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(); } } }
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) { 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(); }
} } } }
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); } } } } } }
|
调用则是正常启动微信主界面,并传递特定参数即可:
1 2 3 4 5 6
| 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