java - 使用 Gson 创建 JSON 会使应用程序崩溃

标签 java android json gson retrofit

我正在使用 Retrofit 和 Gson 将自定义对象列表上传到服务器。我没有遇到任何问题:在 Mororola、Asus 和许多其他设备上进行了测试。从来没有问题!现在我正在使用 Zebra 智能手机,这是一款工业智能手机,我的应用程序几乎总是在创建 JSON 期间崩溃(我已经记录了该应用程序在崩溃之前正在编写 JSON)。我正在使用 TypeAdapter 检查数据,所以这应该不是数据值的问题。这是日志:

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)

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

非常感谢!

更新

会是 Dalvik 吗?我上传了一个对象列表,将其拆分为多个 json 并使用 while(boolean) 发布调用以等待传输完成,然后再进行下一个传输。在网上查看,我发现供应商修改的 dalvik 可能是“ killer ”,不让线程在循环中等待......看到这个答案 https://stackoverflow.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

配置:

为什么会这样?您在设备上的应用程序很可能内存不足,因为您正在写入 StringWriter,它实际上在内存中累积了一个 whole 字符串(另请注意,内存惩罚已支付在 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 与绑定(bind) 写入设备存储、网络连接等的 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 上找到其他流式传输解决方法。祝你好运!

关于java - 使用 Gson 创建 JSON 会使应用程序崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42007286/

相关文章:

java - 如何从 apk 中排除特定资源文件夹

android - 在没有 ArCore 的情况下与 SceneView 内的 3D 模型交互

javascript - 通过 AJAX 加载大型 Javascript 数组

jquery - 如何处理 Ajax JSON 响应?

java - SWT - 使表格可滚动

java - 什么时候销毁数据库连接?

java - 如何在ThreadPoolTask​​Executor中持久化对象

java - 访问 protected 变量

android - 以编程方式获取设备的 EMUI 版本

json - 将多个 JSON 与 id 组合