Moja aplikacja to aplikacja do czatu Wi-Fi, dzięki której możesz komunikować się między dwoma urządzeniami z Androidem za pomocą wiadomości tekstowych i robić zdjęcia z aparatu i wysyłać je. Zdjęcia są zapisywane na karcie SD.

Kiedyś po kilku przesłanych obrazach wyrzucano OutOfMemoryError, ale rozwiązałem ten problem, wysyłając

options.inPurgeable = true;

I

options.inInputShareable = true;

Do metody BitmapFactory.decodeByteArray. To sprawia, że ​​piksele są „usuwalne”, dzięki czemu nowe obrazy mogą korzystać z pamięci. W ten sposób błąd już nie istnieje.

Jednak pamięć wewnętrzna jest nadal pełna obrazów i pojawia się ostrzeżenie „Mało miejsca: Mało miejsca w pamięci telefonu”. Aplikacja nie ulega już awarii, ale po zakończeniu działania aplikacji w telefonie nie ma już pamięci. Muszę ręcznie wyczyścić dane aplikacji w Ustawienia > Aplikacje > Zarządzaj aplikacjami.

Próbowałem przetwarzać mapy bitowe, a nawet próbowałem jawnie opróżnić pamięć podręczną aplikacji, ale wydaje się, że nie robi tego, czego oczekuję.

Ta funkcja odbiera obraz przez gniazdo TCP, zapisuje go na karcie SD i uruchamia mój niestandardowy widok 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); }
}

Oto PictureView. Tworzy byte[ ] z pliku na karcie SD, dekoduje tablicę do bitmapy, kompresuje bitmapę i zapisuje ją z powrotem na karcie SD. Wreszcie, w Progress.onDismiss, obraz jest ustawiony jako obraz pełnego ekranu imageView:

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

Gdy użytkownik naciśnie klawisz wstecz, zdjęcie nie powinno być już dostępne z poziomu aplikacji. Po prostu zapisane na karcie SD.

W onStop() przetwarzam starą mapę bitową, a nawet próbuję opróżnić dane aplikacji. Nadal pojawia się ostrzeżenie „Mało miejsca”. Jak mogę się upewnić, że obrazy nie będą już alokować pamięci, gdy nie są potrzebne?

EDYCJA: Wydaje się, że problemem jest metoda kompresji. Jeśli wszystko po skompresowaniu jest skomentowane, problem pozostaje. Jeśli usunę kompres, problem zniknie. Compress wydaje się alokować pamięć, która nigdy nie została zwolniona, i wynosi 2-3 MB na obraz.

4
JonatanEkstedt 15 luty 2012, 03:26

2 odpowiedzi

Najlepsza odpowiedź

Ok, rozwiązałem to. Problem polegał na tym, że przesyłałem OutputStream do compress, który jest strumieniem do prywatnego pliku w pamięci wewnętrznej aplikacji. To właśnie ustawiłem jako obraz później. Ten plik nigdy nie jest przydzielany.

Nie zrozumiałem, że mam dwa pliki: jeden na karcie SD, a drugi w pamięci wewnętrznej, oba o tej samej nazwie.

Teraz po prostu ustawiam plik karty SD jako obraz ImageView. Nigdy nie czytam pliku do pamięci wewnętrznej jako byte[], dlatego nigdy nie dekoduję tablicy do mapy bitowej, a zatem nigdy nie kompresuję mapy bitowej do pamięci wewnętrznej.

Oto nowy 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);
    }
}

Czy złą praktyką jest umieszczanie zewnętrznego pliku jako obrazu ImageView? Czy powinienem najpierw załadować go do pamięci wewnętrznej?

1
JonatanEkstedt 23 luty 2012, 19:30

Jeśli konkretnie chcesz, aby obraz został usunięty z pamięci na pewno, gdy użytkownik naciśnie wstecz, możesz nadpisać przycisk Wstecz i wywołać tam czyszczenie obrazu. Robię to w niektórych moich aplikacjach i wydaje się, że działa. może coś takiego:

@Override

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

         view.drawable = null;
         jumpBackToPreviousActivity();
       } 

Jestem prawie pewien, że istnieje kilka metod wyświetlania, które czyszczą inne pamięci podręczne i tym podobne. Możesz przetworzyć bitmapę, ale to nie gwarantuje, że zostanie ona zrzucona wtedy, ale tylko w pewnym momencie, gdy gc do niej dotrze ..... ale jestem pewien, że prawdopodobnie już to wiesz :)

EDYCJA: Możesz również zrobić to samo w metodzie onPause. Ten na pewno zostanie wywołany. Zgodnie z dokumentacją Androida, pozostali dwaj mogą nigdy nie zostać wezwani. http://developer.android.com/reference/android/app/Activity.html

0
James andresakis 15 luty 2012, 14:06