1. 程式人生 > >Okhttp3網路請求框架+MVP設計模式簡單實戰

Okhttp3網路請求框架+MVP設計模式簡單實戰

Okhttp
目前最新版本的是okhttp:3.4.1,也稱為Okhttp3。 
OkHttp是一個精巧的網路請求庫,不僅在介面封裝做的簡單易用,在底層實現上也自成一派。比起原生的HttpURLConnection有過之而無不及,現在已經成為廣大開發者的首選網路通訊庫。

特性
支援http2,對一臺機器的所有請求共享同一個socket
內建連線池,支援連線複用,減少延遲
支援透明的gzip壓縮響應體
通過快取避免重複的請求
請求失敗時自動重試主機的其他ip,自動重定向
好用的API
新增依賴
dependencies {
   ...
    compile 'com.squareup.okhttp3:okhttp:3.4.1'
   ...
}
1
2
3
4
5
Android Studio新增上述依賴後會自動下載兩個庫,一個是Okhttp,另一個是Okio。

MVP模式
之前寫了一篇部落格簡單講解了MVP模式的使用,MVP模式&簡單例項,實現的效果不明顯,MVP模式的好處也沒有體現。 
今天結合Okhttp3框架,實現多種網路請求,並從側面表現MVP模式資料傳遞的巧妙之處。

程式碼示例及講解
主要包含以下幾個內容:

Okhttp的具體用法
將Okhttp方法封裝在MVP模式的Model層 
get非同步請求:京東獲取單個商品價格介面
post非同步帶引數請求:阿里雲根據地區名獲取經緯度介面
檔案下載:下載一張圖片儲存並顯示
Okhttp的具體用法
建立OkhttpClient例項,Google官方文件指明,不希望存在多個OkhttpClient例項,造成複用,浪費資源。所以此處我們應該使用單例模式.
public class OkHttp3 {
    private static OkHttpClient client = new OkHttpClient();

    public static OkHttpClient getClient() {
        return client;
    }
}
1
2
3
4
5
6
7
建立Request前物件,傳送Http請求,在build()方法前新增請求設定,如:使用url()設定請求地址:
 Request request = new Request.Builder().url(url).build();
1
對於psot請求帶引數的,需要先構建一個RequestBody物件存在方待提交的引數:
RequestBody requestBody = new FormBody.Builder().add("key","value").build();
1
Request.Builder().post()方法,並將requestBody 物件傳入:
  Request request = new Request.Builder().post(formBody).url(url).build();
1
呼叫newCall()方法建立一個Call物件,使用execute()方法傳送同步請求:
Response response = client.newCall(request).exectue();
1
enqueue()傳送非同步請求:
client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) { 
               //失敗時的事物處理
            }
            @Override
            public void onResponse(Call call, Response response) {
              //成功時的事物處理
            }
        });
1
2
3
4
5
6
7
8
9
10
事實上Android開發過程中在主執行緒進行網路同步請求是一件極其危險的事情,如果耗時太多會造成主執行緒阻塞,進而程序崩潰,所以本文的所有方法都採用非同步請求。

其實也可以開闢一條新執行緒去使用execute()方法完成整個同步請求操作,避免執行緒阻塞。
 /**
     * 建立新執行緒實現同步get請求
     * @param context
     * @param url
     * @return
     * @throws Exception
     */
    public String getSysn(final Context context, final String url) throws Exception {
        FutureTask<String> task = new FutureTask<String>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                Request request = new Request.Builder().url(url).build();
                Response response = client.newCall(request).execute();
                String result = response.body().string();
                return result;
            }
        });
        new Thread(task).start();
        return task.get();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Response物件就是伺服器返回的資料:
response.body().string();//String型別資料
response.body().byteStream();//檔案輸入流資料
response.body().bytes();//二進位制位元組陣列
1
2
3
具體獲取的資料,要看伺服器返回的資料型別。

封裝Model類
如果你仔細看完Okhttp的具體用法,那麼看懂Model類就不是難事。

/**
 * 方法模型層
 * Created by D&LL on 2017/3/13.
 */
public class Model {
    private static Model instance = new Model();//單例

    public static Model getInstance() {
        return instance;
    }

    private ProgressDialog dialog;//顯示下載的進度條

    public OkHttpClient client = OkHttp3.getClient();

    /**
     * 非同步get請求
     * 使用ICallBack介面傳遞返回資料
     * @param context
     * @param url
     * @return
     * @throws Exception
     */
    public void getSynchronized(final Context context, final String url, final ICallBack callback) {
        Request request = new Request.Builder().url(url).build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                callback.result(e.toString());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                    callback.result(response.body().string());
                } else {
                    callback.result("請求失敗!");
                }
            }
        });

    }

    /**
     * post方式提交Map
     * 使用ICallBack介面傳遞返回資料
     * @param url
     * @param map
     * @return
     * @throws Exception
     */
    public void postMap(final Context context, final String url, Map<String, String> map, final ICallBack callback) {
        FormBody.Builder builder = new FormBody.Builder();
        if (map != null) {
            //增強for迴圈遍歷
            for (Map.Entry<String, String> entry : map.entrySet()) {
                builder.add(entry.getKey(), entry.getValue());
            }
        }
        FormBody formBody = builder.build();
        Request request = new Request.Builder().post(formBody).url(url).build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                callback.result(e.toString());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                    callback.result(response.body().string());
                } else {
                    callback.result("請求失敗!");
                }
            }
        });
    }

    /**
     * 非同步下載檔案
     * BitmapCallBack介面進行資料傳遞
     * @param context
     * @param url
     * @param name
     */
    public void downAsynFile(final Context context, String url, final String name, final BitmapCallBack callback) {
        dialog = DialogUtil.getProgressDialog(context);
        dialog.show();
        final Request request = new Request.Builder().url(url).build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                System.out.println(e);
            }

            @Override
            public void onResponse(Call call, Response response) {
                if (response.isSuccessful()) {
                    InputStream inputStream = response.body().byteStream();//獲取檔案輸入流
                    Bitmap bitmap = FileUtil.saveFile(name, inputStream);//獲取的流進行檔案操作
                    callback.imgBitmap(bitmap);
                    System.out.println("下載成功!");

                } else {
                    System.out.println("下載失敗!");
                }
                response.close();//下載檔案耗時較久,完成後需要手動關閉請求
                dialog.dismiss();
            }
        });

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
BitmapCallBack,ICallBack,MainView資料介面
public interface ICallBack {
    void result(String s);
}
public interface BitmapCallBack {
    void imgBitmap(Bitmap bitmap);
}
//檢視介面層,與檢視進行資料傳輸的介面
public interface MainView {
    void getView(String s);
    void postView(String s);
    void imgView(Bitmap bitmap);
}
1
2
3
4
5
6
7
8
9
10
11
12
你可能會發現非同步的請求的void onFailure()與void onResponse()方法是沒有返回值的,而我們請求的結果response卻作為引數在這個方法裡。 沒有返回值,就意味著我們無法在呼叫Model類的方法時獲取response資料。 
仔細觀察enqueue()非同步請求必須重寫new Callback()介面對獲取資料的進行一系列操作,同理我們也可以使用介面的特性將資料傳遞到我們指定的位置。
實際上這幾個方法並沒有太大差別,完全可以進一步封裝。這樣些方便大家看出其中的差別 
對於post帶引數請求來說,引數Map基本能解決80%以上的問題,Map是以鍵值對 <key,value> 的形式儲存資料的,而一般來說我們的請求引數引數都是一個key對應一個value,因此只需要用迴圈遍歷add()請求引數即可。
//增強for迴圈遍歷
for (Map.Entry<String, String> entry : map.entrySet()) {
builder.add(entry.getKey(), entry.getValue());
}
1
2
3
4
對於沒有請求引數的,直接傳一個null即可。
對於請求中一個引數對應多個請求值,使用Json作為value即可。網路請求一個引數對應多個值的方法
即使請求中包含檔案的,我們也可以通過將檔案轉換為編碼(如圖片的base64編碼)或者轉為輸出流,然後作為value。當然大檔案不推薦這麼做。
而這一切的前提都取決於後臺提供的API請求引數格式(所以跟你的後臺搞好關係吧!),然後後臺對你提交的請求引數進行”加工”(反編碼,輸入流)。
對於一些請求時長較長的如下載,我們需要請求完成後呼叫response.close(),手動關閉請求。
Presenter層
/**
 * 方法操作層
 * Created by D&LL on 2017/3/13.
 */

public class MainPresenter {
    private Model model = Model.getInstance();
    private MainView mainView;
    private Context context;
    public MainPresenter(Context context, MainView mainView) {
        this.context = context;
        this.mainView = mainView;

    }
    public void getRequest(String url) {//get非同步請求呼叫
        model.getSynchronized(context, url, new ICallBack() {
            @Override
            public void result(String s) {
                System.out.println(s);
                mainView.getView(s);

            }
        });
    }

    public void postMap(String url, Map<String, String> map) {//post非同步請求
        model.postMap(context, url, map, new ICallBack() {
            @Override
            public void result(String s) {
                System.out.println(s);
                mainView.postView(s);

            }
        });
    }

    public void downFile(String url,String name){//非同步下載
        model.downAsynFile(context, url, name, new BitmapCallBack() {
            @Override
            public void imgBitmap(Bitmap bitmap) {
                mainView.imgView(bitmap);
            }
        });
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
對應Presenter層,我們只用呼叫相應的方法,然後重寫ICallBack,BitmapCallBack介面獲取返回值,再使用mainView介面將資料傳遞到Activity即可。
View檢視層(Activity)
佈局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <TextView
        android:id="@+id/get"
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:textSize="20sp" />
    <TextView
        android:id="@+id/post"
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:textSize="20sp" />
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="down_img" />
    <ImageView
        android:id="@+id/img"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:scaleType="fitXY" />
</LinearLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Activity:

public class MainActivity extends AppCompatActivity implements MainView {
    String geturl = "http://p.3.cn/prices/mgets?skuIds=J_954086&type=1";//京東獲取單個商品價格介面:
    String posturl = "http://gc.ditu.aliyun.com/geocoding";//阿里雲根據地區名獲取經緯度介面
    @BindView(R.id.button)
    Button button;
    @BindView(R.id.get)
    TextView get;
    @BindView(R.id.post)
    TextView post;
    @BindView(R.id.img)
    ImageView img;
    private MainPresenter presenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);//butterknife
        initData();
    }
    private void initData() {
        Map<String, String> map = new HashMap<>();
        map.put("a", "蘇州市");
        presenter = new MainPresenter(this, this);
        presenter.getRequest(geturl);
        presenter.postMap(posturl, map);
    }
    @OnClick(R.id.button)
    public void onClick() {
        presenter.downFile("http://images.csdn.net/20150817/1.jpg", "demo.jpg");//下載並顯示圖片
    }
    @Override
    public void getView(final String s) {//顯示get請求返回值
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                get.setText(s);
            }
        });
    }
    @Override
    public void postView(final String s) {//顯示post請求返回值
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                post.setText(s);
            }
        });
    }
    @Override
    public void imgView(final Bitmap bitmap) {//顯示下載的圖片
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                img.setImageBitmap(bitmap);
            }
        });
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
呼叫MainView介面,重寫方法,顯示傳遞的資料。重寫方法的引數,就是我們請求的結果。
無論多少次介面傳遞,即使傳遞到了activity中,這些資料仍在非同步執行緒中,我們仍無法在主執行緒中使用。Android提供了runOnUiThread()方法,為我們解決在非同步執行緒中更新UI的方法:
runOnUiThread(new Runnable(){  @Override
            public void run() {
               }});
1
2
3
也可以使用Handler機制,傳送一個訊息給主執行緒的Handler(取決於Looper,使用 Looper.getMainLooper() 建立的Handler就是主執行緒Handler):
private Handler mHandler;
private TextView mTxt;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_home);
    mTxt = (TextView) findViewById(R.id.txt);
    mHandler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(Message msg) {
            mTxt.setText((String) msg.obj);
        }
    };
    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder().url("https://github.com").build();
    client.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
        }
        @Override
        public void onResponse(Call call, Response response) throws IOException {
            Message msg = new Message();
            msg.what=0;
            msg.obj = response.body().string();
            mHandler.sendMessage(msg);
        }
    });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
至此整個demo完成,MVP模式巧妙的利用介面的特性進行資料傳遞,解決了資料傳遞的困難,降低了耦合。
新增許可權
<uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
1
2
3
效果圖


Android studio Module程式碼
GitHub下載 
CSDN下載 
API免費測試介面:http://www.bejson.com/knownjson/webInterface/
--------------------- 
作者:DeMonnnnnn 
來源:CSDN 
原文:https://blog.csdn.net/demonliuhui/article/details/71246566 
版權宣告:本文為博主原創文章,轉載請附上博文連結!