My application is a Wifi chat application with which you can chat between two Android blocks with text messages and take pictures of the camera and send them. Images are saved on the SD card.
I used OutOfMemoryError after several images sent, but I solved this problem by sending
options.inPurgeable = true;
and
options.inInputShareable = true;
to the BitmapFactory.decodeByteArray method. This makes the pixels "deallocatable", so new images can use memory. Thus, the error is no longer saved.
But the internal memory is still full of images, and the warning “Low space: phone storage space is getting low” appears. The application will no longer work, but after the application finishes, there is no more memory on the phone. I need to manually clear the application data in the "Settings"> "Applications"> "Application Management" section.
I tried to process the bitmaps and even tried to clear the application cache explicitly, but it doesn't seem to do what I expect.
This function receives an image through a TCP socket, writes it to an SD card, and launches my custom Activity PictureView:
public void receivePicture(String fileName) { try { int fileSize = inStream.readInt(); Log.d("","fileSize:"+fileSize); byte[] tempArray = new byte[200]; byte[] pictureByteArray = new byte[fileSize]; path = Prefs.getPath(this) + "/" + fileName; File pictureFile = new File(path); try { if( !pictureFile.exists() ) { pictureFile.getParentFile().mkdirs(); pictureFile.createNewFile(); } } catch (IOException e) { Log.d("", "Recievepic - Kunde inte skapa fil.", e); } int lastRead = 0, totalRead = 0; while(lastRead != -1) { if(totalRead >= fileSize - 200) { lastRead = inStream.read(tempArray, 0, fileSize - totalRead); System.arraycopy(tempArray, 0, pictureByteArray, totalRead, lastRead); totalRead += lastRead; break; } lastRead = inStream.read(tempArray); System.arraycopy(tempArray, 0, pictureByteArray, totalRead, lastRead); totalRead += lastRead; } BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(pictureFile)); bos.write(pictureByteArray, 0, totalRead); bos.flush(); bos.close(); bos = null; tempArray = null; pictureByteArray = null; setSentence("<"+fileName+">", READER); Log.d("","path:"+path); try { startActivity(new Intent(this, PictureView.class).putExtra("path", path)); } catch(Exception e) { e.printStackTrace(); } } catch(IOException e) { Log.d("","IOException:"+e); } catch(Exception e) { Log.d("","Exception:"+e); } }
Here is a PictureView . It creates byte [] from the file on the SD card, decodes the array in Bitmap, compresses the bitmap image and writes it back to the SD card. Finally, in Progress.onDismiss image is set as a full-screen image:
public class PictureView extends Activity { private String fileName; private ProgressDialog progress; public ImageView view; @Override public void onCreate(Bundle bundle) { super.onCreate(bundle); Log.d("","onCreate() PictureView"); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); view = new ImageView(this); setContentView(view); progress = ProgressDialog.show(this, "", "Laddar bild..."); progress.setOnDismissListener(new OnDismissListener() { public void onDismiss(DialogInterface dialog) { File file_ = getFileStreamPath(fileName); Log.d("","SETIMAGE"); Uri uri = Uri.parse(file_.toString()); view.setImageURI(uri); } }); new Thread() { public void run() { String path = getIntent().getStringExtra("path"); Log.d("","path:"+path); File pictureFile = new File(path); if(!pictureFile.exists()) finish(); fileName = path.substring(path.lastIndexOf('/') + 1); Log.d("","fileName:"+fileName); byte[] pictureArray = new byte[(int)pictureFile.length()]; try { DataInputStream dis = new DataInputStream( new BufferedInputStream( new FileInputStream(pictureFile)) ); for(int i=0; i < pictureArray.length; i++) pictureArray[i] = dis.readByte(); } catch(Exception e) { Log.d("",""+e); e.printStackTrace(); } BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true; options.inInputShareable = true; Bitmap pictureBM = BitmapFactory.decodeByteArray(pictureArray, 0, pictureArray.length, options); OutputStream out = null; try { out = openFileOutput(fileName, MODE_PRIVATE); pictureBM.compress(CompressFormat.PNG, 100, out); pictureBM = null; progress.dismiss(); } catch (IOException e) { Log.e("test", "Failed to write bitmap", e); } finally { if (out != null) try { out.close(); out = null; } catch (IOException e) { } } } }.start(); } @Override protected void onStop() { super.onStop(); Log.d("","ONSTOP()"); Drawable oldDrawable = view.getDrawable(); if( oldDrawable != null) { ((BitmapDrawable)oldDrawable).getBitmap().recycle(); oldDrawable = null; Log.d("","recycle"); } Editor editor = this.getSharedPreferences("clear_cache", Context.MODE_PRIVATE).edit(); editor.clear(); editor.commit(); } }
When the user clicks the back button, the image should not be accessible anymore from the application. Just saved on the SD card.
In onStop() I recycle the old Bitmap and even try to clear the application data. However, the warning "Low on space" appears. How can I be sure that images will no longer allocate memory when they are not needed?
EDIT: Looks like the problem is the compression method. If everything is compiled after compilation, the problem remains. If I remove the compression, the problem will disappear. Compression seems to set aside memory that has never been released, and it's 2-3 MB per image.