Android 內(nèi)存優(yōu)化-ViewPager的內(nèi)存優(yōu)化

????我們知道号醉,在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)存使用情況:
  1. 這個(gè)是最初的使用情況
  2. 當(dāng)滑動(dòng)了100個(gè)View頁面之后,這個(gè)過程內(nèi)存一直在增大排宰,但是這個(gè)不需要太關(guān)注的似芝,關(guān)注的是回收之后之后的內(nèi)存大小。
  3. 手動(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還在顯示,不能被釋放沪羔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末饥伊,一起剝皮案震驚了整個(gè)濱河市象浑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌琅豆,老刑警劉巖愉豺,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異茫因,居然都是意外死亡蚪拦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門冻押,熙熙樓的掌柜王于貴愁眉苦臉地迎上來驰贷,“玉大人,你說我怎么就攤上這事洛巢±ㄌ唬” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵稿茉,是天一觀的道長锹锰。 經(jīng)常有香客問我,道長狈邑,這世上最難降的妖魔是什么城须? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮米苹,結(jié)果婚禮上糕伐,老公的妹妹穿的比我還像新娘。我一直安慰自己蘸嘶,他們只是感情好良瞧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著训唱,像睡著了一般褥蚯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上况增,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天赞庶,我揣著相機(jī)與錄音,去河邊找鬼澳骤。 笑死歧强,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的为肮。 我是一名探鬼主播摊册,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼颊艳!你這毒婦竟也來了茅特?” 一聲冷哼從身側(cè)響起忘分,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎白修,沒想到半個(gè)月后妒峦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡熬荆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年舟山,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卤恳。...
    茶點(diǎn)故事閱讀 40,115評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖寒矿,靈堂內(nèi)的尸體忽然破棺而出突琳,到底是詐尸還是另有隱情,我是刑警寧澤符相,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布拆融,位于F島的核電站,受9級特大地震影響啊终,放射性物質(zhì)發(fā)生泄漏镜豹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一蓝牲、第九天 我趴在偏房一處隱蔽的房頂上張望趟脂。 院中可真熱鬧,春花似錦例衍、人聲如沸昔期。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽硼一。三九已至,卻和暖如春梦抢,著一層夾襖步出監(jiān)牢的瞬間般贼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工奥吩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留哼蛆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓圈驼,卻偏偏與公主長得像人芽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子绩脆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容