Android Okhttp3+MVP简单实战

Okhttp

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

特性
  1. 支持http2,对一台机器的所有请求共享同一个socket
  2. 内置连接池,支持连接复用,减少延迟
  3. 支持透明的gzip压缩响应体
  4. 通过缓存避免重复的请求
  5. 请求失败时自动重试主机的其他ip,自动重定向
  6. 好用的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();
        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();
            }
        });

    }
}
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代码

CSDN下载: https://download.csdn.net/download/demonliuhui/10755566
GitHub下载:https://github.com/MyAndroidDemo/OkhttpMVP

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

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值