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.
2 odpowiedzi
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?
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
Podobne pytania
Nowe pytania
android
Android to mobilny system operacyjny Google, używany do programowania lub tworzenia urządzeń cyfrowych (smartfony, tablety, samochody, telewizory, Wear, Glass, IoT). W przypadku tematów związanych z Androidem użyj tagów specyficznych dla Androida, takich jak android-intent, android-activity, android-adapter itp. W przypadku pytań innych niż programowanie lub programowanie, ale związanych ze strukturą Androida, użyj tego linku: https: // android.stackexchange.com.