fred-ye / summary

my blog
43 stars 9 forks source link

[Android]ViewPager中图片显示的优化 #43

Open fred-ye opened 9 years ago

fred-ye commented 9 years ago

ViewPager中图片显示的优化

首先直接看这么一段代码:

Activity

public class TestViewPagerActivity extends Activity {
    private ViewPager viewPager;
    private ImageView imageView;
    private int res[] = {R.drawable.image_1, R.drawable.image_2, R.drawable.image_3, R.drawable.image_4};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_view_pager);
        viewPager = (ViewPager) findViewById(R.id.viewpager);
        viewPager.setAdapter(adapter);
    }

    private PagerAdapter adapter = new PagerAdapter() {
        @Override
        public int getCount() {
            return 4;
        }

        @Override
        public boolean isViewFromObject(View view, Object o) {
            return view == o;
        }

       @Override
        public Object instantiateItem(ViewGroup container, int position) {
            LayoutInflater inflater = (LayoutInflater) container.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            View view = inflater.inflate(R.layout.view_pager_item, null);
            imageView = (ImageView) view.findViewById(R.id.image);
            imageView.setImageResource(res[position]);
            container.addView(view);
            return view;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView((View) object);
        }
    };
}

布局文件

activity_test_view_pager.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context="com.example.fred.testas.TestViewPagerActivity">
    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
    />
</RelativeLayout>

view_pager_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

在我们的项目中,有一个功能就是采用这种方式来做的。但是从收集到的Crash report中反现这地方出现过几次Crash,于是想着看能不能优化这一块。于是我便采有Android Studio中的Memory Monitor工具进行分析。当采用上面的代码跑在Device上时,App起动时Memory Monitor上显示 Allocated(20.92M), Free(4.15M), 几次滑动后,发现迅速的增长到Allocated(23.25M), Free(15.47M)。接下来再不断的滑动图片,内存的占用会不断的增长,但增长很缓慢。 试着进行如下优化:

1.从网上查询得知当我们在使用imageView.setBackgroundResource,imageView.setImageResource, 或者 BitmapFactory.decodeResource 这样的方法来设置图片,这些方法最终都是通过java层的createBitmap来完成,会消耗更多内存。 改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的 source,decodeStream最大的秘密在于其直接调用JNI>>nativeDecodeAsset()来完成decode,无需再使用java层的createBitmap,从而节省了java层的空间。 此时,我将instantiateItem方法的实现改为:

public Object instantiateItem(ViewGroup container, int position) {
    LayoutInflater inflater = (LayoutInflater) container.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View view = inflater.inflate(R.layout.view_pager_item, null);
    imageView = (ImageView) view.findViewById(R.id.image);
    InputStream is = getResources().openRawResource(res[position]);
    BitmapFactory.Options options = new  BitmapFactory.Options();
    //修改图片属性
    options.inPreferredConfig =  Bitmap.Config.RGB_565;
    Bitmap btp =  BitmapFactory.decodeStream(is, null,  options);
    imageView.setImageBitmap(btp);
    container.addView(view);
    return view;
}

发现,App启动时Memory Monitor显示 Allocated(16.06M), Free(9.01M)。不断滑动,发现内存也增长的比较快,大概每滑动两次,会增长0.15M左右。当Memory Monitor上显示 Allocated(25.12M), Free(0.13M)时,再滑动一次,发现GC触发了,内存回收。于是,显示Allocated(16.01M), Free(11.03M)。整体上看这种方式占用的内存是要少一些。看来这种方式是有效的。

针对内存不断上升的这种情况,看能不能从destroyItem方法上找找思路,于是想着在destroyItem方法中强制回收Bitmap,进行如下代码改动。

public void destroyItem(ViewGroup container, int position, Object object) {
    View view = (View)object;
    imageView = (ImageView) view.findViewById(R.id.image);
    BitmapDrawable bitmapDrawable = ((BitmapDrawable)imageView.getDrawable());
    if (bitmapDrawable != null && bitmapDrawable.getBitmap() != null) {
        bitmapDrawable.getBitmap().recycle();
    }
    container.removeView((View) object);
}

结果发现,效果并不明显,和没有改动之前的效果基本上是一样的。此处暂且放一下,等会再说。

2.另外一种思路是这样,首先就先需要用到的Bitmap全部加载进来,这样就会保证操作的只会是这么几个bitmap.


public class TestViewPagerActivity extends Activity {
    private ViewPager viewPager;
    private ImageView imageView;
    private int res[] = {R.drawable.image_1, R.drawable.image_2, R.drawable.image_3, R.drawable.image_4};
    private List<Bitmap> bitmapList = new ArrayList<Bitmap>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_view_pager);
        viewPager = (ViewPager) findViewById(R.id.viewpager);
        viewPager.setAdapter(adapter);
        for (int resId : res) {
            InputStream is = getResources().openRawResource(resId);
            BitmapFactory.Options options = new  BitmapFactory.Options();
            options.inPreferredConfig =  Bitmap.Config.RGB_565;
            Bitmap btp =  BitmapFactory.decodeStream(is, null,  options);
            bitmapList.add(btp);
        }

    }

    private PagerAdapter adapter = new PagerAdapter() {
        @Override
        public int getCount() {
            return 4;
        }

        @Override
        public boolean isViewFromObject(View view, Object o) {
            return view == o;
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            LayoutInflater inflater = (LayoutInflater) container.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            View view = inflater.inflate(R.layout.view_pager_item, null);
            imageView = (ImageView) view.findViewById(R.id.image);
            imageView.setImageBitmap(bitmapList.get(position));
            container.addView(view);
            return view;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView((View) object);
        }
    };
}

程序一启动时, Allocated(16.35M), Free(8.72M). 左右滑动,每滑动两次,内存增长0.01M。整体感觉第二种思路内存的消耗是要小一些。

关于图片性能处理的总结可以看这里