0%

Android之Dagger2+Retrofit2上传图片

经历了几个小时的折腾之后,终于,用Retrofit2成功上传了一张图片到服务器。
此时我用一句话安慰自己:“生命在于折腾”。
一段正确的Retrofit2上传单张图片的代码:

1
2
3
4
5
public interface ImageService {
@Multipart
@POST("/Web/index.php?m=Home&c=Index&a=upload")
Call<Image> uploadImage(@Part("image\"; filename=\"abc.jpg") RequestBody file);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Module
public class ImageModule {
private String endPoint = "http://115.28.138.1";

private static final MediaType MEDIA_TYPE_IMAGE = MediaType.parse("image/*");

@Provides
public Call<Image> provideImage() {
RequestBody imgFile = RequestBody.create(MEDIA_TYPE_IMAGE, new File(filePath));
return ServiceFactory
.createRetrofitService(ImageService.class, endPoint)
.uploadImage(imgFile);
}
}

由于采用Retrofit2上传图片遇到了很多问题,加上项目进度紧张的缘故,于是拿出以前写过的最原始上传图片的代码,在调通之后,继续转战Retrofit2。

下面的代码是原始上传图片的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

public String uploadFile(String actionUrl, File uploadFile,String token,String babyID,String filename) {
String end = "\r\n";
String twoHyphens = "--";
String boundary = "***joybaby***";
try {
URL url = new URL(actionUrl);
HttpURLConnection con = (HttpURLConnection) url.openConnection();

con.setDoInput(true);
con.setDoOutput(true);
con.setUseCaches(false);

con.setRequestMethod("POST");

con.setRequestProperty("Connection", "Keep-Alive");
con.setRequestProperty("Charset", HTTP.UTF_8);
con.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);

DataOutputStream ds = new DataOutputStream(con.getOutputStream());
ds.writeBytes(twoHyphens + boundary + end);
ds.writeBytes("Content-Disposition: form-data; " + "name=\"token\"" + end);
ds.writeBytes(end);
ds.writeBytes(token + end);

ds.writeBytes(twoHyphens + boundary + end);
ds.writeBytes("Content-Disposition: form-data; " + "name=\"babyID\"" + end);
ds.writeBytes(end);
ds.writeBytes(babyID + end);

ds.writeBytes(twoHyphens + boundary + end);
ds.writeBytes("Content-Disposition: form-data; " + "name=\"file\";filename=\"" + filename + "\"" + end);
ds.writeBytes("Content-Type:image/png" + end);
ds.writeBytes(end);

FileInputStream fStream = new FileInputStream(uploadFile);
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length = -1;
while ((length = fStream.read(buffer)) != -1) {
ds.write(buffer, 0, length);
}
ds.writeBytes(end);
ds.writeBytes(twoHyphens + boundary + twoHyphens + end);
fStream.close();
ds.flush();
InputStream is = con.getInputStream();
int ch;
StringBuffer b = new StringBuffer();
while ((ch = is.read()) != -1) {
b.append((char) ch);
}

if (b.length() == 0)
return null;

ds.close();
return b.toString().trim();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

为了实现Retrofit2上传图片,我真的也是拼了。
总结起来,出现问题的原因就在于:以post方式提交表单文件中的数据格式有误造成上传失败。

下面展示了错误提交后,头部信息的对比;左侧是正确的方式提交图片,右侧是错误的方式提交图片:
post方式提示图片对比图

想了想,错误代码展示就算了,以免误人子弟。

下面记录下利用Fiddler抓包Android手机的网络请求方法:
如何用Fiddler对Android应用进行抓包

Fiddler设置过滤条件如下图所示:
Fiddler设置过滤条件

折腾这个东西这么久,明白了一个道理:尘归尘、土归土。
不管你上层的思想多么前卫、开源框架封装的多么优秀、结构多么清晰,归结起来都是HTTP协议的内容罢了。
当然这里不是说思想、结构就不重要了,但是认识到这一点还是很重要的,起码你知其所以然了。

另外再补充一点,在开发过程中遇到的一个bug:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
12-18 16:40:47.094 4825-4825/com.example.leeyou.drr W/System.err: com.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 48 path $
12-18 16:40:47.098 4825-4825/com.example.leeyou.drr W/System.err: at com.google.gson.Gson.assertFullConsumption(Gson.java:786)
12-18 16:40:47.098 4825-4825/com.example.leeyou.drr W/System.err: at com.google.gson.Gson.fromJson(Gson.java:776)
12-18 16:40:47.098 4825-4825/com.example.leeyou.drr W/System.err: at retrofit.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:36)
12-18 16:40:47.098 4825-4825/com.example.leeyou.drr W/System.err: at retrofit.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:24)
12-18 16:40:47.098 4825-4825/com.example.leeyou.drr W/System.err: at retrofit.OkHttpCall.parseResponse(OkHttpCall.java:148)
12-18 16:40:47.099 4825-4825/com.example.leeyou.drr W/System.err: at retrofit.OkHttpCall.access$100(OkHttpCall.java:29)
12-18 16:40:47.099 4825-4825/com.example.leeyou.drr W/System.err: at retrofit.OkHttpCall$1.onResponse(OkHttpCall.java:94)
12-18 16:40:47.099 4825-4825/com.example.leeyou.drr W/System.err: at com.squareup.okhttp.Call$AsyncCall.execute(Call.java:168)
12-18 16:40:47.099 4825-4825/com.example.leeyou.drr W/System.err: at com.squareup.okhttp.internal.NamedRunnable.run(NamedRunnable.java:33)
12-18 16:40:47.099 4825-4825/com.example.leeyou.drr W/System.err: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
12-18 16:40:47.100 4825-4825/com.example.leeyou.drr W/System.err: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
12-18 16:40:47.100 4825-4825/com.example.leeyou.drr W/System.err: at java.lang.Thread.run(Thread.java:818)
12-18 16:40:47.100 4825-4825/com.example.leeyou.drr W/System.err: Caused by: com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 48 path $
12-18 16:40:47.102 4825-4825/com.example.leeyou.drr W/System.err: at com.google.gson.stream.JsonReader.syntaxError(JsonReader.java:1573)
12-18 16:40:47.102 4825-4825/com.example.leeyou.drr W/System.err: at com.google.gson.stream.JsonReader.checkLenient(JsonReader.java:1423)
12-18 16:40:47.102 4825-4825/com.example.leeyou.drr W/System.err: at com.google.gson.stream.JsonReader.doPeek(JsonReader.java:546)
12-18 16:40:47.102 4825-4825/com.example.leeyou.drr W/System.err: at com.google.gson.stream.JsonReader.peek(JsonReader.java:429)
12-18 16:40:47.102 4825-4825/com.example.leeyou.drr W/System.err: at com.google.gson.Gson.assertFullConsumption(Gson.java:782)
12-18 16:40:47.102 4825-4825/com.example.leeyou.drr W/System.err: ... 11 more

我遇到这个bug的原因是表单提交时,双引号缺失导致的,也是google百度了很久无果,用fiddler抓包比对得出的结论。