OKHttp是一款高效的HTTP客户端,支持连接同一个地址的链接共享同一个socket,通过连接池来减小响应延迟,还有透明的GZIP压缩,请求缓存等优势,其核心主要有路由、连接协议、拦截器、代理、安全性认证、连接池以及网络适配,拦截器主要是指添加、移除或者转换请求或者回应的头部信息。
OKHttp这个库也是square开源的一个网络请求库(OKHttp内部依赖okio),现在已被Google使用到Android源码上了,可见其强大。
关于网络请求库,现在应该还有很多人在使用android_async_http,它内部用的是HttpClient,但是Google在6.0版本里面已经删除了HttpClient相关API,可见这个库已经有点过时了。我之前也写过如何在Android6.0之后使用HttpClient,有兴趣的朋友也可以看看Android Studio与HttpClient这篇博客。
http://square.github.io/okhttp/
官网有一些例子介绍如何使用OKHttp,也有jar包的下载,不过如果是使用Android Studio的朋友只需要添加依赖即可使用OKHttp。
compile 'com.squareup.okhttp3:okhttp:3.*'上面说过OKHttp内部依赖的是okio,所以我们不要忘了添加okio:
compile 'com.squareup.okio:okio:1.13.0' compile 'com.squareup.picasso:picasso:2.5.2'picasso是Square公司开源的一个Android图形缓存库,地址http://square.github.io/picasso/,可以实现图片下载和缓存功能。
大家要添加依赖可以通过Android Studio的project structure导入,具体步骤就不提了。
OKHttp主要实现功能如下:
一般的get请求一般的post请求基于Http的文件上传文件下载加载图片支持请求回调,直接返回对象、对象集合(Json等)支持session的保持我们先来做Get请求,我们Activity的布局就是一个Button和一个TextView,点击Button就会做网络请求,将返回值设置为TextView的文本显示。
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Button btn_get_post; private TextView mText; private OkHttpClient client; private static final int GET = 1; private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case GET : mText.setText((String) msg.obj); break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn_get_post = (Button) findViewById(R.id.btn_get_post); btn_get_post.setOnClickListener(this); mText = (TextView) findViewById(R.id.text); client = new OkHttpClient(); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_get_post: new Thread(){ @Override public void run() { super.run(); try { String result = get("http://www.kuaidi100.com/query?type="); Message msg = Message.obtain(); msg.what = GET; msg.obj = result; handler.sendMessage(msg); } catch (IOException e) { e.printStackTrace(); } } }.start(); break; } } /** * get请求 * @param url 网络链接 * @return * @throws IOException */ private String get(String url) throws IOException { Request request = new Request.Builder() .url(url) .build(); Response response = client.newCall(request).execute(); return response.body().string(); } }因为这里的网络处理要在其它线程中,所以在点击Button时就开启一个线程,将网址传入get()方法,得到Request对象,通过OkHttpClient对象执行这个请求,获得传回的数据,这个网址传回的是单纯的Json数据。因为我们要把返回的字符串更新TextView,而UI更新只能在主线程里,所以我们还要把从子线程里得到的值传递给handler,让handler来处理,换言之,子线程负责网络通信,Handler负责UI更新,当然也可以用runOnUiThread()来做。
从上面的代码编写可以知道,执行get请求的步骤为:
拿到OKHttpClient对象构造Request将Request封装成Call执行Call我们也可以用Call对象的异步的方法处理执行请求,将execute()方法换为enqueue(),在方法里传入Callback对象。
client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { String res = response.body().string(); Message msg = Message.obtain(); msg.what = GET; msg.obj = res; handler.sendMessage(msg); } });也可以在回调中调用handler更新UI,这样做更加简单一些。
Post请求既可以获得数据,也可以上传,我们这里就做得到数据的处理。
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); private String post(String url, String json) throws IOException { RequestBody body = RequestBody.create(JSON, json); Request request = new Request.Builder() .url(url) .post(body) .build(); Response response = client.newCall(request).execute(); return response.body().string(); }我们就在点击事件里把调用的get()方法换为post()方法即可, JSON是我们上传数据的配置信息,因为我们这里并不要上传,所以json可以为空字符串,如果要提交文件,只要将File类型的对象传入RequestBody里,再修改MediaType就可以了,不清楚MediaType的值没关系,可以百度mime type,就可以看到所有mime的类型,可以知道各个值对应的拓展名,对应填上即可。
如果要上传字段数据,比如是登录输入用户名和密码,可以用post这样写:
private String post(String url, String json) throws IOException { FormBody.Builder builder = new FormBody.Builder(); builder.add("userName", "haotian"); builder.add("password", "123456"); Request request = new Request.Builder() .url(url) .post(builder.build()) .build(); Response response = client.newCall(request).execute(); return response.body().string(); }那样服务端可以通过指定的字段从上传数据中获得,这里没有写服务端就不演示了。
我们要上传文件的话就不能像上面提到的提交文件一样把文件对象传入create()方法里了,因为上传的文件肯定是要和用户对应起来的,并且用户提交的文件也不一定是同一种类型的,所以我们要让RequestBody更具体才行。
private String post(String url, String json) throws IOException { MultipartBody body = new MultipartBody.Builder().setType(MultipartBody.FORM) .addFormDataPart("username","haotian") .addFormDataPart("password","123456") .addFormDataPart("photo","lht.jpg",RequestBody.create(MediaType.parse("image/jpeg"), file)) .build(); Request request = new Request.Builder() .url(url) .post(body) .build(); Response response = client.newCall(request).execute(); return response.body().string(); }Json在我们平时开发里还是用到的满多的,对于json的数据如果不编排一下格式查看起来很费劲,在这里我提供一个Chrome/FireFox浏览器下处理JSON的插件JSON-Handle。
这个网址是http://api.m.mtime.cn/PageSubArea/TrailerList.api,有不少最近上映的电影的信息。
还有个小技巧,Android Studio还有把Json数据转换为Java Bean的插件,叫GsonFormat,大家可以使用Android Studio把它安装。
大家都知道,下载文件跟请求信息没什么不同,只是我们会用IO流去接收罢了,我们这个例子是从一个只有一张图片的网页上把那张图片下载下来。
public class DownLoadActivity extends AppCompatActivity implements View.OnClickListener { private Button btn_download; private ImageView mImageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_down_load); btn_download = (Button) findViewById(R.id.btn_download); mImageView = (ImageView) findViewById(R.id.iv_download); btn_download.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_download : download("http://img.bzcm.net/news/attachement/jpg/site2/20141103/0003ffa94ec915c114ff11.jpg"); break; } } private void download(String url) { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url(url) .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 { InputStream is = response.body().byteStream(); int len = 0; File file = new File(Environment.getExternalStorageDirectory(), "okhttp_test.jpg"); FileOutputStream fos = new FileOutputStream(file); byte[] buf = new byte[1024]; while((len = is.read(buf)) != -1) { fos.write(buf, 0, len); } fos.flush(); fos.close(); is.close(); } }); } }get请求仍是和前面一样,不过因为前面我们要获得是文本信息,而这里我们要处理的是一张图片,所以不能用String来接收了,我们要byteStream()方法得到字节流。
加载图片就很简单啦,我们只要把获得的输入流转换为Bitmap对象即可:
@Override public void onResponse(Call call, Response response) throws IOException { InputStream is = response.body().byteStream(); final Bitmap bitmap = BitmapFactory.decodeStream(is); runOnUiThread(new Runnable() { @Override public void run() { mImageView.setImageBitmap(bitmap); } }); is.close(); }获得图片的数据对于OKHttp是很简单的,所以我们如果要应用到开发中去最主要的是对图片的压缩,这点是极其需要注意的。
大家应该还记得在导入包的时候,处理OKHttp的内核okio外还引入了Picasso,说过这是个图像库,我们可以用它来裁剪我们的图片。
public class CropSquareTrans implements Transformation { @Override public Bitmap transform(Bitmap source) { int size = Math.min(source.getWidth(), source.getHeight()); //子位图第一个像素在源位图的坐标 int x = (source.getWidth() - size) / 2; int y = (source.getHeight() - size) / 2; Bitmap result = Bitmap.createBitmap(source, x, y, size, size); if (result != source) { //释放bitmap source.recycle(); } return result; } @Override public String key() { return "square()"; } } @Override public void onResponse(Call call, Response response) throws IOException { InputStream is = response.body().byteStream(); // final Bitmap bitmap = BitmapFactory.decodeStream(is); final Bitmap bitmap = new CropSquareTrans().transform(BitmapFactory.decodeStream(is)); runOnUiThread(new Runnable() { @Override public void run() { mImageView.setImageBitmap(bitmap); } }); is.close(); }我们通常在下载文件的时候也会给用户展示下载的进度,这里就用下载图片的例子小小的做个介绍:
private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); mProgress.setProgress((Integer) msg.obj); } };先创建个Handler对象做UI更新。
@Override public void onResponse(Call call, Response response) throws IOException { long total = response.body().contentLength(); long sum = 0; mProgress.setMax((int) total); InputStream is = response.body().byteStream(); int len = 0; File file = new File(Environment.getExternalStorageDirectory(), "okhttp_test.jpg"); FileOutputStream fos = new FileOutputStream(file); byte[] buf = new byte[1024]; while((len = is.read(buf)) != -1) { fos.write(buf, 0, len); sum += len; Message msg = new Message(); msg.obj = (int) sum; mHandler.sendMessage(msg); } fos.flush(); fos.close(); is.close(); }在onResponse()方法里把文件流的大小记录下来设置为ProgressBar的最大进度,不断下载就用handler不断更新ProgressBar的进度。
在博客的最后稍微提下session的保持问题,例如用户在登录以后,服务端会产生一个sessionID,sessionID会维持一个内存区域,一般是存放用户的登录信息,然后会把这个sessionID作为cookie返回给我们的客户端,客户端如果拿到这个sessionID,在以后的登录请求中去一直附加的话,只要服务器不抛掉这个sessionID,就可以通过sessionID读取到用户的登录信息,这样就不用重复的登录了,sessionID也可以作为判断用户是否已登录的标志。
client = new OkHttpClient.Builder().cookieJar(new CookieJar() { private Map<String, List<Cookie>> cookieStore = new HashMap<>(); @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) { cookieStore.put(url.host(), cookies); } @Override public List<Cookie> loadForRequest(HttpUrl url) { List<Cookie> cookies = cookieStore.get(url.host()); return cookies != null ? cookies : new ArrayList<Cookie>(); } }).build();通过上面的代码就会把我们的cookie保存在内存里面,只要cookie在内存中没有被杀死,以后的每个请求都会设成键值对发送到服务端,其中包括了sessionID,要做持久化的cookie,就要把cookie存入文件中保存,这里就不提了。
结束语:本文仅用来学习记录,参考查阅。