2013-02-03 11 views
6

Mam ViewPager, którego używam do pokazywania obrazów z możliwością powiększania (używając ImageViewTouch). Potrzebuję załadować duże bitmapy z Internetu (http). Przez duże mam na myśli 2000x1000. Obrazy muszą być tak duże, ponieważ można je powiększać i wyświetlać szczegóły. Obrazy na serwerze mają format .jpg, ale to nie problem - mogę to zmienić.Ładowanie dużych bitmap do ImageView w ViewPager - brak pamięci

Jak mogę zarządzać ładowaniem tak dużych obrazów do ImageViewTouch (ImageView) bez tworzenia błędów z pamięcią?

Teraz używam po prostu to (AsyncTask):

ImageView currentView; //ImageView where to place image loaded from Internet 
    String ImageUrl; //URL of image to load 

    protected Bitmap doInBackground(ArrayList... params) { 

      Bitmap bitmap; 
      InputStream in = null; 
      imageUrl = (String)params[0].get(1); 

      try{ 
       HttpClient httpclient = new DefaultHttpClient(); 
       HttpResponse response = httpclient.execute(new HttpGet(imageUrl)); 
       in = response.getEntity().getContent(); 
      } catch(Exception e){ 
       e.printStackTrace(); 
      } 
      try { 
       bitmapa = BitmapFactory.decodeStream(in); 
       in.close(); 
      } catch (IOException e1) { 
       e1.printStackTrace(); 
      } 

      return bitmap; 
     } 


     @Override 
     protected void onPostExecute(Bitmap bitmap) { 

      currentView.setImageBitmap(bitmap); 
     } 

I to spowodować wiele problemów z pamięcią:

E/dalvikvm-heap(369): 69560740-byte external allocation too large for this process. 
E/GraphicsJNI(369): VM won't let us allocate 69560740 bytes 

lub

E/AndroidRuntime(369): java.lang.RuntimeException: An error occured while executing doInBackground() 
E/AndroidRuntime(369): at android.os.AsyncTask$3.done(AsyncTask.java:200) 
E/AndroidRuntime(369): at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:274) 
E/AndroidRuntime(369): at java.util.concurrent.FutureTask.setException(FutureTask.java:125) 
E/AndroidRuntime(369): at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:308) 
E/AndroidRuntime(369): at java.util.concurrent.FutureTask.run(FutureTask.java:138) 
E/AndroidRuntime(369): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1088) 
E/AndroidRuntime(369): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:581) 
E/AndroidRuntime(369): at java.lang.Thread.run(Thread.java:1019) 
E/AndroidRuntime(369): Caused by: java.lang.OutOfMemoryError: bitmap size exceeds VM budget 
E/AndroidRuntime(369): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method) 
E/AndroidRuntime(369): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:470) 
E/AndroidRuntime(369): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:525) 
E/AndroidRuntime(369): at com.package.app.ImageDownloader.doInBackground(ImageDownloader.java:78) 
E/AndroidRuntime(369): at com.package.app.ImageDownloader.doInBackground(ImageDownloader.java:1) 
E/AndroidRuntime(369): at android.os.AsyncTask$2.call(AsyncTask.java:185) 
E/AndroidRuntime(369): at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:306) 
E/AndroidRuntime(369): ... 4 more 
+0

http: // stackoverflow. com/a/24135283/294884 – Fattie

Odpowiedz

2

która wersja OS, czy to testujesz? W starszych wersjach dostępna przestrzeń sterty jest znacznie niższa niż w późniejszych wersjach.

Chciałbym poważnie rozważyć zmniejszenie rozmiaru bitmap, aby tego uniknąć. Jeśli pobierasz plik 2000 x 1000 Bitmap dla telefonu mdpi, prawdopodobnie jest to zły pomysł.

Zalecam również przeczytanie this article, aby uzyskać więcej informacji o tym, jak załadować odpowiedni obraz dokładnie dla określonego ImageView, w oparciu o jego wymiary.

Wreszcie, należy zawsze zamknąć InputStream w finally bloku:

try { 
     bitmap = BitmapFactory.decodeStream(in); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } finally { 
     if (in != null) { in.close(); } 
    } 

Istnieje również android:largeHeap="true" które można rozważyć, wykonany specjalnie dla aplikacji, które dotyczą dużych bitmap, takich jak aplikacje do edycji zdjęć.

+0

Dzięki za odpowiedź. Moja docelowa wersja SDK to 15, a minimalna to 4. Przeczytałem już ten artykuł, ale mi to nie pomogło - problemem jest to, że nie mogę zmienić rozmiaru ani skali obrazu, ponieważ musi on pozostać powiększany i pokazywać szczegóły, gdy powiększony. – michalsol

+0

Nie będę cię okłamywał, nie znajdziesz odpowiedzi, która pasowałaby do obrazu 2000x1000 w pamięci. Pomyśl o tym, bez względu na to, co zrobisz, przekroczycie dostępną przestrzeń.Być może możesz znaleźć inną strategię podobną do działania przeglądarki Chrome, renderować tylko część obrazu w pamięci, ponieważ użytkownik przesuwa się wokół ciebie szybko wczytuje nowe okno w oparciu o bieżącą rzutnię. – dnkoutso

+0

Zastanowiłem się nad tym - ponownie załadowałem powiększoną część obrazu - ale wygląda na dość trudną do zrealizowania. Czy masz jakieś tutoriale tego? Dzięki! – michalsol

2

Obraz o wymiarach 2000 x 1000 pikseli jest duży, ale nie jest TAK duży.

Widzę, że proces próbuje przydzielić 69560740 bajtów przed śmiercią, czyli około 66 MB!

Twój 2000 x 1000, w najgorszym przypadku, ma około 2000 x 1000 x 4 = 8 MB, czyli mniej niż 66 MB. Coś jeszcze się dzieje.

Znalazłem również problem z używaniem bitmap jako wartości zwracanej/wynikowej w AsyncTasks. AsyncTasks, które zostały zakończone, nadal utrzymują wynik, dopóki nie zostaną zebrane śmieci. Ponieważ nie kontrolujesz kolekcji garbników instancji AsyncTask, nie używaj Bitmapy jako wartości zwracanej/wynikowej. Zamiast umieścić bitmapę w polu i przypisać ją gdy doInBackground ma się ku końcowi, i użyć tego pola, aby uzyskać bitmapę w onPostExecute i ustaw to pole na null (!):

Bitmap bitmap; 
protected Void doInBackground(ArrayList... params) { 

     InputStream in = null; 
     imageUrl = (String)params[0].get(1); 

     try{ 
      HttpClient httpclient = new DefaultHttpClient(); 
      HttpResponse response = httpclient.execute(new HttpGet(imageUrl)); 
      in = response.getEntity().getContent(); 
     } catch(Exception e){ 
      e.printStackTrace(); 
     } 
     try { 
      bitmap = BitmapFactory.decodeStream(in); 
      in.close(); 
     } catch (IOException e1) { 
      e1.printStackTrace(); 
     } 

     return null; 
    } 


    @Override 
    protected void onPostExecute(Void unused) { 
     if (bitmap != null) { 
      currentView.setImageBitmap(bitmap); 
     } 

     // And don't forget to null the bitmap field! 
     bitmap = null; 
    } 
+0

Jako dodatkowy komentarz do mojej poprzedniej odpowiedzi. Myślę, że twój serwer zwraca bardzo duże obrazy, ponieważ maszyna wirtualna próbuje przydzielić 69,500 000 bajtów. Byłby to obraz ARGB_8888 o prawie 3000 x 3000 pikseli. –

+0

To rozwiązanie znacznie poprawiło wykorzystanie pamięci. Właśnie robiłem to, co powiedziałeś, poczynając od "Znalazłem również problem z używaniem bitmap jako wartości zwracanej/wynikowej w AsyncTasks ......." Zmodyfikowałem mój kod, jak powiedziałeś i teraz mogę wyświetlać x2 razy więcej obrazu bez awarii. Dziękuję Ci –

Powiązane problemy