在将BASE64中的文件加载到服务器时改造OutOfMemory异常

时间:2019-09-17 20:19:06

标签: android retrofit rx-java rx-android

当尝试在base64中将pdf文件加载为模型引发Retrofit的字段时,我遇到了OOM异常。我知道,这不是上传文件的正常方法,但这不是第三方实现。我该如何解决此类问题?

关闭网络连接时,应用程序崩溃了

Failed to allocate a 30544558 byte allocation with 2085152 free bytes and 26MB until OOM

@Streaming
@POST("/api/order/")
fun makeOrder(@Header("Authorization") token: String, @Body order: OrderMainModel): Single<Response<PhoneNumberResponse>>

1 个答案:

答案 0 :(得分:4)

这是一种非常疯狂的发送文件的方式。但是,尽管它是错误的(就像您通常使用@Multipart一样),但我发现您的问题是一个有趣的练习。我的解决方案充满了技巧,但是如果您绝对确定您不会以任何方式影响API,那么也许可以帮到您。

您需要一个InputStream。没有其他方法可以发送文件而不会最终耗尽内存。但是如何在这样的请求中发送InputStream

我假设OrderMainModel有一个String字段,可保留您的巨型Base64字符串。首先,将其更改为File(或InputStream本身)。

现在,假设您使用的是Gson(这是一个非常大胆的假设,但我使用的是解析器-我很确定您可以使用任何合理的json库实现类似的功能),为此创建一个自定义TypeAdapter输入FileTypeAdapter会迫使您实现此接口:

class Adapter : TypeAdapter<File>() {
    override fun write(out: JsonWriter, value: File) {
        // implement this

    }

    override fun read(`in`: JsonReader): File {
        // ignore
    }
}

您可以不理会read,不需要它。

现在,在write方法中需要做的是部分读取它,并将其连续写入JsonWriter。嗯,无论您读什么书,都需要即时将其转换为base64。 Base64InputStream中有一个android.util,但似乎没有编码能力,您可以在commons-codec中使用它:https://commons.apache.org/proper/commons-codec/apidocs/org/apache/commons/codec/binary/Base64InputStream.html(请记住传递{{ 1}}到true的构造函数中,否则它将在解码模式下工作。)

现在,您要做的就是doEncode包装Base64InputStream,包裹FileInputStream中收到的File

TypeAdapter

然后慢慢地将其重新写回到您的// 0 means no new lines in your Base64. null means no line separator. Base64InputStream(FileInputStream(file)), true, 0, null)

但是等等,这也不容易!

JsonWriter没有提供任何合理的方式以流方式写入单个字符串(值)。我唯一的想法就是反思。

为此,您需要检索JsonWriter的内部JsonWriter对象,类型为out。然后,为了能够进行写操作而不破坏Writer在内部保留的状态,您需要访问JsonWriter的两个私有方法-Writer和{ {1}}并按顺序调用它们。一切都变得非常复杂和不安全。但是,嘿,这全是有趣的,不是吗?

这是一个介绍概念的小型PoC,而不是准备就绪。 ;-)

writeDeferredValue

通过集成一些较低级别的HTTP API,您可能可以实现类似的行为,这将使您无需反射即可写入OutputStream。也许其他解析器(杰克逊,也许?)会使它更加方便。

...或者只是为API更改而战。

祝你好运!