編輯:關於Android編程
前段時間無聊玩了玩微信公告號的實現,現在簡單介紹一下微信公告號的實現原理;
開發者模式:開發者模式其實就是,使用自己的服務器,你可以選擇任何一種後台web開發語言,我以java web的實現;
數據傳遞:手機app微信客服端發送數據,數據先到微信服務器,然後微信服務器直
下面的json解析以JSONObject.fromObject(即JSONObject和JSONArray)的方式解析,最簡單的方式,為簡單案列使用的,對於復制項目,不推薦使用這種方式,建議使用gson包或者fastJSON包或者alibaba包,原理請自行查看
appID 和 appsecret;是在微信公告號官網可以查看到的,。我們利用他獲取憑證,每獲取的憑證有效期只能是2小時;
public final static String appID = “××××××××”;
public final static String appsecret = “××××××××”;
下面是獲取平常或解析後的結果;
/**
* 獲取接口訪問的憑證
* @param appid
* @param appsecret
* @return Token的對象數據
*/
public static Token getToken(String appid,String appsecret){
Token token = null;
String requestUrl = token_url.replace("APPID", appid).replace(
"APPSECRET",appsecret);
JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
if(jsonObject != null){
try {
token = new Token();
token.setAccessToken(jsonObject.getString("access_token"));
token.setExpiresIn(jsonObject.getInt("expires_in"));
} catch (Exception e) {
token = null;
logger.error("獲取 token 失敗,errcode : {} errmsg {}",
jsonObject.getInt("errcode"),jsonObject.getString("errmsg"));
}
}
return token;
}
對請求的封裝:
/**
* 發送 https 請求
* @param requestUrl 訪問的url
* @param requestMethod GET/POST
* @param outputStr 是否有輸出的數據流
* @return JSON對象。服務器返回的數據,json對象
*/
public static JSONObject httpRequest(String requestUrl,String requestMethod,
String outputStr){
JSONObject jsonObject = null;
try {
//創建SSL對象
TrustManager[] tm = {new MyX509TrustManager()};
SSLContext sslContext = SSLContext.getInstance("SSL","SunJSSE");
sslContext.init(null, tm, new SecureRandom());
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url = new URL(requestUrl);
HttpsURLConnection conn =(HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(ssf);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
//設置請求方式(get post)
conn.setRequestMethod(requestMethod);
//當 outputStr 不為null是 。想輸出流寫數據
if(null != outputStr){
OutputStream outputStream = conn.getOutputStream();
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
//從輸入流中讀取返回的數據
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = "";
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine())!= null) {
buffer.append(str);
}
//釋放資源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
inputStream = null;
conn.disconnect();
jsonObject = JSONObject.fromObject(buffer.toString());
} catch(ConnectException ceException){
logger.error("鏈接超過時間:{}",ceException);
}catch (Exception e) {
logger.error("https 異常 :{}",e);
}
return jsonObject;
}
有了憑證,就可以修改微信公告號的界面效果等很多功能了。比如修改界面;
/**
* 創建 菜單
* @param menu 菜單對象
* @param accessToken 憑證
* @return 返回是否 成功或者失敗
*/
public static boolean createMenu(Menu menu, String accessToken) {
boolean result = false;
String url = menu_create_url.replace("ACCESS_TOKEN", accessToken);
// 菜單 對象 轉化 成 json字符串
String jsonMenu = JSONObject.fromObject(menu).toString();
System.out.println(jsonMenu);
// 發送post請求
JSONObject jsonObject = CommonUtil.httpRequest(url, "POST", jsonMenu);
if (jsonObject != null) {
int errorCode = jsonObject.getInt("errcode");
String errorMsg = jsonObject.getString("errmsg");
if (0 == errorCode) {
result = true;
} else {
result = false;
logger.error("創建菜單失敗 errorCode : {} errmsg : {}", errorCode,
errorMsg);
}
}
return result;
}
/**
* 查詢 菜單
* @param accessToken
* @return
*/
public static String getMenu(String accessToken){
String result = null;
String requestUrl = menu_get_url.replace("ACCESS_TOKEN", accessToken);
//發起get請求
JSONObject jsonObject = CommonUtil.httpRequest(requestUrl, "GET", null);
if(null != jsonObject){
result = jsonObject.toString();
}
return result;
}
/**
* 刪除 菜單 功能
* @param accessToken
* @return
*/
public static boolean deleteMenu(String accessToken){
boolean result = false;
String requestUrl = menu_delete_url.replace("ACCESS_TOKEN",accessToken);
//
JSONObject jsonObject = CommonUtil.httpRequest(requestUrl, "GET",null);
if(jsonObject != null){
int errorCode = jsonObject.getInt("errcode");
String errorMsg = jsonObject.getString("errmsg");
if(errorCode == 0){
result = true;
}else{
result = false;
}
}
return result;
}
// 創建菜單(post)
public final static String menu_create_url = ""
+ "https://api.weixin.qq.com/cgi-bin/menu/create?access_token"
+ "=ACCESS_TOKEN";
// 菜單查詢(get)
public final static String menu_get_url = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token="
+ "ACCESS_TOKEN";
// 菜單刪除(GET)
public final static String menu_delete_url = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token"
+ "=ACCESS_TOKEN";
下面說名一下 微信客服端訪問的
創建自己的服務器,要在 微信公告號 處添加
URL(服務器地址);
Token(令牌);和其他的;
url地址寫服務器的訪問地址;
Token可以任意寫,但是要和服務器代碼裡面的一樣,因為微信的訪問是安全加密的,通過一些列的加密後配對成功才可以發送接受消息;
這是在代碼裡面寫的;
// 與接口 配置 信息的中 的 Token 要一致
private static String token = "AlphabetMan";
微信驗證簽名主要就是吧token和timetamp和nonce進行字典排序,然後進行sha1加密算法在轉發成String;
網上找的實現原理:
/**
* 驗證簽名
* @param signature
* @param timestamp
* @param nonce
* @return
*/
public static boolean checkSignature(String signature, String timestamp,
String nonce) {
String[] arr = new String[] { token, timestamp, nonce };
// 將token timestamp nonce 三個參數進行字典排序
Arrays.sort(arr);
StringBuilder content = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
content.append(arr[i]);
}
MessageDigest md = null;
String tmpStr = null;
try {
md = MessageDigest.getInstance("SHA-1");
// 將三個參數字符串拼接一個 字符串 進行sha1加密
byte[] digest = md.digest(content.toString().getBytes());
tmpStr = byteToStr(digest);
} catch (Exception e) {
e.printStackTrace();
}
content = null;
return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
}
/**
* 字節數組 將 字節數組 轉化為 16 進制字符串
*
* @param byteArray
* @return
*/
private static String byteToStr(byte[] byteArray) {
String strDigest = "";
for (int i = 0; i < byteArray.length; i++) {
strDigest += byteToHexStr(byteArray[i]);
}
return strDigest;
}
/**
* 字節 將字節轉化為 16 進制字符串
*
* @param mByte
* @return
*/
private static String byteToHexStr(byte mByte) {
char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
char[] tempArr = new char[2];
tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
tempArr[1] = Digit[mByte & 0X0F];
String s = new String(tempArr);
return s;
}
在微信公告號逛網添加url的時候,會進行驗證;微信官方規定以get方式進行訪問;如下
/**
* 請求校驗 (確定請求來自微信服務器)
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//微信加密簽名
String signature = req.getParameter("signature");
String timestamp = req.getParameter("timestamp");
String nonce = req.getParameter("nonce");
String echostr = req.getParameter("echostr");
System.out.println("get");
PrintWriter out = resp.getWriter();
if(SignUtil.checkSignature(signature,timestamp,nonce)){
//調用核心服務類接收處理請求
out.print(echostr);
}
out.close();
out = null;
}
微信服務器和微信手機app之間的通信,以及和自家服務器的通信規定是以xml文件的格式進行的傳輸;所有,要無時無刻不對xml文件解析;
官方文檔:
文本消息
1348831860
1234567890123456
對xml文件進行解析,方法很多,大約流行的有4中,自行了解,
public static Map parseXml(HttpServletRequest request)
throws Exception {
// 將解析結果,存儲在hashMap中,
Map map = new HashMap();
// 從 request 中取出輸入流
InputStream inputStream = request.getInputStream();
// 讀取輸入流
SAXReader reader = new SAXReader();
//
Document document = reader.read(inputStream);
// 得到根XML元素
Element root = document.getRootElement();
// 得到根元素的所有子節點。
List elementList = root.elements();
//
for (Element element : elementList) {
map.put(element.getName(), element.getText());
}
inputStream.close();
inputStream = null;
return map;
}
以及要有生成xml的函數,但是xml文件是(cdata)
/**
* 擴展 xsstream 使其支持 cdata
*/
private static XStream xstream = new XStream(new XppDriver(){
@Override
public HierarchicalStreamWriter createWriter(Writer out) {
// TODO Auto-generated method stub
return new PrettyPrintWriter(out){
boolean cdata = true;
@Override
public void startNode(String name, Class clazz) {
// TODO Auto-generated method stub
super.startNode(name, clazz);
}
@Override
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("");
// System.out.println(" cdate = true");
} else {
writer.write(text);
// System.out.println(" cdate = false");
}
}
};
}
});
/**
* 文本消息對象,轉化 XML
*/
public static String messageToXml(TextMessage textMessage){
xstream.alias("xml", textMessage.getClass());
return xstream.toXML(textMessage);
}
/**
* 圖像,消息對象 轉化成 xml
*/
public static String messageToXml(ImageMessage imageMessage){
xstream.alias("xml", imageMessage.getClass());
return xstream.toXML(imageMessage);
}
/**
* 語音消息 --》 xml
*/
public static String messageToXml(VoiceMessage voiceMessage){
xstream.alias("xml", voiceMessage.getClass());
return xstream.toXML(voiceMessage);
}
/**
* 視頻
*/
public static String messageToXml(VideoMessage videoMessage){
xstream.alias("xml", videoMessage.getClass());
return xstream.toXML(videoMessage);
}
當然上面的生成對應的要有對應的deam對象;
下面是最核心的類
public class CoreService { /** * 核心服務。處理 數據,並且換回數據。 * @param request * @return */ public static String processRequest(HttpServletRequest request) { // xml格式消息數據 String respXml = null; // 默認返回文本消息內容 String respContent = "未知數據類型"; try { // 調用 parseXml 方法解析請求消息 Map<String, String> requestMap = MessageUtil.parseXml(request); // 發送 賬號 String fromUserName = requestMap.get("FromUserName"); // 開發著微信號 String toUserName = requestMap.get("ToUserName"); // 消息類型 String msgType = requestMap.get("MsgType"); // 回復文本消息 TextMessage textMessage = new TextMessage(); textMessage.setToUserName(fromUserName); textMessage.setFromUserName(toUserName); textMessage.setCreateTime(new Date().getTime()); textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT); /** * 信息類型 */ if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) { //TODO 亂碼 。。。 respContent = "你發送的是 文本 消息\n"; respContent += "帥的回復是: " ; // if(requestMap.get("Content").equals("n")){ // System.out.println("xinagtong"); // }else { // System.out.println("不想同"); // } if(requestMap.get("Content").equals("n")){ Article article = new Article(); article.setTitle("開源中國"); article.setDescription("開源中國社區成立於2008.8.8是目前最大的開源社區," + "\n\n 開源中國的dsfadfadsfadsfasdf多少發多發多少發多發的法規fgsfgaf" + "dafsafad啊的發的發的嘎達" + "dfadadsfadfa。\n\n" + "dsjfkajdkfjad;f空間的發來快點就是拉福建阿斯頓;了大家撒裂縫空間" + "打開來房間裡的。"); article.setPicUrl(""); article.setUrl("http://m.oschina.net"); List<Article> articleList = new ArrayList<Article>(); articleList.add(article); //創建圖文消息 NewsMessage newsMessage = new NewsMessage(); newsMessage.setToUserName(fromUserName); newsMessage.setFromUserName(toUserName); newsMessage.setCreateTime(new Date().getTime());; newsMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_NEWS); newsMessage.setArticleCount(articleList.size()); newsMessage.setArticles(articleList); respXml = MessageUtil.messageToXml(newsMessage); return respXml; }else{ //TODO 存在的問題是,有空個的時候,會出現無法返回數據, //提示,該公共號暫時無法提供服務,請稍後再試。 String title = URLDecoder.decode(requestMap.get("Content"), "utf-8"); title = title.replaceAll(" ", ""); respContent += new TulingController().getTulingRe(title); } // respContent = URLEncoder.encode(respContent, "UTF-8"); // respContent = "luan ma ??"; } else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_IMAGE)) { respContent = "你發送的是 圖片 消息\n"; respContent += "n : 消息推送\n" + "k : 沒啥\n"; } else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_VOICE)) { respContent = "你發送的是 語音 消息"; } else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_VIDEO)) { respContent = "你發送的是 視頻 消息"; } else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LOCATION)) { respContent = "你發送的是 地址 消息"; } else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LINK)) { respContent = "你發送的是鏈接片 消息"; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// } else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) { //事件推送 //事件類型 String eventType = requestMap.get("Event"); //關注 if (eventType.equals(MessageUtil.EVENT_MESSAGE_TYPE_SUBSCRIBE)) { respContent = "hello ,歡迎關注公告號,我們致力打招最好的東西給你," + "從現在開始,以修復部公共 20160331 !!!" ; textMessage.setContent(respContent); respXml = MessageUtil.messageToXml(textMessage); return respXml; //將消息對象轉化成xml //respXml = MessageUtil.messageToXml(textMessage); }else if (eventType.equals(MessageUtil.EVENT_MESSAGE_TYPE_UNSUBSCRIBE)) { //TODO 取消訂閱後,用戶不會在收到公共賬號發送的消息,因此不需要回復 }else if (eventType.equals(MessageUtil.EVENT_MESSAGE_TYPE_SCAN) ){ //TODO 處理二維碼掃描事件; respContent = "二維碼掃"; }else if (eventType.equals(MessageUtil.EVENT_MESSAGE_TYPE_LOCATION)) { //TODO 處理上報的地理位置事件 respContent = "地理位置事件"; }else if (eventType.equals(MessageUtil.EVENT_MESSAGE_TYPE_CLICK)) { //TODO 處理菜單單擊事件 //事件key值,與創建菜單的key值對應 String eventKey = requestMap.get("EventKey"); if(eventKey.equals("oschina")){ Article article = new Article(); article.setTitle("開源中國"); article.setDescription("開源中國社區成立於2008.8.8是目前最大的開源社區," + "\n\n 開源中國的dsfadfadsfadsfasdf多少發多發多少發多發的法規fgsfgaf" + "dafsafad啊的發的發的嘎達" + "dfadadsfadfa。\n\n" + "dsjfkajdkfjad;f空間的發來快點就是拉福建阿斯頓;了大家撒裂縫空間" + "打開來房間裡的。"); article.setPicUrl(""); article.setUrl("http://m.oschina.net"); Article article2 = new Article(); article2.setTitle("開源中國"); article2.setDescription("dkfajldjfjgj;ajdfljd 安靜就放假啊的積分卡倒計時瘋狂" + "的奶粉克拉克;了" + "的刷卡的激發4" + "報告發掘地根據" + "建安費; " + "的深刻了激發的經費拉附近路東方了看見"); article2.setPicUrl(""); article2.setUrl(""); List<Article> articleList = new ArrayList<Article>(); articleList.add(article); articleList.add(article2); //創建圖文消息 NewsMessage newsMessage = new NewsMessage(); newsMessage.setToUserName(fromUserName); newsMessage.setFromUserName(toUserName); newsMessage.setCreateTime(new Date().getTime());; newsMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_NEWS); newsMessage.setArticleCount(articleList.size()); newsMessage.setArticles(articleList); respXml = MessageUtil.messageToXml(newsMessage); return respXml; }else if (eventKey.equals("iteye")) { respContent = "iteye 事件"; textMessage.setContent("Iteye 創辦於 2003,9.javaEye,從最初的討論java技術為主的技術" + "論壇,已朱靜發展成為涵蓋整個軟件開發領域的綜合性網站、\n\n" + "http://www.iteye.com"); respXml = MessageUtil.messageToXml(textMessage); return respXml; } // else { // respContent = "else limian "; // //設置文本消息 內容 //// textMessage.setContent("sdsdasda"); //// //將文本消息轉化成xml //// respXml = MessageUtil.messageToXml(textMessage); // } // respContent = "ccccc"; } } // //設置文本消息 內容 textMessage.setContent(respContent); //將文本消息轉化成xml respXml = MessageUtil.messageToXml(textMessage); } catch (Exception e) { e.printStackTrace(); } return respXml; } }
如果對數據進行處理和封裝的入口就是這個類。
通過if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) 判斷發送來的消息是那種格式的,獲取後,在放回給用戶即可,
比如,以 加載一個機器人案列,我們使用圖靈機器人的接口;
在判斷完是字符串的信息後,訪問圖靈的接口;
String title = URLDecoder.decode(requestMap.get("Content"), "utf-8");
title = title.replaceAll(" ", "");
respContent += new TulingController().getTulingRe(title);
//respContent是最後封裝返回的數據;
public String getTulingRe(String info){
//調用圖靈機器人接口api,獲取結果
//http://www.tuling123.com/openapi/api key:42bca29888818ceea7a214eaadbeb9e7
// String url = "http://www.tuling123.com/openapi/api?key=需要去圖靈官網注冊獲取&info="+info;
String url = "http://www.tuling123.com/openapi/api?key=42bca29××××××××××××××××××××××××&info="+info;
String tlResult = HttpGetRequest.get(url);
//瑙f瀽鍥劇伒缁撴灉鏁版嵁锛屾彁鍙栨墍闇?唴瀹?
JSONObject json = JSONObject.fromObject(tlResult);
tlResult = json.getString("text");
return tlResult;
}
public static String get(String url){
try{
HttpGet request = new HttpGet(url);
//執行http get請求
HttpResponse response = HttpClients.createDefault().execute(request);
//根據返回碼判斷返回是否成功
String result = "";
if(response.getStatusLine().getStatusCode() == 200){
result = EntityUtils.toString(response.getEntity());
}
return result;
}catch(Exception e){
e.printStackTrace();
return "";
}
}
ok
在前面一文中,我們介紹了Android運行時ART,它的核心是OAT文件。OAT文件是一種Android私有ELF文件格式,它不僅包含有從DEX文件翻譯而來的本地機器指令
本文主要內容就是用marix加上漸變色實現圖片倒影的效果,步驟如下: 1. 獲取需要倒影效果的圖片,這裡取原圖片的一半 2. 添加顏色漸變到倒影圖片上
我們知道在Android系統中,我們執行完耗時操作都要另外開啟子線程來執行,執行完線程以後線程會自動銷毀。想象一下如果我們在項目中經常要執行耗時操作,如果經常要開啟線程,
異步任務 AsyncTask使用 android中實現異步機制主要有Thread加Handler和AsyncTask,今天主要記錄一下A