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