SoFunction
Updated on 2025-04-10

Share Optimization Tips for ViewPager in Android that you don't know

Write in front

When it comes to ViewPager, I believe everyone is familiar with it. It is one of the most commonly used components in Android and is generally used with Fragment. There are also many basic usage and regular optimization methods on the Internet. I will not elaborate on it one by one here, but will directly enter the topic of this article-Some new ways to optimize ViewPager

My background on how I got this skill

Recently, a new web container is built in the group, which hosts multiple H5 pages at a time to achieve left and right switching, displays the main venue page by default, and achieves the goal of increasing the opening rate. To achieve this goal, it is necessary to start with load optimization and shorten the page opening time. Optimized points include but are not limited to Activity initialization, ViewPager and Fragment initialization, WebView initialization, etc. The first optimization point I did was ViewPager related.

Solve the problem of ViewPager loading multiple fragments by default

ViewPager will cache multiple Fragments for us by default. The purpose of this design is to improve the smoothness of left and right sliding, and the cost is to reduce the startup time of the first opening. This makes it intolerable for me to have a KPI with an open rate! The first solution that comes to mind isLazy loading, When the Fragment page is visible, the data is loaded from the network and displayed. Doing this still cannot solve the problem of other fragments being cached to occupy startup time. What should I do? Since ViewPager does not give us the opportunity to load only one Fragment, can we forcefully create it? I only stuffed a Fragment into Adapter for the first time, and then called the notifyDataSetChanged method to update other pages after the loading is complete.

Solve the problem of repeated refreshes

FragmentPagerAdapter will not destroy the initialized Fragment

Then why are there repeated refresh problems? Let me tell you slowly

The location of our main venue in the ViewPager is sent from the backend. When a single Fragment is loaded for the first time, the position of the main venue in the ViewPager can only be 0. During subsequent updates, it will be dynamically adjusted according to the position issued by the backend.

//Adjust the position of the main venue pseudo code(new MarketingInfo("", "Main venue"))
for (int i = 0; i <= 3; i++) {
    //It will be placed in front of the first two main venues    if (i < 2) {
        (i, new MarketingInfo("", "simulation" + i));
    } else {
    //The last two are added to the main venue        (new MarketingInfo("", "simulation" + i));
    }
}
();
//Reset the main venue selected(2);

However, during the actual development process, it was found that the main venue was loaded twice, and the ViewPager generated a new Fragment to host the main venue. Our users were full of energy and clicked on our marketing page and were about to place an order when the page suddenly turned blank again. Leave a sentence **** and leave angrily. As a developer who wants to bring growth value to the company, this is unacceptable! So what should I do? Analyze the source code!

ViewPager source code analysis

Function of instantiateItem method

ViewPager will construct Fragment through this method, and FragmentManager and Transaction appear in this method.

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

    //Create the fragment name according to itemId, and use the name to find out whether the fragment has been loaded    String name = makeFragmentName((), itemId);
    Fragment fragment = (name);
    // If the fragment is loaded, it will be attached directly. Otherwise, a new fragment will be generated    if (fragment != null) {
        if (DEBUG) (TAG, "Attaching item #" + itemId + ": f=" + fragment);
        (fragment);
    } else {
        fragment = getItem(position);
        if (DEBUG) (TAG, "Adding item #" + itemId + ": f=" + fragment);
        ((), fragment,
        makeFragmentName((), itemId));
    }
    if (fragment != mCurrentPrimaryItem) {
        (false);
        (false);
    }

    return fragment;
 }

instantiateItem will get itemId through getItemId, generate a unique tag corresponding to the fragment, and use the tag to find out whether the fragment has been loaded. That is to say, as long as the tag is the same, no matter which tab you click, it will be loaded into the same fragment. Let's check the method that generates the tag, makeFragmentName.

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

It turns out that the tag is composed of two values ​​of viewId and itemId passed in instantiateItem. So let's take a look at the generation method of itemId.

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

I was shocked! It's even simpler! In other words, the only tag of Fragment is determined by position. Now the question just now has an answer.

The truth and solution of repeated refresh

When the ViewPager initializes the Fragment, it will look for the Fragment based on the Tag. If there is, it will be loaded directly, and if there is no, it will be regenerated. The position loaded by the main venue for the first time is 0, and after subsequent adjustment of the position, it becomes 2, resulting in different tags from the two times, so there is a problem of repeated loading. Once you know the cause of the problem, it will be easier to solve it. We can rewrite the getItem method and redefine the generation method of itemId.

 public long getItemId(int position) {
     //You can use the backend to directly give the page ID     return pageId;
     //It's okay if the backend doesn't give it, we will generate one by ourselves     return (position).getTitle().hashCode();
 }

Extended: #getItemPosition method

If you do not rewrite the getItemId method, adjust the page position and then jump back to the old position, you will also face the problem of not refreshing the page in the position. Take a chestnut:

The position of Nuggets is 0. I change its position to 2. The 0th position is set to Baidu at this time and you will find that the first page is still Nuggets.

The answer given online is to rewrite the getItemPosition method. Although it can solve the problem, none of them can explain the function of this method. I will add it here.

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

getItemPosition returns POSITION_UNCHANGED by default, indicating that there is no change in the page. There is another default value POSITION_NONE, which means that the page does not exist.

???

Which page does the page refer to? What is the call time? Can you return other values? Dear readers, don’t worry, let me write it slowly, and write a piece of source code:

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

        final int adapterCount = ();
        mExpectedAdapterCount = adapterCount;
        boolean needPopulate = () < mOffscreenPageLimit * 2 + 1
                && () < adapterCount;
        int newCurrItem = mCurItem;

        boolean isUpdating = false;
            //mItems is a container for old data        for (int i = 0; i < (); i++) {
            final ItemInfo ii = (i);
          //Return to the location of the Tab item before refresh            final int newPos = ();
            //The returned position is equal to POSITION_UNCHANGED (-1) means that the current page has not changed and no operation is done            if (newPos == PagerAdapter.POSITION_UNCHANGED) {
                continue;
            }
            //If the returned position is equal to POSITION_NONE (-2), it means that the current page Tab item does not exist after refreshing, and the new page needs to be destroyed and reloaded.            if (newPos == PagerAdapter.POSITION_NONE) {
                (i);
                i--;

                if (!isUpdating) {
                    (this);
                    isUpdating = true;
                }

                (this, , );
                needPopulate = true;

                if (mCurItem == ) {
                    // Keep the current item in the valid range
                    newCurrItem = (0, (mCurItem, adapterCount - 1));
                    needPopulate = true;
                }
                continue;
            }
            //If the new and old positions of the current page are different, it means that the order has been adjusted            if ( != newPos) {
            //This code is to locate the page to the open page before refresh. If the position and mCurItem of the data are equal, it means that the item was opened before and gives it the value of the new position.                if ( == mCurItem) {
                    // Our current item changed position. Follow it.
                    newCurrItem = newPos;
                }

                 = newPos;
                needPopulate = true;
            }
        }

        if (isUpdating) {
            (this);
        }

        (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) ();
                if (!) {
                     = ;
                }
            }

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

The dataSetChanged method will be called after notifyDataSetChanged method, and getItemPosition is called in the dataSetChanged method.

After calling notifyDataSetChanged, the old page will be traversed and the position returned by the getItemPosition method will determine whether the currently traversed page needs to be updated. POSITION_UNCHANGED: means that the page has not changed; POSITION_NONE: means that the page does not exist and needs to be destroyed and reloaded a new page. If the return value returns the specific position of the page, update the position of the current page after refreshing the data and select the corresponding Tab item selected in the Tab column.

The above is the detailed content of the optimization techniques you don’t know about ViewPager in Android. For more information about Android ViewPager optimization, please follow my other related articles!