如何写字节数组而不发生OOM?

问题描述 投票:1回答:1

我有一个安卓应用程序,它可以处理一些大的字节数组,但我在处理字节数组时,在我的Firebase Crashlytics报告中得到了一些OOM崩溃的低内存设备,其大小可能从10 mb到50mb。下面是我使用的方法。谁能帮我改进一下,以避免OOM。

 byte[] decrypt(File files) {

    try {
        FileInputStream fis = new FileInputStream(files);
        SecretKeySpec sks = new SecretKeySpec(getResources().getString(R.string.encryptPassword).getBytes(),
                "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, sks);
        CipherInputStream cis = new CipherInputStream(fis, cipher);
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        int b;
        byte[] d = new byte[1024];
        while ((b = cis.read(d)) != -1) {
            buffer.write(d, 0, b);  //this is one of the line which is being referred for the OOM in firebase
        }

        byte[] decryptedData = buffer.toByteArray();//this is the line which is being referred for the OOM in firebase
        buffer.flush();
        fis.close();
        cis.close();
        return decryptedData;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }

}

EDIT

其实我是用上面的方法对下载的音频文件进行解密,这些文件在下载过程中被加密了。上述方法将加密文件的内容返回给exoplayer来播放其内容,我调用上述方法的方式如下。

ByteArrayDataSource src= new ByteArrayDataSource(decrypt(some_file)); 
Uri uri = new UriByteDataHelper().getUri(decrypt(some_file)); 
DataSpec dataSpec = new DataSpec(uri);  
src.open(dataSpec); 
DataSource.Factory factory = new DataSource.Factory() 
 { 
   @Override public DataSource createDataSource() 
   { 
      return src;  
   } 
 }; 
audioSource = new ProgressiveMediaSource.Factory(factory).createMediaSource(uri);
java android
1个回答
0
投票

首先,我会确保运行这个程序的设备有足够的堆内存来运行这个程序,这可能是软件已经被分配了很多空间,堆上可能没有更多的空间来提供软件。这个操作应该不需要太多的内存,我也没有看到任何明显的迹象表明是在尝试分配大量的内存。

我建议,如果你想做一个快速的测试,其实就是简单地降低字节数组的大小,任何特殊的原因,为什么你使用1024?如果可能的话,也许可以试试。

byte[] d = new byte[8];

还有,如果是我的话,我会把读取的数据暂时存储在一个数组里,也许只有当Cypher的读取完成后,我才会调用... ...

buffer.write()

根据我的经验,不建议在同一时间读写,可能会导致服务器问题,至少你应该确保你有整个cypher,而且是一个有效的cypher(如果你有一些验证要求),然后才发送。

同样,这不应该是核心问题,设备似乎缺乏足够的可用内存来分配,也许是为其他进程保留了太多的内存?


0
投票

你应该考虑将解密后的数据写入tempfile,然后重新加载数据使用。

导致Out of memory错误的主要原因是ByteArrayOutputStream ANDbyte[] decryptedData = buffer.toByteArray(),因为...。两者都拥有完整的(解密)数据。 而这将使你的解密方法的内存消耗增加一倍。

你可以通过在第一步将数据解密到tempfile中,然后再从tempfile中加载数据来避免这种情况。我修改了decrypt方法来处理解密后的输出流,后面还有一个重新加载解密数据的方法(没有propper异常处理,为了测试,我设置了一个静态的encryptPassword-variable...)。

只剩下一个部分--你需要为tempfile找到一个好地方,我不是Android专家。

只需注意两点。你使用的是 不安全的AES ECB模式 和你的密码的字符串到字节[]的转换应该改变为

.getBytes(StandardCharsets.UTF_8) 

在加密和解密端,以避免不同平台上不同编码造成的错误。

public static void decryptNew(File files, File tempfiles) {
    try (FileInputStream fis = new FileInputStream(files);
         BufferedInputStream in = new BufferedInputStream(fis);
         FileOutputStream out = new FileOutputStream(tempfiles);
         BufferedOutputStream bos = new BufferedOutputStream(out)) {
        byte[] ibuf = new byte[1024];
        int len;
        Cipher cipher = Cipher.getInstance("AES");
        SecretKeySpec sks = new SecretKeySpec(encryptPassword.getBytes(),"AES"); // static password
        // SecretKeySpec sks = new SecretKeySpec(getResources().getString(R.string.encryptPassword).getBytes(),"AES");
        cipher.init(Cipher.DECRYPT_MODE, sks);
        while ((len = in.read(ibuf)) != -1) {
            byte[] obuf = cipher.update(ibuf, 0, len);
            if (obuf != null)
                bos.write(obuf);
        }
        byte[] obuf = cipher.doFinal();
        if (obuf != null)
            bos.write(obuf);
    } catch (BadPaddingException | IllegalBlockSizeException | InvalidKeyException | IOException | NoSuchAlgorithmException | NoSuchPaddingException e) {
        e.printStackTrace();
    }
}

public static byte[] loadFile(File filename) throws IOException {
    byte[] filecontent = new byte[0];
    FileInputStream fileInputStream = null;
    try {
        fileInputStream = new FileInputStream(filename);
        // int byteLength = fff.length();
        // In android the result of file.length() is long
        long byteLength = filename.length(); // byte count of the file-content
        filecontent = new byte[(int) byteLength];
        fileInputStream.read(filecontent, 0, (int) byteLength);
    } catch (IOException e) {
        e.printStackTrace();
        fileInputStream.close();
        return filecontent;
    }
    fileInputStream.close();
    return filecontent;
}

将tempfile内容加载到字节数组后,你可以用单行本删除文件(同样没有异常处理)。

Files.deleteIfExists(tempFile.toPath());
© www.soinside.com 2019 - 2024. All rights reserved.