您的位置:首页 > 理论基础 > 计算机网络

Okhttp3网络请求框架+MVP设计模式简单实战

2017-05-05 23:00 651 查看

Okhttp

目前最新版本的是okhttp:3.4.1,也称为Okhttp3。

OkHttp是一个精巧的网络请求库,不仅在接口封装做的简单易用,在底层实现上也自成一派。比起原生的HttpURLConnection有过之而无不及,现在已经成为广大开发者的首选网络通讯库。

特性

支持http2,对一台机器的所有请求共享同一个socket

内置连接池,支持连接复用,减少延迟

支持透明的gzip压缩响应体

通过缓存避免重复的请求

请求失败时自动重试主机的其他ip,自动重定向

好用的API

添加依赖

dependencies {
...
compile 'com.squareup.okhttp3:okhttp:3.4.1'
...
}


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;
}
}


创建Request前对象,发送Http请求,在build()方法前添加请求设置,如:使用url()设置请求地址:

Request request = new Request.Builder().url(url).build();


对于psot请求带参数的,需要先构建一个RequestBody对象存在方待提交的参数:

RequestBody requestBody = new FormBody.Builder().add("key","value").build();


Request.Builder().post()方法,并将requestBody 对象传入:

Request request = new Request.Builder().post(formBody).url(url).build();


调用newCall()方法创建一个Call对象,使用execute()方法发送同步请求:

Response response = client.newCall(request).exectue();


enqueue()发送异步请求:

client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//失败时的事物处理
}
@Override
public void onResponse(Call call, Response response) {
//成功时的事物处理
}
});


事实上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();
}


Response对象就是服务器返回的数据:

response.body().string();//String类型数据
response.body().byteStream();//文件输入流数据
response.body().bytes();//二进制字节数组


具体获取的数据,要看服务器返回的数据类型。

封装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();
finalRequest 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();
}
});

}
}


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);
}


你可能会发现异步的请求的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());
}


对于没有请求参数的,直接传一个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);
}
});
}

}


对应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>


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);
}
});
}
}


调用MainView接口,重写方法,显示传递的数据。重写方法的参数,就是我们请求的结果。

无论多少次接口传递,即使传递到了activity中,这些数据仍在异步线程中,我们仍无法在主线程中使用。Android提供了runOnUiThread()方法,为我们解决在异步线程中更新UI的方法:

runOnUiThread(new Runnable(){  @Override
public void run() {
}});


也可以使用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);
}
});
}


至此整个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" />


效果图





Android studio Module代码

GitHub下载

CSDN下载

API免费测试接口:http://www.bejson.com/knownjson/webInterface/

参考引用

http://www.tuicool.com/articles/6FjAJnV

http://www.qingpingshan.com/rjbc/az/110232.html

http://blog.csdn.net/lmj623565791/article/details/47911083
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐