在開始之前,本片文章使用得是Rxjava和retrofit結合,介紹的文件的上傳和下載的實現,如果還不太瞭解和使用Rxjava和Retrofit的基本使用的同學,可以先了解完這部分內容以後,再閱讀本篇文章。
retrofit的註解字段的介紹和使用:
1.@GET請求的參數解析:標記是GET請求。
/**
* 首頁Banner
* http://www.wanandroid.com/banner/json
* @return BannerResponse
*/
@GET("/banner/json")
Observable<DataResponse<List<Banner>>> getHomeBanners();
1.1@query 封裝GET請求參數的字段
/**
* 知識體系下的文章
* http://www.wanandroid.com/article/list/json?cid=168
*
* @param page page
* @param cid cid
*/
@GET("/article/list/json")
Observable<DataResponse<Article>> getKnowledgeSystemArticles( @Query("cid") int cid);
1.2 @queryMap 和 @ Query的使用一樣,只是當參數不固定或者參數比較多的時候調用
@GET("/friend/json")
Observable<DataResponse<Article>> getFile(@QueryMap Map<String,String> params);
調用處的代碼:
Map<String,String> options = new HashMap<String,String>();
options.put("name",trainName);
options.put("key",KEY);
Call<Movie> movie = service.getFile(options);
1.3 @Path:url中的佔位符,相當於動態的改變url, 當然別掉了{}將動態的配置參數包起來.
/**
* 搜索
* http://www.wanandroid.com/article/query/0/json
*
* @param page page
* @param k POST search key
*/
@POST("/article/query/{page}/json")
@FormUrlEncoded
Observable<DataResponse<Article>> getSearchArticles(@Path("page") int page, @Field("k") String k);
2.@POST請求的參數解析
2.1@FormUrlEncoded
在post請求中配置該參數,說明該請求將表單的形式傳遞參數,它不能用於get請求。
@FormUrlEncoded將會自動將請求參數的類型調整爲application/x-www-form-urlencoded,假如content傳遞的參數爲Good Luck,那麼最後得到的請求體就是
content=Good+Luck
FormUrlEncoded不能用於Get請求
2.2 @Field標記POST請求中,鍵值對參數的key,例:username,和password,當調用的loing()方法時,自動封裝到請求參數中
@Field註解將每一個請求參數都存放至請求體中,還可以添加encoded參數,該參數爲boolean型,具體的用法爲
@Field(value = "book", encoded = true) String book
encoded參數爲true的話,key-value-pair將會被編碼,即將中文和特殊字符進行編碼轉換
/**
* 登錄
*
* @param username username
* @param password password
* @return Deferred<User>
*/
@POST("/user/login")
@FormUrlEncoded
Observable<DataResponse<User>> login(@Field("username") String username, @Field("password") String password);
2.3 @FiledMap:這個跟Field差不多,將所有的參數用Map的方式進行傳遞
@FormUrlEncoded
@POST("voice")
Call<Vioce> sendVoiceMessage(@FieldMap Map<String,String> options);
調用處的代碼:
Map<String,String> options = new HashMap<String,String>();
options.put("valicode","123456");
options.put("to","18772351259");
options.put("key",voice_KEY);
Call<Vioce> voice = service.sendVoiceMessage(options);
2.4 @Body :將所有的參數封住到一個自定義的對象,如果參數過多,統一封裝到類中應該會更好,便於維護代碼。
@FormUrlEncoded
@POST("voice")
Call<Vioce> sendVoiceMessage(@Body AddParams params);
public class AddParams{
String valicode;
String to;
String key;
public void setKey(String key) {
this.key = key;
}
public void setTo(String to) {
this.to = to;
}
public void setValicode(String valicode) {
this.valicode = valicode;
}
3、(單、多)文件上傳實現及參數解析
3.1 @Part:配合@Multipart用於文件上傳
在介紹文件上傳之前,爲了一些比較喜歡騷動的同學,我也貼出java後臺的接受代碼,方便我們更好的理解文件上傳的詳細過程。也方便後面的介紹。但是這個Servlet需要藉助2個jar包。
public class UploadFileServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().write("this is the post request! ");
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
// 創建文件項目工廠對象
DiskFileItemFactory factory = new DiskFileItemFactory();
// 設置文件上傳路徑
String upload = "f:";
// 獲取系統默認的臨時文件保存路徑,該路徑爲Tomcat根目錄下的temp文件夾
String temp = System.getProperty("java.io.tmpdir");
// 設置緩衝區大小爲 5M
factory.setSizeThreshold(1024 * 1024 * 5);
// 設置臨時文件夾爲temp
factory.setRepository(new File(temp));
// 用工廠實例化上傳組件,ServletFileUpload 用來解析文件上傳請求
ServletFileUpload servletFileUpload = new ServletFileUpload(factory);
// 解析結果放在List中
try {
List<FileItem> list = servletFileUpload.parseRequest(request);
for (FileItem item : list) {
String name = item.getFieldName();
InputStream is = item.getInputStream();
System.out.println("the current name is " + name);
if (name.contains("photo") ||name.contains("file")) {
try {
inputStream2File(is,
upload + "\\" + System.currentTimeMillis()
+ item.getName());
} catch (Exception e) {
e.printStackTrace();
}
} else {
String key = item.getName();
String value = item.getString();
// System.out.println(value );
// System.out.println(key + "---" + value);
}
}
out.write("success");
} catch (FileUploadException e) {
e.printStackTrace();
out.write("failure");
}
out.flush();
out.close();
}
// 流轉化成字符串
public static String inputStream2String(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int i = -1;
while ((i = is.read()) != -1) {
baos.write(i);
}
return baos.toString();
}
// 流轉化成文件
public static void inputStream2File(InputStream is, String savePath)
throws Exception {
System.out.println("the file path is :" + savePath);
File file = new File(savePath);
InputStream inputSteam = is;
BufferedInputStream fis = new BufferedInputStream(inputSteam);
FileOutputStream fos = new FileOutputStream(file);
int f;
while ((f = fis.read()) != -1) {
fos.write(f);
}
fos.flush();
fos.close();
fis.close();
inputSteam.close();
}
}
@Multipart
@POST("UploadServlet")
Call<ResponseBody> upLoadPrefectFile( @Part("description") RequestBody description,@Part MultipartBody.Part file);
在Activity中的代碼:
final RequestBody requestBody = createPartFromString("this is des!");
retrofit2.Call call = RetrofitHelper.getUpLoadFileAPI().upLoadPrefectFile(requestBody, prepareFilePart(new File("/sdcard/1.zip"), "file"));
call.enqueue(new retrofit2.Callback() {
@Override
public void onResponse(retrofit2.Call call, retrofit2.Response response) {
String s = response.body().toString();
String s1 = response.message().toString();
Log.d("TAG", "onResponse: " + s1);
Toast.makeText(TestUploadFileActivity.this, "上傳成功!" + s, Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(retrofit2.Call call, Throwable t) {
String s = t.getMessage().toString();
Log.d(TAG, s);
Toast.makeText(TestUploadFileActivity.this, "上傳失敗!" + s, Toast.LENGTH_SHORT).show();
}
});
在上面的代碼中出現了,我們也貼出該方法的代碼:
@NonNull
private MultipartBody.Part prepareFilePart(File file, String partName) {
// 爲file建立RequestBody實例
RequestBody requestFile =
RequestBody.create(MediaType.parse(MULTIPART_FORM_DATA), file);
// MultipartBody.Part藉助文件名完成最終的上傳
return MultipartBody.Part.createFormData(partName, file.getName(), requestFile);
}
這裏解釋一下 MultipartBody.Part.createFormData()方法的使用和參數說明。第一個參數partName爲與後臺協調的進行文件檢索的名稱,也可以這樣理解,如果是txt文件,我們標記爲txt,如果是照片,標記爲jpg,其他文件標記爲file,方便後臺管理和存儲文件。
我們在看看partName在後臺是如何進行區分和使用的?
根據檢索出不同的文件類型,進行不同的操作。
3.1.1瞭解 multipart/form-data
我們這裏這是簡單的介紹一下:
RequestBody requestFile =
RequestBody.create(MediaType.parse("multipart/form-data"), file);
這裏在看看我們構建RequestBody的代碼:這裏參數至於爲什麼使用MediaType.parse("multipart/form-data"),需要我們瞭解http的傳輸協議:
在最初的http協議中,沒有定義上傳文件的Method,爲了實現這個功能,http協議組改造了post請求,添加了一種post規範,設定這種規範的Content-Type爲multipart/form-data;boundary=bound,其中{bound}是定義的分隔符,用於分割各項內容(文件,key-value對),不然服務器無法正確識別各項內容。post body裏需要用到,儘量保證隨機唯一。
3.1.2 post格式如下:
–${bound}
Content-Disposition: form-data; name=”Filename”
HTTP.pdf
–${bound}
Content-Disposition: form-data; name=”file000”; filename=”HTTP協議詳解.pdf”
Content-Type: application/octet-stream
%PDF-1.5
file content
%%EOF
–${bound}
Content-Disposition: form-data; name=”Upload”
Submit Query
–${bound}–
${bound}是Content-Type裏boundary的值
3.1.3 Retrofit2 對multipart/form-data的封裝
Retrofit其實是個網絡代理框架,負責封裝請求,然後把請求分發給http協議具體實現者-httpclient。retrofit默認的httpclient是okhttp。
既然Retrofit不實現http,爲啥還用它呢。因爲他方便!!
Retrofit會根據註解封裝網絡請求,待httpclient請求完成後,把原始response內容通過轉化器(converter)轉化成我們需要的對象(object)。
具體怎麼使用 retrofit2,請參考: Retrofit2官網
那麼Retrofit和okhttp怎麼封裝這些multipart/form-data上傳數據呢
在retrofit中:
@retrofit2.http.Multipart: 標記一個請求是multipart/form-data類型,需要和@retrofit2.http.POST一同使用,並且方法參數必須是@retrofit2.http.Part註解。
@retrofit2.http.Part: 代表Multipart裏的一項數據,即用${bound}分隔的內容塊。
在okhttp3中:
okhttp3.MultipartBody: multipart/form-data的抽象封裝,繼承okhttp3.RequestBody
okhttp3.MultipartBody.Part: multipart/form-data裏的一項數據。
以上內容摘自 一葉扁舟的博客
3.2 多文件上傳:(2種方式:)
3.2.1 第一種是方式:文件個數固定
@Multipart
@POST("UploadServlet")
Call<ResponseBody> uploadMultipleFiles(
@Part("description") RequestBody description,
@Part MultipartBody.Part file1,
@Part MultipartBody.Part file2);
3.2.2 第二種是方式:文件個數不固定
@Multipart
@POST("UploadServlet")
Call<ResponseBody> uploadMapFile(@PartMap Map<String, RequestBody> params);
這裏我們只展示文件個數不固定的上傳方法的使用:
File file=new File("/sdcard/img.jpg");
File file1=new File("/sdcard/ic.jpg");
File file2=new File("/sdcard/1.txt");
RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
RequestBody requestBody1 = RequestBody.create(MediaType.parse("multipart/form-data"), file1);
RequestBody requestBody2 = RequestBody.create(MediaType.parse("multipart/form-data"), file2);
Map<String, RequestBody> params=new HashMap<>() ;
params.put("file\"; filename=\""+ file.getName(), requestBody);
params.put("file\"; filename=\""+ file1.getName(), requestBody1);
params.put("file\"; filename=\""+ file2.getName(), requestBody2);
retrofit2.Call call = RetrofitHelper.getUpLoadFileAPI().uploadMapFile(params);
call.enqueue(new retrofit2.Callback() {
@Override
public void onResponse(retrofit2.Call call, retrofit2.Response response) {
Toast.makeText(TestUploadFileActivity.this, "上傳成功!", Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(retrofit2.Call call, Throwable t) {
Log.d("Tag",t.getMessage().toString());
Toast.makeText(TestUploadFileActivity.this, "上傳失敗!", Toast.LENGTH_SHORT).show();
}
});
4、文件的下載
我們都實現了文件的上傳了,還不能堅持一下,把文件下載搞定?那必須的必啊!下載文件其實就一個普通的GET 請求,只不過我們處理好IO操作,將response.body()進行保存爲自己想要的位置或者處理。
@GET("u=107188706,3427188039&fm=27&gp=0.jpg")
Call<ResponseBody> downloadFile();
Activity中的使用:
Call<ResponseBody> call = RetrofitHelper.getDownloadApi().downloadFile();
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
boolean writtenToDisk = writeResponseBodyToDisk(response.body());
btnDownload.setText(writtenToDisk ? "success" : "false");
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Toast.makeText(TestUploadFileActivity.this, "fail", Toast.LENGTH_SHORT).show();
}
});
writeResponseBodyToDisk()方法的具體實現:
private boolean writeResponseBodyToDisk(ResponseBody body) {
try {
// todo change the file location/name according to your needs
File futureStudioIconFile = new File(Environment.getExternalStorageDirectory() + File.separator + "taylor.png");
Log.d(TAG, "writeResponseBodyToDisk: " + Environment.getExternalStorageDirectory().getAbsolutePath());
InputStream inputStream = null;
OutputStream outputStream = null;
try {
byte[] fileReader = new byte[4096];
long fileSize = body.contentLength();
long fileSizeDownloaded = 0;
inputStream = body.byteStream();
outputStream = new FileOutputStream(futureStudioIconFile);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) {
break;
}
outputStream.write(fileReader, 0, read);
fileSizeDownloaded += read;
Log.d(TAG, "file download: " + fileSizeDownloaded + " of " + fileSize);
}
outputStream.flush();
return true;
} catch (IOException e) {
return false;
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
} catch (IOException e) {
return false;
}
}
5、其他細節問題總結
5.1 @Header:添加http header
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization);
等同於:
@Headers("Authorization: authorization")//這裏authorization就是上面方法裏傳進來變量的值
@GET("widget/list")
Call<User> getUser()
5.2爲某個請求設置完整的URL
假如說你的某一個請求不是以baseUrl開頭該怎麼辦呢?彆着急,辦法很簡單,看下面這個例子你就懂了
public interface BlueService {
@GET
public Call<ResponseBody> profilePicture(@Url String url);
}
Retrofit retrofit = Retrofit.Builder()
.baseUrl("https://your.api.url/");
.build();
BlueService service = retrofit.create(BlueService.class);
service.profilePicture("https://s3.amazon.com/profile-picture/path");
還有一些添加Header的具體的用法:
Header