天天看點

Android繞過微信包名限制對接微信登入和支付前言最終效果原理代碼End

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 裡面沒有”。

畫面效果不好,意思意思就行了

​​

​​

Android繞過微信包名限制對接微信登入和支付前言最終效果原理代碼End
  • 添加依賴

    在 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 檔案。不知道你們有沒有反正我有,截圖為證。

Android繞過微信包名限制對接微信登入和支付前言最終效果原理代碼End

我們來看一下我們生成的 WXEntryActivity 是怎樣的

Android繞過微信包名限制對接微信登入和支付前言最終效果原理代碼End

WXEntryActivity 繼承了我們自己寫的 WeChatSignTemple ,是以這裡就很好了解了,我們在 WeChatSignTemple 寫的在 WXEntryActivity 就可以實作了。

上面代碼所提到的 WeChatSignTemple 這個類是真正你對微信進行處理的類,也就是我們原先放到 WXEntryActivity 這個類裡的東西。

接下來就可以好好來寫我們的這個 WeChatSignTemple 類了

對接微信SDK

  • 添加微信相關的依賴

    在我們 app 的 gradle 裡導入我們的微信依賴以及 網絡依賴(這個個人喜歡就行)

// 微信相關
api 'com.tencent.mm.opensdk:wechat-sdk-android-without-mta:+'
           
  • 整體項目結構

    我們先捋一捋我們的 app module 的項目結構

    Android繞過微信包名限制對接微信登入和支付前言最終效果原理代碼End
  • 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 提供了兩個接口分别是

  1. void onReq(BaseReq var1); //微信發送請求到APP後的回調
  2. 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