我有一个安卓应用程序,它可以处理一些大的字节数组,但我在处理字节数组时,在我的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);
首先,我会确保运行这个程序的设备有足够的堆内存来运行这个程序,这可能是软件已经被分配了很多空间,堆上可能没有更多的空间来提供软件。这个操作应该不需要太多的内存,我也没有看到任何明显的迹象表明是在尝试分配大量的内存。
我建议,如果你想做一个快速的测试,其实就是简单地降低字节数组的大小,任何特殊的原因,为什么你使用1024?如果可能的话,也许可以试试。
byte[] d = new byte[8];
还有,如果是我的话,我会把读取的数据暂时存储在一个数组里,也许只有当Cypher的读取完成后,我才会调用... ...
buffer.write()
根据我的经验,不建议在同一时间读写,可能会导致服务器问题,至少你应该确保你有整个cypher,而且是一个有效的cypher(如果你有一些验证要求),然后才发送。
同样,这不应该是核心问题,设备似乎缺乏足够的可用内存来分配,也许是为其他进程保留了太多的内存?
你应该考虑将解密后的数据写入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());