【问题标题】:Creating a JSON with Gson makes the app crash使用 Gson 创建 JSON 会使应用程序崩溃
【发布时间】:2017-06-19 19:46:06
【问题描述】:

我正在使用 Retrofit 和 Gson 将自定义对象列表上传到服务器。我这样做没有任何问题:在 Mororola、Asus 和许多其他设备上进行了测试。从来没有问题!现在我正在使用 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)

我研究了几天,但没有得到具体结果:可能是内存问题吗?(我用内存较少的旧设备测试了该应用程序,但没有出现问题。) p>

非常感谢!

更新

可以是达尔维克吗?我上传了一个对象列表,将其拆分为多个 json 并使用 while(boolean) 发布调用以等待传输完成,然后再进行下一个。在网上查看我发现供应商修改的 dalvik 可能是“杀手”,不让线程在循环中等待.....看这个答案https://stackoverflow.com/a/20804679/2306946

【问题讨论】:

  • 一个简单的问题:你为什么使用StringWriter
  • 你启用了proguard吗?
  • @Lyubomyr : gson 库使用它;
  • @Mike:不,我没有使用 proguard
  • 是的,我认为它是修改后的 dalvik 问题,或者手机可能已植根?

标签: java android json gson retrofit


【解决方案1】:

几乎可以在“桌面”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

配置:

为什么会这样?您在设备上的应用程序很可能内存不足,因为您正在写入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);
        }
    });

Writer 实例,一般来说,应该将数据写入内部不累积全部数据的地方(因此用于缓冲的小型内部缓冲区很好)。解决问题所需要做的就是避免使用StringWriter,并将其替换为可以写入更大容量的其他内容:例如,OutputStreamWriter 绑定OutputStream,写入设备存储、网络连接、等等。Retrofit v1(以及 Retrofit v2)提供的GsonConverter 确实写入输出流,但只是先将请求累积到内存中。我不确定以下自定义 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 服务器也因大规模洪水而停机)。上面的解决方案可能会解决您的问题,但我认为必须先对其进行微调,或者应该通过 Internet 找到其他流式解决方法。祝你好运!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-03-26
    • 2017-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多