在開始之前,本片文章使用得是Rxjava和retrofit結(jié)合啄清,介紹的文件的上傳和下載的實(shí)現(xiàn)六水,如果還不太了解和使用Rxjava和Retrofit的基本使用的同學(xué),可以先了解完這部分內(nèi)容以后,再閱讀本篇文章掷贾。
retrofit的注解字段的介紹和使用:
1.@GET請(qǐng)求的參數(shù)解析:標(biāo)記是GET請(qǐng)求睛榄。
/**
* 首頁(yè)Banner
* http://www.wanandroid.com/banner/json
* @return BannerResponse
*/
@GET("/banner/json")
Observable<DataResponse<List<Banner>>> getHomeBanners();
1.1@query 封裝GET請(qǐng)求參數(shù)的字段
/**
* 知識(shí)體系下的文章
* 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的使用一樣,只是當(dāng)參數(shù)不固定或者參數(shù)比較多的時(shí)候調(diào)用
@GET("/friend/json")
Observable<DataResponse<Article>> getFile(@QueryMap Map<String,String> params);
調(diào)用處的代碼:
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中的占位符,相當(dāng)于動(dòng)態(tài)的改變url, 當(dāng)然別掉了{(lán)}將動(dòng)態(tài)的配置參數(shù)包起來.
/**
* 搜索
* 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請(qǐng)求的參數(shù)解析
2.1@FormUrlEncoded
在post請(qǐng)求中配置該參數(shù)想帅,說明該請(qǐng)求將表單的形式傳遞參數(shù)场靴,它不能用于get請(qǐng)求。
@FormUrlEncoded將會(huì)自動(dòng)將請(qǐng)求參數(shù)的類型調(diào)整為application/x-www-form-urlencoded港准,假如content傳遞的參數(shù)為Good Luck旨剥,那么最后得到的請(qǐng)求體就是
content=Good+Luck
FormUrlEncoded不能用于Get請(qǐng)求
2.2 @Field標(biāo)記POST請(qǐng)求中,鍵值對(duì)參數(shù)的key,例:username叉趣,和password泞边,當(dāng)調(diào)用的loing()方法時(shí),自動(dòng)封裝到請(qǐng)求參數(shù)中
@Field注解將每一個(gè)請(qǐng)求參數(shù)都存放至請(qǐng)求體中疗杉,還可以添加encoded參數(shù)阵谚,該參數(shù)為boolean型,具體的用法為
@Field(value = "book", encoded = true) String book
encoded參數(shù)為true的話烟具,key-value-pair將會(huì)被編碼梢什,即將中文和特殊字符進(jìn)行編碼轉(zhuǎn)換
/**
* 登錄
*
* @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:這個(gè)跟Field差不多,將所有的參數(shù)用Map的方式進(jìn)行傳遞
@FormUrlEncoded
@POST("voice")
Call<Vioce> sendVoiceMessage(@FieldMap Map<String,String> options);
調(diào)用處的代碼:
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 :將所有的參數(shù)封住到一個(gè)自定義的對(duì)象,如果參數(shù)過多朝聋,統(tǒng)一封裝到類中應(yīng)該會(huì)更好嗡午,便于維護(hù)代碼。
@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冀痕、(單荔睹、多)文件上傳實(shí)現(xiàn)及參數(shù)解析
3.1 @Part:配合@Multipart用于文件上傳
在介紹文件上傳之前,為了一些比較喜歡騷動(dòng)的同學(xué)言蛇,我也貼出java后臺(tái)的接受代碼僻他,方便我們更好的理解文件上傳的詳細(xì)過程。也方便后面的介紹腊尚。但是這個(gè)Servlet需要借助2個(gè)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();
// 創(chuàng)建文件項(xiàng)目工廠對(duì)象
DiskFileItemFactory factory = new DiskFileItemFactory();
// 設(shè)置文件上傳路徑
String upload = "f:";
// 獲取系統(tǒng)默認(rèn)的臨時(shí)文件保存路徑,該路徑為Tomcat根目錄下的temp文件夾
String temp = System.getProperty("java.io.tmpdir");
// 設(shè)置緩沖區(qū)大小為 5M
factory.setSizeThreshold(1024 * 1024 * 5);
// 設(shè)置臨時(shí)文件夾為temp
factory.setRepository(new File(temp));
// 用工廠實(shí)例化上傳組件,ServletFileUpload 用來解析文件上傳請(qǐng)求
ServletFileUpload servletFileUpload = new ServletFileUpload(factory);
// 解析結(jié)果放在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();
}
// 流轉(zhuǎn)化成字符串
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();
}
// 流轉(zhuǎn)化成文件
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();
}
});
在上面的代碼中出現(xiàn)了婿斥,我們也貼出該方法的代碼:
@NonNull
private MultipartBody.Part prepareFilePart(File file, String partName) {
// 為file建立RequestBody實(shí)例
RequestBody requestFile =
RequestBody.create(MediaType.parse(MULTIPART_FORM_DATA), file);
// MultipartBody.Part借助文件名完成最終的上傳
return MultipartBody.Part.createFormData(partName, file.getName(), requestFile);
}
這里解釋一下 MultipartBody.Part.createFormData()方法的使用和參數(shù)說明劝篷。第一個(gè)參數(shù)partName為與后臺(tái)協(xié)調(diào)的進(jìn)行文件檢索的名稱,也可以這樣理解民宿,如果是txt文件娇妓,我們標(biāo)記為txt,如果是照片,標(biāo)記為jpg活鹰,其他文件標(biāo)記為file,方便后臺(tái)管理和存儲(chǔ)文件哈恰。
我們?cè)诳纯磒artName在后臺(tái)是如何進(jìn)行區(qū)分和使用的坟桅?
根據(jù)檢索出不同的文件類型,進(jìn)行不同的操作蕊蝗。
3.1.1了解 multipart/form-data
我們這里這是簡(jiǎn)單的介紹一下:
RequestBody requestFile =
RequestBody.create(MediaType.parse("multipart/form-data"), file);
這里在看看我們構(gòu)建RequestBody的代碼:這里參數(shù)至于為什么使用MediaType.parse("multipart/form-data"),需要我們了解http的傳輸協(xié)議:
在最初的http協(xié)議中赖舟,沒有定義上傳文件的Method蓬戚,為了實(shí)現(xiàn)這個(gè)功能,http協(xié)議組改造了post請(qǐng)求宾抓,添加了一種post規(guī)范子漩,設(shè)定這種規(guī)范的Content-Type為multipart/form-data;boundary=bound,其中{bound}是定義的分隔符,用于分割各項(xiàng)內(nèi)容(文件,key-value對(duì))石洗,不然服務(wù)器無法正確識(shí)別各項(xiàng)內(nèi)容幢泼。post body里需要用到,盡量保證隨機(jī)唯一。
3.1.2 post格式如下:
–${bound}
Content-Disposition: form-data; name=”Filename”
HTTP.pdf
–${bound}
Content-Disposition: form-data; name=”file000”; filename=”HTTP協(xié)議詳解.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 對(duì)multipart/form-data的封裝
Retrofit其實(shí)是個(gè)網(wǎng)絡(luò)代理框架讲衫,負(fù)責(zé)封裝請(qǐng)求缕棵,然后把請(qǐng)求分發(fā)給http協(xié)議具體實(shí)現(xiàn)者-httpclient。retrofit默認(rèn)的httpclient是okhttp涉兽。
既然Retrofit不實(shí)現(xiàn)http招驴,為啥還用它呢。因?yàn)樗奖悖枷畏。?br> Retrofit會(huì)根據(jù)注解封裝網(wǎng)絡(luò)請(qǐng)求别厘,待httpclient請(qǐng)求完成后,把原始response內(nèi)容通過轉(zhuǎn)化器(converter)轉(zhuǎn)化成我們需要的對(duì)象(object)拥诡。
具體怎么使用 retrofit2,請(qǐng)參考: Retrofit2官網(wǎng)
那么Retrofit和okhttp怎么封裝這些multipart/form-data上傳數(shù)據(jù)呢
在retrofit中:
@retrofit2.http.Multipart: 標(biāo)記一個(gè)請(qǐng)求是multipart/form-data類型,需要和@retrofit2.http.POST一同使用触趴,并且方法參數(shù)必須是@retrofit2.http.Part注解。
@retrofit2.http.Part: 代表Multipart里的一項(xiàng)數(shù)據(jù),即用${bound}分隔的內(nèi)容塊渴肉。
在okhttp3中:
okhttp3.MultipartBody: multipart/form-data的抽象封裝,繼承okhttp3.RequestBody
okhttp3.MultipartBody.Part: multipart/form-data里的一項(xiàng)數(shù)據(jù)冗懦。
以上內(nèi)容摘自 一葉扁舟的博客
3.2 多文件上傳:(2種方式:)
3.2.1 第一種是方式:文件個(gè)數(shù)固定
@Multipart
@POST("UploadServlet")
Call<ResponseBody> uploadMultipleFiles(
@Part("description") RequestBody description,
@Part MultipartBody.Part file1,
@Part MultipartBody.Part file2);
3.2.2 第二種是方式:文件個(gè)數(shù)不固定
@Multipart
@POST("UploadServlet")
Call<ResponseBody> uploadMapFile(@PartMap Map<String, RequestBody> params);
這里我們只展示文件個(gè)數(shù)不固定的上傳方法的使用:
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、文件的下載
我們都實(shí)現(xiàn)了文件的上傳了宾娜,還不能堅(jiān)持一下批狐,把文件下載搞定?那必須的必扒八嚣艇!下載文件其實(shí)就一個(gè)普通的GET 請(qǐng)求,只不過我們處理好IO操作华弓,將response.body()進(jìn)行保存為自己想要的位置或者處理食零。
@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()方法的具體實(shí)現(xiàn):
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、其他細(xì)節(jié)問題總結(jié)
5.1 @Header:添加http header
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization);
等同于:
@Headers("Authorization: authorization")//這里authorization就是上面方法里傳進(jìn)來變量的值
@GET("widget/list")
Call<User> getUser()
5.2為某個(gè)請(qǐng)求設(shè)置完整的URL
? 假如說你的某一個(gè)請(qǐng)求不是以baseUrl開頭該怎么辦呢寂屏?別著急贰谣,辦法很簡(jiǎn)單娜搂,看下面這個(gè)例子你就懂了
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