亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android中ViewPager你所不知道的優(yōu)化技巧分享

 更新時間:2024年04月22日 11:33:33   作者:圖靈1024  
提到ViewPager想必各位同學一點都不陌生,它是Android中最常用的組件之一,這篇文章小編就帶大家一起來看看ViewPager一些新的優(yōu)化方式吧

寫在前面

提到ViewPager想必各位同學一點都不陌生,它是Android中最常用的組件之一,一般配合Fragment一起使用。網(wǎng)上關于它的基本使用和常規(guī)優(yōu)化方式也有很多,在這里我就不一一贅述,而是直接進入這篇文章的主題--ViewPager一些新的優(yōu)化方式。

我獲得這項技能的背景

最近組里做新的Web容器的,一次承載多個H5頁面,以實現(xiàn)左右切換,默認展示主會場頁,并要達到提升打開率的目標。要達到這個目標,那勢必要從加載優(yōu)化入手,縮短頁面的打開時間。 優(yōu)化的點包括但不限于,Activity初始化、ViewPager和Fragment的初始化、WebView的初始化等等。我做的第一個優(yōu)化點便是ViewPager相關。

解決ViewPager默認加載多個Fragment的問題

ViewPager會默認給我們緩存多個Fragment,這樣設計的目的是為了提升左右滑動的流暢度,代價就是會降低首次打開的啟動時間。這讓一個以打開率為KPI的我來說是不能容忍的!首先想到的解決方案便是懶加載,當Fragment頁面可見時,才從網(wǎng)絡加載數(shù)據(jù)并顯示出來。這樣做還是不能解決其它Fragment被緩存,以導致占用啟動時間的問題,那怎么辦?既然ViewPager不給我們只加載一個Fragment的機會,那我們強行創(chuàng)造行不行。我首次只往Adapter塞一個Fragment,等加載完成后再調用notifyDataSetChanged方法更新其它頁面行不行!

解決重復刷新的問題

FragmentPagerAdapter不會銷毀已經(jīng)初始化完畢的Fragment

那為什么會有重復刷新的問題?且聽我慢慢道來

我們的主會場在ViewPager中的位置是由后端下發(fā)的。首次加載單個Fragment,主會場在ViewPager中的位置只能是0,后續(xù)更新時根據(jù)后端下發(fā)的position動態(tài)調整其所在的位置。

//調整主會場位置偽代碼
marketingInfoList.add(new MarketingInfo("www.juejin.com", "主會場"))
for (int i = 0; i <= 3; i++) {
    //將放在前兩個主會場前面
    if (i < 2) {
        marketingInfoList.add(i, new MarketingInfo("www.baidu.com", "模擬" + i));
    } else {
    //后兩個往主會場后面添加
        marketingInfoList.add(new MarketingInfo("www.baidu.com", "模擬" + i));
    }
}
mPagerAdapter.notifyDataSetChanged();
//重新設置選中主會場
mViewPager.setCurrentItem(2);

可在實際開發(fā)的過程中卻發(fā)現(xiàn),主會場重復加載了兩次,ViewPager生成了一個新的Fragment去承載主會場。我們的用戶元氣滿滿的點開我們的營銷頁,正準備下單呢,頁面突然又重新白屏了一下。留下一句****,憤然離去。作為一名要給公司帶來增長價值的開發(fā)這是不能接受的!那怎么辦呢?分析源碼!

ViewPager源碼解析

instantiateItem方法作用

ViewPager會通過這個方法將構造Fragment,F(xiàn)ragmentManager和Transaction都在這個方法里出現(xiàn)

public Object instantiateItem(ViewGroup container, int position) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    
    final long itemId = getItemId(position);

    //跟據(jù)itemId生成fragment名字,通過名字去查找fragment是否加載過
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    //fragment加載過則直接attach,否則的話新生成一個fragment
    if (fragment != null) {
        if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        mCurTransaction.attach(fragment);
    } else {
        fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
        mCurTransaction.add(container.getId(), fragment,
        makeFragmentName(container.getId(), itemId));
    }
    if (fragment != mCurrentPrimaryItem) {
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
    }

    return fragment;
 }

instantiateItem會通過getItemId獲取到itemId,再生成與fragment對應的唯一tag,通過tag查找fragment是否加載過。也就是說只要tag相同,無論你點擊的是哪個Tab都會加載到同一個fragment。我們再接著查看生成tag的方法makeFragmentName。

private static String makeFragmentName(int viewId, long id) {
    return "android:switcher:" + viewId + ":" + id;
}

原來Tag就是由instantiateItem傳入的viewId和itemId兩個值組成,那么我們再看看itemId的生成方式

public long getItemId(int position) {
    return position;
}

我驚了!更加的簡單!也就是說Fragment的唯一Tag是又position決定的。這下剛剛的問題有答案了吧。

重復刷新的真相與解決

ViewPager在初始化Fragment時,會根據(jù)Tag尋找Fragment,有則直接加載,無則重新生成。主會場首次加載的position是0,后續(xù)調整位置后變成了2,導致兩次的Tag不一至,所以就出現(xiàn)了重復加載的問題。知道了問題產(chǎn)生的原因,再來想解決辦法就好辦了。我們可以重寫getItem方法,重新定義itemId的生成方式。

 public long getItemId(int position) {
     //可以直接使用后端給頁面ID
     return pageId;
     //后端不給也沒事,我們自己生成一個
     return data.get(position).getTitle().hashCode();
 }

延伸: #getItemPosition方法

如果不重寫getItemId這方法,將頁面位置調整后再跳切回舊的位置,還會面臨就位置的頁面不刷新的問題。舉個栗子:

掘金的position是0,我將它的position改為2,第0個position這個時候設置為百度,會發(fā)現(xiàn)首個頁面依然是掘金。

網(wǎng)上給出的答案是重寫getItemPosition方法,雖然可以解決問題,但是沒有一個能講明白這個方法的作用,在這里我來補充一下

public int getItemPosition(Object object) {
        return POSITION_UNCHANGED;
}

getItemPosition默認返回POSITION_UNCHANGED,表示頁面無變化。還有另外一個默認值POSITION_NONE,表示頁面不存在。

???

頁面指的是哪個頁面?調用時機又是什么?還能再返回其它值嗎? 各位看官先別急且看我慢慢寫來,寫帖一段源碼:

void dataSetChanged() {
        // This method only gets called if our observer is attached, so mAdapter is non-null.

        final int adapterCount = mAdapter.getCount();
        mExpectedAdapterCount = adapterCount;
        boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1
                && mItems.size() < adapterCount;
        int newCurrItem = mCurItem;

        boolean isUpdating = false;
            //mItems為舊數(shù)據(jù)的容器
        for (int i = 0; i < mItems.size(); i++) {
            final ItemInfo ii = mItems.get(i);
          //返回刷新之前Tab項所處的位置
            final int newPos = mAdapter.getItemPosition(ii.object);
            //返回的位置等于POSITION_UNCHANGED(-1)表示當前頁未有變更,不做任何操作
            if (newPos == PagerAdapter.POSITION_UNCHANGED) {
                continue;
            }
            //如果返回的位置等于POSITION_NONE(-2)表示當前頁Tab項刷新后不存在,需要銷毀并重新加載新的頁面
            if (newPos == PagerAdapter.POSITION_NONE) {
                mItems.remove(i);
                i--;

                if (!isUpdating) {
                    mAdapter.startUpdate(this);
                    isUpdating = true;
                }

                mAdapter.destroyItem(this, ii.position, ii.object);
                needPopulate = true;

                if (mCurItem == ii.position) {
                    // Keep the current item in the valid range
                    newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
                    needPopulate = true;
                }
                continue;
            }
            //如果當前頁的新的位置和和舊位置不等,則說明調整了順序
            if (ii.position != newPos) {
            //這段代碼是將頁面定位到刷新之前的打開頁,據(jù)數(shù)據(jù)的position和mCurItem相等的話,則表示這個item是之前打開的,賦予它新位置的值
                if (ii.position == mCurItem) {
                    // Our current item changed position. Follow it.
                    newCurrItem = newPos;
                }

                ii.position = newPos;
                needPopulate = true;
            }
        }

        if (isUpdating) {
            mAdapter.finishUpdate(this);
        }

        Collections.sort(mItems, COMPARATOR);

        if (needPopulate) {
            // Reset our known page widths; populate will recompute them.
            final int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                final View child = getChildAt(i);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                if (!lp.isDecor) {
                    lp.widthFactor = 0.f;
                }
            }

            setCurrentItemInternal(newCurrItem, false, true);
            requestLayout();
        }
    }

notifyDataSetChanged方法之后會調用dataSetChanged方法,getItemPosition又是在dataSetChanged方法被調用的。

調用notifyDataSetChanged的后,會遍歷舊的頁面,通過getItemPosition方法返回的位置去決定當前遍歷到的頁面是否需要更新。POSITION_UNCHANGED:表示頁面無變化;POSITION_NONE:表示頁面不存在,需要銷毀,重新加載新的頁面。如果返回值返回的是頁面具體的位置,則更新當前頁在刷新數(shù)據(jù)后的位置,將Tab欄選中的對應的Tab項選中。

以上就是Android中ViewPager你所不知道的優(yōu)化技巧分享的詳細內容,更多關于Android ViewPager優(yōu)化的資料請關注腳本之家其它相關文章!

相關文章

最新評論