Внутренняя память заполнена изображениями, вероятно, вызвана Bitmap.compress (format, int, stream) - PullRequest
4 голосов
/ 15 февраля 2012

Мое приложение - это приложение для чата Wifi, с помощью которого вы можете общаться между двумя устройствами Android с помощью текстовых сообщений, снимков с камеры и отправлять их.Изображения сохраняются на SD-карту.

Раньше было выброшено OutOfMemoryError после нескольких отправленных изображений, но я решил эту проблему, отправив

options.inPurgeable = true;

и

options.inInputShareable = true;

в метод BitmapFactory.decodeByteArray.Это делает пиксели «освобождаемыми», чтобы новые изображения могли использовать память.Таким образом, ошибка больше не сохраняется.

Но внутренняя память по-прежнему заполнена изображениями и появляется предупреждение «Недостаточно места: в памяти телефона недостаточно места».Приложение больше не падает, но на телефоне больше нет памяти после его завершения.Я должен вручную очистить данные приложения в меню «Настройки»> «Приложения»> «Управление приложениями».

Я пытался перерабатывать растровые изображения и даже пытался явно очистить кэш приложения, но, похоже, он не выполняет то, что я ожидал.

Эта функция получает изображение через TCP-сокет, записывает его на SD-карту и запускает мой пользовательский 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); }
}

Вот PictureView.Он создает байт [] из файла на SD-карте, декодирует массив в растровое изображение, сжимает растровое изображение и записывает его обратно на SD-карту.Наконец, в Progress.onDismiss изображение устанавливается как изображение полноэкранного изображения. Просмотр:

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(); }

        /**
         * Passing these options to decodeByteArray makes the pixels deallocatable
         * if the memory runs out.
         */
        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);

                    /**
                      * COMPRESS !!!!!
                    **/
            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();
    }
}

Когда пользователь нажимает клавишу возврата, изображение больше не должно быть доступно изнутри.приложение.Просто хранится на SD-карте.

В onStop() Я перезаписываю старое растровое изображение и даже пытаюсь очистить данные приложения.Тем не менее появляется предупреждение «Низкое пространство».Как я могу быть уверен, что изображения больше не будут выделять память, когда они не нужны?

РЕДАКТИРОВАТЬ: Кажется, проблема заключается в методе сжатия.Если все после сжатия комментируется, проблема остается.Если я удаляю компресс, проблема исчезает.Сжатие, кажется, выделяет память, которая никогда не освобождается, и составляет 2-3 МБ на изображение.

Ответы [ 2 ]

1 голос
/ 17 февраля 2012

Хорошо, я решил это. Проблема заключалась в том, что я передавал OutputStream в compress, который представляет собой поток в закрытый файл во внутренней памяти приложения. Это то, что я установил в качестве изображения позже. Этот файл никогда не выделяется.

Я не понял, что у меня есть два файла: один на SD-карте и один во внутренней памяти, оба с одинаковым именем.

Теперь я просто устанавливаю файл SD-карты как изображение ImageView. Я никогда не считывал файл во внутреннюю память как байт [], поэтому никогда не декодировал массив в растровое изображение, поэтому никогда не сжимал растровое изображение во внутреннюю память.

Это новый PictureView:

public class PictureView extends Activity {

    public ImageView view;
    private String path;

    @Override
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        Log.d("","onCreate() PictureView");

        path = getIntent().getStringExtra("path");

        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
                WindowManager.LayoutParams.FLAG_FULLSCREEN);

        view = new ImageView(this);
        setContentView(view);

        Uri uri = Uri.parse( new File(path).toString() );
        view.setImageURI(uri);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            Log.d("","Back key pressed");

            Drawable oldDrawable = view.getDrawable();
                if( oldDrawable != null) {
                    ((BitmapDrawable)oldDrawable).getBitmap().recycle();
                    oldDrawable = null;
                    Log.d("","recycle");
                }
                view = null;
        }
        return super.onKeyDown(keyCode, event);
    }
}

Это плохая практика - помещать внешний файл в качестве изображения ImageView? Должен ли я сначала загрузить его во внутреннюю память?

0 голосов
/ 15 февраля 2012

Если вы специально хотите, чтобы изображение обнулялось из памяти, чтобы наверняка, когда пользователь нажимает назад, вы можете переопределить кнопку «Назад» и заставить ваше изображение очищать вызовы там. Я делаю это в некоторых своих приложениях, и это похоже на работу. может быть что-то вроде этого:

@Override

protected void onBackPressed() {
         super.onBackPressed();

         view.drawable = null;
         jumpBackToPreviousActivity();
       } 

Я почти уверен, что есть некоторые методы просмотра, которые очищают другие кэши и тому подобное. Вы можете повторно использовать растровое изображение, но это не гарантирует, что оно будет сброшено прямо тогда, но только в какой-то момент, когда gc доберется до него ..... но я уверен, что вы, вероятно, уже знаете это:)

РЕДАКТИРОВАТЬ: Вы также можете сделать то же самое в методе onPause. Этот гарантированно будет вызван. Два других могут никогда не позвонить в соответствии с документами Android. http://developer.android.com/reference/android/app/Activity.html

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...