什么,集成微信登录只要三行代码

2,897 阅读6分钟
原文链接: www.jianshu.com

对你没有看错,不是标题党,真的只要三行代码,先上结论代码:

  1. 在Application的onCreate中写:
    // GeneralAppliction.java
    public static IWXAPI sApi;
    @Override
    public void onCreate() {
     super.onCreate();
     sApi = WXEntryActivity.initWeiXin(this, AppConst.WEIXIN_APP_ID);
    }
  2. 在需要登录的地方添加:
    // MainActivity.java
    WXEntryActivity.loginWeixin(MainActivity.this, GeneralAppliction.sApi);

下面对具体的集成步骤做详细的描述。

集成步骤:

  1. 在开放平台注册创建应用,申请登录权限
  2. 下载sdk,拷贝相关文件到项目工程目录
  3. 全局初始化微信组件
  4. 请求授权登录,获取code
  5. 通过code获取授权口令access_token
  6. 在第5步判断access_token是否存在和过期
  7. 如果access_token过期无效,就用refresh_token来刷新
  8. 使用access_token获取用户信息

1. 在开放平台注册创建应用,申请登录权限

这一步其实不用怎么讲,无法就是在微信开放平台上注册一个账号,然后创建移动应用。


Paste_Image.png


需要注意的是:应用签名的部分


Paste_Image.png


此处应用签名我使用的是线上的key的md5,关于这个需要注意的问题可以看我的另外一篇文章:Android的签名总结

2. 下载sdk,拷贝相关文件到项目工程目录

下载后把libammsdk.jar文件拷贝到AS工程的libs目录,并把示例Demo里源文件目录下的wxapi目录整个拷贝到,工程目录的src下的根包下:


微信目录所放的正确位置.png


如果wxapi这个文件夹放的位置不对,讲无法登录,微信sdk无法找到登录的Activity授权功能。然后在Manifest.xml里面加入:

3. 全局初始化微信组件

全局初始化微信组件,当然是Application的onCreate里(当然Activity的onCreate也是可以的,为了全局使用微信api对象方便操作):

@Override
public void onCreate() {    
    super.onCreate();
    // 初始化微信组件
    initWeiXin();
}

public static IWXAPI sApi;
private void initWeiXin() {
    sApi = WXEntryActivity.initWeiXin(this, AppConst.WEIXIN_APP_ID);
}

4. 请求授权登录,获取code

为了同一业务的单一原则我把微信相关的都统一封装到了wxapi包下和WXEntryActivity中:

// 实现IWXAPIEventHandler 接口,以便于微信事件处理的回调
public class WXEntryActivity extends Activity implements IWXAPIEventHandler {

    private static final String WEIXIN_ACCESS_TOKEN_KEY = "wx_access_token_key";
    private static final String WEIXIN_OPENID_KEY = "wx_openid_key";
    private static final String WEIXIN_REFRESH_TOKEN_KEY = "wx_refresh_token_key";

    private Gson mGson;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 微信事件回调接口注册
        GeneralAppliction.sApi.handleIntent(getIntent(), this);
        mGson = new Gson();
    }

    /**
     * 微信组件注册初始化
     * @param context       上下文
     * @param weixin_app_id appid
     * @return              微信组件api对象
     *
    /
    public static IWXAPI initWeiXin(Context context, @NonNull String weixin_app_id) {
        if (TextUtils.isEmpty(weixin_app_id)) {
            Toast.makeText(context.getApplicationContext(), "app_id 不能为空", Toast.LENGTH_SHORT).show();
        }
        IWXAPI api = WXAPIFactory.createWXAPI(context, weixin_app_id, true);
        api.registerApp(weixin_app_id);
        return api;
    }

    /** 
    * 登录微信 
    * 
    * @param api 微信服务api 
    */
    public static void loginWeixin(Context context, IWXAPI api) {
        // 判断是否安装了微信客户端    
        if (!api.isWXAppInstalled()) {  
            Toast.makeText(context.getApplicationContext(), "您还未安装微信客户端!", Toast.LENGTH_SHORT).show(); 
           return;    
        }
        // 发送授权登录信息,来获取code
        SendAuth.Req req = new SendAuth.Req();    
        // 应用的作用域,获取个人信息
        req.scope = "snsapi_userinfo";    
        /**     
        * 用于保持请求和回调的状态,授权请求后原样带回给第三方
        * 为了防止csrf攻击(跨站请求伪造攻击),后期改为随机数加session来校验     
        */    
        req.state = "app_wechat";
        api.sendReq(req);
    }

    // 微信发送请求到第三方应用时,会回调到该方法
    @Override
    public void onReq(BaseReq req) {    
        switch (req.getType()) {        
            case ConstantsAPI.COMMAND_GETMESSAGE_FROM_WX: 
              break;        
            case ConstantsAPI.COMMAND_SHOWMESSAGE_FROM_WX:
              break;        
            default:            
              break;    
        }
    }
    // 第三方应用发送到微信的请求处理后的响应结果,会回调到该方法
    @Override
    public void onResp(BaseResp resp) {
        switch (resp.errCode) {   
            // 发送成功     
            case BaseResp.ErrCode.ERR_OK:
                // 获取code
                String code = ((SendAuth.Resp) resp).code;
                // 通过code获取授权口令access_token
                getAccessToken(code);
            break;
        }
    }
}

小伙伴有疑问code是啥玩意:
第三方通过code进行获取access_token的时候需要用到,code的超时时间为10分钟,一个code只能成功换取一次access_token即失效。code的临时性和一次保障了微信授权登录的安全性。第三方可通过使用https和state参数,进一步加强自身授权登录的安全性。

这样客户端使用的地方只要:

WXEntryActivity.loginWeixin(MainActivity.this, GeneralAppliction.sApi);

5. 通过code获取授权口令access_token

我们在onResp的回调方法中获取了code,然后通过code获取授权口令access_token:

/** 
* 获取授权口令 
*/
private void getAccessToken(String code) {    
    String url = "https://api.weixin.qq.com/sns/oauth2/access_token?" +
       "appid=" + AppConst.WEIXIN_APP_ID +
       "&secret=" + AppConst.WEIXIN_APP_SECRET +
       "&code=" + code +
       "&grant_type=authorization_code";    
    // 网络请求获取access_token    
    httpRequest(url, new ApiCallback() {        
        @Override        
        public void onSuccess(String response) {            
            Logger.e(response);            
            // 判断是否获取成功,成功则去获取用户信息,否则提示失败            
            processGetAccessTokenResult(response);        
        }        
        @Override        
        public void onError(int errorCode, final String errorMsg) {
            Logger.e(errorMsg);            
            showMessage("错误信息: " + errorMsg);        
        }        
        @Override        
        public void onFailure(IOException e) {
            Logger.e(e.getMessage());            
            showMessage("登录失败");        
        }    
    });
}

/** 
* 处理获取的授权信息结果 
* @param response  授权信息结果 
*/
private void processGetAccessTokenResult(String response) {    
    // 验证获取授权口令返回的信息是否成功
    if (validateSuccess(response)) {
        // 使用Gson解析返回的授权口令信息        
        WXAccessTokenInfo tokenInfo = mGson.fromJson(response, WXAccessTokenInfo.class);
        Logger.e(tokenInfo.toString());
        // 保存信息到手机本地
        saveAccessInfotoLocation(tokenInfo);
        // 获取用户信息        
        getUserInfo(tokenInfo.getAccess_token(), tokenInfo.getOpenid());    
    } else {
        // 授权口令获取失败,解析返回错误信息        
        WXErrorInfo wxErrorInfo = mGson.fromJson(response, WXErrorInfo.class);        
        Logger.e(wxErrorInfo.toString()); 
        // 提示错误信息
        showMessage("错误信息: " + wxErrorInfo.getErrmsg());    
    }
}

/** 
* 验证是否成功 
* 
* @param response 返回消息 
* @return 是否成功 
*/
private boolean validateSuccess(String response) {    
    String errFlag = "errmsg";    
    return (errFlag.contains(response) && !"ok".equals(response))            
            || (!"errcode".contains(response) && !errFlag.contains(response));
}

6. 在第5步判断access_token是否存在和过期

在回调的onResp方法中获取code后,处理access_token是否登录过或者过期的问题:

// 从手机本地获取存储的授权口令信息,判断是否存在access_token,不存在请求获取,存在就判断是否过期
String accessToken = (String) ShareUtils.getValue(this, WEIXIN_ACCESS_TOKEN_KEY, "none");
String openid = (String) ShareUtils.getValue(this, WEIXIN_OPENID_KEY, "");
if (!"none".equals(accessToken)) {
    // 有access_token,判断是否过期有效
    isExpireAccessToken(accessToken, openid);
} else {
    // 没有access_token
    getAccessToken(code);
}

判断授权口令是否有效:

/** 
* 判断accesstoken是过期 
* @param accessToken   token 
* @param openid        授权用户唯一标识 
*/
private void isExpireAccessToken(final String accessToken, final String openid) {
    String url = "https://api.weixin.qq.com/sns/auth?" + 
           "access_token=" + accessToken + 
           "&openid=" + openid;
    httpRequest(url, new ApiCallback() {
        @Override
        public void onSuccess(String response) {
            Logger.e(response);
            if (validateSuccess(response)) {
                // accessToken没有过期,获取用户信息
                getUserInfo(accessToken, openid);
            } else {
                // 过期了,使用refresh_token来刷新accesstoken
                refreshAccessToken();
            }
        }
        @Override
        public void onError(int errorCode, final String errorMsg) {
            Logger.e(errorMsg);
            showMessage("错误信息: " + errorMsg);
        }
        @Override
        public void onFailure(IOException e) {
            Logger.e(e.getMessage());
            showMessage("登录失败");
        }
    });
}

7. 如果access_token过期无效,就用refresh_token来刷新

/**
 * 刷新获取新的access_token
 *
/
private void refreshAccessToken() {
    // 从本地获取以存储的refresh_token
    final String refreshToken = (String) ShareUtils.getValue(this, WEIXIN_REFRESH_TOKEN_KEY, "");
    if (TextUtils.isEmpty(refreshToken)) {
        return;
    }
    // 拼装刷新access_token的url请求地址
    String url = "https://api.weixin.qq.com/sns/oauth2/refresh_token?" +
            "appid=" + AppConst.WEIXIN_APP_ID +
            "&grant_type=refresh_token" +
            "&refresh_token=" + refreshToken;
    // 请求执行
    httpRequest(url, new ApiCallback() {
        @Override
        public void onSuccess(String response) {
            Logger.e("refreshAccessToken: " + response);
            // 判断是否获取成功,成功则去获取用户信息,否则提示失败
            processGetAccessTokenResult(response);
        }
        @Override
        public void onError(int errorCode, final String errorMsg) {
            Logger.e(errorMsg);
            showMessage("错误信息: " + errorMsg);
            // 重新请求授权
            loginWeixin(WXEntryActivity.this.getApplicationContext(),        GeneralAppliction.sApi);
        }
        @Override
        public void onFailure(IOException e) {
            Logger.e(e.getMessage());
            showMessage("登录失败");
            // 重新请求授权
            loginWeixin(WXEntryActivity.this.getApplicationContext(),        GeneralAppliction.sApi);
        }
    });
}

8. 使用access_token获取用户信息

/**
 * 获取用户信息
 *
/
private void getUserInfo(String access_token, String openid) {
    String url = "https://api.weixin.qq.com/sns/userinfo?" +
            "access_token=" + access_token +
            "&openid=" + openid;
    httpRequest(url, new ApiCallback() {
        @Override
        public void onSuccess(String response) {
            // 解析获取的用户信息
            WXUserInfo userInfo = mGson.fromJson(response, WXUserInfo.class);
            Logger.e("用户信息获取结果:" + userInfo.toString());        }
        @Override
        public void onError(int errorCode, String errorMsg) {
            showMessage("错误信息: " + errorMsg);
        }
        @Override
        public void onFailure(IOException e) {
            showMessage("获取用户信息失败");
        }
    });
}

通信部分

private OkHttpClient mHttpClient = new OkHttpClient.Builder().build();
private Handler mCallbackHandler = new Handler(Looper.getMainLooper());
/**
 * 通过Okhttp与微信通信
 * * @param url 请求地址
 * @throws Exception
 */
public void httpRequest(String url, final ApiCallback callback) {
    Logger.e("url: %s", url);
    final Request request = new Request.Builder()
            .url(url)
            .get()
            .build();
    mHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, final IOException e) {
            if (callback != null) {
                mCallbackHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        // 请求失败,主线程回调
                        callback.onFailure(e);
                    }
                });
            }
        }
        @Override
        public void onResponse(Call call, final Response response) throws IOException {
            if (callback != null) {
                if (!response.isSuccessful()) {
                    mCallbackHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            // 请求出错,主线程回调
                            callback.onError(response.code(), response.message());
                        }
                    });
                } else {
                    mCallbackHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                // 请求成功,主线程返回请求结果
                                callback.onSuccess(response.body().string());
                            } catch (final IOException e) {
                                // 异常出错,主线程回调
                                mCallbackHandler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        callback.onFailure(e);
                                    }
                                });
                            }
                        }
                    });
                }
            }
        }
    });
}

// Api通信回调接口
public interface ApiCallback {
    /**
     * 请求成功
     *
     * @param response 返回结果
     */
    void onSuccess(T response);
    /**
     * 请求出错
     *
     * @param errorCode 错误码
     * @param errorMsg  错误信息
     */
    void onError(int errorCode, String errorMsg);
    /**
     * 请求失败
     */
    void onFailure(IOException e);
}

总结

集成的详细描述就这样,至于获取的用户信息,小伙伴们应该知道后续自己业务的需求,该怎么处理了。