一、HTTP协议原理:
1.简介:HTTP是一个属于应用层的面向对象的协议,由于其简洁欸,快速的方式,适用于分布式超媒体信息系统。
2.特点:
(1)支持C/S(客户/服务器)模式。
(2)简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST,每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
(3)灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
(4)无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
(5)无状态:HTTP协议是无状态协议,无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
3.HTTP URL格式:
http://host[“:”port][abs_path]
http表示要通过HTTP协议来定位网络资源;host表示合法的Internet主机域名或者IP地址;port指定一个端口号,为空则使用默认端口80;abs_path指定请求资源的URI(Web上任意的可用资源)。
二、Okhttp3使用:
1.Android Studio 配置gradle:
compile 'com.squareup.okhttp3:okhttp:3.2.0'
compile 'com.squareup.okio:okio:1.7.0'
2.添加网络权限:
<uses-permission android:name="android.permission.INTERNET"/>
3.异步get请求:
private void getAsynHttp() {
mOkHttpClient=new OkHttpClient();
Request.Builder requestBuilder = new Request.Builder().url("http://www.baidu.com");
//可以省略,默认是GET请求
requestBuilder.method("GET",null);
Request request = requestBuilder.build();
Call mcall= mOkHttpClient.newCall(request);
mcall.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (null != response.cacheResponse()) {
String str = response.cacheResponse().toString();
Log.i("wangshu", "cache---" + str);
} else {
response.body().string();
String str = response.networkResponse().toString();
Log.i("wangshu", "network---" + str);
}
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "请求成功", Toast.LENGTH_SHORT).show();
}
});
}
});
}
4.异步Post请求:
private void postAsynHttp() {
mOkHttpClient=new OkHttpClient();
RequestBody formBody = new FormBody.Builder()
.add("size", "10")
.build();
Request request = new Request.Builder()
.url("http://api.1-blog.com/biz/bizserver/article/list.do")
.post(formBody)
.build();
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String str = response.body().string();
Log.i("wangshu", str);
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "请求成功", Toast.LENGTH_SHORT).show();
}
});
}
});
}
5.异步上传文件:
上传文件本身也是一个post请求,首先定义上传文件类型:
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.parse("text/x-markdown; charset=utf-8");
将sdcard根目录的hehehe.txt文件上传到服务器上:
private void postAsynFile() {
mOkHttpClient=new OkHttpClient();
File file = new File("/sdcard/hehehe.txt");
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
.build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.i("cjf666",response.body().string());
}
});
}
如果想同步上传文件,只需要调用
mOkHttpClient.newCall(request).execute()
就可以了。
不要忘了加权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
6.异步下载文件:
private void downAsynFile() {
mOkHttpClient = new OkHttpClient();
String url = "https://img-my.csdn.net/uploads/201603/26/1458988468_5804.jpg";
Request request = new Request.Builder().url(url).build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) {
InputStream inputStream = response.body().byteStream();
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(new File("/sdcard/cjf666.jpg"));
byte[] buffer = new byte[2048];
int len = 0;
while ((len = inputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, len);
}
fileOutputStream.flush();
} catch (IOException e) {
Log.i("cjf666", "IOException");
e.printStackTrace();
}
Log.d("cjf666", "文件下载成功");
}
});
}
7.异步上传Multipart文件:
这种场景很常用,我们有时会上传文件同时还需要传其他类型的字段,OkHttp3实现起来很简单,需要注意的是没有服务器接收我这个Multipart文件,所以这里只是举个例子,具体的应用还要结合实际工作中对应的服务器。
首先定义上传文件类型:
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
private void sendMultipart(){
mOkHttpClient = new OkHttpClient();
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("title", "wangshu")
.addFormDataPart("image", "wangshu.jpg",
RequestBody.create(MEDIA_TYPE_PNG, new File("/sdcard/wangshu.jpg")))
.build();
Request request = new Request.Builder()
.header("Authorization", "Client-ID " + "...")
.url("https://api.imgur.com/3/image")
.post(requestBody)
.build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.i("wangshu", response.body().string());
}
});
}
8.设置超时时间和缓存:
和OkHttp2.x有区别的是不能通过OkHttpClient直接设置超时时间和缓存了,而是通过OkHttpClient.Builder来设置,通过builder配置好OkHttpClient后用builder.build()来返回OkHttpClient,所以我们通常不会调用new OkHttpClient()来得到OkHttpClient,而是通过builder.build():
File sdcache = getExternalCacheDir();
int cacheSize = 10 * 1024 * 1024;
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.cache(new Cache(sdcache.getAbsoluteFile(), cacheSize));
OkHttpClient mOkHttpClient=builder.build();
9.取消请求:
使用call.cancel()可以立即停止掉一个正在执行的call。如果一个线程正在写请求或者读响应,将会引发IOException。当用户离开一个应用时或者跳到其他界面时,使用Call.cancel()可以节约网络资源,另外不管同步还是异步的call都可以取消。
也可以通过tags来同时取消多个请求。当你构建一请求时,使用RequestBuilder.tag(tag)来分配一个标签。之后你就可以用OkHttpClient.cancel(tag)来取消所有带有这个tag的call。
为了模拟这个场景我们首先创建一个定时的线程池:
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
接下来的代码为:
private void cancel(){
final Request request = new Request.Builder()
.url("http://www.baidu.com")
.cacheControl(CacheControl.FORCE_NETWORK)
.build();
Call call=null;
call = mOkHttpClient.newCall(request);
final Call finalCall = call;
//1毫秒后取消call
executor.schedule(new Runnable() {
@Override public void run() {
finalCall.cancel();
}
}, 1, TimeUnit.MILLISECONDS);
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
}
@Override
public void onResponse(final Response response) {
if (null != response.cacheResponse()) {
String str = response.cacheResponse().toString();
Log.i("cjf666", "cache---" + str);
} else {
try {
response.body().string();
} catch (IOException e) {
Log.i("cjf666", "IOException");
e.printStackTrace();
}
String str = response.networkResponse().toString();
Log.i("cjf666", "network---" + str);
}
}
});
Log.i("cjf666", "是否取消成功"+call.isCanceled());
}
10.封装:
对Okhttp封装最需要解决的是以下两点:
(1)避免重复调用代码。
(2)将请求结果回掉改为UI线程。
如果想使用Okhttp封装的开源库,推荐:
https://github.com/pengjianbo/OkHttpFinal
三、Retrofit的使用:
Retrofit2底层是基于Okhttp的。
1.使用前准备:
dependencies {
...
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.retrofit2:converter-scalars:2.1.0'//ConverterFactory的String依赖包
}
2.Manifest中添加权限:
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
3.准备一个api接口用于测试:
https://api.douban.com/v2/book/search?q=金瓶梅&tag=&start=0&count=1
用浏览器打开,返回内容:
{"count":1,"start":0,"total":535,"books":[{"rating":{"max":10,"numRaters":3684,"average":"8.5","min":0},"subtitle":"张竹坡批评第一奇书","author":["兰陵笑笑生"],"pubdate":"1991","tags":[{"count":1534,"name":"金瓶梅","title":"金瓶梅"},{"count":982,"name":"古典文学","title":"古典文学"},{"count":647,"name":"兰陵笑笑生","title":"兰陵笑笑生"},{"count":623,"name":"小说","title":"小说"},{"count":471,"name":"中国古典文学","title":"中国古典文学"},{"count":321,"name":"中国文学","title":"中国文学"},{"count":281,"name":"中国","title":"中国"},{"count":277,"name":"古典","title":"古典"}],"origin_title":"(明)兰陵笑笑生","image":"https://img1.doubanio.com\/mpic\/s10069398.jpg","binding":"","translator":[],"catalog":"\n ","pages":"","images":{"small":"https://img1.doubanio.com\/spic\/s10069398.jpg","large":"https://img1.doubanio.com\/lpic\/s10069398.jpg","medium":"https://img1.doubanio.com\/mpic\/s10069398.jpg"},"alt":"https:\/\/book.douban.com\/subject\/1456692\/","id":"1456692","publisher":"齐鲁出版社","isbn10":"7533300815","isbn13":"9787533300814","title":"金瓶梅","url":"https:\/\/api.douban.com\/v2\/book\/1456692","alt_title":"(明)兰陵笑笑生","author_intro":"","summary":"本书由王汝梅与李昭恂、于凤树校点。","series":{"id":"4279","title":"明代四大奇书"},"price":"268.00元"}]}
4.新建一个实体类Book(GsonFormat):
5.定义一个接口:
public interface RetrofitService { @GET("book/search") Call<Book> getSearchBook(@Query("q") String name, @Query("tag") String tag, @Query("start") int start, @Query("count") int count); }
这个方法做的其实很简单,就是拼接一个URL,然后进行网络请求。这里我们拼接的URL就是上文我们的测试URL:https://api.douban.com/v2/book/search?q=金瓶梅&tag=&start=0&count=1;
在这个URL中book/search就是GET后的值,而?后的q、tag、start、count等入参就是这个方法的入参。
上面我们用的Get方法,当然我们也可以根据需要选择用其他方法:
* GET ----------查找资源(查)
* POST --------修改资源(改)
* PUT ----------上传文件(增)
* DELETE ----删除文件(删)
* HEAD--------只请求页面的首部
6.Retrofit注解:@Query
前面的例子就用了Query用来查询参数。
erface IpService{
@GET("getIpInfo.php")
Call<IpModel> getIpMsg(@Query("ip")String ip);
}
@QueryMap
如果Query参数比较多,那么可以通过@QueryMap方式将所有的参数集成在一个Map统一传递。
erface BlueService {
@GET("book/search")
Call<BookSearchResponse> getSearchBooks(@QueryMap Map<String, String> options);
@Path
@Path用来替换路径
public interface ApiStores {
@GET("adat/sk/{cityId}.html")
Call<ResponseBody> getWeather(@Path("cityId") String cityId);
};
}
@Body
@Body与@POST注解一起使用,提供查询主体内容,其中ApiInfo是一个bean类erface ApiStores {
@POST("client/shipper/getCarType")
Call<ResponseBody> getCarType(@Body ApiInfo apiInfo);
}
@Headers
interface SomeService {
@GET("some/endpoint")
@Headers("Accept-Encoding: application/json")
Call<ResponseBody> getCarType();
}
@Headers用来添加头部信息,上面用的是固定头部,也可以采用动态头部SomeService {
@GET("some/endpoint")
Call<SomeResponse> someEndpoint(
@Header("Location") String location);
}
@Multipart
@Mulipart用来上传文件
public interface FileUploadService {
@Multipart
@POST("upload")
Call<ResponseBody> upload(@Part("description") RequestBody description,
@Part MultipartBody.Part file);
}