我正在使用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的120+調用'getView'聽起來很奇怪。 – kabuko 2013-02-14 18:30:49
我想你也需要發佈ImageLoader類... – dmon 2013-02-14 18:38:40
感謝您的回覆我添加了imageloader的代碼。 @kabuko:圖像只加載一次。它使用url作爲標識符,所以我最初發布的代碼看起來就像它不止一次加載。 – joshplusa 2013-02-15 09:03:05