Android聊天软件的开发(三)--网络连接
2014-06-18 01:00
459 查看
一,服务器网络接口
服务器网络接口通过Servlet实现,可以获得客户端提交的数据,对数据进行查询存储操作,以及返回结果数据给客户端。客户端可以通过HTTP协议直接访问网络接口。HTTP协议有两种请求方法:GET请求和POST请求。这两者的主要区别在于GET请求通过URL提交数据,数据在URL中可以看到,对于敏感数据来说,是不安全的。而且GET方式提交的数据最多只能有1024字节。对于POST请求,数据放置在HTML HEADER内提交,提交数据没有大小限制。因此选择POST请求方式比较合适。
Servlet接口访问的URL格式为:http://[服务器IP]:[服务器端口号(默认8080)]/[项目名称]/[Servlet名称]。其内部可以针对GET请求和POST请求作出不同的处理方式:doGet函数处理GET请求,doPost函数处理POST请求。所以只需要实现doPost函数。当然,为了方便通过浏览器测试网络接口(在地址栏输入URL,后面加上参数,“http:\\...?a=123&b=456”),可以在doGet函数中调用已经实现的doPost函数。
接下来主要分析注册接口,主要有三部分:获取验证码,校验验证码和注册基本信息。其中,获取和校验验证码的过程如下图(图1):
1.获取验证码
验证码通过天翼开放平台下发,需要在 天翼开放平台 进行申请,会赠送你20天,每天调用次数100次以下的套餐。自从6月1号收费后,又增加了一个限制,使用者需要配置10个一下的测试手机号,接口只对测试手机号开放。(感觉天翼越来越抠门了,之前是免费使用的,现在是收费,还限制使用。。。)
申请步骤:
a,注册登录,可以使用电信手机号,或者微博账号登录。
b,创建应用,填写相关的应用信息。
c,应用创建后,会得到app_id和app_secret。这是你使用接口的凭证。
调用下发验证码接口的时序过程,如下图(此过程即为图1的1-4步骤)。
1.客户端提交手机号码到下发短信验证码接口,发起下发验证码请求。
2.服务器通过HTTPS协议,以app_id和app_secret为主要参数,访问天翼的令牌接口,获取access_token。
3.服务器通过HTTP协议,以app_id和access_token为主要参数,访问天翼的信任码接口,获取token。
4.服务器随机生成6位数字的验证码作为verify_code,并将verify_code和当前时间time(检查验证码是否超时,时效为2分钟)以用户ID(手机号)作为key,存储在session中。然后通过Http协议,以app_id,access_token,token,phone和verify_code为主要参数,访问天翼的下发验证码接口,实现验证码的下发操作。
2.校验验证码
如下图(此过程即为图1的5-7步骤),客户端接收到验证码短信之后,将phone(手机号),verify_code(验证码)和timestamp(时间)提交到校验验证码接口进行校验。首先根据phone获取服务器中code(验证码)和time(下发时间)的缓存。然后对比timestamp和time,如果超过两分钟,则返回“验证码超时”。否则,继续校验verify_code和code。如果不一致,则返回“验证码错误”。否则,返回“验证成功”。
3.注册基本信息
在验证码校验成功后,客户端将用户ID,用户昵称,性别和密码提交到服务器完成注册。
关于服务器的网络接口,有一个需要注意的地方。服务器与客户端的通信参数,都是JSON格式的。Servlet中使用JSON,必须导入6个jar包:commons-beanutils.jar,commons-lang.jar, commons-collections.jar,commons-logging.jar,ezmorph.jar,json-lib.jar。
如果出现Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/lang/exception/NestableRuntimeException异常,则可能是缺少jar包,或者commons-lang.jar的版本为3.1。
二,Servlet使用HttpClient
由于下发验证码接口中需要访问其他网络接口,所以需要使用Apache的HttpClient。可以在此处 下载相应的jar包,和查看使用实例。目前HttpClient最新的版本是4.3,与4.2版本有一定的区别,具体可以查看官网介绍(我这里使用的是4.3版本)。
Servlet中使用HttpClient:
1.导入httpclient.jar和httpcore.jar。
2.创建可以访问HTTPS协议的HttpClient。这里只有令牌接口需要通过HTTPS访问,其他接口的访问可以直接通过HttpClients.createDefault()获得HttpClient。
/** 获得可用于访问Https的HttpClient */ private static CloseableHttpClient CreateSSLInsecureClient() { try { SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() { // 信任所有 public boolean isTrusted(X509Certificate[] chain,String authType) throws CertificateException { return true; } }).build(); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext); return HttpClients.custom().setSSLSocketFactory(sslsf).build(); } catch (Exception e) { logger.error("Https配置出现异常:", e); } return HttpClients.createDefault(); }3.访问接口和得到返回数据(以令牌接口为例)
/** * 调用令牌接口,获取access_token<BR/> * <BR/> * * HTTPS请求格式:POST <BR/> * 响应格式:JOSN * * @return access_token 如果调用接口失败,则返回null */ private static String GetAccessToken() { String accessToken = null; CloseableHttpClient httpClient = CreateSSLInsecureClient(); HttpPost httpPost = new HttpPost(Constants.GetVerifyCode.GET_ACCESS_TOKEN_URL); List<NameValuePair> params = new ArrayList<NameValuePair>(); params.add(new BasicNameValuePair( Constants.GetVerifyCode.Params.GRANT_TYPE_KEY, Constants.GetVerifyCode.Params.GRANT_TYPE_VALUE)); params.add(new BasicNameValuePair(Constants.GetVerifyCode.Params.APP_ID, Constants.GetVerifyCode.APP_ID)); params.add(new BasicNameValuePair( Constants.GetVerifyCode.Params.APP_SECRET, Constants.GetVerifyCode.APP_SECRET)); try { HttpEntity reqEntity = new UrlEncodedFormEntity(params);//自动编码特殊字符 logger.debug("获取access_token的请求参数:" + EntityUtils.toString(reqEntity)); httpPost.setEntity(reqEntity); HttpResponse response = httpClient.execute(httpPost); HttpEntity resEntity = response.getEntity(); if (resEntity != null) { String resStr = EntityUtils.toString(resEntity); JSONObject resJson = JSONObject.fromObject(resStr); int resCode = resJson.getInt(Constants.GetVerifyCode.Params.RES_CODE); if (resCode == 0)// 请求成功 { accessToken = resJson.getString(Constants.GetVerifyCode.Params.ACCESS_TOKEN); logger.debug("获取access_token成功 access_token=" + accessToken); } else { String resMsg = resJson.getString(Constants.GetVerifyCode.Params.RES_MSG); logger.error("获取access_token失败 res_message=" + resMsg); } return accessToken; } } catch (Exception e) { logger.error("调用令牌接口失败:", e); } finally { httpPost.releaseConnection(); try { httpClient.close(); } catch (IOException e) { logger.error("调用令牌接口时关闭HttpClient异常:", e); } } return accessToken; }
三,客户端网络连接
Android的HttpClient是提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。使用HttpClient来实现客户端与服务器的网络通信,可以简化代码和提高可靠性。客户端网络连接的主要架构如下图。由于各个功能模块向服务器发起网络请求的步骤都是一样的,所以将其封装为一个工具类。但是,因为需要访问不同的服务器接口,返回相应的结果,以及处理的方式都不同,所以需要让每个功能模块的Activity实现回调函数接口,用于处理返回数据。
具体步骤如下:
1.Activity实现HttpCallBackListener接口,以及根据自身需求实现callBack函数,用于处理服务器返回的数据。
2.开启新的线程,整理参数,和通过HttpUtils的sendRequest方法发起网络请求。其中id为服务器的接口ID,data为RSA加密后的参数,this为当前实现了HttpCallBackListener接口的Activity。
3.HttpUtils的sendRequest方法会根据接口ID,设置对应URL。并且向服务器发起访问。
4.如果访问成功,则回调callBack函数。
具体代码:
修改密码的Activity
public class ModifyPsdActivity extends Activity implements HttpCallBackListener{ private static final String TAG = ModifyPsdActivity.class.getSimpleName(); private Handler mMultiHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_menu_modify_psd); mContext = this; mMultiHandler = ThreadUtils.GetMultiHandler(TAG); //发起修改密码的请求 mMultiHandler.post(new Runnable() { @Override public void run() { JSONObject reqJson = new JSONObject(); try { reqJson.put(Constants.UserInfo.RequestParams.USER_ID,CacheUtils.GetUserId()); reqJson.put(Constants.UserInfo.RequestParams.OLD_PSD,oldPsdMd5); reqJson.put(Constants.UserInfo.RequestParams.PASSWORD,newPsdMd5); Log.d(TAG, "修改登录密码请求,加密内容:" + reqJson.toString()); // RSA 加密 String data = EncryptUtils.GetRsaEncrypt(reqJson.toString()); HttpUtils.sendRequest(Constants.ID.MODIFY_PSD,data, ModifyPsdActivity.this); } catch (ConnectTimeoutException e) { Log.e(TAG, "网络连接超时", e); // 提示用户 } catch (SocketTimeoutException e) { Log.e(TAG, "系统繁忙", e); // 提示用户 } catch (Exception e) { e.printStackTrace(); } } }); /**网络连接成功后的回调函数*/ @Override public void httpCallBack(int id, JSONObject resp) { switch (id) { case Constants.ID.MODIFY_PSD: Log.d(TAG, "修改登录密码的回调函数"); String resCode = resp.optString(Constants.ResponseParams.RES_CODE); if ("0".equals(resCode)) { Log.d(TAG, "修改用户信息成功"); //提示用户 } else { String resMsg = resp.optString(Constants.ResponseParams.RES_MSG); Log.e(TAG, "修改登录密码失败 :" + resMsg); //显示错误 } break; } } }
HttpUtils工具类
public class HttpUtils { private static final String TAG = HttpUtils.class.getSimpleName(); /** * 发送请求 * * @param id * 模块ID * @param data * RSA加密后的数据 * @param listener * 响应的回调函数 * @throws ConnectTimeoutException * @throws SocketTimeoutException * @throws Exception */ public static void sendRequest(int id, String data, HttpCallBackListener listener) throws ConnectTimeoutException, SocketTimeoutException, Exception { String url = null; //需要上传photo的接口,则将photo作为一个参数进行传递 String photo = null; boolean isNeedPhoto = false; switch (id) { case Constants.ID.GET_CODE: url = Constants.Register.GET_VERIFY_CODE_URL; break; case Constants.ID.CHECK_CODE: url = Constants.Register.CHECK_VERIFY_CODE_URL; break; case Constants.ID.REGISTER: url = Constants.Register.REGISTER_URL; break; case Constants.ID.LOGIN: url = Constants.Login.LOGIN_URL; break; case Constants.ID.MODIFY_USERINFO: url = Constants.UserInfo.MODIFY_USERINFO_URL; isNeedPhoto = true; String[] strArr = data.split("&"); if(strArr.length == 2)//有photo的数据 { photo = strArr[1]; } data = strArr[0]; Log.d(TAG, "photo="+photo); break; case Constants.ID.MODIFY_PSD: url = Constants.UserInfo.MODIFY_PSD_URL; break; case Constants.ID.GET_FRIEND_LIST: url = Constants.GetFriendList.GET_FRIEND_LIST_URL; break; case Constants.ID.SEARCH_USER: url = Constants.AddFriend.SEARCH_USER_URL; break; case Constants.ID.REMOVE_FRIEND: url = Constants.RemoveFriend.REMOVE_FRIEND_URL; break; default: Log.e(TAG, "业务ID不存在"); return; } Log.d(TAG, "url = " + url); // HttpPost连接对象 HttpPost httpPost = new HttpPost(url); //使用UTF-8编码 httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"); // 使用NameValuePair来保存要传递的Post参数 List<NameValuePair> params = new ArrayList<NameValuePair>(); // 添加要传递的参数 params.add(new BasicNameValuePair(Constants.RequestParams.DATA, data)); if(isNeedPhoto) { params.add(new BasicNameValuePair(Constants.RequestParams.PHOTO, photo)); } // 设置字符集 HttpEntity httpEntity = new UrlEncodedFormEntity(params, HTTP.UTF_8); httpPost.setEntity(httpEntity); // 使用自定义的HttpClient HttpClient httpClient = CustomerHttpClient.getHttpClient(); // 取得HttpResponse HttpResponse httpResponse = httpClient.execute(httpPost); // HttpStatus.SC_OK表示连接成功 if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { Log.d(TAG, "连接成功"); // 取得返回的字符串 String respStr = EntityUtils.toString(httpResponse.getEntity()); JSONObject respJson = new JSONObject(respStr); //回调 listener.httpCallBack(id, respJson); } else { Log.e(TAG, "连接失败"); } } }
自定义HttpClient,设置基本参数
public class CustomerHttpClient { private static final String CHARSET = HTTP.UTF_8; private static HttpClient customerHttpClient; private CustomerHttpClient() { } public static synchronized HttpClient getHttpClient() { if (customerHttpClient == null) { HttpParams params = new BasicHttpParams(); // 设置一些基本参数 HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); HttpProtocolParams.setContentCharset(params, CHARSET); HttpProtocolParams.setUseExpectContinue(params, true); HttpProtocolParams.setUserAgent(params, "Mozilla/5.0(Linux;U;Android 2.2.1;en-us;Nexus One Build.FRG83) " + "AppleWebKit/553.1(KHTML,like Gecko) Version/4.0 Mobile Safari/533.1"); // 超时设置 /* 从连接池中取连接的超时时间 */ ConnManagerParams.setTimeout(params, 2000); /* 连接超时 */ HttpConnectionParams.setConnectionTimeout(params, 20000); /* 请求超时 : 获取服务器响应超时*/ HttpConnectionParams.setSoTimeout(params, 20000); // 设置我们的HttpClient支持HTTP和HTTPS两种模式 SchemeRegistry schReg = new SchemeRegistry(); schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); schReg.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443)); // 使用线程安全的连接管理来创建HttpClient ClientConnectionManager conMgr = new ThreadSafeClientConnManager(params, schReg); customerHttpClient = new DefaultHttpClient(conMgr, params); } return customerHttpClient; } }
首页
Android聊天软件的开发
相关文章推荐
- android开发(12) 使用无线网络和切换到GPRS连接
- android开发第七天网络连接的四种方式
- android 开发中判断网络是否连接的代码
- Android聊天软件的开发(七)--聊天通信
- Android聊天软件的开发(二)--数据库
- android开发训练——网络操作(一)连接网络
- Android官网培训课:App开发中的网络连接和云计算
- Android聊天软件的开发
- 在android的开发中,判断是否连接网络
- android开发中监控android软件网络请求的软件Charles使用入门
- Android聊天软件的开发(五)--头像设置
- android开发第七天网络连接的四种方式
- Android开发_判断网络连接是否可用
- 自用连接:android经典教程,软件开发,项目管理,论坛链接,连接link
- Android开发:休眠唤醒或开机后cmwap/cmnet网络不能连接的解决办法
- android开发省电之--Determining and Monitoring the Connectivity Status(根据网络连接状况去省电)
- android 开发中判断网络是否连接的代码
- android 开发中判断网络是否连接的代码
- Android开发之判断网络(wifi、3G)是否连接
- Android聊天软件的开发(一)--预备知识