我有一个应用程序,允许用户从图像创建PDF文档。这样工作正常(虽然有点慢,每页大约等待3秒),除非文档大约10页或更多,然后等待大约30秒后,应用程序将崩溃并伴随以下堆栈跟踪:
01-06 15:28:32.430 27558-27780/appuccino.simplyscan E/art﹕ Throwing OutOfMemoryError "Failed to allocate a 58363924 byte allocation with 16777216 free bytes and 53MB until OOM"
--------- beginning of crash
01-06 15:28:32.442 27558-27780/appuccino.simplyscan E/AndroidRuntime﹕ FATAL EXCEPTION: AsyncTask #1
Process: appuccino.simplyscan, PID: 27558
java.lang.RuntimeException: An error occured while executing doInBackground()
at android.os.AsyncTask$3.done(AsyncTask.java:300)
at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:355)
at java.util.concurrent.FutureTask.setException(FutureTask.java:222)
at java.util.concurrent.FutureTask.run(FutureTask.java:242)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)
Caused by: java.lang.OutOfMemoryError: Failed to allocate a 58363924 byte allocation with 16777216 free bytes and 53MB until OOM
at java.lang.AbstractStringBuilder.enlargeBuffer(AbstractStringBuilder.java:95)
at java.lang.AbstractStringBuilder.append0(AbstractStringBuilder.java:146)
at java.lang.StringBuilder.append(StringBuilder.java:216)
at appuccino.simplyscan.PDFWriter.List.renderList(List.java:24)
at appuccino.simplyscan.PDFWriter.Body.render(Body.java:88)
at appuccino.simplyscan.PDFWriter.Body.toPDFString(Body.java:93)
at appuccino.simplyscan.PDFWriter.PDFDocument.toPDFString(PDFDocument.java:57)
at appuccino.simplyscan.PDFWriter.PDFWriter.asString(PDFWriter.java:129)
at appuccino.simplyscan.AsyncTasks.PDFZIPAsyncTask$PDFAsyncTask.doInBackground(PDFZIPAsyncTask.java:99)
at appuccino.simplyscan.AsyncTasks.PDFZIPAsyncTask$PDFAsyncTask.doInBackground(PDFZIPAsyncTask.java:36)
at android.os.AsyncTask$2.call(AsyncTask.java:288)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)
01-06 15:28:33.200 27558-27558/appuccino.simplyscan E/WindowManager﹕ android.view.WindowLeaked: Activity appuccino.simplyscan.Activities.MainActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView{1ccc212f V.E..... R......D 0,0-1026,441} that was originally added here
at android.view.ViewRootImpl.<init>(ViewRootImpl.java:363)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:261)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)
at android.app.Dialog.show(Dialog.java:298)
at android.app.ProgressDialog.show(ProgressDialog.java:116)
at android.app.ProgressDialog.show(ProgressDialog.java:104)
at appuccino.simplyscan.AsyncTasks.PDFZIPAsyncTask$PDFAsyncTask.<init>(PDFZIPAsyncTask.java:52)
at appuccino.simplyscan.Extra.DocumentAdapter.startPDFCreateAsyncTask(DocumentAdapter.java:255)
at appuccino.simplyscan.Extra.DocumentAdapter.access$100(DocumentAdapter.java:36)
at appuccino.simplyscan.Extra.DocumentAdapter$4.onClick(DocumentAdapter.java:181)
at com.android.internal.app.AlertController$AlertParams$3.onItemClick(AlertController.java:1017)
at android.widget.AdapterView.performItemClick(AdapterView.java:300)
at android.widget.AbsListView.performItemClick(AbsListView.java:1143)
at android.widget.AbsListView$PerformClick.run(AbsListView.java:3044)
at android.widget.AbsListView$3.run(AbsListView.java:3833)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5221)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
虽然它指向堆栈跟踪中的窗口泄漏,但在查看它之后,您可以从应用程序中的OutOfMemory错误中看到它。目前我在Manifest中有android:largeHeap="true"
这对此有很大的帮助,因为之前应用程序会崩溃3页文档。
是否有办法更改我的代码以允许用户创建大页面文档,即使他们必须等待,而不会因为OutOfMemory错误导致应用程序崩溃?我的PDF创建基于PDFWriter Android库(which can be found here,以防您想要跟踪堆栈跟踪到库的代码中)。这是我的AsyncTask类,它实现导致崩溃的PDF创建。 请注意我留下的堆栈跟踪指向哪些行的注释 :
public static class PDFAsyncTask extends AsyncTask<Document, Integer, Boolean> {
Context context;
DocumentAdapter documentAdapter;
Document document;
File pdfPath;
ProgressDialog dialog;
PreviewActivity previewActivity;
public PDFAsyncTask() {
}
public PDFAsyncTask(Context context, DocumentAdapter documentAdapter) {
this.documentAdapter = documentAdapter;
this.context = context;
previewActivity = null;
//THIS IS THE LINE THAT THE WINDOW LEAK IN THE STACKTRACE POINTS TO
dialog = ProgressDialog.show(context, "Converting to PDF...", null, true, true);
View dialogLayout = ((MainActivity)context).getLayoutInflater().inflate(R.layout.progress_dialog, null);
dialog.setContentView(dialogLayout);
dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
cancel(true);
}
});
}
public PDFAsyncTask(Context context, PreviewActivity previewActivity) {
this.previewActivity = previewActivity;
this.context = context;
documentAdapter = null;
dialog = ProgressDialog.show(context, "Converting to PDF...", null, true, false);
View dialogLayout = ((Activity)context).getLayoutInflater().inflate(R.layout.progress_dialog, null);
dialog.setContentView(dialogLayout);
}
@Override
protected Boolean doInBackground(Document... docs) {
document = docs[0];
PDFWriter pdfWriter = new PDFWriter(PaperSize.A4_WIDTH, PaperSize.A4_HEIGHT);
for(int i = 0; i < document.getBitmapList().size(); i++){
Bitmap bitmap = document.getBitmapList().get(i);
//resize bitmap so that it fits in document
Bitmap resizedBitmap = resizeBitmapForPDF(bitmap, PaperSize.A4_WIDTH, PaperSize.A4_HEIGHT);
//find out side or top margin of image so that it is centered
int sideMargin = 0, topMargin = 0;
//there is margin on top and bottom
if(resizedBitmap.getHeight() < PaperSize.A4_HEIGHT){
float halfOfDoc = PaperSize.A4_HEIGHT / 2.0f;
topMargin = Math.round(halfOfDoc - ((float)resizedBitmap.getHeight() / 2.0f));
}
//there is margin on left and right
else if (resizedBitmap.getWidth() < PaperSize.A4_WIDTH){
float halfOfDoc = PaperSize.A4_WIDTH / 2.0f;
sideMargin = Math.round(halfOfDoc - ((float)resizedBitmap.getWidth() / 2.0f));
}
pdfWriter.addImageKeepRatio(sideMargin, topMargin, resizedBitmap.getWidth(), resizedBitmap.getHeight(), bitmap);
if(i != document.getBitmapList().size() - 1)
pdfWriter.newPage();
}
//THIS IS THE LINE THE STACK TRACE POINTS TO FOR THE OUTOFMEMORY ERROR FROM THE STRINGBUILDER
String pdfAsString = pdfWriter.asString();
String externalStorageRoot = Environment.getExternalStorageDirectory().toString();
File pdfDirectory = new File(externalStorageRoot + "/SimplyScan/Exports");
pdfDirectory.mkdirs();
pdfPath = new File(pdfDirectory, document.getName() + ".pdf");
try {
FileOutputStream pdfFile = new FileOutputStream(pdfPath);
pdfFile.write(pdfAsString.getBytes("ISO-8859-1"));
pdfFile.close();
return true;
} catch(Exception e) {
e.printStackTrace();
return false;
}
}
/*
First resize to if width is over maxWidth, then resize if height is over maxHeight
*/
private Bitmap resizeBitmapForPDF(Bitmap bitmap, int maxWidth, int maxHeight) {
int newWidth, newHeight;
Bitmap compressedBitmap = Bitmap.createBitmap(bitmap);
//if width greater than maxWidth, match the maxWidth and scale the height
if(compressedBitmap.getWidth() > maxWidth){
newWidth = maxWidth;
newHeight = Math.round((float) compressedBitmap.getHeight() * (float) maxWidth / (float) compressedBitmap.getWidth());
compressedBitmap = Bitmap.createScaledBitmap(compressedBitmap, newWidth, newHeight, true);
}
//if height greater than maxHeight, match the maxHeight and scale the width
if(compressedBitmap.getHeight() > maxHeight){
newHeight = maxHeight;
newWidth = Math.round((float)compressedBitmap.getWidth() * (float)maxHeight / (float)compressedBitmap.getHeight());
compressedBitmap = Bitmap.createScaledBitmap(compressedBitmap, newWidth, newHeight, true);
}
return compressedBitmap;
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
dialog.dismiss();
//success from documentAdapter
if(result && documentAdapter != null){
documentAdapter.showSendOrViewDialog(document, pdfPath);
} else if (result && previewActivity != null){ //success from PreviewActivity
previewActivity.showPDFSendOrViewDialog(document, pdfPath);
} else {
Toast.makeText(context, "PDF creation failed, please try again", Toast.LENGTH_LONG).show();
}
}
}
编辑:作为解决此问题的一种可能方法,有没有办法更改库的这一部分,而不是将所有内存直接放在堆上?这是堆栈跟踪在PDFDocument
:中指向的库的一部分
@Override
public String toPDFString() {
StringBuilder sb = new StringBuilder();
sb.append(mHeader.toPDFString());
sb.append(mBody.toPDFString());
mCRT.setObjectNumberStart(mBody.getObjectNumberStart());
int x = 0;
while (x < mBody.getObjectsCount()) {
IndirectObject iobj = mBody.getObjectByNumberID(++x);
if (iobj != null) {
mCRT.addObjectXRefInfo(iobj.getByteOffset(), iobj.getGeneration(), iobj.getInUse());
}
}
mTrailer.setObjectsCount(mBody.getObjectsCount());
mTrailer.setCrossReferenceTableByteOffset(sb.length());
mTrailer.setId(Indentifiers.generateId());
return sb.toString() + mCRT.toPDFString() + mTrailer.toPDFString();
}
答案 0 :(得分:-1)
此问题的一个可能解决方案是避免窗口泄漏。
Window leak occurs when you hold a reference to the Activity context even after the activity is destroyed or even after a new instance of the activity is created.
在您的情况下,它是MainActivity的上下文。要解决此问题,您可以尝试以下操作。
而不是
View dialogLayout = ((MainActivity)context).getLayoutInflater().inflate(R.layout.progress_dialog, null);
试
View dialogLayout = (context.getLayoutInflater().inflate(R.layout.progress_dialog, null);