天天看点

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