Android 你必須了解的網絡框架Retrofit2.0
這一篇學習的retrofit底層默認使用的就是okhttp,相信大家多少也聽過這個框架,下面我們就來一起學習下,講真,學會之后這個框架用起來真的很爽,特別靈活。
按照習慣先來說一下它的優缺點
優點:
- 可以配置不同HTTP client來實現網絡請求,如okhttp、httpclient等
- 請求的方法參數注解都可以定制
- 支持同步、異步和RxJava
- 超級解耦
- 可以配置不同的反序列化工具來解析數據,如json、xml等
- 使用非常方便靈活
- 框架使用了很多設計模式(感興趣的可以看看源碼學習學習)
缺點:
- 不能接觸序列化實體和響應數據
- 執行的機制太嚴格
- 使用轉換器比較低效
- 只能支持簡單自定義參數類型
相關學習資料的網址
- retrofit官網: http://square.github.io/retrofit/
- github地址: https://github.com/square/retrofit
- Simple HTTP with Retrofit2:
https://realm.io/news/droidcon-jake-wharton-simple-http-retrofit-2/
環境配置
在builde.gradle里面添加上
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.okhttp3:okhttp:3.4.1'
在AndroidManifest.xml添加所需權限
<uses-permission android:name="android.permission.INTERNET" />
基本使用
-
get異步請求
public interface GitHubService {
@GET("users/{user}/repos")
Call<ResponseBody> listRepos(@Path("user") String user);
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
GitHubService service = retrofit.create(GitHubService.class);
Call<ResponseBody> repos = service.listRepos("octocat");
repos.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
Log.e("APP",response.body().source().toString());
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
t.printStackTrace();
}
});
.baseUrl 設置最基本url,也就是http請求的url前綴,可以把項目中重復的前綴用這個來設置
.addConverterFactory(GsonConverterFactory.create()) 是添加Gson數據解析ConverterFactory,后面會專門介紹下這個,這里就不做過多解釋
ResponseBody 這個是okhttp里面的對象,可以直接返回整個字符串,也可以獲取流的形式
-
post異步請求
POST與GET實現基本上是一樣的,只是把注解GET換成POST就OK.為了測試POST,專門去網上找了個接口測試,下面就分享給大家,既可以用GET也可以用POST請求
http://www.kuaidi100.com/query?type=快遞公司代號&postid=快遞單號
ps:快遞公司編碼:申通="shentong" EMS="ems" 順豐="shunfeng" 圓通="yuantong" 中通="zhongtong" 韻達="yunda" 天天="tiantian" 匯通="huitongkuaidi" 全峰="quanfengkuaidi" 德邦="debangwuliu" 宅急送="zhaijisong"
拿著這個接口來實現一下POST異步請求
public interface GitHubService {
@POST("query")
Call<PostQueryInfo> search(@Query("type") String type, @Query("postid") String postid);
}
public class PostQueryInfo {
private String message;
private String nu;
private String ischeck;
private String com;
private String status;
private String condition;
private String state;
private List<DataBean> data;
public static class DataBean {
private String time;
private String context;
private String ftime;
}
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://www.kuaidi100.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
GitHubService apiService = retrofit.create(GitHubService.class);
Call<PostQueryInfo> call = apiService.search("yuantong","500379523313");
call.enqueue(new Callback<PostQueryInfo>(){
@Override
public void onResponse(Call<PostQueryInfo> call,Response<PostQueryInfo> response){
Log.e("APP",response.body().getNu());
}
@Override
public void onFailure(Call<PostQueryInfo> call,Throwable t){
t.printStackTrace();
}
});
PostQueryInfo實體類省略了get和set方法,大家可以自行快捷鍵,最終打印返回回來的快遞單號,實現POST異步請求就是這么簡單
http://www.bejson.com/knownjson/webInterface/ 這網站里面還有一些其它免費接口,感興趣的可以去看看
-
常用注解的使用介紹
上面GitHubService里面的注解大家應該都能猜它的作用了吧,下面就給大家介紹下
@GET 和 @POST 分別是get和post請求。括號里面的value值與上面 .baseUrl 組成完整的路徑
@Path 動態的URL訪問。像上面get請求中的 {user} 可以把它當做一個占位符,通過 @Path("user") 標注的參數進行替換
@Query 請求參數。無論是GET或POST的參數都可以用它來實現
@QueryMap 請求參數使用Map集合。可以傳遞一個map集合對象
@Body 實體請求參數。顧名思義可以傳遞一個實體對象來作為請求的參數,不過實體屬性要與參數名一一致
@FormUrlEncoded 和 @Field 簡單的表單鍵值對。兩個需要結合使用,使用如下:
@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);
@Multipart 和 @Part POST表單的方式上傳文件可以攜帶參數。兩個需要結合使用,使用方式查看下面 文件上傳 中介紹。
@PartMap 和 @Part POST表單上傳多個文件攜帶參數。兩個結合使用,使用方式查看下面 文件上傳 中介紹。
這里只介紹了一些常用的,大家如果想了解更多可以查看相關文檔
-
文件上傳
1、 單文件上傳攜帶參數 (使用注解 @Multipart 和 @Part ),需要在手機SD卡目錄下的Pictures文件夾下添加xuezhiqian.png圖片
@Multipart
@POST("UploadServlet")
Call<ResponseBody> uploadfile(@Part MultipartBody.Part photo, @Part("username") RequestBody username, @Part("password") RequestBody password);
Retrofit retrofitUpload = new Retrofit.Builder()
.baseUrl("http://192.168.1.8:8080/UploadFile/")
.addConverterFactory(GsonConverterFactory.create())
.build();
GitHubService service = retrofitUpload.create(GitHubService.class);
File file = new File(Environment.getExternalStorageDirectory()+"/Pictures", "xuezhiqian.png");
//設置Content-Type:application/octet-stream
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
//設置Content-Disposition:form-data; name="photo"; filename="xuezhiqian.png"
MultipartBody.Part photo = MultipartBody.Part.createFormData("photo", file.getName(), photoRequestBody);
//添加參數用戶名和密碼,并且是文本類型
RequestBody userName = RequestBody.create(MediaType.parse("text/plain"), "abc");
RequestBody passWord = RequestBody.create(MediaType.parse("text/plain"), "123");
Call<ResponseBody> loadCall = service.uploadfile(photo, userName,passWord);
loadCall.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
Log.e("APP", response.body().source().toString());
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
t.printStackTrace();
}
});
2、 多文件上傳攜帶參數 (使用注解 @PartMap 和 @Part ),需要再在手機SD卡目錄下的Pictures文件夾下添加xuezhiqian2.png圖片
@Multipart
@POST("UploadServlet")
Call<ResponseBody> uploadfile(@PartMap Map<String, RequestBody> params, @Part("password") RequestBody password);
Retrofit retrofitUpload = new Retrofit.Builder()
.baseUrl("http://192.168.1.8:8080/UploadFile/")
.addConverterFactory(GsonConverterFactory.create())
.build();
GitHubService service = retrofitUpload.create(GitHubService.class);
File file = new File(Environment.getExternalStorageDirectory()+"/Pictures", "xuezhiqian.png");
File file2 = new File(Environment.getExternalStorageDirectory()+"/Pictures", "xuezhiqian2.png");
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
RequestBody photoRequestBody2 = RequestBody.create(MediaType.parse("application/octet-stream"), file2);
RequestBody userName = RequestBody.create(MediaType.parse("text/plain"), "abc");
RequestBody passWord = RequestBody.create(MediaType.parse("text/plain"), "123");
Map<String,RequestBody> photos = new HashMap<>();
//添加文件一
photos.put("photos\\"; filename=\\""+file.getName(), photoRequestBody);
//添加文件二
photos.put("photos\\"; filename=\\""+file2.getName(), photoRequestBody2);
//添加用戶名參數
photos.put("username", userName);
Call<ResponseBody> loadCall = service.uploadfile(photos, passWord);
loadCall.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
Log.e("APP", response.body().source().toString());
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
t.printStackTrace();
}
});
要說明的是,多個文件上傳不能像單個文件上傳使用 MultipartBody.Part 對象,而是使用注解 @PartMap 添加多個RequestBody拼接filename來實現, @part("?") 和 @Multipart 注解已經幫我們設置好成 Content-Disposition:form-data; name="?" 這樣子了, @part 里的問號對應 name 后面的問號,而我們要添加多個文件,則需要在name里面作文章。所以就有了上面MAP集合中的KEY的拼接字符串,我們想設置成 Content-Disposition:form-data; name="photo"; filename="xuezhiqian.png" ,MAP集合KEY的值設置為 photo"; filename="xuezhiqian.png 替換name后面的問號就OK了,里面的引號在使用的時候需要加上反斜杠轉義嵌套使用。
多文件上傳攜帶參數,參考自:
http://blog.csdn.net/jdsjlzx/article/details/52246114
一直找不到免費上傳文件的接口來做測試,我就花了點時間自己做了一個,代碼也是參考網上的,這里也給大家貼一下后臺核心接收文件保存的代碼,希望對大家有所幫助,自己動手豐衣足食。
/**
* 上傳文件
* @param request
* @param response
* @throws IOException
*/
@SuppressWarnings("unchecked")
private void uploadFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html;charset=utf-8");// 設置響應編碼
request.setCharacterEncoding("utf-8");
PrintWriter writer = response.getWriter();// 獲取響應輸出流
//ServletInputStream inputStream = request.getInputStream();// 獲取請求輸入流
/*
* 1、創建DiskFileItemFactory對象,設置緩沖區大小和臨時文件目錄 該類有兩個構造方法一個是無參的構造方法,
* 另一個是帶兩個參數的構造方法
*
* @param int sizeThreshold,該參數設置內存緩沖區的大小,默認值為10K。當上傳文件大于緩沖區大小時,
* fileupload組件將使用臨時文件緩存上傳文件
*
* @param java.io.File
* repository,該參數指定臨時文件目錄,默認值為System.getProperty("java.io.tmpdir");
*
* 如果使用了無參的構造方法,則使用setSizeThreshold(int
* sizeThreshold),setRepository(java.io.File repository) 方法手動進行設置
*/
DiskFileItemFactory factory = new DiskFileItemFactory();
int sizeThreshold = 1024 * 1024;
factory.setSizeThreshold(sizeThreshold);
File repository = new File(request.getSession().getServletContext().getRealPath("temp"));
// System.out.println(request.getSession().getServletContext().getRealPath("temp"));
// System.out.println(request.getRealPath("temp"));
factory.setRepository(repository);
/*
* 2、使用DiskFileItemFactory對象創建ServletFileUpload對象,并設置上傳文件的大小
*
* ServletFileUpload對象負責處理上傳的文件數據,并將表單中每個輸入項封裝成一個FileItem 該對象的常用方法有:
* boolean isMultipartContent(request);判斷上傳表單是否為multipart/form-data類型
* List parseRequest(request);解析request對象,并把表單中的每一個輸入項包裝成一個fileItem
* 對象,并返回一個保存了所有FileItem的list集合 void setFileSizeMax(long
* filesizeMax);設置單個上傳文件的最大值 void setSizeMax(long sizeMax);設置上傳溫江總量的最大值
* void setHeaderEncoding();設置編碼格式,解決上傳文件名亂碼問題
*/
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setHeaderEncoding("utf-8");// 設置編碼格式,解決上傳文件名亂碼問題
/*
* 3、調用ServletFileUpload.parseRequest方法解析request對象,得到一個保存了所有上傳內容的List對象
*/
List<FileItem> parseRequest = null;
try {
parseRequest = upload.parseRequest(request);
} catch (FileUploadException e) {
e.printStackTrace();
}
/*
* 4、對list進行迭代,每迭代一個FileItem對象,調用其isFormField方法判斷是否是文件上傳
* true表示是普通表單字段,則調用getFieldName、getString方法得到字段名和字段值
* false為上傳文件,則調用getInputStream方法得到數據輸入流,從而讀取上傳數據
* FileItem用來表示文件上傳表單中的一個上傳文件對象或者普通的表單對象 該對象常用方法有: boolean
* isFormField();判斷FileItem是一個文件上傳對象還是普通表單對象 true表示是普通表單字段,
* 則調用getFieldName、getString方法得到字段名和字段值 false為上傳文件,
* 則調用getName()獲得上傳文件的文件名,注意:有些瀏覽器會攜帶客戶端路徑,需要自己減除
* 調用getInputStream()方法得到數據輸入流,從而讀取上傳數據
* delete();表示在關閉FileItem輸入流后,刪除臨時文件。
*/
for (FileItem fileItem : parseRequest) {
if (fileItem.isFormField()) {// 表示普通字段
if ("username".equals(fileItem.getFieldName())) {
String username = fileItem.getString();
writer.write("您的用戶名:" + username + "<br>");
}
if ("password".equals(fileItem.getFieldName())) {
String userpass = fileItem.getString();
writer.write("您的密碼:" + userpass + "<br>");
}
} else {// 表示是上傳的文件
// 不同瀏覽器上傳的文件可能帶有路徑名,需要自己切割
String clientName = fileItem.getName();
String filename = "";
if (clientName.contains("\\\\")) {// 如果包含"\\"表示是一個帶路徑的名字,則截取最后的文件名
filename = clientName.substring(clientName.lastIndexOf("\\\\")).substring(1);
} else {
filename = clientName;
}
UUID randomUUID = UUID.randomUUID();// 生成一個128位長的全球唯一標識
filename = randomUUID.toString() + filename;
/*
* 設計一個目錄生成算法,如果所用用戶上傳的文件總數是億數量級的或更多,放在同一個目錄下回導致文件索引非常慢,
* 所以,設計一個目錄結構來分散存放文件是非常有必要,且合理的 將UUID取哈希算法,散列到更小的范圍,
* 將UUID的hashcode轉換為一個8位的8進制字符串,
* 從這個字符串的第一位開始,每一個字符代表一級目錄,這樣就構建了一個八級目錄,每一級目錄中最多有16個子目錄
* 這無論對于服務器還是操作系統都是非常高效的目錄結構
*/
int hashUUID = randomUUID.hashCode();
String hexUUID = Integer.toHexString(hashUUID);
// System.out.println(hexUUID);
// 獲取將上傳的文件存存儲在哪個文件夾下的絕對路徑
String filepath = request.getSession().getServletContext().getRealPath("upload");
System.out.println("filePath="+filepath);
for (char c : hexUUID.toCharArray()) {
filepath = filepath + "/" + c;
}
// 如果目錄不存在就生成八級目錄
File filepathFile = new File(filepath);
if (!filepathFile.exists()) {
filepathFile.mkdirs();
}
// 從Request輸入流中讀取文件,并寫入到服務器
InputStream inputStream2 = fileItem.getInputStream();
// 在服務器端創建文件
File file = new File(filepath + "/" + filename);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
byte[] buffer = new byte[10 * 1024];
int len = 0;
while ((len = inputStream2.read(buffer, 0, 10 * 1024)) != -1) {
bos.write(buffer, 0, len);
}
writer.write("您上傳文件" + clientName + "成功<br>");
// 關閉資源
bos.close();
inputStream2.close();
}
}
// 注意Eclipse的上傳的文件是保存在項目的運行目錄,而不是workspace中的工程目錄里。
}
其實很簡單就是一個servelt,利用了commons-fileupload.jar和commons-io.jar兩個庫來實現,兩個庫網上都可以找到
-
文件下載
可以采用OKHTTP下載文件的方式,利用 ResponseBody 對象,調用 response.body().byteStream() 方法獲取InputStream輸入流,通過寫文件操作來實現。
-
同步請求和結合RxJava的使用
1、同步請求
Call.execute() 同步請求網絡,要注意的是Android4.0以后不能在主線程里調用,要開一個異步線程來使用,
Call.enqueue() 異步請求網絡,加入一個回調,同步異步需要可按照不同的場景來使用。
Call.cancel() 取消此次請求,有一些場景還是會用到該方法的。
2、結合RxJava使用
添加RxJava環境,在builde.gradle里面添加上
compile 'io.reactivex:rxandroid:1.2.1'
compile 'io.reactivex:rxjava:1.1.6'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
我們就拿上面post異步請求改成RxJava模式
@POST("query")
Observable<PostQueryInfo> searchRx(@Query("type") String type, @Query("postid") String postid);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://www.kuaidi100.com/")
//添加數據解析ConverterFactory
.addConverterFactory(GsonConverterFactory.create())
//添加RxJava
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
GitHubService apiService = retrofit.create(GitHubService.class);
apiService.searchRx("yuantong","500379523313")
//訪問網絡切換異步線程
.subscribeOn(Schedulers.io())
//響應結果處理切換成主線程
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<PostQueryInfo>() {
@Override
public void onCompleted() {
//請求結束回調
}
@Override
public void onError(Throwable e) {
//錯誤回調
e.printStackTrace();
}
@Override
public void onNext(PostQueryInfo postQueryInfo) {
//成功結果返回
Log.e("APP",postQueryInfo.getNu());
}
});
可能有些童鞋沒接觸過RxJava,這里了解一下就可以,后面我會單獨寫一篇關于RxJava的文章,到時可以再回來看看。
配置設置
-
配置OKHttp
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10000L,TimeUnit.MILLISECONDS) //設置連接超時
.readTimeout(10000L,TimeUnit.MILLISECONDS) //設置讀取超時
.writeTimeout(10000L,TimeUnit.MILLISECONDS) //設置寫入超時
.cache(new Cache(getCacheDir(),10 * 1024 * 1024)) //設置緩存目錄和10M緩存
.addInterceptor(interceptor) //添加日志攔截器(該方法也可以設置公共參數,頭信息)
.build();
Retrofit retrofit = new Retrofit.Builder()
.client(client) //設置OkHttp
.baseUrl("http://www.kuaidi100.com/")
.addConverterFactory(GsonConverterFactory.create()) // 添加數據解析ConverterFactory
.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) //添加RxJava
.build();
...省略后面的代碼
日志攔截器需要添加OkHttp對應庫,okhttp的庫是3.4.1,這里也需要設成3.4.1
compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
-
多種ConverterFactory設置
常見的ConverterFactory有
Gson: com.squareup.retrofit2:converter-gson:2.1.0
Jackson: com.squareup.retrofit2:converter-jackson:2.1.0
Moshi: com.squareup.retrofit2:converter-moshi:2.1.0
Protobuf: com.squareup.retrofit2:converter-protobuf:2.1.0
Wire: com.squareup.retrofit2:converter-wire:2.1.0
Simple XML: com.squareup.retrofit2:converter-simplexml:2.1.0
Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars:2.1.0
添加對應得ConverterFactory只需要先在builde.gradle里面配置好上面對應的庫,然后通過 .addConverterFactory 該方法添加即可
-
添加混淆
# Platform calls Class.forName on types which do not exist on Android to determine platform.
-dontnote retrofit2.Platform
# Platform used when running on RoboVM on iOS. Will not be used at runtime.
-dontnote retrofit2.Platform$IOS$MainThreadExecutor
# Platform used when running on Java 8 VMs. Will not be used at runtime.
-dontwarn retrofit2.Platform$Java8
# Retain generic type information for use by reflection by converters and adapters.
-keepattributes Signature
# Retain declared checked exceptions for use by a Proxy instance.
-keepattributes Exceptions
介紹兩個好用好玩的AS插件
1、GsonFormat JSON解析成對象
2、Sexy Editor 工作區間背景設置
都可以在Preferences下Plugins里搜索到
來自:http://www.jianshu.com/p/a94e38636fde