1. 程式人生 > >Android學習筆記(十)--OKHttp持久化cookies實現模擬登陸

Android學習筆記(十)--OKHttp持久化cookies實現模擬登陸

最近打算寫一個實現登陸學校的網站,實現查詢成績選課等等功能的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<Cookie> cookies) {
                                Log.e(TAG, "login in  save" );
                            }
                            @Override
                            public List<Cookie> 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<SerializableOkHttpCookies> cookies;
    public void setCookies(List<SerializableOkHttpCookies> cookies){
        this.cookies=cookies;
    }
    public List<SerializableOkHttpCookies> 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<SerializableOkHttpCookies> cookies_in_Ser=serial.getCookies();
        for (SerializableOkHttpCookies c:cookies_in_Ser){
            Cookie co=c.getCookies();
            cookies.add(co);
        }

於是在另一個activity中我們又可以愉快的傳送請求獲取資料了。