Retrofit是Square開發的一個用于網絡請求的開源庫,內部封裝了okhttp,并且和RxAndroid完美的兼容,使得Android的開發效率增加不少的同時也使代碼變得清晰易讀。
本次的學習建立在上次okhttp學習的基礎之上,service端的程序也是通過自己搭建并完成的。服務端的程序比較簡單,本次的retrofit學習不對服務端的程序進行過多的講解。如果有疑問,可以參考上次okhttp的相關內容。首先還是先列舉出本次學習用到的資源。
搭建使用環境
下載最新的jar或者構建Maven倉庫:
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>retrofit</artifactId>
<version>2.2.0</version>
</dependency>
或者直接在項目的build.gradle
中添加如下的依賴
compile 'com.squareup.retrofit2:retrofit:2.2.0'
ps,在添加依賴之前,最好先去github上看當前的最新版本。
在使用之前需要先添加網絡訪問權限。
<uses-permission android:name="android.permission.INTERNET"/>
使用示例
下面通過使用retrofit實現get、post、文件上傳、下載來演示retrofit的使用。
get請求
retrofit在使用的過程中,需要定一個接口對象,我們將它命名為IUserService:
public interface IUserService {
@GET("rlogin")
Call<ResponseBody> loginByGet(@Query("user") String user, @Query("passwd") String passwd);
}
然后在需要MainActivity中構建Retrofit,并生成一個實現接口的方法。
retrofit = new Retrofit.Builder()
.baseUrl("http://172.18.9.31:8080/OkhttpTestService/")
.build();//構建一個retrofit 對象
IUserService repo = retrofit.create(IUserService.class);
Call<ResponseBody> call = repo.loginByGet("reoger","123456");//實例loginByGet對象
call.enqueue(new Callback<ResponseBody>() {//異步執行
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
mImageView.setText(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
mImageView.setText("file to get");
}
});
簡要的說明一下上后面的代碼。首先,在IUserSevice接口中,通過@GET("rlogin")注釋指定了路徑,然后通過后面的loginByGet具體化了url。結合BaseUrl,實例化出來的Url實際上是:http://172.18.9.31:8080/OkhttpTestService/rlogin?user=reoger&passwd=123456,后面的user和passwd是在實例化時傳入的。
可能這么講會有點難懂,先看一個簡單的例子。
注解中的參數為請求的相對URL路徑@GET("users/list")
就相當與在BaseUrl后加上/users/list。在本例中就相當于:
http://172.18.9.31:8080/OkhttpTestService/users/list
當然,我們可以會遇到URL并不是固定的那種情況。這個時候我們就可以這么寫:
@GET("group/{id}/users") //注意 字符串id
List<ResponseBody> groupList(@Path("id") int groupId); //注意 Path注解的參數要和前面的字符串一樣 id
這個時候我們構造groupList時會傳入id,而這個id的值會代替傳入的groupId值代替。
{}用于表示是可以替換的區域,而函數參數必須用@Path注解聲明。參見上例。
然后,當需要用我們的請求含有參數的時候,這個時候就需要使用@Query
注解來完成。
例如訪問:http://baseurl/users?user=username
就需要:
@GET("users")
Call<ResponseBody> getUsersBySort(@Query("user") String sort);
點擊之后,發現服務端能正確接收來自客服端的請求,并且客服端也能正確接收來自服務端的反饋信息。
這里有一點還需要提出來一下,Call<T> 中的T可以是返回的數據對象,如果返回的是Json數據,我們可以將其解析成一個Java對象的話,可以直接使用該Bean作為返回值,在構建retrofit的時候加上轉換方法即可。
為了后面后面的代碼比較簡潔,我們直接在這里先將后面用到的retrofit和repo對象,已經實現方法executeByEn()統一聲明。
retrofit = new Retrofit.Builder()
.baseUrl("http://172.18.9.31:8080/OkhttpTestService/")
.addConverterFactory(GsonConverterFactory.create())
.build();
repo = retrofit.create(IUserService.class);
private void executeByEn(Call<ResponseBody> call) {
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
mImageView.setText(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
mImageView.setText("file to get");
}
});
}
Post請求
還是先在IUserService中進行申明:
@POST("rlogin")
@FormUrlEncoded
Call<ResponseBody> loginByPost(@Field("user")String user,@Field("passwd") String passwd);
可以看到,這里我們使用Post作為注解,說明這是一個Post請求。添加FormUrlEncoded
,然后通過@Field
添加參數即可。
訪問的代碼:
Call<ResponseBody> call = repo.loginByPost("reoger","12346");
executeByEn(call);
此時,我們應該就能同get請求一樣將數據傳遞到服務端,并接收來及服務端的消息。
Post向服務端傳遞String對象。
首先還是在IUserService中進行聲明:
@POST("rpostString")
Call<ResponseBody> postString(@Body RequestBody user);
通過 @Body 注解可以聲明一個對象作為請求體發送到服務器。
訪問代碼:
RequestBody body=RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"),"Here is my server to pass the data, can be a string, but of course, can also be JSON data");
Call<ResponseBody> call = repo.postString(body);
executeByEn(call);
這里還是給出服務端的代碼吧,服務端接收到數據后會給客戶端返回一個收到的信息。
HttpServletRequest request = ServletActionContext.getRequest();
ServletInputStream is = request.getInputStream();
StringBuilder sb = new StringBuilder();
int len = 0;
byte[] buf = new byte[1024];
while((len=is.read(buf))!=-1){
sb.append(new String(buf,0,len));
}
System.out.println(sb.toString());
HttpServletResponse response = ServletActionContext.getResponse();
PrintWriter writer = response.getWriter();
writer.write("reces: "+sb.toString());
writer.flush();
return null;
訪問之后,會發現我們的服務端正確接收到了我們發送的string數據,客戶端也成功接收到了來自服務端的數據。
Post 上傳Json格式數據
如果你只是單獨想上傳Json格式的數據到服務器,你完全可以寫的更加簡單。
@POST("rpostString")
Call<ResponseBody> postJson(@Body User user);
訪問接口:
Call<ResponseBody> call = repo.postJson(new User("reoger","love"));
executeByEn(call);
是吧。兩三行代碼就解決了。那為什么我們傳入User這樣一個Bean對象傳到服務端的時候就變成了Json數據呢。主要原因我我們在構造retrofit的時候添加的轉換方法、
.addConverterFactory(GsonConverterFactory.create())
為此,我們還需要添加額外的依賴:
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
具體的轉換過程就完成不需要我們來實現了。是不是很方便。
Post上傳單個文件
還是先聲明:
@Multipart
@POST("rpostSingerFile")
Call<ResponseBody> uploadSingerFile(@Part MultipartBody.Part mPhoto, @Part("user")RequestBody user,@Part("passwd") RequestBody passwd);
這里@MultiPart的意思就是允許多個@Part了,我們這里使用了3個@Part,第一個我們準備上傳個文件,使用了MultipartBody.Part類型,其余兩個均為簡單的鍵值對。
使用:
File file = new File(Environment.getExternalStorageDirectory(),"test.jpg");
if(!file.exists()){
Log.e("TAG","file is not exit!");
return ;
}
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
MultipartBody.Part photo = MultipartBody.Part.createFormData("mPhoto", "test.jpg", photoRequestBody);
Call<ResponseBody> call = repo.uploadSingerFile(photo, RequestBody.create(null, "abc"), RequestBody.create(null, "123"));
executeByEn(call);
代碼形式和前面的Okhttp基本差不多。
Post上傳多個文件:
這里可能和服務端實際接收的代碼有關,我這里是用了一種簡單的方法來接收多個文件。服務端的代碼如下:
public List<File> image; // 上傳的文件
public List<String> imageFileName; // 文件名稱
public List<String> imageContentType; // 文件類型
//上傳圖片
public String upLoadMulitFile() throws IOException{
ServletActionContext.getRequest().setCharacterEncoding("UTF-8");
System.out.println("開始接收文件");
String dir = ServletActionContext.getServletContext().getRealPath("files");
// 取得需要上傳的文件數組
List<File> files = image;
if (files != null && files.size() > 0) {
System.out.println("image ="+image.get(0).getName());
for (int i = 0; i < files.size(); i++) {
FileOutputStream fos = new FileOutputStream(dir + "\\" + imageFileName.get(i));
FileInputStream fis = new FileInputStream(files.get(i));
byte[] buffer = new byte[1024];
int len = 0;
while ((len = fis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
fis.close();
fos.close();
}
}
return null;
}
客服端我們是這么寫的。:
@Multipart
@POST("rpostMulitFile")
Call<ResponseBody> upload(@Part()List<MultipartBody.Part> parts);
使用:
File file1 = new File(Environment.getExternalStorageDirectory(),"test.jpg");
File file2 = new File(Environment.getExternalStorageDirectory(),"test.JPEG");
RequestBody photoRequestBody1 = RequestBody.create(MediaType.parse("application/octet-stream"), file1);
RequestBody photoRequestBody2 = RequestBody.create(MediaType.parse("application/octet-stream"), file2);
MultipartBody.Part photo1 = MultipartBody.Part.createFormData("image", "test22.jpg", photoRequestBody1);
MultipartBody.Part photo2 = MultipartBody.Part.createFormData("image", "test33.jpg", photoRequestBody2);
List<MultipartBody.Part> parts = new ArrayList<>();
parts.add(photo1);
parts.add(photo2);
Call<ResponseBody> call = repo.upload(parts);
executeByEn(call);
其實還有很多可優化的余地,只是當作demo,學習,就并沒有使用跟好的寫法。比如鴻洋大神是這么干的。
public interface IUserBiz
{
@Multipart
@POST("register")
Call<User> registerUser(@PartMap Map<String, RequestBody> params, @Part("password") RequestBody password);
}
執行的代碼:
File file = new File(Environment.getExternalStorageDirectory(), "messenger_01.png");
RequestBody photo = RequestBody.create(MediaType.parse("image/png", file);
Map<String,RequestBody> photos = new HashMap<>();
photos.put("photos\"; filename=\"icon.png", photo);
photos.put("username", RequestBody.create(null, "abc"));
Call<User> call = userBiz.registerUser(photos, RequestBody.create(null, "123"));
但是我并沒有將其實現,可能是我服務端的代碼限制了吧 。
下載
還是在IUserService中添加聲明:
@GET("files/test.jpg")
Call<ResponseBody> download();
執行:
Call<ResponseBody> call = repo.download();
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
//save file in here
Log.d("TAG","downFile...");
InputStream is = response.body().byteStream();
int len;
try {
File file = new File(Environment.getExternalStorageDirectory(),"download.jpg");
FileOutputStream fos = new FileOutputStream(file);
byte[] buf = new byte[128];
while( (len=is.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fos.flush();
fos.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
Log.d("TAG","down success!");
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
也比較簡單吧。不過對象OkHttp好像也沒有特別大的優勢。
OkHttp的寫法是這樣的:
Request.Builder builder = new Request.Builder();
Request request = builder.get().url(BASE_URL+"files/test2.jpg").build();
okhttp3.Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(okhttp3.Call call, IOException e) {
Log.e("TAG","Error"+e);
}
@Override
public void onResponse(okhttp3.Call call, Response response) throws IOException {
//保存到本地,
downSavetoFile(response);
}
});
當然,這些都只是一些比較簡單的用法,也算是比較核心的用法了。接下來我們要學的可能就是細節上的設置和一些優化、封裝等等了。暫時先告一段落吧~。明天看看還能能不能更一篇。
最后,有需要源代碼的,可以戳這里。
五一快樂~~~。