Android实战----基于Retrofit实现多图片/文件图文上传

Posted Herman-Hong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android实战----基于Retrofit实现多图片/文件图文上传相关的知识,希望对你有一定的参考价值。

本文代码详见:https://github.com/honghailiang/RetrofitUpLoadImage

一、再次膜拜下Retrofit

Retrofit无论从性能还是使用方便性上都很屌!!!,本文不去介绍其运作原理(虽然很想搞明白),后面会出专题文章解析Retrofit的内部原理;本文只是从使用上解析Retrofit实现多图片/文件、图文上传的功能。文件上传相关可参考Multipart/form-data文件上传简介 Apache FileUpload文件上传功能


二、概念介绍

1)注解@Multipart

从字面上理解就是与多媒体文件相关的,没错,图片、文件等的上传都要用到该注解,其中每个部分需要使用@Part来注解。。看其注释

/**
 * Denotes that the request body is multi-part. Parts should be declared as parameters and
 * annotated with @link Part @Part.
 */

2)注解@PartMap

当然可以理解为使用@PartMap注释,传递多个Part,以实现多文件上传。注释

/**
 * Denotes name and value parts of a multi-part request.
 * <p>
 * Values of the map on which this annotation exists will be processed in one of two ways:
 * <ul>
 * <li>If the type is @link okhttp3.RequestBody RequestBody the value will be used
 * directly with its content type.</li>
 * <li>Other object types will be converted to an appropriate representation by using
 * @linkplain Converter a converter.</li>
 * </ul>
 * <p>
 * <pre><code>
 * @Multipart
 * @POST("/upload")
 * Call<ResponseBody> upload(
 *     @Part("file") RequestBody file,
 *     @PartMap Map<String, RequestBody> params);
 * </code></pre>
 * <p>
 * A @code null value for the map, as a key, or as a value is not allowed.
 *
 * @see Multipart
 * @see Part
 */

3)RequestBody

从上面注释中就可以看到参数类型是RequestBody,其就是请求体。文件上传就需要参数为RequestBody。官方使用说明如下http://square.github.io/retrofit/

Multipart parts use one of Retrofit's converters or they can implement RequestBody to handle their own serialization.


四、基本实现

了解了以上概念,下面就一一实现

1)接口定义

public interface IHttpService 
@Multipart
    @POST("file/upLoad.do")
    Call<BaseBean> upLoadAgree(@PartMap Map<String, RequestBody>params);

BaseBean是根据服务端返回数据进行定义的,这个使用时可以根据自有Server定义。

2)Retrofit实现

/**
 * Created by DELL on 2017/3/16.
 * 上传文件用(包含图片)
 */

public class RetrofitHttpUpLoad 
    /**
     * 超时时间60s
     */
    private static final long DEFAULT_TIMEOUT = 60;
    private volatile static RetrofitHttpUpLoad mInstance;
    public Retrofit mRetrofit;
    public IHttpService mHttpService;
    private static Map<String, RequestBody> params;

    private RetrofitHttpUpLoad() 
        mRetrofit = new Retrofit.Builder()
                .baseUrl(UrlConfig.ROOT_URL)
                .client(genericClient())
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        mHttpService = mRetrofit.create(IHttpService.class);
    

    public static RetrofitHttpUpLoad getInstance() 
        if (mInstance == null) 
            synchronized (RetrofitHttpUpLoad.class) 
                if (mInstance == null)
                    mInstance = new RetrofitHttpUpLoad();
                params = new HashMap<String, RequestBody>();
            
        
        return mInstance;
    

    /**
     * 添加统一超时时间,http日志打印
     *
     * @return
     */
    private OkHttpClient genericClient() 
        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
        logging.setLevel(HttpLoggingInterceptor.Level.BODY);
        OkHttpClient httpClient = new OkHttpClient.Builder()
                .addInterceptor(logging)
                .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .build();
        return httpClient;
    


    /**
     * 将call加入队列并实现回调
     *
     * @param call             调入的call
     * @param retrofitCallBack 回调
     * @param method           调用方法标志,回调用
     * @param <T>              泛型参数
     */
    public <T> void addToEnqueue(Call<T> call, final RetrofitCallBack retrofitCallBack, final int method) 
        final Context context = MyApplication.getContext();
        call.enqueue(new Callback<T>() 
            @Override
            public void onResponse(Call<T> call, Response<T> response) 
                LogUtil.d("retrofit back code ====" + response.code());
                if (null != response.body()) 
                    if (response.code() == 200) 
                        LogUtil.d("retrofit back body ====" + new Gson().toJson(response.body()));
                        retrofitCallBack.onResponse(response, method);
                     else 
                        LogUtil.d("toEnqueue, onResponse Fail:" + response.code());
                        ToastUtil.makeShortText(context, "网络连接错误" + response.code());
                        retrofitCallBack.onFailure(response, method);
                    
                 else 
                    LogUtil.d("toEnqueue, onResponse Fail m:" + response.message());
                    ToastUtil.makeShortText(context, "网络连接错误" + response.message());
                    retrofitCallBack.onFailure(response, method);
                
            

            @Override
            public void onFailure(Call<T> call, Throwable t) 
                LogUtil.d("toEnqueue, onResponse Fail unKnown:" + t.getMessage());
                t.printStackTrace();
                ToastUtil.makeShortText(context, "网络连接错误" + t.getMessage());
                retrofitCallBack.onFailure(null, method);
            
        );
    

    /**
     * 添加参数
     * 根据传进来的Object对象来判断是String还是File类型的参数
     */
    public RetrofitHttpUpLoad addParameter(String key, Object o) 

        if (o instanceof String) 
            RequestBody body = RequestBody.create(MediaType.parse("text/plain;charset=UTF-8"), (String) o);
            params.put(key, body);
         else if (o instanceof File) 
            RequestBody body = RequestBody.create(MediaType.parse("multipart/form-data;charset=UTF-8"), (File) o);
            params.put(key + "\\"; filename=\\"" + ((File) o).getName() + "", body);
        
        return this;
    

    /**
     * 构建RequestBody
     */
    public Map<String, RequestBody> bulider() 

        return params;
    

    public void clear()
        params.clear();
    


其中定义了Retrofit实例、还用拦截器定义了统一的超时时间和日志打印;将call加入队列并实现回调。最重要的就是添加参数:

 /**
     * 添加参数
     * 根据传进来的Object对象来判断是String还是File类型的参数
     */
    public RetrofitHttpUpLoad addParameter(String key, Object o) 

        if (o instanceof String) 
            RequestBody body = RequestBody.create(MediaType.parse("text/plain;charset=UTF-8"), (String) o);
            params.put(key, body);
         else if (o instanceof File) 
            RequestBody body = RequestBody.create(MediaType.parse("multipart/form-data;charset=UTF-8"), (File) o);
            params.put(key + "\\"; filename=\\"" + ((File) o).getName() + "", body);
        
        return this;
    


这里就是根据传入的参数,返回不同的RequestBody, 注意文件的key值。


3)使用

private void upLoadAgree() 
        showWaitDialog();
        RetrofitHttpUpLoad retrofitHttpUpLoad = RetrofitHttpUpLoad.getInstance();
        retrofitHttpUpLoad.clear();
        if (!StringUtil.isEmpty(pathImage[0]))
            retrofitHttpUpLoad = retrofitHttpUpLoad.addParameter("pic1",new File(pathImage[0]));
        
        if (!StringUtil.isEmpty(pathImage[1]))
            retrofitHttpUpLoad = retrofitHttpUpLoad.addParameter("pic2", new File(pathImage[1]));
        
        if (!StringUtil.isEmpty(pathImage[2]))
            retrofitHttpUpLoad = retrofitHttpUpLoad.addParameter("zip", new File(pathImage[2]));
        

        Map<String, RequestBody> params = retrofitHttpUpLoad
                .addParameter("status", "4")
                .addParameter("pickupId", tv_orderquality_pid.getText().toString())
                .addParameter("cause", reason)
                .addParameter("connectname", et_orderquality_lxrname.getText().toString())
                .addParameter("connectphone", et_orderquality_lxrphone.getText().toString())
                .addParameter("details", et_orderquality_xqms.getText().toString())
                .bulider();
        retrofitHttpUpLoad.addToEnqueue(retrofitHttpUpLoad.mHttpService.upLoadAgree(params),
                this, HttpStaticApi.HTTP_UPLOADAGREE);
    



需要注意的是要对图片及文件路径进行判空操作,否则会报异常W/System.err: java.io.FileNotFoundException: /: open failed: EISDIR (Is a directory)


五、报文日志

其中图片报文有省略

D/OkHttp: --> POST http://192.168.xxx.xxx:8880/xxx/nocheck/file/agree.do http/1.1
D/OkHttp: Content-Type: multipart/form-data; boundary=f3e7369a-ead9-46e2-9ddd-448442fd5108
D/OkHttp: Content-Length: 300580
D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108
D/OkHttp: Content-Disposition: form-data; name="pic2"; filename="90079.jpg"
D/OkHttp: Content-Transfer-Encoding: binary
D/OkHttp: Content-Type: multipart/form-data;charset=UTF-8
D/OkHttp: Content-Length: 149456
D/OkHttp: ??????JFIF????H??H????????C??		
D/OkHttp: 	"##!  %*5-%'2(  .?/279<<<$-BFA:F5;<9????C
D/OkHttp: 9& &99999999999999999999999999999999999999999999999999?????8"????????????????????????????????????????????????????????????????????|???/n?s??y?]8ug?7?R??????h?tΘa?T?T<?U???z???+3C?w??tdf??=<??????fN??s??x??hhzd??X~?X??i?~/?<^??~=zX??\\??4?U?ɡ)I???????????????????$??@??? iM?"J?R	2?f?MK5x#w?????r?I?3?Y??l???V?Bj??>??t?u????]????g>?o??o???dM?U??????J?R??<?+?;??????????OG>????=?5?L?9?&???_/\\?yu??~|*,???My?r????????='?d?=?t*??*?Y??(????????????????	YB i?Jlv??d?"l????Y??4??X?7???;??sY?\\κ+?N??;?L??&?(?MJ???@w~~???a?qs??m7??y???Ns?\\?C?g??>???N%??N??gs?Q??c????Z?t???x????^_s?s??gO?????N?|?;??y?y?ǎ|?v??N?l???????????*k5?(?????????1$?B??j?+,?l???hN??U?<sgb?g?x?S??;;c?,??7?0Z?J?I?r??X?9?t?'\\?1W+
D/OkHttp: [?? ????=/X???n??T*4?u??<?????s?q??????c???\\?6?YV?p????oB%??|?s????????????g??k??t??d]^W???v?WB?x???|z9?>V?ǒ>?o??Y????xk?k7???w????b?!JQjRQ%RH?%8?H??Q??Ys????u??(?`?b\\??k?cC:u#???d?C??&??W.CXd?e??N??n????.?%v?,.zW?>??&??+??r??S'?.n?[	V?	?q??oGL?,:?S??????/?o<???,?B???;??????^[?#Lm??7.Q?6sONz??fwN?
D/OkHttp: ???,?\\????
D/OkHttp: ???U<???1?Z???=??pn?~q??[-?P??=?j?va?R?	??4X*???nv?H??j?j?p??`h#-???qiA?U?????x?&v?b?R??o?.??H[M5??Y??5?>%Y?j????x?2.m??=??GG???
D/OkHttp: \\?D??(?JK9<J???JE?jl??pW
D/OkHttp: ????i?6??R??:!J??FT?!e???
D/OkHttp: ????:??5????%??`?|???;z?G??[?P???N=???T??X?-?okNe???Y??f8?`:?P???x?1?I?g	?0?)?fe*P?qU?~?jSY%??gɡR?(?$A?|y???s?2?<?/??4?s??@-?,??AZ?az?,??bl?.??WD??????q?X?u?+G?z?h8=?w[`?j??g&q?c????????<|??|? 1????q^???
D/OkHttp: 5??)x???:*dK??|?KPz5v?4?+?>eO?4??i?P2F?&\\9???? -V?esf~&F?Q??S?\\???8??*B?1qVO????-S??!?????????*6??
D/OkHttp: 3?5W?r?x??+?\\?r???6??C??Ms,H?AE??=q??????(??f?=,????Z??+????L??<??v_i-?m|????6??L???=?4?Y??W??9=??\\AW???~??@!?^ Z6??,?k>|??C
D/OkHttp: aZ??-?ы??R?K???1?6`'??F%/4wZ?Cn????[?]?el??U&[???1db-4????????~er!?4??>ji?]??AV?[v??e??`θo???帏!(??Pk?XCI??Glk-p??p	?B?b???ae???d?]-|"??*??`??l??Tmn`???
D/OkHttp: R?G??h?DETp???i???^????u?E??1?wW%?<????????3e??V????	**m??9V??O?R??f?b?
D/OkHttp: ??j%)^?$??g?\\?Qm^`??
D/OkHttp: ?	?[*?\\?@/T@,?|@\\$F?????v_??uA???:?9>%B????1 =?D]?"??*^??q1??i??B?bs?L_???? e??W?2??pDR?Q??M?? ?????7S?????Dzcb\\??????0?????u? h???e??7X??)?s??DIqf???QdM??V?????r??MTk?=??+?>b0?b???i?\\?lI??H?P??,?Pn[]??.?`.X?=A?I?P?@?<~??Px??.???9?(??:=??5E?n?!l??? 5???ee_??'[????p?d??1?)g?s<????kop?вd?19m?ft??ab????????j?5/pT?M?xBb??8???z??????wX??V??|~x????????c?Rsx???D???ixH??ud?50??MΘ7???<?^I???i?`?????f?A?????????;?U?H????a~?W臸?@O?'u\\-???????CN,????-???@?+"n?:y???G	|S??C?F5?????Ix?????)?????b	22???jRj????j?,K?K"¥</?G?w/	*?W????sn??L????n?n????? k????"?*??~?9??<4?,c?d>?EG??iB??0+??i?Y??D???p???????S|.?2???# &??"v?Y??P??O?#EK???,J?6U?>a???;?-rM??@???^b??@??K?????PI??4?qM|??V??h[Ld??R????or?U?M??)_?J?^S?41n?@n|??
D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108
D/OkHttp: Content-Disposition: form-data; name="cause"
D/OkHttp: Content-Transfer-Encoding: binary
D/OkHttp: Content-Type: text/plain;charset=UTF-8
D/OkHttp: Content-Length: 33
D/OkHttp: 对货物数量、质量有异议
D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108
D/OkHttp: Content-Disposition: form-data; name="details"
D/OkHttp: Content-Transfer-Encoding: binary
D/OkHttp: Content-Type: text/plain;charset=UTF-8
D/OkHttp: Content-Length: 9
D/OkHttp: 哈哈哈
D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108
D/OkHttp: Content-Disposition: form-data; name="status"
D/OkHttp: Content-Transfer-Encoding: binary
D/OkHttp: Content-Type: text/plain;charset=UTF-8
D/OkHttp: Content-Length: 1
D/OkHttp: 4
D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108
D/OkHttp: Content-Disposition: form-data; name="pickupId"
D/OkHttp: Content-Transfer-Encoding: binary
D/OkHttp: Content-Type: text/plain;charset=UTF-8
D/OkHttp: Content-Length: 6
D/OkHttp: 105329
D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108
D/OkHttp: Content-Disposition: form-data; name="connectphone"
D/OkHttp: Content-Transfer-Encoding: binary
D/OkHttp: Content-Type: text/plain;charset=UTF-8
D/OkHttp: Content-Length: 11
D/OkHttp: 13xxxxxxxxx
D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108
D/OkHttp: Content-Disposition: form-data; name="connectname"
D/OkHttp: Content-Transfer-Encoding: binary
D/OkHttp: Content-Type: text/plain;charset=UTF-8
D/OkHttp: Content-Length: 3
D/OkHttp: 111
D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108--
D/OkHttp: --> END POST (300580-byte body)


六、代码托管

https://github.com/honghailiang/RetrofitUpLoadImage


七、效果图:


八、服务端代码

FileItemFactory factory = new DiskFileItemFactory();
		ServletFileUpload upload = new ServletFileUpload(factory);
		File directory = null;
		List<FileItem> items = new ArrayList<FileItem>();

		InputStream is = null;

		FileOutputStream fos = null;
		

		try 
			items = upload.parseRequest(request);
			// 得到所有的文件
			Iterator<FileItem> it = items.iterator();
			while (it.hasNext()) 
				FileItem fItem = (FileItem) it.next();
				String fName = "";
				Object fValue = null;
				if (fItem.isFormField())  // 普通文本框的值
					fName = fItem.getFieldName();
					fValue = fItem.getString("UTF-8");
					
					if("pickupId".equals(fName))
						pickupAppeal.setPickupId(fValue.toString());
					else if("cause".equals(fName))
						pickupAppeal.setCause(fValue.toString());
					else if("connectname".equals(fName))
						pickupAppeal.setConnectname(fValue.toString());
					else if("connectphone".equals(fName))
						pickupAppeal.setConnectphone(fValue.toString());
					else if("details".equals(fName))
						pickupAppeal.setDetails(fValue.toString());
					else if("status".equals(fName))
						String status = fValue.toString();
						BigDecimal big = new BigDecimal(status);
						pickupAppeal.setStatus(big);
					
					
					//map.put(fName, fValue);
				 else  // 获取上传文件的值
					fName = fItem.getFieldName();
					fValue = fItem.getInputStream();
					String name = fItem.getName();
					if (name != null && !("".equals(name))) 
						name = name.substring(name.lastIndexOf(File.separator) + 1);
						int lenN =name.indexOf(".");
						String suf = name.substring(lenN, name.length());
						
						String day = DateUtil.format(Calendar.getInstance().getTime(), 
								"yyyy-MM-dd");
						String path = PATH+File.separator+day;
						directory = new File(path);
						if (!directory.exists()) 
							directory.mkdirs();
						

						String preFile =UUID.randomUUID().toString().replace("-", "");
						
						String filePath = path + File.separator+preFile+suf;
						String serverPath = day+ File.separator+preFile+suf;
						map.put(fName,  serverPath);
						map.put(fName+"m", name);
						is = fItem.getInputStream();
						fos = new FileOutputStream(filePath);
						byte[] buffer = new byte[1024];
						int len = 0;
						while ((len = is.read(buffer, 0, 1024)) != -1) 
							fos.write(buffer, 0, len);
						
						fos.flush();

					
				
			
		 catch (Exception e) 
			pLog.error("接收参数错误:" + e.getMessage());
			resultInfo.setStatus(ComStatus.RESULTINFO_STATUS_FAILE);
			resultInfo.setMessage(ComStatus.RESULTINFO_MESSAGE_FAILE);
			
			resultInfo.setData(map);
			resReslt.setResData(resultInfo);
		 finally 
			try 
				if (null != fos) 
					fos.close();
				

				if (null != is) 
					is.close();
				
			 catch (IOException e) 
				e.printStackTrace();
			
		






  

以上是关于Android实战----基于Retrofit实现多图片/文件图文上传的主要内容,如果未能解决你的问题,请参考以下文章

Android实战----基于Retrofit实现多图片/文件图文上传

Android实战----基于Retrofit实现多图片/文件图文上传

Android实战----从Retrofit源码分析到Java网络编程以及HTTP权威指南想到的

Android实战----从Retrofit源码分析到Java网络编程以及HTTP权威指南想到的

Android实战----从Retrofit源码分析到Java网络编程以及HTTP权威指南想到的

Android网络实战篇——OkHttp3(Retrofit2)五种缓存模式的实现