????我們知道号醉,在Android中ViewPager主要是用于多個(gè)View的切換,本文不會介紹ViewPager的基本使用辛块,而是介紹怎么對ViewPager的內(nèi)存優(yōu)化畔派。ViewPager是繼承于ViewGroup,所以它里面會有很多的子View润绵,這里的目的就是對那些子View的復(fù)用线椰,同時(shí)如果還有ImageView的話,還對Bitmap的回收進(jìn)行介紹授药。
ViewPager的基本原理
????由于筆者的水平有限士嚎,所以對ViewPager的基本介紹可能不是很正確,這里介紹ViewPager的基本原理目的是:有利于對PagerAdaper代碼的理解悔叽。
????筆者認(rèn)為,ViewPager是Google爸爸遺忘的控件爵嗅,因?yàn)榉彩巧婕暗紸dapter的控件(MVC模式)娇澎,內(nèi)部幾乎都實(shí)現(xiàn)了ViewHolder來對View復(fù)用。在MVC模式中的控件中睹晒,如果不對View進(jìn)行復(fù)用的話趟庄,意思就是說括细,每一個(gè)Item都要?jiǎng)?chuàng)建一個(gè)View對象,而每個(gè)View被ViewGroup(這里用ViewGroup來表示戚啥,通常例子是有ListView奋单、ViewPager)持有,所以導(dǎo)致View對象不能被GC正常的回收猫十,除非當(dāng)前的ViewPager被回收了览濒,或者當(dāng)前的Activity聲明周期結(jié)束了。
????所以在ViewPager中拖云,View的復(fù)用是非常的重要贷笛。
????下面將簡單的介紹一些ViewPager的基本原理。
1.PagerAdapter的介紹
????不出意外宙项,凡是需要加載多個(gè)View的控件幾乎都是MVC模式乏苦,也就是說,有自己的Adapter尤筐。其實(shí)這類的控件汇荐,我們見到的不少,比如盆繁,RecyclerView掀淘,ListView、GridView等等改基。
????ViewPager也是屬于這一類的繁疤,其實(shí)如果我們想要在ViewPager中加載多個(gè)View(這里不涉及到加載Fragment),要用到的Adapter就是PagerAdapter秕狰。
????PagerAdapter是一個(gè)抽象類稠腊,這里將對PagerAdapter內(nèi)部的幾個(gè)方法進(jìn)行簡單的介紹。
(1).getCount方法
????這個(gè)方法通常來獲取ViewPager需要加載的數(shù)據(jù)個(gè)數(shù)鸣哀,這個(gè)方法非常的常見架忌,在RecyclerView和ListView的Adapter幾乎都有這個(gè)方法。
(2).isViewFromObject(View view, Object object)方法
????這個(gè)方法是來判斷當(dāng)前的View跟傳入進(jìn)來的Object是否有關(guān)系我衬。這里我們通常的寫的是return view == object叹放。其實(shí)這種寫法是有問題的。首先簡單的介紹一下挠羔,在PagerAdapter每一個(gè)View是以key-value的鍵值對來存儲的井仰,這里的object是View的key,之所以我們在這里寫view == object沒有錯(cuò)誤破加,是因?yàn)槲覀冊趇nstantiateItem方法里面返回的是當(dāng)前的View俱恶,而正確的理解不是返回的是當(dāng)前View,而是當(dāng)前View的key。由于當(dāng)前的View的key和value都是同一個(gè)對象合是,所以這里這樣寫是沒有錯(cuò)的了罪。但是實(shí)際上不是這樣理解,這里需要注意聪全!
(3).instantiateItem(ViewGroup container, int position)方法和 destroyItem(ViewGroup container, int position, Object object)方法
?????這里將兩個(gè)方法合并起來講泊藕。
?????首先,instantiateItem方法是用來初始化當(dāng)前的View难礼,返回值為Object類型娃圆,實(shí)際上不是當(dāng)前的View,而是當(dāng)前View的key值鹤竭,這一點(diǎn)需要注意踊餐!之前的用法沒有錯(cuò),只是在理解上有一定的問題臀稚。
?????其次吝岭,destroyItem表示的意思就是說,需要銷毀當(dāng)前傳入進(jìn)來的View吧寺,這個(gè)沒有什么好講解的窜管。
?????在這里,在講解一下兩個(gè)方法的調(diào)用時(shí)機(jī):
1. 首次進(jìn)入ViewPager稚机,會調(diào)用instantiateItem方法初始化第一個(gè)(position = 0)和初始化第二個(gè)幕帆,當(dāng)然如果有第二個(gè)的話。
2. 當(dāng)滑動(dòng)的時(shí)候赖条,會調(diào)用instantiateItem方法初始化當(dāng)前View的下一個(gè)View失乾,當(dāng)然是有下一個(gè)View。
3. 滑動(dòng)的時(shí)候纬乍,同時(shí)調(diào)用destroyItem方法來銷毀帶當(dāng)前View的前面的第二個(gè)View碱茁,就是說當(dāng)前的position為2的話,那么會銷毀position為0的View仿贬。
?????如圖所示:
ViewPager使用ViewHolder實(shí)現(xiàn)View復(fù)用
?????在上面的介紹中纽竣,我們發(fā)現(xiàn),在ViewPager的內(nèi)部沒有實(shí)現(xiàn)View復(fù)用的功能茧泪,這個(gè)ListView差不多蜓氨,所以,ViewPager的內(nèi)存優(yōu)化第一步就是View的復(fù)用队伟。
?????在我們通過手指滑動(dòng)ViewPager,ViewPager不斷的切換View頁面的過程穴吹,instantiateItem方法和destroyItem方法都會不斷調(diào)用,不斷的創(chuàng)建View對象嗜侮,不斷的銷毀對象刀荒。
?????其實(shí)我們可以來思考代嗤,在destroyItem方法中棘钞,我們會通過container.removeViewf方法來移除View,這個(gè)便達(dá)到普通意義上的銷毀View對象缠借,實(shí)際上這個(gè)View只是失去了使用的意義,等等GC來回收它宜猜。但是我們可以通過將它利用起來泼返,將這個(gè)View對象放入我們的數(shù)據(jù)結(jié)構(gòu)中(這里使用的是鏈表),當(dāng)在調(diào)用instantiateItem方法初始化新的View對象時(shí)姨拥,我們先判斷我們的數(shù)據(jù)結(jié)構(gòu)中是否有回收過來的View绅喉,如果有的話,那么重用叫乌,而不創(chuàng)建柴罐,如果沒有的話,才進(jìn)行創(chuàng)建新的View對象憨奸。
?????我們來看看代碼革屠,最基本的View復(fù)用:
public class MyPagerAdapter2 extends PagerAdapter {
private int count = 0;
private Activity mActivity = null;
private LinkedList<View> mViews = null;
public MyPagerAdapter2(int count, Activity activity) {
this.count = count;
this.mActivity = activity;
mViews = new LinkedList<>();
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Log.i("pby123", "init position = " + position);
View convertView = null;
ViewHolder holder = null;
if (mViews.size() == 0) {
holder = new ViewHolder();
convertView = mActivity.getLayoutInflater().inflate(R.layout.activity_item2, null);
holder.button = convertView.findViewById(R.id.id_button);
convertView.setTag(holder);
} else {
Log.i("pby123", "init position = " + position + "重用了Button");
convertView = mViews.removeFirst();
holder = (ViewHolder) convertView.getTag();
}
holder.button.setText("position = " + position);
container.addView(convertView);
return convertView;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
View convertView = (View) object;
container.removeView(convertView);
mViews.add(convertView);
Log.i("pby123", "destroy position = " + position);
}
@Override
public int getCount() {
return count;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
public class ViewHolder {
public Button button = null;
}
}
?????看看日志輸出:
?????看看內(nèi)存使用情況:
- 這個(gè)是最初的使用情況
- 當(dāng)滑動(dòng)了100個(gè)View頁面之后,這個(gè)過程內(nèi)存一直在增大排宰,但是這個(gè)不需要太關(guān)注的似芝,關(guān)注的是回收之后之后的內(nèi)存大小。
- 手動(dòng)GC之后的內(nèi)存大小
?????這個(gè)過程中可能內(nèi)存有所偏差板甘,但是沒有多大的關(guān)系党瓮,我們可以看到的是,我們經(jīng)過View的復(fù)用的盐类,即使是100個(gè)View頁面內(nèi)存的消耗也不是很大寞奸,這個(gè)就是我們想要的效果!
ViewPager中的Bitmap的釋放
?????要想說的是內(nèi)存優(yōu)化在跳,Bitmap是必說的話題枪萄,因?yàn)锽itmap在內(nèi)存消耗是及其的高, 并且GC又不能自動(dòng)給我們釋放硬毕,需要我們手動(dòng)的調(diào)用Bitmap的recycle方法進(jìn)行回收Bitmap
代碼:
public class MyAdapter extends PagerAdapter {
private List<Integer> mImages = null;
private LayoutInflater mInflater = null;
private Activity mActivity = null;
private LinkedList<View> mCacheViews = null;
public MyAdapter(Activity activity, List<Integer> images) {
this.mImages = images;
this.mInflater = LayoutInflater.from(activity);
this.mActivity = activity;
mCacheViews = new LinkedList<>();
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
View convertView = null;
ViewHolder viewHolder = null;
if (mCacheViews.size() == 0) {
viewHolder = new ViewHolder();
convertView = mInflater.inflate(R.layout.viewparger_item, null);
viewHolder.mImageView = convertView.findViewById(R.id.id_imageView);
convertView.setTag(viewHolder);
} else {
convertView = mCacheViews.removeFirst();
viewHolder = (ViewHolder) convertView.getTag();
}
//Glide加載圖片
//這里必須調(diào)用skipMemoryCache方法來設(shè)置設(shè)置跳過內(nèi)存緩存
//如果不設(shè)置的話呻引,Glide會拋出異常
Glide.with(mActivity).load(mImages.get(position)).asBitmap().skipMemoryCache(true).into(viewHolder.mImageView);
container.addView(convertView);
return convertView;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
View view = (View) object;
ImageView imageView = ((ViewHolder) view.getTag()).mImageView;
Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
//對Bitmap進(jìn)行回收
bitmap.recycle();
imageView.setImageBitmap(null);
container.removeView(view);
//將之前的View保存我們的數(shù)據(jù)結(jié)構(gòu)中,以便后面的View復(fù)用
mCacheViews.add(view);
}
@Override
public int getCount() {
return mImages.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
public final class ViewHolder {
public ImageView mImageView = null;
}
}
我們再看看Activity的代碼:
public class MainActivity extends AppCompatActivity {
private ViewPager mViewPager = null;
private PagerAdapter mAdapter = null;
private List<Integer> list = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
list = new ArrayList<>();
addData();
init();
}
private void init() {
mViewPager = (ViewPager) findViewById(R.id.id_viewPager);
mAdapter = new MyPagerAdapter(this, list);
mViewPager.setAdapter(mAdapter);
}
private void addData() {
list.add(R.mipmap.welcome1);
list.add(R.mipmap.welcome2);
list.add(R.mipmap.welcome3);
list.add(R.mipmap.welcome4);
list.add(R.mipmap.welcome5);
list.add(R.mipmap.welcome6);
list.add(R.mipmap.welcome7);
list.add(R.mipmap.welcome8);
list.add(R.mipmap.welcome9);
list.add(R.mipmap.welcome11);
list.add(R.mipmap.welcome12);
list.add(R.mipmap.welcome13);
list.add(R.mipmap.welcome14);
list.add(R.mipmap.welcome15);
list.add(R.mipmap.welcome16);
list.add(R.mipmap.welcome17);
list.add(R.mipmap.welcome18);
list.add(R.mipmap.welcome19);
list.add(R.mipmap.welcome20);
list.add(R.mipmap.welcome21);
list.add(R.mipmap.welcome22);
list.add(R.mipmap.welcome23);
list.add(R.mipmap.welcome24);
list.add(R.mipmap.welcome25);
list.add(R.mipmap.welcome26);
list.add(R.mipmap.welcome27);
list.add(R.mipmap.welcome28);
list.add(R.mipmap.welcome29);
list.add(R.mipmap.welcome30);
list.add(R.mipmap.welcome31);
list.add(R.mipmap.welcome32);
list.add(R.mipmap.welcome33);
list.add(R.mipmap.welcome34);
list.add(R.mipmap.welcome35);
list.add(R.mipmap.welcome36);
list.add(R.mipmap.welcome37);
list.add(R.mipmap.welcome38);
list.add(R.mipmap.welcome39);
list.add(R.mipmap.welcome40);
list.add(R.mipmap.welcome41);
list.add(R.mipmap.welcome42);
list.add(R.mipmap.welcome43);
list.add(R.mipmap.welcome44);
list.add(R.mipmap.welcome45);
list.add(R.mipmap.welcome46);
list.add(R.mipmap.welcome47);
list.add(R.mipmap.welcome48);
list.add(R.mipmap.welcome49);
list.add(R.mipmap.welcome50);
}
}
我們從Activity中看到的是吐咳,我們加載了50張圖片在ViewPager中去逻悠,夠多了吧!究竟內(nèi)存使用情況怎么樣韭脊,我們看看:
1. 這個(gè)是應(yīng)用才啟動(dòng)的內(nèi)存使用情況:
2. 滑動(dòng)了50張圖片之后的內(nèi)存使用情況:
3. GC之后內(nèi)存使用情況:
?????之所以內(nèi)存沒有釋放完畢童谒,那是因?yàn)楫?dāng)前View的Bitmap還在顯示,不能被釋放沪羔。