美文网首页
编译时注解带你封装微信支付

编译时注解带你封装微信支付

作者: 有没有口罩给我一个 | 来源:发表于2019-09-27 17:29 被阅读0次

    1、Android注解快速入门和实用解析
    2、Annotation Processor重复造轮子ARouter
    3、编译时注解带你封装微信支付

    概述

    总所周知,接入支付宝是非常容易的,文档也非常清楚,而微信就有一点坑,微信需要在你的应用下提供一个:包名.wxapi.WXEntryActivity,对我们的项目的侵入性很强,基本就是模板代码,为何我们不用注解在编译期去生成这样的代码。

    开始之前就稍微提一下支付宝,如果你是组件化开发,那么就要注意的是支付宝有一个坑,比如:我们把支付分装成一个组件PaySdk,然后你再A组件使用支付组件需要把下面添加到该组件配置到module的gradle中:

    repositories {
    flatDir {
        dirs 'libs','../module name/libs'
    }
    

    }
    而如果其他组件依赖A组件也需要添加上面的配置到module的gradle中。

    注解

    注解我在之前的两篇文章我又介绍过,这里就不会去详细的介绍注解了,不了解的可以去看看文首这两篇文章,注解的重要性我不过多介绍,注解是非常有用的功能,很多常用的库和框架都使用了 Annotation Processor 来生成代码,比如Butter Knife 、ARouter等都是用来生成 代码的。不过我这里要提一下注解的工具类:AnnotationValueVisitor 名称可以看出这是访问注解值的工具类,看看AnnotationValueVisitor的定义:

    public interface AnnotationValueVisitor<R, P> {
    R visit(AnnotationValue av, P p);
    R visit(AnnotationValue av);
    R visitBoolean(boolean b, P p);
    R visitByte(byte b, P p);
    R visitChar(char c, P p);
    R visitDouble(double d, P p);
    R visitFloat(float f, P p);
    R visitInt(int i, P p);
    R visitLong(long i, P p);
    R visitShort(short s, P p);
    R visitString(String s, P p);
    R visitType(TypeMirror t, P p);
    R visitEnumConstant(VariableElement c, P p);
    R visitAnnotation(AnnotationMirror a, P p);
    R visitArray(List<? extends AnnotationValue> vals, P p);
    R visitUnknown(AnnotationValue av, P p);
    

    }

    可以看到AnnotationValueVisitor的定义是不是恍然大悟?首先我们在Proccessor中会调用 accept方法,这个方法接受一个实现了 AnnotationValueVisitor接口的对象实例作为参数,然后依次调用 AnnotationValueVisitor接口的各个方法,visit事件时间上调用的先后,所谓 visit 事件是指对各种不同 visit 函数的调用,AnnotationValueVisitor知道如何调用各种 visit 函数实际上及时根据不同的类型获取注解中指定的值value, 这些visitXXX的方法,就要看你需要处理什么类型,那么你就实现什么类型,如果你是有了解ASM字节码处理框架,就明白了AnnotationValueVisitor和ASM的ClassVisitor的操作是一样的,AnnotationValueVisitor实际上是把遍历算法的过程都抽取出来,使用者不需要关注这些过程就可以完成对注解的访问,比如我随便写个注解,没有任何实际意义的:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.SOURCE)
    public @interface PayrGenerator {
         String packageName();
         Class<?> registerTemplate();
    }
    

    这里注解有两个值,那么使用AnnotationValueVisitor访问时,你需要复写visitType方法和visitString方法,分别意思就是访问该注解的值为类型和字符串的值。AnnotationValueVisitor会有一个子类AbstractAnnotationValueVisitor6,后面那个数字表示java版本,AbstractAnnotationValueVisitor6有两个子类分别是AbstractAnnotationValueVisitor7和SimpleAnnotationValueVisitor6,我还是上个类图吧,清晰一些:


    注解值访问器.png

    这里在提一下注解其他相关的知识:

    • PackageElement 表示一个包程序元素
    • TypeElement 表示一个类或接口程序元素
    • VariableElement 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数
    • ExecutableElement 表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素
    • TypeParameterElement 表示一般类、接口、方法或构造方法元素的泛型参数

    什么是 AnnotationMirror?

    • 我们自定义 annotation 时常见的@Retention(RetentionPolicy.RUNTIME) 就是一个 AnnotationMirror。
    • 它通过方法 getAnnotationType 来获得具体注解的 DeclaredType 申明类型。
    • 这个注解的申明类型就是 java.lang.annotation.Retention
    • 它通过方法 getElementValues 来获得一个 ExecutableElement - AnnotationValue 列表。
    • 其实这个注解有个隐藏 ExecutableElement:@Retention(value = RetentionPolicy.RUNTIME),即 value,它对应的 AnnotationValue 是 RetentionPolicy.RUNTIME。

    关于注解就暂时提到这里,进入正题通过注解在编译期绕过微信的限制,这里我就直接上代码了。

    通过注解在编译期绕过微信的限制

    1、定义微信支付的模板类

    abstract class BaseWXActivity : AppCompatActivity(), IWXAPIEventHandler {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //这个必须写在onCreate中
        WeChatHelper.build(this).WXAPI.handleIntent(intent, this)
    }
    
    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        setIntent(intent)
        WeChatHelper.build(this).WXAPI.handleIntent(getIntent(), this)
    }
    

    }

    微信支付,必须在包名.wxapi.WXPayEntryActivity路径中实现WXPayEntryActivity类(包名或类名不一致会造成无法回调),不清楚的可以去看微信文档,这些是接入微信的基本操作,这里使用了模板方法设计模式,对微信官方的基本操作和配置全部放在模板类中,因为这些配置可能有微信支付和登录分享功能,所以抽取最基本的配置放到BaseWXActivity类中。

    2、实现微信支付的回调,做我们想要做的处理,比如封装回调之类的。

    abstract class BaseWXPayEntryActivity : BaseWXActivity() {
    
    protected abstract fun onPaySuccess()
    
    protected abstract fun onPayFail()
    
    protected abstract fun onPayCancel()
    
    override fun onResp(baseResp: BaseResp) {
        if (baseResp.type == ConstantsAPI.COMMAND_PAY_BY_WX) {
            when (baseResp.errCode) {
                WX_PAY_SUCCESS -> onPaySuccess()
                WX_PAY_FAIL -> onPayFail()
                WX_PAY_CANCEL -> onPayCancel()
                else -> {
                }
            }
        }
    }
    
    companion object {
        /*展示成功页面*/
        private const val WX_PAY_SUCCESS = 0
        /*可能的原因:签名错误、未注册APPID、项目设置APPID不正确、注册的APPID与设置的不匹配、其他异常等*/
        private const val WX_PAY_FAIL = -1
        /*无需处理。发生场景:用户不支付了,点击取消,返回APP。*/
        private const val WX_PAY_CANCEL = -2
    }
    }
    

    在WXPayEntryActivity类中实现onResp函数,对支付的基本配置,支付完成后,微信APP会返回到商户APP并回调onResp函数,开发者需要在该函数中接收通知,判断返回错误码,如果支付成功则去后台查询支付结果再展示用户实际支付结果。 注意一定不能以客户端返回作为用户支付的结果,应以服务器端的接收的支付通知或查询API返回的结果为准。

    3、定义模板类,继承至BaseWXPayEntryActivity

    open class WXPayEntryTemplate : BaseWXPayEntryActivity() {
    
    override fun onPaySuccess() {
        WeChatHelper.build(this).callback()?.get()?.onPaySuccess()
        //不想看到微信的支付完成页面,取消动画
        finish()
        overridePendingTransition(0, 0)
    }
    
    override fun onPayFail() {
        WeChatHelper.build(this).callback()?.get()?.onPayFail()
        Toast.makeText(this, getString(R.string.pay_text_failed), Toast.LENGTH_SHORT).show()
        finish()
        overridePendingTransition(0, 0)
    }
    
    override fun onPayCancel() {
        WeChatHelper.build(this).callback()?.get()?.onPayCancel(this)
        finish()
        overridePendingTransition(0, 0)
    }
    
    override fun onReq(baseReq: BaseReq) {}
    }
    

    这个是我们的模板类,我们在注解处理器中生成的WXPayEntryActivity类就是继承这个模板类,为什么这么做相信大家都明白,我不可能在注解中还要生成这些回调和相关配置,那就增加复杂度了,其中WeChatHelper类是一个提供给支付帮助类,就是个封装支付的API,还有就是支付完成我把微信WXPayEntryActivity设置了透明,支付操作完成直接finish调并取消动画。

    位置支付帮助类

    internal class WXPayHelper private constructor(context: Context) {
    val WX_API: IWXAPI
    
    private var mIPayResultListenerRef: SoftReference<IPayResultListener>? = null
    
    
    fun callback() = mIPayResultListenerRef
    
    fun callback(callback: IPayResultListener?): WXPayHelper {
        mIPayResultListenerRef?.let { if (it.get() != null) it.clear() }
        callback?.let { mIPayResultListenerRef = SoftReference(it) }
        return this
    }
    
    init {
        // 通过WXAPIFactory工厂,获取IWXAPI的实例
        WX_API = WXAPIFactory.createWXAPI(context, WX_APP_ID, true)
        // 将应用的appId注册到微信
        WX_API.registerApp(WX_APP_ID)
    }
    
    companion object {
        private var INSTANCE: WXPayHelper? = null
        /*WX_APP_ID 替换为你的应用从官方网站申请到的合法appID*/
        const val WX_APP_ID = "wx11111111111111111111"
    
        fun build(activity: Activity): WXPayHelper {
            if (INSTANCE == null) {
                synchronized(WXPayHelper::class.java) {
                    if (INSTANCE == null) {
                        INSTANCE = WXPayHelper(activity)
                    }
                }
            }
            return INSTANCE!!
        }
    }
    

    }

    数据转换类

    /**
       * 微信数据转换
     */
    class WXJsonDataConverter private constructor(private val jsonStr: String) : IDataConverter {
    companion object {
        fun create(data: String): IDataConverter {
            return WXJsonDataConverter(data)
        }
    }
    
    override fun converter(): PayReq {
        val jsonObject = JSONObject(jsonStr)
        val payReq = PayReq()
        payReq.appId = jsonObject.getString(WXPayParamConfigKeys.WX_APP_ID.value)
        payReq.prepayId = jsonObject.getString(WXPayParamConfigKeys.WX_PREPAY_ID.value)
        payReq.partnerId = jsonObject.getString(WXPayParamConfigKeys.WX_PARTNER_ID.value)
        payReq.packageValue = jsonObject.getString(WXPayParamConfigKeys.WX_PACKAGE.value)
        payReq.timeStamp = jsonObject.getString(WXPayParamConfigKeys.WX_TIME_STAMP.value)
        payReq.nonceStr = jsonObject.getString(WXPayParamConfigKeys.WX_NONCE_STR.value)
        payReq.sign = jsonObject.getString(WXPayParamConfigKeys.WX_SIGN.value)
        return payReq
    }
    

    }

    服务返回的参数类型

    enum class WXPayParamConfigKeys(val value:String) {
           WX_APP_ID("appId"),
          WX_PREPAY_ID("prepayId"),
          WX_PARTNER_ID("partnerId"),
          WX_PACKAGE("wxPackage"),
          WX_TIME_STAMP("timestamp"),
          WX_NONCE_STR("nonceStr"),
          WX_SIGN("sign")
    }
    

    支付结果码

    enum class WXPayResultConfigKeys(val code: Int) {
    /*展示成功页面*/
    WX_PAY_SUCCESS(0),
    
    /*可能的原因:签名错误、未注册APPID、项目设置APPID不正确、注册的APPID与设置的不匹配、其他异常等*/
    WX_PAY_FAIL(-1),
    
    /*无需处理。发生场景:用户不支付了,点击取消,返回APP。*/
    WX_PAY_CANCEL(-2)
    }
    

    4、注解处理器生成代码

    4.1、定义注解

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.SOURCE)
    public @interface PayEntryGenerator {
       String packageName();
    
      //传入生成类的父类
      Class<?> payEntryTemplate();
    }
    

    实际上微信支付可以不用APPID,因为这些服务会返回来,当我们发起支付的时候使用这个appid即可,你也可以进行二次签名,当然如果服务器不肯做签名获取做不了,那么前端可以签名,就要用到appid和prepayId就是WXPayParamConfigKeys枚举里面的参数,因为签名会用到这些参数,签名算法就是把这些参数进行排序,然后使用secret加签,之后进行md5 32位的那种,最后说一下,微信需要申请商家并绑定开放平台,

    4.2、定义注解访问器

    class PayEntryVisitor extends SimpleAnnotationValueVisitor7<Void, Void> {
    
    private final Filer FILER;
    private String mPackageName = null;
    
    PayEntryVisitor(Filer FILER) {
        this.FILER = FILER;
    }
    
    @Override
    public Void visitString(String s, Void p) {
        mPackageName = s;
        return p;
    }
    
    @Override
    public Void visitType(TypeMirror t, Void p) {
        generateJavaCode(t);
        return p;
    }
    
    private void generateJavaCode(TypeMirror typeMirror) {
        final TypeSpec targetActivity = TypeSpec.classBuilder(Constants.WXPAY_ENTRY_ACTIVITY)
                .addModifiers(Modifier.PUBLIC)
                .addModifiers(Modifier.FINAL)
                .superclass(TypeName.get(typeMirror))
                .build();
        final JavaFile javaFile = JavaFile.builder(mPackageName + ".wxapi", targetActivity)
                .addFileComment("微信支付入口")
                .build();
        try {
            javaFile.writeTo(FILER);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    }
    

    注解处理器上面也介绍了这里不多说,我们定义的注解PayEntryGenerator,有两个值一个是包名和模板父类,这些visitXXX的方法,就要看你需要处理什么类型,那么你就实现什么类型,就这么简单。

    4.3、注解处理器

    class WeChatProcessor extends AbstractProcessor {
    
    /*Types是一个用来处理TypeMirror的工具*/
    private Types typeUtils;
    
    private Elements elementUtils;
    
    private Messager messager;
    
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        typeUtils = processingEnv.getTypeUtils();
        elementUtils = processingEnv.getElementUtils();
        messager = processingEnv.getMessager();
    }
    
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        final Set<String> types = new LinkedHashSet<>();
        final Set<Class<? extends Annotation>> supportAnnotations = getSupportedAnnotations();
        for (Class<? extends Annotation> annotation : supportAnnotations) {
            types.add(annotation.getCanonicalName());
        }
        return types;
    }
    
    private Set<Class<? extends Annotation>> getSupportedAnnotations() {
        final Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
        annotations.add(PayEntryGenerator.class);
        return annotations;
    }
    
    
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
        generatePayEntryCode(env);
        return true;
    }
    
    private void scan(RoundEnvironment env,
                      Class<? extends Annotation> annotation,
                      AnnotationValueVisitor visitor) {
    
        for (Element typeElement : env.getElementsAnnotatedWith(annotation)) {
    
    
            //注解镜像环境
            final List<? extends AnnotationMirror> annotationMirrors =
                    typeElement.getAnnotationMirrors();
    
            for (AnnotationMirror annotationMirror : annotationMirrors) {
    
                //ExecutableElement 表示注解类型元素
                final Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues
                        = annotationMirror.getElementValues();
    
                for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry
                        : elementValues.entrySet()) {
                    AnnotationValue annotationValue = entry.getValue();
                    //通过注解访问器访问注解的值
                    annotationValue.accept(visitor, null);
                }
            }
        }
    }
    
    private void generatePayEntryCode(RoundEnvironment env) {
        final PayEntryVisitor payEntryVisitor =
                new PayEntryVisitor(processingEnv.getFiler());
        scan(env, PayEntryGenerator.class, payEntryVisitor);
    }
    

    }

    这里基本没有什么说的,就是通过注解处理器去处理注解,并把注解交给注解访问器去生成代annotationValue.accept(visitor, null)

    5、这么使用,代码生成了,怎么使用?

    @PayEntryGenerator(
    packageName = "应用包名",
    payEntryTemplate = WXPayEntryTemplate::class
    )
    class MainActivity : AppCompatActivity
    

    WXPayEntryTemplate类是我们的模板类要传进去,到此绕过微信支付限制已经结束。

    最后贴上一行代码完成支付宝和微信支付:

     /**
     * 统一支付入
     *
     * @param paySign 支付宝和微信支付参数
     *
     * */
    fun pay(payWay: PayWayConfigKeys, paySign: Any) {
        if (payWay == PayWayConfigKeys.ALI_PAY) return aliPay("$paySign")
        if (payWay == PayWayConfigKeys.WX_PAY) return wxPay(Gson().toJson(paySign))
    }
    

    调用

     PayHelper.create(this).setPayResultCallback(this).pay(mPayWay, paySign)
    

    相关文章

      网友评论

          本文标题:编译时注解带你封装微信支付

          本文链接:https://www.haomeiwen.com/subject/yedvyctx.html