上一篇讲了界面的实现,详情请戳Android利用Cookie实现码源登录效果(一) - 界面实现,本篇将对功能的实现进行分析。
具体实现
功能实现
使用HttpClient进行post请求
在进行post请求的时候,我们先看看网页版登录是如何发送数据的。打开登录页面,点击键盘上的F12,在
Sources
中找到login
,找到发送ajax请求进行登录的部分,加入断点:点击登录,然后在
Network
中看到有个发送到http://www.codefrom.com/login/ajax 的网络请求,点开它,我们看看Header里面有什么数据:可以看到,有三部分数据:
Request URL
、Request Header
和Form Data
,他们的含义我想大家应该都很清楚。那么我们就尝试在LoginActivity
中模拟一下这个操作。首先,我们写一个方法,封装我们的登录请求:
public String sendPost(String url, String username, String password) { // 根据url获得HttpPost对象 HttpPost httpRequest = new HttpPost(url); // 取得默认的HttpClient DefaultHttpClient httpclient = new DefaultHttpClient(); String strResult = null; // NameValuePair实现请求参数的封装 List<NameValuePair> params = new ArrayList<NameValuePair>(); params.add(new BasicNameValuePair("_tk", "codefrom")); params.add(new BasicNameValuePair("name", username)); params.add(new BasicNameValuePair("pass", password)); httpRequest.addHeader("Accept", "application/json, text/javascript, */*; q=0.01"); httpRequest.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); httpRequest.addHeader("Origin", "http://www.codefrom.com"); httpRequest.addHeader("Referer", "http://www.codefrom.com/login"); httpRequest.addHeader("X-Requested-With", "XMLHttpRequest"); try { // 添加请求参数到请求对象 httpRequest.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8)); // 获得响应对象 HttpResponse httpResponse = httpclient.execute(httpRequest); // 判断是否请求成功 if (httpResponse.getStatusLine().getStatusCode() == 200) { // 获得响应返回Json格式数据 strResult = EntityUtils.toString(httpResponse.getEntity()); return strResult; } else { strResult = "错误响应:" + httpResponse.getStatusLine().toString(); } } catch (ClientProtocolException e) { strResult = "错误响应:" + e.getMessage().toString(); e.printStackTrace(); return strResult; } catch (IOException e) { strResult = "错误响应:" + e.getMessage().toString(); e.printStackTrace(); return strResult; } catch (Exception e) { strResult = "错误响应:" + e.getMessage().toString(); e.printStackTrace(); return strResult; } return strResult; }
(注:当前版本网络请求只能异步操作)
然后,在UserLoginTask
的doInBackground
中,当睡眠结束后,我们对登录方法进行调用:String res = sendPost("http://www.codefrom.com/login/ajax", mEmail, mPassword); Log.d("CodeFromLogin", res);
我们运行项目,看看日志中打印:
没错,将Unicode转成中文之后,得到“操作成功”的返回信息!
需要注意一点,一开始,我没有添加httpRequest.addHeader
这部分的内容,得到的结果如下:
感兴趣的同学可以尝试一下,服务器返回提示我们操作失败,缺少ajax头部信息!保存来自服务器的Cookie
能够成功发送数据给服务器,并且服务器能响应结果,那么,我们能否获取更多内容呢?当然可以!HttpClient
提供了下面的方法供我们获取Cookie:// 取得Cookie CookieStore mCookieStore = httpclient.getCookieStore(); List<Cookie> cookies = mCookieStore.getCookies();
那么,Android又是如何对Cookie进行管理的呢?
// 设置cookie public static void synCookies(Context context, String url) { CookieSyncManager.createInstance(context); CookieManager cookieManager = CookieManager.getInstance(); cookieManager.setCookie(url, "uid=1243432"); CookieSyncManager.getInstance().sync(); } // 清除cookie private void removeCookie(Context context) { CookieSyncManager.createInstance(context); CookieManager cookieManager = CookieManager.getInstance(); cookieManager.removeAllCookie(); CookieSyncManager.getInstance().sync(); }
接下来,我们就可以对
sendPost
方法进行一些改造了:public CookieManager cookieManager = null; public static String cookies; public String sendPost(String url, String username, String password) { CookieSyncManager.createInstance(LoginActivity.this); // 每次登录操作的时候先清除cookie removeAllCookie(); // 根据url获得HttpPost对象 HttpPost httpRequest = new HttpPost(url); // 取得默认的HttpClient DefaultHttpClient httpclient = new DefaultHttpClient(); String strResult = null; // NameValuePair实现请求参数的封装 List<NameValuePair> params = new ArrayList<NameValuePair>(); params.add(new BasicNameValuePair("_tk", "codefrom")); params.add(new BasicNameValuePair("name", username)); params.add(new BasicNameValuePair("pass", password)); httpRequest.addHeader("Accept", "application/json, text/javascript, */*; q=0.01"); httpRequest.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); httpRequest.addHeader("Origin", "http://www.codefrom.com"); httpRequest.addHeader("Referer", "http://www.codefrom.com/login"); httpRequest.addHeader("X-Requested-With", "XMLHttpRequest"); try { // 添加请求参数到请求对象 httpRequest.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8)); // 获得响应对象 HttpResponse httpResponse = httpclient.execute(httpRequest); // 判断是否请求成功 if (httpResponse.getStatusLine().getStatusCode() == 200) { // 获得响应返回Json格式数据 strResult = EntityUtils.toString(httpResponse.getEntity()); // 取得Cookie CookieStore mCookieStore = httpclient.getCookieStore(); List<Cookie> cookies = mCookieStore.getCookies(); if (cookies.isEmpty()) { System.out.println("Cookies为空"); } else { for (int i = 0; i < cookies.size(); i++) { // 保存cookie Cookie cookie = cookies.get(i); Log.d("Cookie", cookies.get(i).getName() + "=" + cookies.get(i).getValue()); cookieManager = CookieManager.getInstance(); String cookieString = cookie.getName() + "=" + cookie.getValue() + "; domain=" + cookie.getDomain(); cookieManager.setCookie("http://www.codefrom.com/", cookieString); } } return strResult; } else { strResult = "错误响应:" + httpResponse.getStatusLine().toString(); } } catch (ClientProtocolException e) { strResult = "错误响应:" + e.getMessage().toString(); e.printStackTrace(); return strResult; } catch (IOException e) { strResult = "错误响应:" + e.getMessage().toString(); e.printStackTrace(); return strResult; } catch (Exception e) { strResult = "错误响应:" + e.getMessage().toString(); e.printStackTrace(); return strResult; } return strResult; } private void removeAllCookie() { cookieManager = CookieManager.getInstance(); cookieManager.removeAllCookie(); CookieSyncManager.getInstance().sync(); }
运行改造之后的代码,我们先进行登录,然后点击按钮打开码源首页,可以看到,首页导航是可以看到登录信息的!
使用本地广播更新界面
完成登录之后,我们需要修改主界面的控件文字,比如我们想显示用户名等等。理论上来讲,使用startActivity
将数据通过Intent从登录界面传递给主界面的方法是可行的。但是,我们需要考虑在实际中,如果多个地方校验到用户没有登录,都需要弹出登录界面,然后登录完了之后,我们需要返回的界面不一定都是主界面(如果PM要求返回主界面,当我没说),那我们该怎么办呢?
这时,我们可以通过本地广播来实现:在主界面(或者其他需要得到登录信息的界面),动态注册一个本地广播,通过广播获取到的Intent来进行数据分析,部分代码如下:localBroadcastManager = LocalBroadcastManager.getInstance(MainActivity.this); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction("com.codefrom.broadcastreceiver.LOGIN_BROADCAST");//建议把它写一个公共的变量,这里方便阅读就不写了。 loginBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Bundle bundle = intent.getExtras(); boolean result = bundle.getBoolean("result"); String user = bundle.getString("username"); if (result) { username.setText(user); Toast.makeText(MainActivity.this, "登录成功", Toast.LENGTH_SHORT).show(); loginbtn.setText("退出登录"); isLogin = true; } else { Toast.makeText(MainActivity.this, "登录失败", Toast.LENGTH_SHORT).show(); username.setText("未登录"); loginbtn.setText("登录"); isLogin = false; } } }; localBroadcastManager.registerReceiver(loginBroadcastReceiver, intentFilter);
同时,在登录界面,登录成功之后,我们可以发送一个广播告知其他界面登录结果,由于登录在子线程中进行,我们就需要借助
Handler
进行广播发送:Handler handler = new Handler() { public void handleMessage(android.os.Message msg) { LoginInfo loginInfo = (LoginInfo) msg.obj; Toast.makeText(LoginActivity.this, loginInfo.getStatus() + " - " + loginInfo.getMsg() + " - " + Uri.decode(loginInfo.getMsg()) , Toast.LENGTH_SHORT).show(); if("1".equals(loginInfo.getStatus())) { Intent intent = new Intent("com.codefrom.broadcastreceiver.LOGIN_BROADCAST"); Bundle bundle = new Bundle(); bundle.putBoolean("result", true); bundle.putString("username", "单车武士"); intent.putExtras(bundle); localBroadcastManager.sendBroadcast(intent); } }; };
其实,如果观察
LoginActivity
,在成功以后,它做的操作是调用Activity的finish()
,也就是直接关闭登录界面了,我猜系统应该也是不推荐直接使用startActivity进行显示跳转的。
由于服务器并没有给我们返回更多用户的信息,我们先返回一个写死的username过去……当然,在网页中是根据cookie来获取用户信息的,这方面的东西在Android上面的实现我们今后再探讨。
以上就是我们实现的模拟登录码源效果,如果有什么不对的地方或者更好的建议,欢迎回复交流。
附:Demo下载