Android绕过微信包名限制对接微信登录和支付
- 前言
- 最终效果
- 原理
- 代码
-
- 通过 APT 生成 WXEntryActivity 文件
- 对接微信SDK
- End
前言
Android对接微信登录和支付几乎是现在所有的商用Android APP都需要做的一个东西,不过每次开发我们都需要去新建微信官方要求的指定包名+Activity名字,这个还是有点烦的。下面我将通过 APT 封装一个可以绕过微信包名限制的微信登录和支付功能。(其实也不算是真的绕过啦)
最终效果
先上最后封装好我调用起微信登录的代码:
public class MainActivity extends AppCompatActivity {
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
WeChatAPI.getInstance().setSingInCallBack(new IWXSignCallBack() {
@Override
public void signSuccess(String info) {
Toast.makeText(MainActivity.this, "info: " + info, Toast.LENGTH_SHORT).show();
}
}).signIn();
}
});
}
}
效果还可以吧,只需要加入几行代码就可以调起微信登录 。
原理
这里用到的和 ButterKnife 一样的技术叫做编译时注解的技术即 APT ,代码在编译时会扫描AbstractProcessor的所有子类,并且调用这些子类的process函数,在这个函数就会将所有的代码元素传递进来。此时我们只需要在这个process函数中获取所有添加了某个注解的元素,然后对这些元素进行操作,使之能够满足我们的需求,这样我们就可以在编译期对源代码进行处理,例如生成新的类等。在运行时,我们通过一些接口对这些新生成的类进行调用以此完成我们的功能。
emmmmm 很抽象,其实我也觉得挺抽象的,我们直接看代码吧~
代码
代码我们分出两块讲: 通过 APT 生成 WXEntryActivity 文件 以及 对接微信SDK;
通过 APT 生成 WXEntryActivity 文件
我们封装这个东西就是为了方便还有通用性,我们用了组件化的思想,用两个 Module 完成我们这个功能所以我们要新建两个 Java Library 的 Module 分别为:annotation(放注解类),compile (放对注解类的处理)。
-
新建Module
新建两个 Java Library 分别为: annotation( 放注解类 ),compile ( 放对注解类的处理)。
注意:这里新建 Java Library 而不是 Android Library 是因为我们的注解这里其实会用到很多标准 Java SDK的一些注解类。其中一些是 Android Library 里面没有”。
画面效果不好,意思意思就行了
-
添加依赖
在 annotation Module 的 gradle 文件中添加支持中文的依赖
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//添加支持中文
tasks.withType(JavaCompile){
options.encoding='UTF-8'
}
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
在 compile Module 的 gradle 中添加一下依赖
apply plugin: 'java-library'
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
api project(':annotation')
//生成 java library 的一个工具
api 'com.squareup:javapoet:1.10.0'
//注解 processor 类并生成 META-INF 的配置信息
api 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
//添加支持中文
tasks.withType(JavaCompile){
options.encoding='UTF-8'
}
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
-
创建微信登录注解类
在 annotations Module 下创建 EntryGeneral 注解
@Target(ElementType.TYPE)//作用于类和接口
@Retention(RetentionPolicy.SOURCE)//只在源码阶段
public @interface EntryGeneral {
String packageName();
//要继承的类
Class<?> entryTemplete();
}
-
写一个 AbstractProcessor 的子类
在 compile Module 下写一个 AbstractProcessor 的子类并添加我们的注解元素,对注解元素进行扫描操作然后再生成我们指定包名下的指定名文件。
注意:这里要记得在 compile Module 下的依赖文件引入 annotation Module
@AutoService(Processor.class)
public class WeChatProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//扫描注解并生成我们的微信登录入口文件
generateEntryCode(roundEnvironment);
return false;
}
//设置为最大版本
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* 获取到注解的名字
*
* @return Set
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
/**
* 获取到注解类列表
*
* @return Set
*/
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
annotations.add(EntryGeneral.class);
return annotations;
}
/**
* 扫描解析我们注解类的 方法等
*
* @param env RoundEnvironment
* @param annotation 注解类
* @param visitor 可以理解成生成文件的这么一个类
*/
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) {
final Map<? extends ExecutableElement, ? extends AnnotationValue> elementValue
= annotationMirror.getElementValues();
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry
: elementValue.entrySet()) {
//将获得的注解上的实际的值给visitor
entry.getValue().accept(visitor, null);
}
}
}
}
/**
* 生成 微信入口 文件
*
* @param env RoundEnvironment
*/
private void generateEntryCode(RoundEnvironment env) {
EntryVistor entryVistor = new EntryVistor();
//processingEnv 是父类过来的
entryVistor.setmFiler(processingEnv.getFiler());
scan(env, EntryGeneral.class, entryVistor);
}
}
注意: @AutoService(Processor.class) 这个注解一定要加,auto-services是一个注解处理器,会在编译时为该module生成声明文件。
上面代码提到的 EntryVistor 是 SimpleAnnotationValueVisitor7 的子类。
关于 SimpleAnnotationValueVisitor7 官方的解释是这样的:
A simple visitor for annotation values with default behavior appropriate for the RELEASE_7 source version. Visit methods call defaultAction passing their arguments to defaultAction’s corresponding parameters.
我个人的理解就是它的作用就是可以访问到注解里的值
下面是 EntryVistor 的代码
public final class EntryVistor extends SimpleAnnotationValueVisitor7<Void, Void> {
private Filer mFiler = null;
private TypeMirror mTypeMirror = null;
private String mPackageName = null;
public void setmFiler(Filer mFiler) {
this.mFiler = mFiler;
}
@Override
public Void visitString(String s, Void aVoid) {
mPackageName = s;
return aVoid;
}
@Override
public Void visitType(TypeMirror typeMirror, Void aVoid) {
mTypeMirror = typeMirror;
generateJavaCode();
return aVoid;
}
/**
* build WXEntryActivity.java
*/
private void generateJavaCode() {
final TypeSpec targetActivity =
TypeSpec.classBuilder("WXEntryActivity")
.addModifiers(Modifier.PUBLIC)
.addModifiers(Modifier.FINAL)
.superclass(TypeName.get(mTypeMirror))
.build();
final JavaFile javaFile = JavaFile.builder(mPackageName + ".wxapi", targetActivity)
.addFileComment("微信入口文件").build();
try {
javaFile.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
System.out.println("build WXEntryActivity.java failed");
}
}
}
JavaFile.builder(String packageName, TypeSpec typeSpec); packageName: 包名 typeSpec : 一个 class 文件的定义
到这里我们就完成了我们通过 APT 生成 WXEntryActivity.java 文件。
-
使用 @EntryGeneral
我们随便新建一个类或者接口注解都行,这里我就新建一个 @WXEntry 的注解
@EntryGeneral(
packageName = "你的包路径",//你的app项目包路径
entryTemplete = WeChatSignTemple.class //你要继承的类
)
public @interface WXSignEntry {
}
注意: 在你这个注解所在的 module 引入 compile 和 annotation 的 module
annotationProcessor project(':compile')
api project(':annotation')
好了,见证奇迹的时刻到了。我们 build 一下项目。
build 完看一下 app -> build -> source -> apt -> debug -> 包名 -> wxapi 有没有 WXEntryActivity.java 文件。不知道你们有没有反正我有,截图为证。
我们来看一下我们生成的 WXEntryActivity 是怎样的
WXEntryActivity 继承了我们自己写的 WeChatSignTemple ,所以这里就很好理解了,我们在 WeChatSignTemple 写的在 WXEntryActivity 就可以实现了。
上面代码所提到的 WeChatSignTemple 这个类是真正你对微信进行处理的类,也就是我们原先放到 WXEntryActivity 这个类里的东西。
接下来就可以好好来写我们的这个 WeChatSignTemple 类了
对接微信SDK
-
添加微信相关的依赖
在我们 app 的 gradle 里导入我们的微信依赖以及 网络依赖(这个个人喜欢就行)
// 微信相关
api 'com.tencent.mm.opensdk:wechat-sdk-android-without-mta:+'
-
整体项目结构
我们先捋一捋我们的 app module 的项目结构
- ConstantUtils 这个用来获取到全局的Context 记得要在 Application 中初始化
- WXSignEntry 微信登录注解,直接在我们要使用到的 Activity 加上这个注解
- BaseWeChatEntryActivity 微信入口类的基类(后面我们的微信登录,支付和分享都要继承到),做绑定 IWXAPIEventHandler 的
- IWXSignCallBack 微信登录回调接口
- WeChatSignTemple WXEntryActivity 的父类,做微信登录的具体操作
- Config 微信配置信息 APPID 之类的
- WeChatApi 调用微信相关东西的工具类(可以获取到IWXAPI 设置登录回调函数 IWXSignCallBack ,发起登录等等)
接下来具体讲一下类
- WeChatAPI
public class WeChatAPI {
private IWXAPI iwxapi;
//微信登录回调接口
private IWXSignCallBack weChatSignInCallBack;
private static final class Holder {
private static final WeChatAPI WECHAT_API = new WeChatAPI();
}
public static WeChatAPI getInstance() {
return Holder.WECHAT_API;
}
private WeChatAPI() {
//实例化IWXAPI
iwxapi = WXAPIFactory.createWXAPI(ConstantUtils.getAPPContext(), Config.APP_ID, true);
iwxapi.registerApp(Config.APP_ID);
}
// 获取到 IWXAPI
public final IWXAPI getWXAPI() {
return iwxapi;
}
//设置微信登录回调接口
public WeChatAPI setSingInCallBack(IWXSignCallBack weChatSignInCallBack) {
this.weChatSignInCallBack = weChatSignInCallBack;
return this;
}
//获取到微信登录回调接口
public IWXSignCallBack getWeChatSignInCallBack() {
return weChatSignInCallBack;
}
/**
* 发起登录
*/
public void signIn() {
final SendAuth.Req req = new SendAuth.Req();
req.scope = "snsapi_userinfo";
req.state = "random_state";
iwxapi.sendReq(req);
}
}
这个类主要是做发起登录并且设置微信登录接口回调作用的(后期可以加入支付分享相关的)
- BaseWeChatEntryActivity
public abstract class BaseWeChatEntryActivity extends AppCompatActivity implements IWXAPIEventHandler {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WeChatAPI.getInstance().getWXAPI().handleIntent(getIntent(), this);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
WeChatAPI.getInstance().getWXAPI().handleIntent(getIntent(), this);
}
}
这个是我们所有的微信相关(登录,支付,分享)的基类,做绑定 IWXAPIEventHandler 的。
IWXAPIEventHandler 提供了两个接口分别是
- void onReq(BaseReq var1); //微信发送请求到APP后的回调
- void onResp(BaseResp var1); //APP发送请求到微信后的回调
-
WeChatSignTemple
在这个接口写具体的微信登录相关的操作
@Override
protected void onResume() {
super.onResume();
//再次进入这个页面直接消失
finish();
//无动画效果
overridePendingTransition(0, 0);
}
@Override
public void onReq(BaseReq baseReq) {
}
//APP发送请求到微信后的回调
@Override
public void onResp(BaseResp baseResp) {
if (baseResp.errCode == BaseResp.ErrCode.ERR_OK) {
getAccessToken(((SendAuth.Resp) baseResp).code);
} else {
Toast.makeText(this, "微信授权登录失败", Toast.LENGTH_SHORT).show();
finish();
}
}
//获取到token
private void getAccessToken(String code) {
OkHttpUtils.get("https://api.weixin.qq.com/sns/oauth2/access_token?APPID=" + Config.APP_ID +
"&secret=" + Config.APP_SECRET +
"&code=" + code +
"&grant_type=authorization_code").tag(this)
.execute(new StringCallback() {
@Override
public void onSuccess(String s, Call call, Response response) {
try {
JSONObject object = new JSONObject(s);
String acceeeToken = object.getString("access_token");
String open_id = object.getString("openid");
getInfo(acceeeToken, open_id);
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(WeChatSignTemple.this, "微信授权登录失败", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onError(Call call, Response response, Exception e) {
Toast.makeText(WeChatSignTemple.this, "微信授权登录失败", Toast.LENGTH_SHORT).show();
}
});
}
//获取用户数据
private void getInfo(String at, String open_id) {
OkHttpUtils.get("https://api.weixin.qq.com/sns/userinfo?access_token="
+ at + "&openid=" + open_id).tag(this)
.execute(new StringCallback() {
@Override
public void onSuccess(String s, Call call, Response response) {
WeChatAPI.getInstance().getWeChatSignInCallBack().signSuccess(s);
}
@Override
public void onError(Call call, Response response, Exception e) {
super.onError(call, response, e);
Toast.makeText(WeChatSignTemple.this, "微信授权登录失败", Toast.LENGTH_SHORT).show();
}
});
}
这里发起了两次网络请求(这个没办法微信就是这么要求,包括传那些数据)所以我们的微信授权登录是会相对慢一点的。
我们到这里就基本完成了我们的博客的 title 说的东西啦~
对了别忘记在 app 的 AndroidManifest.xml 加入 WXEntryActivity(如果有用到支付也需要增加.wxapi.WXPayEntryActivity 这个 Activity ,别忘记了)
<activity
android:name=".wxapi.WXEntryActivity"
android:exported="true"
android:label="微信回调Activity"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Translucent">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
还有别忘记添加网络权限
Theme.AppCompat.Translucent 是我自己写的一个透明主题
在style.xml加入
<!--透明Activity-->
<style name="Theme.AppCompat.Translucent">
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowAnimationStyle">@android:style/Animation</item>
</style>
好了然后就回到我们开头的代码了
- MainActivity
public class MainActivity extends AppCompatActivity {
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
WeChatAPI.getInstance().setSingInCallBack(new IWXSignCallBack() {
@Override
public void signSuccess(String info) {
Toast.makeText(MainActivity.this, "info: " + info, Toast.LENGTH_SHORT).show();
}
}).signIn();
}
});
}
}
只需要简单的在 Activity 设置登录回调发起登录,简简单单几句代码就可以完成。
End
其实微信支付和分享其实也差不多了,只是时间的问题,就不赘述啦~
鉴于很多人说编译出来并没有对应的文件,这个链接是完成了第一步的源码,我更希望大家能自己弄出来的~
链接:https://pan.baidu.com/s/1Sdc2JDC9yuQzxZT6kqmWiA
提取码:9x90