个人开发者开发出来的 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:
| 12
 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:
| 12
 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);
 }
 }
 }
 }
 }
 }
 
 | 
调用则是正常启动微信主界面,并传递特定参数即可:
| 12
 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