最近打算写一个实现登陆学校的网站,实现查询成绩选课等等功能的app。于是就要用到OKHttp持久化cookie的相关知识,没有使用retrofit,所以打算认真写一篇博客来和大家分享一下我的学习心得。
GITHUB的地址是:https://github.com/CallMeSp/University_in_Hand.git
添加依赖什么的就不多说了,直接进入正文吧。
学校教务处的网站是:http://202.119.225.34/default2.aspx
我们会发现是带验证码的登陆,所以就不是简单的填表单然后post就可以的了,首先要获取这张验证码的图片,让它在我们的界面中显示出来。先来看一下效果吧:
在教务处网站查看源代码即可知道验证码的地址为:http://202.119.225.34/CheckCode.aspx
接下来我们要做的事情就是用okhttp把它缓存下来,换成成bitmap格式,然后在ImageView中显示出来即可。思路清晰后就开始行动。
1.定义Request:
Request request=new Request.Builder()
.url("http://202.119.225.34/CheckCode.aspx")
.build();
这里说句题外话,我个人还是非常喜欢这种流式的方法定义形式的,所以以后如果自己写一些工具库也会尽量用这种方式,看起来结构清晰容易阅读。
2.定义CLient:
OkHttpClient myclient=new OkHttpClient.Builder()
.build();
3.用刚刚定义的CLient来发送刚刚定义的Request请求:
myclient.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(okhttp3.Call call, IOException e) {
}
@Override
public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {
InputStream is = response.body().byteStream();
final Bitmap bm = BitmapFactory.decodeStream(is);
runOnUiThread(new Runnable() {
@Override
public void run() {
Img_check.setImageBitmap(bm);
}
});
}
});
这里代码也很清晰,我们只是一个简单的测试,所以也不用去管onFailure里面的内容,直接空着就可以了。在onResponse中将返回的response转换成InputStream字节流,然后用bitmap读取就可以了。
InputStream is = response.body().byteStream();
final Bitmap bm = BitmapFactory.decodeStream(is);
另外一个要注意的细节就是更改UI要在主线程中实现:
runOnUiThread(new Runnable() {
@Override
public void run() {
Img_check.setImageBitmap(bm);
}
});
既然准备工作做好了,剩下的就是填表单了,我们在chrome浏览器的开发者模式中可以看这个网站所需要填的信息有下面这几个:
__VIEWSTATE:dDwtNTE2MjI4MTQ7Oz5orbfashgbxfXkq3NcS/ZmZ8y+iA==
txtUserName:“这是是学号。。为了隐私去掉了”
Textbox1:
TextBox2:“这里是密码”
txtSecretCode:dgdv
RadioButtonList1:(unable to decode value)
Button1:
lbLanguage:
hidPdrs:
hidsc:
学号和密码都是自己填的,还有一个ViewState则是需要自己来获取了:
//获取viewState
Request request1=new Request.Builder()
.url("http://202.119.225.34/default2.aspx")
.build();
OkHttpClient postClient=new OkHttpClient.Builder()
.build();
postClient.newCall(request1)
.enqueue(new okhttp3.Callback() {
@Override
public void onFailure(okhttp3.Call call, IOException e) {
}
@Override
public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {
String str=response.body().string();
Document document= Jsoup.parse(str);
Elements elements=document.select("input[name=__VIEWSTATE]");
Element element=elements.get(0);
viewstate=element.attr("value");
Log.e(TAG, "onResponse: valuestate"+viewstate );
}
});
做法也很粗暴。。就是直接再访问一次这个页面然后用Jsoup解析出所需要的这个字段就可以了。
既然我们已经有了所有要填的表单信息,那就可以开始发送post请求了。
1.创建Formbody:
FormBody formbody = new FormBody.Builder()
.add("__VIEWSTATE",viewstate)
.add("txtUserName", account)
.add("TextBox2", passwd)
.add("txtSecretCode", secretcode)
.add("ASP.NET_SessionId",cook.substring(19,43))
.add("RadioButtonList1", "%D1%A7%C9%FA")
.add("Button1","")
.add("lbLanguage","")
.build();
这也是OKHttp3的一个新改动。
2.创建Request:
Request request2=new Request.Builder()
.url("http://202.119.225.34/default2.aspx")
.post(formbody)
.build();
这里就用了post方法,通过post(formbody)上传了表单
3.创建client
OkHttpClient postClient2=new OkHttpClient.Builder()
.build()
4.用client发送request请求
postClient2.newCall(request2)
.enqueue(new okhttp3.Callback() {
@Override
public void onFailure(okhttp3.Call call, IOException e) {
}
@Override
public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {
}
});
你可以用log查看一下返回的response信息,会发现,提示你验证码错误.WTF????
然而细想一下就知道,刚刚那个验证码只是我们随便获取的一个验证码,然后我们填完表单发送请求,服务器并不知道刚刚获取验证码的和这个填表单的是同一个人。所以这里就需要用到cookies,就像是身份证一样,为每个访问的用户标识了一个身份,只有相同的cookies才被认为是同一个人。
原理清楚了,要做的事情也就很明了了。
在获取验证码的时候,拿到cookies,存下来,然后发送post表单信息的时候带上这个cookie,这样就匹配了“身份证”,服务器也就知道这个验证码是这个填表单的人申请的。
Okhttp3为我们提供了很方便的封装,可以使用cookiejar很简单的实现所需求的功能。现在我们重写获取验证码的部分,添加获取cookies的功能。别的部分没有变化,只是在client中增添上cookiejar方法:
OkHttpClient myclient=new OkHttpClient.Builder().
cookieJar(new CookieJar() {
private final HashMap<String, List<Cookie>> cookieStore = new HashMap<String, List<Cookie>>();
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
cookieStore.put(url.host(), cookies);
mylist=cookies;
cook=mylist.toString();
Log.e(TAG, "saveFromResponse: "+cook+","+cook.substring(19,43));
}
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
List<Cookie> cookies = cookieStore.get(url.host());
return cookies != null ? cookies : new ArrayList<Cookie>();
}
})
.build();
cookiejar有两个方法要重写:saveFromResponse和loadForRequest。字面意思也很清楚,一个是从response中获取到cookie,一个事在发送请求时加载保存的cookie。
在获取验证码时发挥作用的就是savefromresponse,你也一定想到了,一会儿重写post请求时就是将其client加上cookiejar,利用loadforrequest加载保存的cookies;代码如下:这里的mylist,就是我自己定义的一个全局变量private List < Cookie > mylist;用来存取在savefromresponse中获取的cookies。
OkHttpClient postClient2=new OkHttpClient.Builder()
.cookieJar(new CookieJar() {
@Override
public void saveFromResponse(HttpUrl url, List cookies) {
Log.e(TAG, "login in save" );
}
@Override
public List loadForRequest(HttpUrl url) {
Log.e(TAG, "loadForRequest: login" +mylist.toString());
return mylist;
}
}).build();
然后你就会惊喜的发现,登陆成功了。然后当然要转到另外一个活动:但是我们存在这里的cookies怎么办?
你可能会说用intent传啊!
但是并没有那么简单,intent只能传基本类型的数据和序列化的数据。这里我们要传的是List< Cookie > 显然不是一个基本类型的数据,所以我们就要实现序列化。有两种方法,Serializable和Parcelable。这里我们采用Serializable方法实现。
public class SerializableOkHttpCookies implements Serializable {
private transient final Cookie cookies;
private transient Cookie clientCookies;
public SerializableOkHttpCookies(Cookie cookies) {
this.cookies = cookies;
}
public Cookie getCookies() {
Cookie bestCookies = cookies;
if (clientCookies != null) {
bestCookies = clientCookies;
}
return bestCookies;
}
}
public class SerializableCookies implements Serializable {
private List cookies;
public void setCookies(List cookies){
this.cookies=cookies;
}
public List getCookies(){
return cookies;
}
}
Serializable要注意的是 这个里面的任何一个对象都要是实现Serializable否则就会出错。
在intent中的应用:
Bundle bundle=new Bundle();
SerializableCookies serializableCookies=new SerializableCookies();
newlists.clear();
for (Cookie cookie:mylist){
SerializableOkHttpCookies newcook=new SerializableOkHttpCookies(cookie);;
newlists.add(newcook);
}
serializableCookies.setCookies(newlists);
bundle.putSerializable("list_cookies",serializableCookies);
intent.putStringArrayListExtra("url",url_list);
intent.putExtra("cookies_list",bundle);
startActivity(intent);
取值的时候:
Intent intent=getIntent();
url_list=intent.getStringArrayListExtra("url");
Bundle bundle=intent.getBundleExtra("cookies_list");
SerializableCookies serial=(SerializableCookies)bundle.getSerializable("list_cookies");
List cookies_in_Ser=serial.getCookies();
for (SerializableOkHttpCookies c:cookies_in_Ser){
Cookie co=c.getCookies();
cookies.add(co);
}
于是在另一个activity中我们又可以愉快的发送请求获取数据了。
项目的github地址:https://github.com/CallMeSp/University_in_Hand.git