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/6FjAJnVhttp://www.qingpingshan.com/rjbc/az/110232.html
http://blog.csdn.net/lmj623565791/article/details/47911083
相关文章推荐
- Android 框架设计Demo,一个简单的MVP示例搜索功能,网络请求用Retrofit+RxJava实现
- Android三大设计模式之一------------------MVP设计模式(包括rxjava+retrofit网络请求框架)
- Android——网络框架篇:OkHttpUtils一个专注于让网络请求更简单的框架
- MVP-简单的OkHttp网络请求数据
- 网络层架构设计与实战九框架拓展设计之请求成功类型转换包装处理
- 优雅设计封装基于Okhttp3的网络框架(二):多线程下载功能原理设计 及 简单实现
- 实战3--设计管理模块 第二步, 分析功能和请求, 搭建简单框架
- 网络请求框架 okhttp 简单的使用总结(一)
- 网络层架构设计与实战七框架拓展设计之支持原生的HttpUrlConnection方式请求和响应
- 网络请求框架 Rxjava+ReTrofit+okHttp+MVP
- MVP模式的OKhttp请求网络数据,xrecyclerview上拉刷新,下拉加载
- 使用Retrofit+RxJava搭建简单的MVP网络请求框架
- 优雅设计封装基于Okhttp3的网络框架(五):多线程、单例模式优化 及 volatile、构建者模式使用解析
- Android项目MVP模式框架+okhttp+rxjava+retrofit网络框架
- 网络层架构设计与实战八框架拓展设计之业务层多线程分发处理及请求成功移除处理
- [置顶] 优雅设计封装基于Okhttp3的网络框架(完):原生HttpUrlConnction请求、多线程分发 及 数据转换
- OkhttpUtils一个专注于让网络请求更简单的框架
- okhttp网络请求框架的简单使用
- OkHttpUtils一个专注于让网络请求更简单的框架
- OkHttpUtils一个专注于让网络请求更简单的框架