微信開放平臺開發系列文章:
目錄
4)根據服務端返回數據進行解析並顯示給前端Android頁面
當微信開放平臺開發第三方授權登陸(一):開發前期準備完成後,已經獲取到應用的AppID和AppSecret、且已經成功申請到微信登陸功能。可以進行第三方登陸授權開發。
注意:
目前移動應用上微信登錄只提供原生的登錄方式,需要用戶安裝微信客戶端才能配合使用。
對於Android應用,建議總是顯示微信登錄按鈕,當用戶手機沒有安裝微信客戶端時,請引導用戶下載安裝微信客戶端
開放平臺中創建移動應用時,需要添加包名(一定要和開發的包名完全一致,不能是填寫的包名的子包,否則微信無法回調成功)
安裝驗籤工具:Gen_Signature_Android2.apk。
填寫包名,然後會生成應用簽名,填寫應用簽名就可以了。
一、需求
擁有第三方微信登錄功能,並獲取到用戶信息。
二、開發流程
Android移動應用:(App喚醒微信客戶端授權登陸)
1. 應用發起微信授權登錄請求,用戶允許授權應用後,微信會拉起應用或重定向到第三方網站(服務端),並且帶上授權臨時票據code參數;
2. 通過code參數加上AppID和AppSecret等,通過API換取access_token;
3. 通過access_token進行接口調用,獲取用戶基本數據資源或幫助用戶實現基本操作。
獲取用戶基本信息的流程
三、開發使用的技術及工具
1、.後端採用IDEA2017 進行開發
2、使用Android Studio 3.1.3 進行開發
3、後端必須基於JDK7以上版本,採用JDK8開發,前端基於Android SDK4.4
4、使用fastJson對json數據進行處理
四、具體實現步驟
1.前端(Android)
目錄結構如下:
1)Android微信授權登錄開發環境配置
I.添加微信依賴
Android Studio環境
在build.gradle文件中,添加依賴
dependencies {
compile 'com.tencent.mm.opensdk:wechat-sdk-android-with-mta:+'
}
或
dependencies {
compile 'com.tencent.mm.opensdk:wechat-sdk-android-without-mta:+'
}
Eclipse環境下:
在工程中新建一個libs目錄,將開發工具包中libs目錄下的libammsdk.jar複製到該目錄中(如下圖所示,建立了一個名爲SDK_Sample 的工程,並把jar包複製到libs目錄下)。
右鍵單擊工程,選擇Build Path中的Configure Build Path...,選中Libraries這個tab,並通過Add Jars...導入工程libs目錄下的libammsdk.jar文件。(如下圖所示)。
在需要使用微信終端API的文件中導入相應的類。
import com.tencent.mm.opensdk.openapi.WXTextObject;
II. AndroidManifest.xml 設置
添加如下權限支持:
<!--權限聲明-->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
III.若要混淆代碼,爲保證sdk正常使用,需在配置proguard.cfg(proguard-rule.pro):
# wechat
-keep class com.tencent.mm.opensdk.** {*;}
-keep class com.tencent.wxop.** {*;}
-keep class com.tencent.mm.sdk.** {*;}
2)引導用戶點擊登錄並授權
I.layout.xml
添加button:
<Button
android:id="@+id/wechat_login_btn"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="@string/wechat_login"/>
II.監聽button點擊事件,拉起微信授權頁
findViewById(R.id.wechat_login_btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isWXAppInstalledAndSupported()) { // 用戶是否安裝微信客戶端
// send oauth request
final SendAuth.Req req = new SendAuth.Req();
req.scope = "snsapi_userinfo";
req.state = "none";
api.sendReq(req);
finish();
} else {
// TODO: 這裏需要引導用戶去下載微信客戶端
Toast.makeText(WXEntryActivity.this, "用戶沒有安裝微信", Toast.LENGTH_SHORT).show();
}
}
});
III.用戶手機是否安裝微信客戶端檢查
private boolean isWXAppInstalledAndSupported() {
IWXAPI msgApi = WXAPIFactory.createWXAPI(this, null);
msgApi.registerApp(Constants.APP_ID);
boolean sIsWXAppInstalledAndSupported = msgApi.isWXAppInstalled()
&& msgApi.isWXAppSupportAPI();
return sIsWXAppInstalledAndSupported;
}
3)接收微信服務端返回的數據並向服務端發送請求
用戶統一授權後,微信會返回數據,需要在.wxapi.WXEntryActivity下對數據進行處理。
I.新建wxapi包(包名固定,且必須是在微信開放平臺註冊的包名下)
II.新建Activity類,命名爲WXEntryActivity
WXEntryActivity,並繼承Activity類,實現IWXAPIEventHandler接口的兩個方法
public interface IWXAPIEventHandler {
void onReq(BaseReq var1);
void onResp(BaseResp var1);
}
WXEntryActivity實現
public class WXEntryActivity extends Activity implements IWXAPIEventHandler {
private IWXAPI api; // 在onCreate中進行了初始化
onReq方法
// 微信發送請求到第三方應用時,會回調到該方法
@Override
public void onReq(BaseReq req) {
Toast.makeText(this, "Test ", Toast.LENGTH_SHORT).show();
switch (req.getType()) {
case ConstantsAPI.COMMAND_GETMESSAGE_FROM_WX:
break;
case ConstantsAPI.COMMAND_SHOWMESSAGE_FROM_WX:
break;
default:
break;
}
}
onResp方法:
在onResp中需要實現邏輯,微信返回的數據在這裏會被接收。
微信返回的數據包含code。在onResp需要實現向服務端發送請求,帶上code等參數,後端再通過相應的參數去請求微信服務端,最終將獲取到的用戶信息返回給前端Android。
// 第三方應用發送到微信的請求處理後的響應結果,會回調到該方法
@Override
public void onResp(final BaseResp resp) {
int result = 0;
Toast.makeText(this, "baseresp.getType = " + resp.getType(), Toast.LENGTH_SHORT).show();
//成功後發送請求
switch (resp.errCode) {
case BaseResp.ErrCode.ERR_OK:
result = R.string.errcode_success;
final String code = ((SendAuth.Resp) resp).code;//需要轉換一下才可以
new Thread(new Runnable() {
@Override
public void run() {
//向服務端發送請求,預計返回用戶信息數據,返回給前端進行顯示。
String url = "http://p2a3b8.natappfree.cc" +
"/wechat/open/callback/android" + "?" +
"state=" + "android" +//這裏的state需與後端進行探討
"&code=" + code;
String str = ApacheHttpUtil.get(url);
JSONObject jsonObject = (JSONObject) JSONObject.parse(str);
weChatUserInfo = (WeChatUserInfo) JSON.parseObject(jsonObject.get("data").toString(), new TypeReference<WeChatUserInfo>() {
});
}
}).start();
while (true) {
// TODO: 這裏處理方案不合理,死循環或將造成界面卡死(需要前端優化)
if (weChatUserInfo != null) {
Intent intent = new Intent(WXEntryActivity.this, WechatUserInfoViewItem.class);
/* 通過Bundle對象存儲需要傳遞的數據 */
Bundle bundle = new Bundle();
bundle.putString("wechatopenid", weChatUserInfo.getOpenid());
bundle.putString("wechatnickname", weChatUserInfo.getNickname());
bundle.putString("wechatsex", weChatUserInfo.getSex().toString());
bundle.putString("wechatprovince", weChatUserInfo.getProvince());
bundle.putString("wechatcity", weChatUserInfo.getCity());
bundle.putString("wechatcountry", weChatUserInfo.getCountry());
bundle.putString("wechatheadimgurl", weChatUserInfo.getHeadimgurl());
bundle.putString("wechatprivilege", weChatUserInfo.getPrivilege());
bundle.putString("wechatunionid", weChatUserInfo.getOpenid());
/*把bundle對象assign給Intent*/
intent.putExtras(bundle);
startActivity(intent);
break;
}
}
break;
case BaseResp.ErrCode.ERR_USER_CANCEL:
result = R.string.errcode_cancel; // 發送取消
break;
case BaseResp.ErrCode.ERR_AUTH_DENIED:
result = R.string.errcode_deny; // 發送被拒絕
break;
case BaseResp.ErrCode.ERR_UNSUPPORT:
result = R.string.errcode_unsupported; // 不支持錯誤
break;
default:
result = R.string.errcode_unknown; // 發送返回
break;
}
Toast.makeText(this, result, Toast.LENGTH_LONG).show();
}
重寫onNewIntent方法
在WXEntryActivity中將接收到的intent及實現了IWXAPIEventHandler接口的對象傳遞給IWXAPI接口的handleIntent方法,
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
api.handleIntent(intent, this);
}
III.在manifest文件添加WXEntryActivity,並加上exported屬性,設置爲true,:
<activity
android:name=".wxapi.WXEntryActivity"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTask">
<!--android:launchMode="singleTop">-->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="sdksample" />
</intent-filter>
</activity>
4)根據服務端返回數據進行解析並顯示給前端Android頁面
獲取數據已經跳轉代碼如上(onResp方法中)。需要前端優化處理
public class WechatUserInfoViewItem extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.wechat_user_info);
Bundle bundle = this.getIntent().getExtras();
TextView wechatopenid = (TextView) findViewById(R.id.wechatopenid);
wechatopenid.setText(bundle.getString("wechatopenid"));
TextView wechatnickname = (TextView) findViewById(R.id.wechatnickname);
wechatnickname.setText(bundle.getString("wechatnickname"));
TextView wechatsex = (TextView) findViewById(R.id.wechatsex);
wechatsex.setText(bundle.getString("wechatsex"));
TextView wechatprovince = (TextView) findViewById(R.id.wechatprovince);
wechatprovince.setText(bundle.getString("wechatprovince"));
TextView wechatcity = (TextView) findViewById(R.id.wechatcity);
wechatcity.setText(bundle.getString("wechatcity"));
TextView wechatcountry = (TextView) findViewById(R.id.wechatcountry);
wechatcountry.setText(bundle.getString("wechatcountry"));
TextView wechatunionid = (TextView) findViewById(R.id.wechatunionid);
wechatunionid.setText(bundle.getString("wechatunionid"));
}
}
佈局xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/wechatheadimgurl"
android:layout_width="200dp"
android:layout_height="100dp"
android:scaleType="center"/>
<!--用戶特權信息,json 數組,如微信沃卡用戶爲(chinaunicom)[這裏是一個list]-->
<!--String privilege;-->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="openid:"
tools:layout_editor_absoluteX="158dp"
tools:layout_editor_absoluteY="216dp" />
<TextView
android:id="@+id/wechatopenid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:layout_editor_absoluteX="158dp"
tools:layout_editor_absoluteY="216dp" />
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="unionid:"
tools:layout_editor_absoluteX="158dp"
tools:layout_editor_absoluteY="216dp" />
<TextView
android:id="@+id/wechatunionid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:layout_editor_absoluteX="158dp"
tools:layout_editor_absoluteY="216dp" />
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="用戶暱稱:"
tools:layout_editor_absoluteX="158dp"
tools:layout_editor_absoluteY="216dp" />
<TextView
android:id="@+id/wechatnickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:layout_editor_absoluteX="85dp"
tools:layout_editor_absoluteY="212dp" />
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="用戶城市:"
tools:layout_editor_absoluteX="158dp"
tools:layout_editor_absoluteY="216dp" />
<TextView
android:id="@+id/wechatcity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:layout_editor_absoluteX="118dp"
tools:layout_editor_absoluteY="302dp" />
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="用戶性別:"
tools:layout_editor_absoluteX="158dp"
tools:layout_editor_absoluteY="216dp" />
<TextView
android:id="@+id/wechatsex"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:layout_editor_absoluteX="118dp"
tools:layout_editor_absoluteY="302dp" />
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="省份:"
tools:layout_editor_absoluteX="158dp"
tools:layout_editor_absoluteY="216dp" />
<TextView
android:id="@+id/wechatprovince"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:layout_editor_absoluteX="118dp"
tools:layout_editor_absoluteY="302dp" />
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="國家:"
tools:layout_editor_absoluteX="158dp"
tools:layout_editor_absoluteY="216dp" />
<TextView
android:id="@+id/wechatcountry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:layout_editor_absoluteX="118dp"
tools:layout_editor_absoluteY="302dp" />
</LinearLayout>
</LinearLayout>
2.服務端(Java)
服務端需要做的是:接受Android前端發送的請求,獲取code,根據AppId和APPSecret等向微信服務端發送請求,然後獲取到Token,再根據Token獲取到用戶基本信息。最終通過JSON的方式返回給前端。
1).統一返回JSON
@Getter @Setter
public class Result<T> {
private int code;
private String msg;
private T data;
/*** 成功時候的調用* */
public static <T> Result<T> success(T data){
return new Result<T>(data);
}
/*** 失敗時候的調用* */
public static <T> Result<T> error(CodeMsg cm){
return new Result<T>(cm);
}
private Result(T data) {
this.code = 0;
this.msg = "success";
this.data = data;
}
private Result(CodeMsg cm) {
if(cm == null) {
return;
}
this.code = cm.getCode();
this.msg = cm.getMsg();
}
}
2).相關參數配置:
# 微信開放平臺Android
wechat.open.android.appid =
wechat.open.android.appsecret =
3).請求響應邏輯
@ResponseBody
@RequestMapping("/callback/android")
public Result openWeChatCallback(HttpServletRequest httpServletRequest) {
String code = httpServletRequest.getParameter("code");
//String state = httpServletRequest.getParameter("state"); // TODO:
String url = null;
url = "https://api.weixin.qq.com/sns/oauth2/access_token?" +
"appid=" +
env.getProperty("wechat.open.android.appid").trim() +
"&secret=" +
env.getProperty("wechat.open.android.appsecret").trim() +
"&code=" +
code +
"&grant_type=authorization_code";
JSONObject wechatAccessToken = HttpClientUtils.httpGet(url);
if (wechatAccessToken.get("errcode") != null) {
return Result.error(CodeMsg.FAIL_GETTOKEN);
}
String accessToken = (String) wechatAccessToken.get("access_token");
String openid = (String) wechatAccessToken.get("openid");
String unionid = (String) wechatAccessToken.get("unionid");
if (StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(openid) || StringUtils.isEmpty(unionid)) {
return Result.error(CodeMsg.FAIL_GETTOKEN);
}
// TODO:根據Openid或Unionid對數據庫進行查詢,如果查詢到對應的用戶數據,則不需要再向微信服務器發送請求去返回數據。
// TODO: 建議使用Unionid作爲查詢條件。
WeChatUserInfo weChatUserInfo = null;
wechatAccessToken = null; // FIXME: 這裏應該是從數據庫中查詢獲取用戶信息邏輯。
if (wechatAccessToken == null) {
// 新用戶
weChatUserInfo = getUserInfoByAccessToken(accessToken);
// 數據庫插入的操作
}
if (weChatUserInfo != null) {
return Result.success(weChatUserInfo);
}
return Result.error(CodeMsg.FAIL_GETUSERINFO);
}
4).根據Token獲取用戶信息:
/**
* 根據accessToken獲取用戶個人公開信息
*
* @param accessToken
* @return
*/
private WeChatUserInfo getUserInfoByAccessToken(String accessToken) {
if (StringUtils.isEmpty(accessToken)) {
return null; //"accessToken爲空";
}
String get_userInfo_url = "https://api.weixin.qq.com/sns/userinfo?" +
"access_token=" +
accessToken +
"&openid=" +
env.getProperty("wechat.open.android.appid").trim();
String userInfo_result = HttpClientUtils.httpGet(get_userInfo_url, "utf-8");
if (!userInfo_result.equals("errcode")) {
WeChatUserInfo weChatUserInfo = JSON.parseObject(userInfo_result, new TypeReference<WeChatUserInfo>() {
});
// TODO: 需要把頭像信息下載到文件服務器,然後替換掉頭像URL。微信的或許不可靠,假設微信用戶更換了頭像,舊頭像URL是否會保存?而這個URL信息卻存放在我們的數據庫中,不可靠
return weChatUserInfo;
}
return null; //"獲取用戶信息失敗"
}
五、注意事項:
1.Android4.0以上版本,發送網絡請求時,必須是以線程異步的方式發送請求,否則發送請求會失敗。
六、應用關鍵參數位置