2.ViewPager显示图片
Android中Gridview和ViewPager显示图片的优化处理(1)。
在GridView中,要查看图片的大图时,通常用ViewPager进行显示,而在ViewPager中由于图片过大,加载几张就会导致OOM,因此要及时的回收图片占用的内存。解决思路:只保证当前页、上一页、下一页加载图片或者只有当前页加载图片,滑到上一页和下一页时再加载图片。下面将从ViewPager和ImageSwitcher来解决显示大图出现的问题。
1.ViewPager
在ViewPager中有一个重要的方法viewPager.setOffscreenPageLimit(limit),该方法表示设置ViewPager加载页数。在默认情况下,通过Log分析,有的手机加载7页才开始销毁View,有的手机默认加载3页开始销毁View。
加载7页很容易就出现了OOM,加载3页也只是推迟了出现OOM,因此可以设置viewPager.setOffscreenPageLimit(0),加载的页数为3页(当前页,上一页,下一页),但是这样也并不能解决OOM。在PagerAdapter的destroyItem()方法中((ViewPager) container).removeView((View) object),只是移出了View,但是并没有清空Bitmap占用的内存。因此可以在这个方法中清空Bitmap占用的内存。另外在instantiateItem()方法中,不要加载图片,在接口OnPageChangeListener的onPageSelected()方法中再加载图片。
package com.example.album;
import java.util.ArrayList;
import java.util.List;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v4.util.LruCache;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import com.example.album.adapter.BitmapAsyncTask;
import com.example.album.view.MyImageView;
public class VPActivity extends Activity implements OnPageChangeListener {
public static final String URIS = "uris";
public static final String POSITION = "position";
private List<View> mViews;
private ArrayList<String> mUris;
private ViewPager mViewPager;
private LruCache<String, Bitmap> mLruCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_vp);
mViewPager = (ViewPager) findViewById(R.id.viewpager);
mUris = getIntent().getStringArrayListExtra(URIS);
mViews = new ArrayList<View>(mUris.size());
mLruCache = new LruCache<String, Bitmap>((int) Runtime.getRuntime()
.maxMemory() / 8) {
@SuppressLint("NewApi")
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
};
mViewPager.setOffscreenPageLimit(0);// 设置加载页数,为0的时候是3页,默认会加载7页
// viewPager.setPageMargin(marginPixels);设置页边间距
mViewPager.setOnPageChangeListener(this);
mViewPager.setAdapter(new VPActivityAdpter());
mViewPager.setCurrentItem(getIntent().getIntExtra(POSITION, 0));
}
@Override
protected void onDestroy() {
if (mLruCache != null) {
mLruCache.evictAll();
mLruCache = null;
}
super.onDestroy();
}
@Override
public void onBackPressed() {
super.onBackPressed();
}
private class VPActivityAdpter extends PagerAdapter {
VPActivityAdpter() {
int size = mUris.size();
for (int i = 0; i < size; i++) {
MyImageView picture = new MyImageView(VPActivity.this);
picture.setLayoutParams(new ViewPager.LayoutParams());
mViews.add(picture);
}
}
@Override
public int getCount() {
return mUris.size();
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Bitmap bitmap = mLruCache.get(mUris.get(position));
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
mLruCache.remove(mUris.get(position));
bitmap = null;
Log.d("TAG", "destroyItem=" + position);
}
((ViewPager) container).removeView((View) object);
}
@SuppressWarnings("unchecked")
@Override
public Object instantiateItem(ViewGroup container, int position) {
Log.d("TAG", "instantiateItem=" + position);
container.addView(mViews.get(position), 0);
// 当postition=0的时候,不会调用接口OnPageChangeListener中的方法
if (position == 0) {
new BitmapAsyncTask(
VPActivity.this,
(MyImageView) mViews.get(position),
mUris.get(position),
new int[] {
getResources().getDisplayMetrics().widthPixels,
getResources().getDisplayMetrics().heightPixels })
.execute(mLruCache);
}
return mViews.get(position);
}
@Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == arg1;
}
}
@Override
public void onPageScrollStateChanged(int arg0) {
}
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
@SuppressWarnings("unchecked")
@Override
public void onPageSelected(int arg0) {
MyImageView picture = (MyImageView) mViews.get(arg0);
if (mLruCache.get(mUris.get(arg0)) == null) {
new BitmapAsyncTask(VPActivity.this,
(MyImageView) mViews.get(arg0), mUris.get(arg0), new int[] {
getResources().getDisplayMetrics().widthPixels,
getResources().getDisplayMetrics().heightPixels })
.execute(mLruCache);
} else {
picture.setImageBitmap(mLruCache.get(mUris.get(arg0)));
}
}
}
附加:最近修改了一下,只显示当前,上一张,下一张的片,并且不用回调的方法onPageSelected()中才加载图片,在instantiateItem()方法中就加载图片,防止了加载缓慢,黑屏的问题。代码如下:
package com.example.album;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v4.util.LruCache;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import com.example.album.adapter.BitmapAsyncTask;
import com.example.album.views.RecycleImageView;
import java.util.ArrayList;
public class VPActivity extends Activity {
public static final String URIS = "uris";
public static final String POSITION = "position";
//图片路径
private ArrayList<String> mUris;
private ViewPager mViewPager;
//缓存图片
private LruCache<String, Bitmap> mLruCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_vp);
mViewPager = (ViewPager) findViewById(R.id.viewpager);
mUris = getIntent().getStringArrayListExtra(URIS);
mLruCache = new LruCache<String, Bitmap>((int) Runtime.getRuntime().maxMemory() / 8) {
@SuppressLint("NewApi")
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
};
mViewPager.setOffscreenPageLimit(0);// 设置加载页数,为0的时候是3页,默认会加载7页
// viewPager.setPageMargin(marginPixels);设置页边间距
mViewPager.setAdapter(new VPActivityAdpter());
mViewPager.setCurrentItem(getIntent().getIntExtra(POSITION, 0));
}
@Override
protected void onDestroy() {
if (mLruCache != null) {
mLruCache.evictAll();
mLruCache = null;
}
super.onDestroy();
}
@Override
public void onBackPressed() {
super.onBackPressed();
}
private class VPActivityAdpter extends PagerAdapter {
private int width = 0;
private int height = 0;
VPActivityAdpter() {
width = getResources().getDisplayMetrics().widthPixels;
height = getResources().getDisplayMetrics().heightPixels;
}
@Override
public int getCount() {
return mUris.size();
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (position >= 0 && position <= mUris.size() - 1) {
recycleBitmap(position);
}
((ViewPager) container).removeView((View) object);
}
/*
* 回收除当前,上一张,下一张以外的图片
* */
private void recycleBitmap(int position) {
String uri = mUris.get(position);
Bitmap bitmap = mLruCache.get(uri);
if (bitmap != null) {
bitmap.recycle();
mLruCache.remove(uri);
bitmap = null;
Log.d("TAG", "destroyItem=" + position);
}
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Log.d("TAG", "instantiateItem=" + position);
RecycleImageView pictureView = new RecycleImageView(VPActivity.this);
pictureView.setLayoutParams(new ViewPager.LayoutParams());
container.addView(pictureView, 0);
new BitmapAsyncTask(VPActivity.this, pictureView, mUris.get(position), new int[]{width, height}).execute(mLruCache);
return pictureView;
}
@Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == arg1;
}
}
}
注:RecycleImageView是以前的MyImageView。
在测试过程中,除了小米手机之外,其它测试的手机三星、索尼、联想、酷派、中兴等等都没有出现OOM。因此还需要继续优化,那么可以只加载当前显示的图片在内存中,不要预先加载上一页和下一页的图片。刚好发现Android提供了一个ImageSwitcher类,可以用来显示图片。
2.ImageSwitcher
下面为使用ImageSwitcher显示图片,要监听手势滑动。用一个CacheBitmap来缓存当前显示的Bitmap,当显示下一个Bitmap时,就清空上一个Bitmap占用的内存,这样只保证内存只有一张图片。
package com.example.album;
import java.util.ArrayList;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.ImageSwitcher;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.ViewSwitcher.ViewFactory;
import com.example.album.utils.BitmapUtil;
import com.example.album.view.MyImageView;
public class ISActivity extends Activity implements ViewFactory,
OnTouchListener {
private static final int ID = 0x123;
private float startX = 0.0f;
private float endX = 0.0f;
private int index = 0;
private boolean isMove = false;
private Animation inAnimation;
private Animation outAnimation;
private Bitmap cacheBitmap;
private ArrayList<String> uris;
private ImageSwitcher switcher;
private ImageSwitcher.LayoutParams params;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_imageswithcer);
index = getIntent().getIntExtra(VPActivity.POSITION, 0);
uris = getIntent().getStringArrayListExtra(VPActivity.URIS);
switcher = (ImageSwitcher) findViewById(R.id.imageswither);
params = new ImageSwitcher.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
switcher.setFactory(this);
switcher.setOnTouchListener(this);
}
@Override
public void onBackPressed() {
super.onBackPressed();
}
@Override
public View makeView() {
MyImageView picture = new MyImageView(this);
picture.setId(ID);
cacheBitmap = BitmapUtil.compress(this, uris.get(index), getResources()
.getDisplayMetrics().widthPixels, getResources()
.getDisplayMetrics().heightPixels);
picture.setScaleType(ScaleType.CENTER);
picture.setLayoutParams(params);
picture.setImageBitmap(cacheBitmap);
return picture;
}
@SuppressLint({ "NewApi", "ClickableViewAccessibility" })
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
startX = event.getX();
} else if (event.getAction() == MotionEvent.ACTION_UP) {
if (isMove) {
endX = event.getX();
// 判断向左滑动
if (startX - endX > 50) {
index = (index + 1 < uris.size()) ? ++index : 0;
inAnimation = new TranslateAnimation(-100f, 0f, 0f, 0f);
outAnimation = new TranslateAnimation(0f, -100f, 0f, 0f);
// 判断向右滑动
} else if (endX - startX > 50) {
index = index - 1 > 0 ? --index : uris.size() - 1;
inAnimation = new TranslateAnimation(100f, 0f, 0f, 0f);
outAnimation = new TranslateAnimation(0f, 100f, 0f, 0f);
}
recycleCacheBitmap();
Bitmap bitmap = BitmapUtil.compress(this, uris.get(index),
getResources().getDisplayMetrics().widthPixels,
getResources().getDisplayMetrics().heightPixels);
ImageView picture = (ImageView) switcher.findViewById(ID);
picture.setImageBitmap(bitmap);
switcher.setInAnimation(inAnimation);
switcher.setOutAnimation(outAnimation);
inAnimation.startNow();
outAnimation.startNow();
cacheBitmap = bitmap;
}
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
isMove = true;
}
return true;
}
// 回收掉缓存的Bitmap
private void recycleCacheBitmap() {
if (cacheBitmap != null && !cacheBitmap.isRecycled()) {
cacheBitmap.recycle();
cacheBitmap = null;
}
}
}
通过ImageSwitcher,解决了在小米手机上显示图片时的OOM问题。虽然这样动画效果不是很好,但是不是主要的问题了,后面可以再仔细研究下切换图片时的过度动画效果。已经尝试用Animator去做过度动画,但还没有达到像Viewpager那样显示的效果,努力研究中。
到此,查看大图片的问题已经解决。源代码地址