我正在使用Retrofit和Gson将自定义对象列表上传到服务器.我没有遇到任何问题:使用Mororola,华硕和许多其他设备进行测试.从来没有问题!现在我正在使用Zebra智能手机,一个工业智能手机,而且我的应用程序几乎总是在创建JSON期间崩溃(我记录了应用程序在崩溃之前编写了JSON).我正在使用TypeAdapters检查数据,因此它不应该是数据值的问题.这是日志:
D/dalvikvm: JIT unchain all for threadid=13
W/dalvikvm: threadid=15: spin on suspend #1 threadid=13 (pcf=0)
W/dalvikvm: threadid=15: spin on suspend #2 threadid=13 (pcf=0)
I/dalvikvm: "Retrofit-Idle" prio=5 tid=15 RUNNABLE JIT
I/dalvikvm: | group="main" sCount=0 dsCount=0 obj=0x41d52cc8 self=0x6065ec38
I/dalvikvm: | sysTid=14452 nice=10 sched=0/0 cgrp=apps/bg_non_interactive handle=1599766536
I/dalvikvm: | state=R schedstat=( 0 0 0 ) utm=9 stm=2 core=0
I/dalvikvm: at java.lang.AbstractStringBuilder.enlargeBuffer(AbstractStringBuilder.java:~94)
I/dalvikvm: at java.lang.AbstractStringBuilder.append0(AbstractStringBuilder.java:145)
I/dalvikvm: at java.lang.StringBuffer.append(StringBuffer.java:219)
I/dalvikvm: at java.io.StringWriter.write(StringWriter.java:167)
I/dalvikvm: at com.google.gson.stream.JsonWriter.string(JsonWriter.java:570)
I/dalvikvm: at com.google.gson.stream.JsonWriter.value(JsonWriter.java:419)
I/dalvikvm: at my.company.app.rest.RestClient$1.write(RestClient.java:197)
I/dalvikvm: at my.company.app.rest.RestClient$1.write(RestClient.java:165)
I/dalvikvm: at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
I/dalvikvm: at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:113)
I/dalvikvm: at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:240)
I/dalvikvm: at com.google.gson.internal.bind.ObjectTypeAdapter.write(ObjectTypeAdapter.java:107)
I/dalvikvm: at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
I/dalvikvm: at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:96)
I/dalvikvm: at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:60)
I/dalvikvm: at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
I/dalvikvm: at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:113)
I/dalvikvm: at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:240)
I/dalvikvm: at com.google.gson.Gson.toJson(Gson.java:605)
I/dalvikvm: at com.google.gson.Gson.toJson(Gson.java:584)
I/dalvikvm: at com.google.gson.Gson.toJson(Gson.java:539)
I/dalvikvm: at com.google.gson.Gson.toJson(Gson.java:519)
I/dalvikvm: at retrofit.converter.GsonConverter.toBody(GsonConverter.java:80)
I/dalvikvm: at retrofit.RequestBuilder.setArguments(RequestBuilder.java:375)
I/dalvikvm: at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:298)
I/dalvikvm: at retrofit.RestAdapter$RestHandler.access$100(RestAdapter.java:220)
I/dalvikvm: at retrofit.RestAdapter$RestHandler$2.obtainResponse(RestAdapter.java:278)
I/dalvikvm: at retrofit.CallbackRunnable.run(CallbackRunnable.java:42)
I/dalvikvm: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
I/dalvikvm: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
I/dalvikvm: at retrofit.Platform$Android$2$1.run(Platform.java:142)
I/dalvikvm: at java.lang.Thread.run(Thread.java:841)
我工作了几天没有具体的结果:这可能是一个记忆问题吗? (我用较旧的设备测试了应用程序,内存较少,我没有问题.)
非常感谢你!
UPDATE
可以是达尔维克吗?我上传一个对象列表,将它分成多个json并使用while(boolean)发布调用以等待传输完成后再创建下一个.在网上看,我发现供应商修改的dalvik可能是“杀手”,不让线程等待循环…..看到这个答案https://*.com/a/20804679/2306946
解决方法:
几乎可以在“桌面”JVM上复制完全重复的内容.例如,如果我充斥测试回显HTTP服务器,这就是我得到的:
retrofit.RetrofitError: Java heap space
at retrofit.RetrofitError.unexpectedError(RetrofitError.java:44)
at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:400)
at retrofit.RestAdapter$RestHandler.access$100(RestAdapter.java:220)
at retrofit.RestAdapter$RestHandler$2.obtainResponse(RestAdapter.java:278)
at retrofit.CallbackRunnable.run(CallbackRunnable.java:42)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at retrofit.Platform$Base$2$1.run(Platform.java:94)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:137)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:121)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:421)
at java.lang.StringBuffer.append(StringBuffer.java:272)
at java.io.StringWriter.write(StringWriter.java:101)
at com.google.gson.stream.JsonWriter.open(JsonWriter.java:327)
at com.google.gson.stream.JsonWriter.beginObject(JsonWriter.java:308)
at q1.Q1$1$1.write(Q1.java:39)
at com.google.gson.Gson.toJson(Gson.java:669)
at com.google.gson.Gson.toJson(Gson.java:648)
at com.google.gson.Gson.toJson(Gson.java:603)
at com.google.gson.Gson.toJson(Gson.java:583)
at retrofit.converter.GsonConverter.toBody(GsonConverter.java:80)
at retrofit.RequestBuilder.setArguments(RequestBuilder.java:375)
at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:298)
... 7 more
组态:
> JVM:-Xms4m -Xmx8m
>网络服务器:npm install http-echo-server -g&& export PORT = 8080&& http-echo-server(详见Generic HTTP server that just dumps POST requests?)
为什么会这样?您在设备上的应用程序很可能是内存不足,因为您正在写入StringWriter,它基本上会在内存中累积整个字符串(另请注意,由于内部缓冲区,内存处罚由StringBuffer支付).看看这段代码:
interface IService {
@POST("/")
void query(@Body String s, Callback<String> callback);
}
final int floodLength = 1024 * 1024;
// This flooding type adapter factory substitutes any type, and this is OK for this case
final TypeAdapterFactory flooder = new TypeAdapterFactory() {
@Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
return new TypeAdapter<T>() {
@Override
public void write(final JsonWriter out, final T value)
throws IOException {
// Just flood with `[{},{},{},...]`
out.beginArray();
for ( int i = 0; i < floodLength; i++ ) {
out.beginObject();
out.endObject();
}
out.endArray();
}
@Override
@SuppressWarnings("unchecked")
public T read(final JsonReader in)
throws IOException {
// Parse JSON response, however it does not work for this server giving war "POST "
// (the echo HTTP server dumps requests back as response bodies -- maybe another HTTP echo server is more appropriate for the experiment?)
loop:
for ( ; ; ) {
final JsonToken token = in.peek();
// @formatter:off
switch ( token ) {
case BEGIN_ARRAY: in.beginArray(); break;
case END_ARRAY: in.endArray(); break;
case BEGIN_OBJECT: in.beginObject(); break;
case END_OBJECT: in.endObject(); break;
case NAME: in.nextName(); break;
case STRING: in.nextString(); break;
case NUMBER: in.nextDouble(); break;
case BOOLEAN: in.nextBoolean(); break;
case NULL: in.nextNull(); break;
case END_DOCUMENT: break loop;
default: throw new AssertionError("no case for " + token);
}
// @formatter:on
}
return (T) "<mock>";
}
};
}
};
final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(flooder) // registering the evil flooder for the testing purposes
.create();
final RestAdapter retrofit = new Builder()
.setEndpoint("http://localhost:8080/")
.setConverter(new GsonConverter(gson))
.build();
final IService service = retrofit.create(IService.class);
service.query("foo", new Callback<String>() {
@Override
public void success(final String string, final Response response) {
System.out.println("OK: " + string);
System.exit(0);
}
@Override
public void failure(final RetrofitError retrofitError) {
retrofitError.printStackTrace(System.err);
System.exit(1);
}
});
一般来说,写入器实例应该将数据写入某个内部而不是在内部累积整个数据(因此用于缓冲的小内部缓冲区很好).解决问题所需要做的就是避免使用StringWriter并将其替换为可以写入容量更大的东西:比如,带有绑定OutputStream的OutputStreamWriter写入设备存储,网络连接等.GsonConverter由Retrofit提供v1(以及Retrofit v2)会写入输出流,而只是先将请求累积到内存中.我不确定以下自定义Gson Converter实现是否编写良好,但至少它使用流式传输:
final class StreamingGsonConverter
implements Converter {
private static final Charset charset = StandardCharsets.UTF_8;
private static final String MIME_TYPE = "application/json; charset=" + charset;
private final Gson gson;
StreamingGsonConverter(final Gson gson) {
this.gson = gson;
}
@Override
public Object fromBody(final TypedInput body, final Type type)
throws ConversionException {
try {
// Reading a stream can be much cheaper
final Reader reader = new InputStreamReader(body.in(), charset);
return gson.fromJson(reader, type);
// No need to close the underlying InputStreamReader, let Retrofit do it
} catch ( final IOException ex ) {
throw new ConversionException(ex);
}
}
@Override
public TypedOutput toBody(final Object object) {
return new BodyTypedOutput(object);
}
private final class BodyTypedOutput
implements TypedOutput {
private final Object object;
private BodyTypedOutput(final Object object) {
this.object = object;
}
@Override
public String fileName() {
return null;
}
@Override
public String mimeType() {
return MIME_TYPE;
}
@Override
public long length() {
// No one can know the length in advance for such cases...
return -1;
}
@Override
public void writeTo(final OutputStream out) {
final Appendable appendable = new OutputStreamWriter(out, charset);
// This is where the original GsonConverter fails for you: don't collect all the data in memory, but rather write it directly to the output stream
gson.toJson(object, appendable);
// No need to close the underlying OutputStreamWriter prematurely -- let Retrofit manage it itself because it's the owner of the resource
}
}
}
然后用以下代码替换GsonConverter:
.setConverter(new StreamingGsonConverter(gson))
对我来说,客户端现在失败了JSON解析异常(因为HTTP服务器回应了POST的性质),但是在收到响应后现在不会浪费内存(但是测试HTTP服务器也会因为大量泛洪而停止运行).上面的解决方案可以解决您的问题,但我认为必须首先进行微调,或者应该通过互联网找到其他流式解决方案.祝好运!