2013-02-14 57 views
3

我正在使用GridView來顯示圖像。圖像從Feed中下載並添加到BitmapCache。 GridView位於ViewFlipper(它具有作爲第二個View的ListView)內部。我第一次使用GridView,但在使用ListView時,我多次使用適配器。由GridView中的多個(> 120)getView調用導致的OutOfMemoryError

此刻,Feed只傳送兩個圖像。但是當我啓動包含GridView的Fragment時,我得到了一個由BitmapFactory.decodeStream()引起的OutOfMemoryError。當我深入瞭解logcat時,我注意到GridView的適配器內部的getView()被多次調用。 我知道getView()不止一次被調用,但我的Adapter中的getView()方法被調用超過120次,只有位置爲0.我不明白爲什麼它經常被調用。但我很確定這是由於此方法試圖在幾秒鐘內加載超過100次的位圖而導致我的內存問題。

由於我已經試圖用ViewHolder回收我的視圖,現在我很無奈,我希望有人能夠解釋我這個getView()的大規模調用和/或可能給我一個提示來解決我的問題問題。

的getView() - mthod我適配器:

@Override 
public View getView(int position, View convertView, ViewGroup parent) { 
    ViewHolder holder; 

    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 

    if (convertView == null) { 
     holder = new ViewHolder(); 
     convertView = inflater.inflate(R.layout.pictures_grid_item, parent, false); 
     holder.image = (ImageView) convertView.findViewById(R.id.picturesGridImage); 
     convertView.setTag(holder); 
    } else { 
     holder = (ViewHolder) convertView.getTag(); 
     holder.image.setImageBitmap(null); 
    } 

    Picture picture = (Picture) pictureList.get(position); 
    String imageUrl = picture.getUrl(); 

    if (!TextUtils.isEmpty(imageUrl)) { 
     holder.image.setTag(imageUrl); 
     ImageLoader.getInstance(context).loadImageWithTagCheck(holder.image); 
    } 

    return convertView; 
} 


private static class ViewHolder { 
    ImageView image; 
} 

loadImageWithTagCheck() - 方法只檢查如果圖像已經被下載(這deffinitely應該如此)

該片段持有的觀點:

public class PicturesFragment extends BaseFragment { 

private List<Parcelable> pictureList; 
private PicturesGridAdapter adapter; 

@Override 
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 
    View view = inflater.inflate(R.layout.pictures_fragment, container, false); 
    // TODO: Remove final after development 
    final MediaActivity activity = (MediaActivity) getActivity(); 

    pictureList = activity.getPictures(); 

    adapter = new PicturesGridAdapter(activity, pictureList); 

    GridView gridview = (GridView) view.findViewById(R.id.picturesGrid); 
    gridview.setAdapter(adapter); 

    gridview.setOnItemClickListener(new OnItemClickListener() { 
     @Override 
     public void onItemClick(AdapterView<?> parent, View v, int position, long id) { 
      Toast.makeText(activity, "" + position, Toast.LENGTH_SHORT).show(); 
     } 
    }); 
    return view; 
} 
} 

順便說一句:我沒有使用* wrap_content *任何地方。

編輯: 以下是圖像加載器的代碼。當然,ImageLoader是導致outOfMemoryError的問題。但是我認爲這個問題與適配器有些相似,因爲在創建視圖之後,爲位置0調用getView()120是不對的。並且適配器只創建一次,因此它在我的適配器的單個實例中大於120次調用。 (這是一個非常龐大而複雜的項目,所以「簡單」 ImageLoader的有很多的代碼)

public void loadImageWithTagCheck(final ImageView view) { 
    final String url = (String) view.getTag(); 
    final Handler uiHandler = new Handler() { 
     @Override 
     public void handleMessage(Message msg) { 
     } 
    }; 
    if (imageHandler != null) { 
     imageHandler.post(new Runnable() { 

      @Override 
      public void run() { 
       final Bitmap bmp = getImage(url, view); 
       uiHandler.post(new Runnable() { 

        @Override 
        public void run() { 
         String tagUrl = (String) view.getTag(); 
         if (tagUrl.equals(url) && bmp != null 
           && !bmp.isRecycled()) { 
          scaleBitmapAndAdjustViewByHeight(view, bmp); 
         } else if (bmp != null) { 
          bmp.recycle(); 
         } 
        } 
       }); 
      } 
     }); 
    } 
} 

    private Bitmap getImage(String url, View v) { 
     Bitmap bmp = null; 

     if (url != null && !TextUtils.isEmpty(url)) { 
      String md5Url = Utility.md5(url); 
      if (cache.containsKey(md5Url)) { 
       bmp = cache.getBitmap(md5Url); 
      } else { 
       HttpGet httpGet = new HttpGet(); 
       HttpClient httpClient = new DefaultHttpClient(); 

       HttpResponse response = null; 
       try { 
        URI uri = new URI(url); 
        httpGet.setURI(uri); 
        response = httpClient.execute(httpGet); 

        if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { 
         HttpEntity entity = response.getEntity(); 
         if (entity != null) { 
          final BufferedInputStream buffIn = new BufferedInputStream(
            entity.getContent(), Utils.IO_BUFFER_SIZE); 
          BitmapFactory.Options options = new BitmapFactory.Options(); 
          options.inJustDecodeBounds = true; 
          options.outWidth = v.getWidth(); 
          options.outHeight = v.getHeight(); 
          options.inPurgeable = true; 
          options.inInputShareable = true; 
          options.inPreferredConfig = Bitmap.Config.RGB_565; 

          bmp = BitmapFactory.decodeStream(buffIn, null, 
            options); 
         } 
        } 
       } catch (URISyntaxException e) { 
        e.printStackTrace(); 
       } catch (ClientProtocolException e) { 
        e.printStackTrace(); 
       } catch (IOException e) { 
        e.printStackTrace(); 
       } catch (IllegalStateException e) { 
        e.printStackTrace(); 
       } 

       if (bmp != null) { 
        cache.put(md5Url, bmp); 
       } 
      } 
     } 

     return bmp; 
    } 

private void scaleBitmapAndAdjustViewByHeight(final ImageView view, 
     final Bitmap bmp) { 
    ViewTreeObserver vto = view.getViewTreeObserver(); 
    vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { 

     @SuppressLint("NewApi") 
     @SuppressWarnings("deprecation") 
     @Override 
     public void onGlobalLayout() { 
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 
       view.getViewTreeObserver().removeOnGlobalLayoutListener(
         this); 
      } else { 
       view.getViewTreeObserver().removeGlobalOnLayoutListener(
         this); 
      } 

      // Get current dimensions 
      int width = bmp.getWidth(); 
      int height = bmp.getHeight(); 

      // Determine how much to scale: the dimension requiring less 
      // scaling is closer to the its side. This way the image always 
      // stays inside your bounding box AND either x/y axis touches 
      // it. 
      int imageViewHeightFromXMLinPixels = view.getHeight(); 
      float xScale = (float) ((imageViewHeightFromXMLinPixels * 2.75)/width); 
      float yScale = ((float) imageViewHeightFromXMLinPixels) 
        /height; 
      float scale = (xScale <= yScale) ? xScale : yScale; 

      // Create a matrix for the scaling and add the scaling data 
      Matrix matrix = new Matrix(); 
      matrix.postScale(scale, scale); 

      // Create a new bitmap 
      Bitmap scaledBitmap = Bitmap.createBitmap(bmp, 0, 0, width, 
        height, matrix, true); 
      width = scaledBitmap.getWidth(); // re-use 

      view.setImageBitmap(scaledBitmap); 
      view.getLayoutParams().width = width; 
     } 
    }); 
    view.requestLayout(); 
} 
+0

我還沒有完成所有的代碼,但如果你有一個位圖緩存,爲什麼你不止一次加載相同的圖像?你不應該在第一次之後從緩存中讀取它嗎?只有位置0的120+調用'getView'聽起來很奇怪。 – kabuko 2013-02-14 18:30:49

+0

我想你也需要發佈ImageLoader類... – dmon 2013-02-14 18:38:40

+0

感謝您的回覆我添加了imageloader的代碼。 @kabuko:圖像只加載一次。它使用url作爲標識符,所以我最初發布的代碼看起來就像它不止一次加載。 – joshplusa 2013-02-15 09:03:05

回答

2

擺脫scaleBitmapAndAdjustViewByHeight(...)方法。 而是,做一個簡單的view.setImageBitmap(bmp)

爲什麼? scaleBitmapAndAdjustViewByHeight(...)調用view.requestLayout()這可能導致調用您的適配器getView(...)並以死鎖結束,最後導致OutOfMemoryError。

+0

謝謝!你的權利,這是問題。在註釋掉'view.requestLayout()'之後,我只能得到10個'getView()'的調用。我不知道視圖項目的每一個變化都會調用'getView'方法。但現在這一切都有道理。 – joshplusa 2013-02-15 12:09:59

相關問題