2

I have an app that generates a report in a TableLayout with a variable number of TableRows.

I use the following code to capture the Table into a bitmap:

TableLayout tl = (TableLayout)findViewById(R.id.CSRTableLayout);
Bitmap csr = Bitmap.createBitmap(tl.getWidth(), tl.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(csr);
tl.draw(canvas);

After I capture the screen as a bitmap, I attach it to an email using:

//folder already exists
File file = new File(folder.getAbsolutePath()+"/CSR"+csrnum+".jpg");
BufferedOutputStream bos = null;
FileOutputStream fos = null;
try {
    fos=new FileOutputStream(file);
    bos=new BufferedOutputStream(fos);
    if(bos!=null) {
        try {
            csr.compress(Bitmap.CompressFormat.JPEG, 60, bos);
        } catch (OutOfMemoryError e) {
            Toast.makeText(CustomerReportActivity.this, "Out of Memory!", Toast.LENGTH_SHORT).show();
        } finally {
            fos.flush();
            fos.close();
            bos.flush();
            bos.close();
        }
    }
} catch (Exception e) {
    e.printStackTrace();
}
if(file.exists()) {
    Intent i = new Intent(Intent.ACTION_SEND);
    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    i.setType("message/rfc822");
    String emailTo[] = {"***@****.***"}; 
    i.putExtra(Intent.EXTRA_EMAIL,emailTo);
    i.putExtra(Intent.EXTRA_SUBJECT,"...");
    i.putExtra(Intent.EXTRA_TEXT, "...");
    i.putExtra(Intent.EXTRA_STREAM,Uri.parse("file://"+file.getAbsolutePath()));
    startActivity(i);
} else {
    Toast.makeText(CustomerReportActivity.this, "Error attaching report to email!", Toast.LENGTH_SHORT).show();
}

The problem is that sometimes the table can get quite large, such as 1600x2400dp. I have gotten an OutOfMemoryError on the line "Bitmap.createBitmap(...)" Is there an alternative method to capture the screen while not overflowing the VM heap? If I understand correctly, calling Bitmap.createBitmap(...) is creating a bitmap the full size that is all just plain white. Is it possible to create it with NO pixel data and only fill it in once I called csr.compress(...) so that way it stays small? Any ideas/suggestions are greatly appreciated! Thanks in advance!

D.R.
  • 1,199
  • 5
  • 19
  • 42
  • I think this one is your buddy http://stackoverflow.com/questions/5663671/creating-an-empty-bitmap-and-drawing-though-canvas-in-android and I would recommend using Bitmap.Config.ARGB_8 at start then update it. – Sergey Benner Jan 16 '12 at 21:47
  • More to that http://stackoverflow.com/questions/477572/android-strange-out-of-memory-issue – Sergey Benner Jan 16 '12 at 21:48
  • try adding to your manifest file – asenovm Jan 16 '12 at 21:52
  • @iLate: largeHeap=true only works for Android 3.0+ I should have specified that we're running it on an Android 2.3.3 tablet – D.R. Jan 16 '12 at 22:32
  • 1
    Why do you need to send the table formated as a bitmap? Why not generate a textual representation of the table data so that you wouldn't have to generate huge bitmaps at all? – Martin Nordholts Jan 23 '12 at 10:06
  • The table is a report, the report is then captured as a JPEG and attached to an email. Part of the report captures signatures so it must be sent pictorially instead of textually. – D.R. Jan 23 '12 at 19:14
  • Then send only the pictorial parts of the table as JPEGs and the rest textually? – Martin Nordholts Jan 24 '12 at 06:50

5 Answers5

0

Unfortunately there was no way to avoid my issue. I simply had to use a lower quality setting and hope the size did not become too big. Trying to scale the bitmap did not work properly and ended up with a distorted image.

D.R.
  • 1,199
  • 5
  • 19
  • 42
0

Some days before i have the same task to do. If you want to capture the Screen and that image should be post via Email then please study the below code that i have used to do it:

saveImageInLandscapFunction(); // function to save the Image

                            Intent picMessageIntent = new Intent(android.content.Intent.ACTION_SEND);   
                            picMessageIntent.setType("image/jpeg");   
                            File f = new File(APP_FILE_PATH + "/"+filename+".jpg");
                            picMessageIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f));
                            startActivity(picMessageIntent);

Function that take screenShot and and save to the sdcard

protected void saveImageInLandscapFunction() {
    View root = findViewById(android.R.id.content);
    root.setDrawingCacheEnabled(true);
    Bitmap bm = Bitmap.createBitmap(root.getDrawingCache());
    //Bitmap bm = Bitmap.createBitmap(root.getDrawingCache(), 0, 0, display.getWidth(), display.getHeight());
    root.setDrawingCacheEnabled(false);

   // Bitmap overlayBitmap = overlay(photoBitmap, mBitmap); // overlay the Bitmap

    new ExportBitmapToFile(DrawMainActivity.this, bm).execute(); 
    sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"+ Environment.getExternalStorageDirectory())));

}

Class that handle the main save image process and show the progressbar till image is saved:

// for to saw progressBar
public static class ExportBitmapToFile extends AsyncTask<Intent,Void,Boolean> {
    private Context mContext;
    private Handler mHandler;
    private Bitmap nBitmap;
    private ProgressDialog  m_progressDialog = null; 
    @Override     
    protected void onPreExecute(){         
        m_progressDialog = new ProgressDialog(mContext);  
        m_progressDialog.setTitle("Draw");
        m_progressDialog.setMessage("Please wait...");
        m_progressDialog.setCancelable(false);         
        m_progressDialog.show();     
    }

    public ExportBitmapToFile(Context context,Bitmap bitmap) {
        mContext = context;
        nBitmap = bitmap;

    }

    @Override
    protected Boolean doInBackground(Intent... arg0) {
        try {
            if (!APP_FILE_PATH.exists()) {
                APP_FILE_PATH.mkdirs();
            }
            final FileOutputStream out = new FileOutputStream(new File(APP_FILE_PATH + "/"+filename+".jpg"));
            nBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
            out.flush();
            out.close();
            return true;
        }catch (Exception e) {
            e.printStackTrace();
        }
        //mHandler.post(completeRunnable);
        return false;
    }

    @Override
    protected void onPostExecute(Boolean bool) {
        super.onPostExecute(bool);
        if ( bool ){
            //mHandler.sendEmptyMessage(1);
        }
        if (m_progressDialog.isShowing()) {             
            m_progressDialog.dismiss();          
        }  
    }
}

Hope this will surly help you.

Let me know if you want amy other help.

Enjoy. :)

Shreyash Mahajan
  • 23,386
  • 35
  • 116
  • 188
  • Your solution does not address the memory issues. It is essentialyl the same as what I'm currently doing – D.R. Jan 27 '12 at 18:08
0

Why don't you split the images into multiple smaller regions and send....you can then stitch them back....or if you have the patience and skill...you can stitch the images by reading the smaller image files saved on disk and send the stitched image...You can use translate and clip the canvas before sending to the draw() method to get your required regions

Navin Ilavarasan
  • 1,271
  • 11
  • 15
  • Could you provide an example or sample code? Unfortunately all this needs to be done within the app and can't be done by a server elsewhere. My understanding of your answer is to capture the screen into multiple bitmaps, then combine them and attach it to the email. Is that correct? Will I have any issues with memory in the app when I recombine and then pass it to the email intent? – D.R. Jan 27 '12 at 04:41
  • i don't have an example/source for this...just had an idea on it...and i don't think you would run into memory issue as you would be working with files once you store the part images on the disk...and you can use the buffered file stream to work with it....again all this is theoretical...you might face some other issues while developing which I cannot guarantee for... – Navin Ilavarasan Jan 27 '12 at 15:58
0

While creating the bitmap, why dont you hardcore the width and height.

Bitmap result = Bitmap.createScaledBitmap(bitmapPicture,
                        640, 480, false);

Try this out, let me know if this is of any help or not.

Shishir Shetty
  • 2,021
  • 3
  • 20
  • 35
0

Try to combine your solution and iDroid Explorer's one. Check if you can write to file straight from drawing cache. You code will look this way:

try {
    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
    if(bos!=null) {
        try {
            TableLayout tl = (TableLayout)findViewById(R.id.CSRTableLayout);
            tl.setDrawingCacheEnabled(true);
            Bitmap csr = tl.getDrawingCache();
            csr.compress(Bitmap.CompressFormat.JPEG, 60, bos);
            tl.setDrawingCacheEnabled(false);
        } catch (OutOfMemoryError e) {
            Toast.makeText(CustomerReportActivity.this, "Out of Memory!", Toast.LENGTH_SHORT).show();
        } finally {
            bos.close();
        }
    }
} catch (Exception e) {
    e.printStackTrace();
}

Maybe, you'll need to recycle csr after switching off drawing cache.

OleGG
  • 8,589
  • 1
  • 28
  • 34
  • I'll be testing this out tomorrow. Apparently you can't catch and OutOfMemoryError, so it's a pain to test! I'll report back and select an answer as soon as I'm done! – D.R. Jan 29 '12 at 20:20