在Android中防止OutOfMemoryError

时间:2015-01-06 21:43:08

标签: android pdf android-asynctask out-of-memory

我有一个应用程序,允许用户从图像创建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();
    }

1 个答案:

答案 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);