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)
网友评论