Android组件之Fragment(二)---实战知识

一、Fragment、Activity通信

1.Activity->Fragment

传递数据:
基本的数据通信方式,我们这里不再赘述,例如:BrocastReceiver、Handler、ContentProvider、File、Socket、IPC。
Activity->Fragment传递数据,可以使用setArguments函数,由于activity内部可以拿到fragment实例,所以直接调用fragment的方法setArguments,是最直接、最安全的。

 public static BlankFragment1 newInstance(String param1, String param2) {
        BlankFragment1 fragment = new BlankFragment1();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    }

也许会有人问,最直接我能理解,为什么是最安全的,有兴趣的可以看小编另外一篇文章。
【Android面试】为什么要用newInstance来实例化Fragment

函数调用:
Activity->Fragment函数调用,这个相对来说简单,因为activity内部可以拿到fragment实例,所以直接调用fragment的相应函数方法即可。

2.Fragment->Activity

传递数据:
基本的数据通信方式,我们这里不再赘述,例如:BrocastReceiver、Handler、ContentProvider、File、Socket、IPC。
Fragment->Activity传递数据,可以使用接口函数,即activity实现相应接口,fragment通过setListener(xxx)方法,持有接口引用,从而可以在fragment内部调用相应的activity接口方法,将fragment相应数据通过接口参数的形式,传递给activity。

public class FragmentHideAndShowTestActivity extends FragmentActivity implements View.OnClickListener, BlankFragment1.ActivityListener {

    private static final String TAG = FragmentHideAndShowTestActivity.class.getSimpleName();
    BlankFragment1 fragment1;
    BlankFragment2 fragment2;
    FragmentManager fragmentManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.fragment__dynamic_test);
        Log.e(TAG, TAG + " onCreate");
        fragmentManager = getSupportFragmentManager();
        findViewById(R.id.button1).setOnClickListener(this);
        findViewById(R.id.button2).setOnClickListener(this);
        fragment1 = BlankFragment1.newInstance("name1", "age1");
        fragment1.setActivityListener(this);
        fragment2 = BlankFragment2.newInstance("name2", "age2");
    }
}

public class BlankFragment1 extends Fragment {
    ActivityListener activityListener;

    public void setActivityListener(ActivityListener listener) {
        activityListener = listener;
    }

    public interface ActivityListener {
        void showMessage(String msg);
    }
}

函数调用:
Fragment->Activity函数调用,和上面的方法一样,不建议很多博客里面说的,自己fragment里面直接通过getActivity拿到activity,然后调用相应find方法,这样是可以,但是存在风险,而且也不符合设计基本原则。

3.Fragment->Fragment

既然Fragment和Activity之间的通信问题解决了,那Fragment与Fragment之间的通信也没有那么复杂。例如可以直接在fragment内部通过activity获取到另外一个fragment的引用,从而调用其方法,但是依然那句话,这样严重违反了设计基本原则。Fragment->Fragment传递数据,最好还是通过基本的传递手段BrocastReceiver、Handler、ContentProvider、File等,如果这些满足不了您的需求,那就通过接口,或者modem共享的方式,千万不要到处获取实例引用,然后直接调用方法。

二、Fragment与ViewPager

1.ViewPager是什么?

一个页面切换的组件,我们可以往里面填充多个View,然后我们可以通过触摸屏幕左右滑动 切换不同的View,和前面学习的ListView一样,我们需要一个Adapter(适配器),将要显示的View和我们的ViewPager进行绑定,而ViewPager有他自己特定的Adapter——PagerAdapter。
大家感觉是不是之前,适配器和观察者设计模式文章中讲到的,ListView和ListAdapter关系差不多呢?ListView里面是若干个item,ListAdapter负责数据与ListView的绑定。
ViewPager经常和Fragment一起使用,并且提供了专门的FragmentPagerAdapter和FragmentStatePagerAdapter类供Fragment中的ViewPager使用。
初学者有时会有这样的疑问,PagerAdapter、FragmentStatePagerAdapter、FragmentPagerAdapter有什么区别?这里我们通过源码解答一下。

  • PagerAdapter:ViewPager的适配器基类,可以看到源码中,此类是抽象类,和ListView的适配器BaseAdapter一样,需要由具体的实现类去实现其关键方法。
public abstract class PagerAdapter {
    private DataSetObservable mObservable = new DataSetObservable();

    public static final int POSITION_UNCHANGED = -1;
    public static final int POSITION_NONE = -2;
    //view的数量
    public abstract int getCount();

    // 用于创建当前位置的View,并返回与之关联的Object,和listview的getview几乎一样
    public Object instantiateItem(View container, int position) {
        throw new UnsupportedOperationException(
                "Required method instantiateItem was not overridden");
    }
    //用于销毁当前View
    public void destroyItem(View container, int position, Object object) {
        throw new UnsupportedOperationException("Required method destroyItem was not overridden");
    }

    //用于表示View 和 Object是否对应
    public abstract boolean isViewFromObject(View view, Object object);

    //通知
    public void notifyDataSetChanged() {
        mObservable.notifyChanged();
    }
    //注册
    public void registerDataSetObserver(DataSetObserver observer) {
        mObservable.registerObserver(observer);
    }
    //注销
    public void unregisterDataSetObserver(DataSetObserver observer) {
        mObservable.unregisterObserver(observer);
    }
  • FragmentPagerAdapter:即使fragment不可见了,实例还是存在于内存中,当 fragment比较多的时候,会占用较大的内存.
    这里我们看一下源码中,确认一下
    FragmentPagerAdapter#instantiateItem & destroyItem
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        final long itemId = getItemId(position);

        // 可以看到这里,通过创建新的fragment之后,添加到了事务中,并未有fragment的销毁操作
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        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);
            if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
            } else {
                fragment.setUserVisibleHint(false);
            }
        }

        return fragment;
    }

    // TODO(b/141958824): Suppressed during upgrade to AGP 3.6.
    @SuppressWarnings("ReferenceEquality")
    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        Fragment fragment = (Fragment) object;

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
                + " v=" + fragment.getView());
        // 可以看到这里,只是进行了解绑操作,并未有fragment的销毁操作
        mCurTransaction.detach(fragment);
        if (fragment.equals(mCurrentPrimaryItem)) {
            mCurrentPrimaryItem = null;
        }
    }
  • FragmentStatePagerAdapter:当fragment不可见时,可能会将fragment的实例会销毁,所以内存占会小一些

FragmentStatePagerAdapter#instantiateItem & destroyItem

 @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        // If we already have this item instantiated, there is nothing
        // to do.  This can happen when we are restoring the entire pager
        // from its saved state, where the fragment manager has already
        // taken care of restoring the fragments we previously had instantiated.
        if (mFragments.size() > position) {
            Fragment f = mFragments.get(position);
            if (f != null) {
                return f;
            }
        }

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        Fragment fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
        if (mSavedState.size() > position) {
            Fragment.SavedState fss = mSavedState.get(position);
            if (fss != null) {
                fragment.setInitialSavedState(fss);
            }
        }
        while (mFragments.size() <= position) {
            mFragments.add(null);
        }
        fragment.setMenuVisibility(false);
        if (mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) {
            fragment.setUserVisibleHint(false);
        }
        //可以看到这里,直接进行了缓存的替换操作
        mFragments.set(position, fragment);
        mCurTransaction.add(container.getId(), fragment);

        if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
            mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
        }

        return fragment;
    }

    // TODO(b/141958824): Suppressed during upgrade to AGP 3.6.
    @SuppressWarnings("ReferenceEquality")
    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        Fragment fragment = (Fragment) object;

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
                + " v=" + ((Fragment)object).getView());
        while (mSavedState.size() <= position) {
            mSavedState.add(null);
        }
        mSavedState.set(position, fragment.isAdded()
                ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
        //可以看到这里,直接进行了缓存的置空操作
        mFragments.set(position, null);

        mCurTransaction.remove(fragment);
        if (fragment.equals(mCurrentPrimaryItem)) {
            mCurrentPrimaryItem = null;
        }
    }

2.ViewPager 的缓存机制

话不多说,我们自己上源码,看一下答案。
ViewPager #setOffscreenPageLimit

 //默认缓存一个
 private static final int DEFAULT_OFFSCREEN_PAGES = 1;
 public void setOffscreenPageLimit(int limit) {
        //即使我们传入的是负数,由于小于1,所以此处依然会赋值为1
        if (limit < DEFAULT_OFFSCREEN_PAGES) {
            Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
                    + DEFAULT_OFFSCREEN_PAGES);
            limit = DEFAULT_OFFSCREEN_PAGES;
        }
        if (limit != mOffscreenPageLimit) {
            mOffscreenPageLimit = limit;
            //开始缓存页加载逻辑
            populate();
        }
    }

好了,看了上面的代码,大家是否已有答案。不错,使用setOffscreenPagerLimit方法可以设置缓存页面的个数,默认为1,就算你传入负数他还是默认缓存旁边页面,也就是1。也就是说,ViewPager 最少也会进行两个页面的加载。
代码实践一下:
FragmenViewPagerTestActivity.java

package com.itbird.fragment;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.RadioButton;
import android.widget.RadioGroup;

import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.viewpager.widget.ViewPager;

import com.itbird.R;

import java.util.List;

/**
 *
 */
public class FragmenViewPagerTestActivity extends FragmentActivity implements View.OnClickListener, ViewPager.OnPageChangeListener, RadioGroup.OnCheckedChangeListener {

    private static final String TAG = FragmenViewPagerTestActivity.class.getSimpleName();
    ViewPager viewPager;
    FragmentManager fragmentManager;
    MyFragmentPagerAdapter myFragmentPagerAdapter;
    RadioGroup radioGroup;
    RadioButton radioButton1;
    RadioButton radioButton2;
    RadioButton radioButton3;
    RadioButton radioButton4;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.fragment__viewpager_test);
        Log.e(TAG, TAG + " onCreate");
        fragmentManager = getSupportFragmentManager();
        viewPager = findViewById(R.id.viewpager);
        radioGroup = findViewById(R.id.line1);
        radioGroup.setOnCheckedChangeListener(this);
        myFragmentPagerAdapter = new MyFragmentPagerAdapter(fragmentManager);
        viewPager.setAdapter(myFragmentPagerAdapter);
        viewPager.setCurrentItem(0);
        viewPager.addOnPageChangeListener(this);
        radioButton1 = findViewById(R.id.button1);
        radioButton1.setOnClickListener(this);
        radioButton2 = findViewById(R.id.button2);
        radioButton2.setOnClickListener(this);
        radioButton3 = findViewById(R.id.button3);
        radioButton3.setOnClickListener(this);
        radioButton4 = findViewById(R.id.button4);
        radioButton4.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button1:
                Log.d(TAG, "fragment1");
                viewPager.setCurrentItem(0);
                break;
            case R.id.button2:
                Log.d(TAG, "fragment2");
                viewPager.setCurrentItem(1);
                break;
            case R.id.button3:
                Log.d(TAG, "fragment3");
                viewPager.setCurrentItem(2);
                break;
            case R.id.button4:
                Log.d(TAG, "fragment4");
                viewPager.setCurrentItem(3);
                break;
        }
    }


    @Override
    public void onStart() {
        Log.d(TAG, "onStart");
        super.onStart();
    }

    @Override
    public void onStop() {
        Log.d(TAG, "onStop");
        super.onStop();
    }

    @Override
    public void onResume() {
        Log.d(TAG, "onResume");
        super.onResume();
    }

    @Override
    protected void onPause() {
        Log.d(TAG, "onPause");
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        Log.e(TAG, TAG + " onDestroy");
        super.onDestroy();
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    @Override
    public void onPageSelected(int position) {
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        //state的状态有三个,0表示什么都没做,1正在滑动,2滑动完毕
        if (state == 2) {
            switch (viewPager.getCurrentItem()) {
                case 0:
                    radioButton1.setChecked(true);
                    break;
                case 1:
                    radioButton2.setChecked(true);
                    break;
                case 2:
                    radioButton3.setChecked(true);
                    break;
                case 3:
                    radioButton4.setChecked(true);
                    break;
            }
        }
    }

    @Override
    public void onCheckedChanged(RadioGroup group, int checkedId) {
        switch (checkedId) {
            case R.id.button1:
                viewPager.setCurrentItem(0);
                break;
            case R.id.button2:
                viewPager.setCurrentItem(1);
                break;
            case R.id.button3:
                viewPager.setCurrentItem(2);
                break;
            case R.id.button4:
                viewPager.setCurrentItem(3);
                break;
        }
    }
}

MyFragmentPagerAdapter.java

package com.itbird.fragment;

import android.view.View;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager2.adapter.FragmentStateAdapter;

/**
 * Created by itbird on 2022/3/18
 */
public class MyFragmentPagerAdapter extends FragmentPagerAdapter {

    private final int PAGER_COUNT = 4;
    Fragment fragment1;
    Fragment fragment2;
    Fragment fragment3;
    Fragment fragment4;

    public MyFragmentPagerAdapter(@NonNull FragmentManager fm) {
        super(fm);
        fragment1 = BlankFragment1.newInstance("name1", "age1");
        fragment2 = BlankFragment2.newInstance("name2", "age2");
        fragment3 = BlankFragment1.newInstance("name3", "age3");
        fragment4 = BlankFragment2.newInstance("name4", "age4");
    }

    @Override
    public int getCount() {
        return PAGER_COUNT;
    }

    @NonNull
    @Override
    public Fragment getItem(int position) {
        Fragment fragment = null;
        switch (position) {
            case 0:
                fragment = fragment1;
                break;
            case 1:
                fragment = fragment2;
                break;
            case 2:
                fragment = fragment3;
                break;
            case 3:
                fragment = fragment4;
                break;
        }
        return fragment;
    }

    @Override
    public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
        return super.isViewFromObject(view, object);
    }
}

代码很简单,就是显示四个fragment而已,我们看一下运行效果(整个工程的代码,在文章末尾)。



我们看一下运行日志,是否是如上面代码分析,加上预加载总共默认加载了两个页面。

2022-03-22 20:22:30.380 7651-7651/? I/zygote: Late-enabling -Xcheck:jni
2022-03-22 20:22:30.438 7651-7651/? W/zygote: Unexpected CPU variant for X86 using defaults: x86
2022-03-22 20:22:30.894 7651-7651/com.itbird E/FragmenViewPagerTestActivity: FragmenViewPagerTestActivity onCreate
2022-03-22 20:22:30.898 7651-7651/com.itbird D/FragmenViewPagerTestActivity: onStart
2022-03-22 20:22:30.903 7651-7651/com.itbird D/FragmenViewPagerTestActivity: onResume
2022-03-22 20:22:30.979 7651-7673/com.itbird D/OpenGLRenderer: HWUI GL Pipeline
2022-03-22 20:22:31.206 7651-7651/com.itbird D/BlankFragment1: onAttach
2022-03-22 20:22:31.206 7651-7651/com.itbird D/BlankFragment1: onCreate
2022-03-22 20:22:31.207 7651-7651/com.itbird D/BlankFragment1: onCreateView
2022-03-22 20:22:31.213 7651-7651/com.itbird D/BlankFragment1: onViewCreated
2022-03-22 20:22:31.214 7651-7651/com.itbird D/BlankFragment1: onActivityCreated
2022-03-22 20:22:31.215 7651-7651/com.itbird D/BlankFragment1: onStart

2022-03-22 20:22:31.216 7651-7651/com.itbird D/BlankFragment2: onAttach
2022-03-22 20:22:31.216 7651-7651/com.itbird D/BlankFragment2: onCreate
2022-03-22 20:22:31.217 7651-7651/com.itbird D/BlankFragment2: onCreateView
2022-03-22 20:22:31.219 7651-7651/com.itbird D/BlankFragment2: onViewCreated
2022-03-22 20:22:31.220 7651-7651/com.itbird D/BlankFragment2: onActivityCreated
2022-03-22 20:22:31.222 7651-7651/com.itbird D/BlankFragment1: onResume
2022-03-22 20:22:31.222 7651-7651/com.itbird D/BlankFragment2: onStart
2022-03-22 20:22:31.223 7651-7651/com.itbird D/BlankFragment2: onResume

从日志可以看到,的确运行了两个页面的相关生命周期onAttach->onCreate->onCreateView->onViewCreated->onActivityCreated->onStart>onResume。

3.ViewPager 的预加载机制

上面一节,通过源码和Demo
,看过了ViewPager默认会去加载两个页面,但是mOffscreenPageLimit说白了也只是一个数值
ViewPager #setOffscreenPageLimit

 public void setOffscreenPageLimit(int limit) {
        if (limit < DEFAULT_OFFSCREEN_PAGES) {
            Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
                    + DEFAULT_OFFSCREEN_PAGES);
            limit = DEFAULT_OFFSCREEN_PAGES;
        }
        if (limit != mOffscreenPageLimit) {
            mOffscreenPageLimit = limit;
            populate();
        }
    }

所以真正的预加载实现,应该是在populate函数中了。
函数代码很长,我就不在这里贴了,我们看一下populate函数中,主要调用了哪些函数,从而总结一下它的职能。

startUpdate()--startUpdate函数,告诉PagerAdapter开始更新要显示的页面
getCount()--数据源中页面个数
instantiateItem()--初始化当前位置的item,类似于listview的getView方法,滑动到一个新的位置时,需要构建当前view 。
destroyItem()--销毁当前位置的item
setPrimaryItem()--调用该方法来通知PageAdapter当前ViewPager显示的主要项
finishUpdate()--当ViewPager的内容变化结束时,进行调用。当该方法被调用时,必须确定所有的操作已经结束

主要流程:ViewPager的时候会调用populate,主要根据制定的页面缓存大小(mOffscreenPageLimit),做了页面的销毁和重建。
1、调用mAdapter.startUpDate(this),表示开始加载
2、调用addNewItem->mApdater.instantiateItem()创建Item(Fragment),同时获取了FragmentManager,调用了beginTransaction()
3、循环遍历当前Item左边的item,如果不在预加载范围内,就调用mAdapter.destroyItem()销毁Item,否则如果在预加载范围内,如果已经被加载,就忽略,否则就调用addNewItem加载新的Item
4、循环遍历当前item右边的item,同上
5、调用setPrimaryItem,调用离开的fragment.setUserVisibleHint(false),调用当前的fragment,setUservisibleHint(true)
6、调用mAdapter.finishUpdate,即调用了transaction.commitNowAllowingStateLoss();

从上述流程不难发现,Fragment生命周期在最后finishiUpdate时通过transaction才开始执行,因此,setUserVisibleHint()函数的调用在Fragment生命周期执行之前

4.ViewPager 的setUserVisibleHint调用时机和次数

ViewPager#setUserVisibleHint


    /**
     * Set a hint to the system about whether this fragment's UI is currently visible
     * to the user. This hint defaults to true and is persistent across fragment instance
     * state save and restore.
     *
     * 

An app may set this to false to indicate that the fragment's UI is * scrolled out of visibility or is otherwise not directly visible to the user. * This may be used by the system to prioritize operations such as fragment lifecycle updates * or loader ordering behavior.

* *

Note: This method may be called outside of the fragment lifecycle. * and thus has no ordering guarantees with regard to fragment lifecycle method calls.

* * @param isVisibleToUser true if this fragment's UI is currently visible to the user (default), * false if it is not. * * @deprecated If you are manually calling this method, use * {@link FragmentTransaction#setMaxLifecycle(Fragment, Lifecycle.State)} instead. If * overriding this method, behavior implemented when passing in true should be * moved to {@link Fragment#onResume()}, and behavior implemented when passing in * false should be moved to {@link Fragment#onPause()}. */ @Deprecated public void setUserVisibleHint(boolean isVisibleToUser) { if (!mUserVisibleHint && isVisibleToUser && mState < STARTED && mFragmentManager != null && isAdded() && mIsCreated) { mFragmentManager.performPendingDeferredStart( mFragmentManager.createOrGetFragmentStateManager(this)); } mUserVisibleHint = isVisibleToUser; mDeferStart = mState < STARTED && !isVisibleToUser; if (mSavedFragmentState != null) { // Ensure that if the user visible hint is set before the Fragment has // restored its state that we don't lose the new value mSavedUserVisibleHint = isVisibleToUser; } }

由函数注释可知,此方法主要是fragment被add or remove时调用的,代表的是fragment是否可见。而且结合上节ViewPager#populate源码可知,setUserVisibleHint的调用发生在fragment生命周期之前。我们通过Demo验证一下。

2022-03-23 15:07:24.596 27584-27584/? I/zygote: Late-enabling -Xcheck:jni
2022-03-23 15:07:24.611 27584-27584/? W/zygote: Unexpected CPU variant for X86 using defaults: x86
2022-03-23 15:07:24.904 27584-27584/com.itbird E/FragmenViewPagerTestActivity: FragmenViewPagerTestActivity onCreate
2022-03-23 15:07:24.910 27584-27584/com.itbird D/FragmenViewPagerTestActivity: onStart
2022-03-23 15:07:24.915 27584-27584/com.itbird D/FragmenViewPagerTestActivity: onResume
2022-03-23 15:07:24.928 27584-27606/com.itbird D/OpenGLRenderer: HWUI GL Pipeline
2022-03-23 15:07:24.954 27584-27584/com.itbird D/BlankFragment1: setUserVisibleHint =false
2022-03-23 15:07:24.955 27584-27584/com.itbird D/BlankFragment2: setUserVisibleHint =false
2022-03-23 15:07:24.955 27584-27584/com.itbird D/BlankFragment1: setUserVisibleHint =true

2022-03-23 15:07:24.956 27584-27584/com.itbird D/BlankFragment1: onAttach
2022-03-23 15:07:24.956 27584-27584/com.itbird D/BlankFragment1: onCreate
2022-03-23 15:07:24.957 27584-27584/com.itbird D/BlankFragment1: onCreateView
2022-03-23 15:07:24.961 27584-27584/com.itbird D/BlankFragment1: onViewCreated
2022-03-23 15:07:24.963 27584-27584/com.itbird D/BlankFragment1: onActivityCreated
2022-03-23 15:07:24.967 27584-27584/com.itbird D/BlankFragment1: onStart
2022-03-23 15:07:24.968 27584-27584/com.itbird D/BlankFragment2: onAttach
2022-03-23 15:07:24.968 27584-27584/com.itbird D/BlankFragment2: onCreate
2022-03-23 15:07:24.969 27584-27584/com.itbird D/BlankFragment2: onCreateView
2022-03-23 15:07:24.972 27584-27584/com.itbird D/BlankFragment2: onViewCreated
2022-03-23 15:07:24.972 27584-27584/com.itbird D/BlankFragment2: onActivityCreated
2022-03-23 15:07:24.974 27584-27584/com.itbird D/BlankFragment1: onResume
2022-03-23 15:07:24.975 27584-27584/com.itbird D/BlankFragment2: onStart
2022-03-23 15:07:24.975 27584-27584/com.itbird D/BlankFragment2: onResume

如日志,BlankFragment1和BlankFragment2的setUserVisibleHint调用是发生在fragment生命周期之前。
但是我们也发现一个有趣的现象,BlankFragment1的setUserVisibleHint方法调用了两次,分别是在哪里调用的呢?我们不妨先猜想一下,一处应该是在ViewPager 初始化缓存时,也就是populator里面调用了instantiateItem时调用,另外一处应该是ViewPager 设置当前页面是调用。接下来我们看一下两处的代码,是否是这样的。
第一处调用
ViewPager#populator#addNewItem

ItemInfo addNewItem(int position, int index) {
        ItemInfo ii = new ItemInfo();
        ii.position = position;
        ii.object = mAdapter.instantiateItem(this, position);
        ii.widthFactor = mAdapter.getPageWidth(position);
        if (index < 0 || index >= mItems.size()) {
            mItems.add(ii);
        } else {
            mItems.add(index, ii);
        }
        return ii;
    }

FragmentPagerAdapter#instantiateItem

public Object instantiateItem(@NonNull ViewGroup container, int position) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        final long itemId = getItemId(position);

        // Do we already have this fragment?
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        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);
            if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
            } else {
                //这里调用了
                fragment.setUserVisibleHint(false);
            }
        }

        return fragment;
    }

第二处调用
ViewPager#populator中会调用一个函数 mAdapter.setPrimaryItem(this, mCurItem, curItem.object);

FragmentPagerAdapter#setPrimaryItem

public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        Fragment fragment = (Fragment)object;
        if (fragment != mCurrentPrimaryItem) {
            if (mCurrentPrimaryItem != null) {
                mCurrentPrimaryItem.setMenuVisibility(false);
                if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                    if (mCurTransaction == null) {
                        mCurTransaction = mFragmentManager.beginTransaction();
                    }
                    mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
                } else {
                    mCurrentPrimaryItem.setUserVisibleHint(false);
                }
            }
            fragment.setMenuVisibility(true);
            if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                if (mCurTransaction == null) {
                    mCurTransaction = mFragmentManager.beginTransaction();
                }
                mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
            } else {
                //调用显示的fragment的setUserVisibleHint函数
                fragment.setUserVisibleHint(true);
            }

            mCurrentPrimaryItem = fragment;
        }
    }

5.ViewPager懒加载

好了,上面讲了很多原理性的知识,这里我们趁热打铁,说一下ViewPager为什么要实现懒加载、如何实现懒加载。

5.1 为什么要实现懒加载?

Fragment的出现,是为了在一个Activity中,可以显示多个view,而且重要的是,每个view还有自己的生命周期,当然,最重要的是为了适配各种大屏页面。
ViewPager提供了一种方案,帮我们自动化管理这些view,官方建议我们可以往里面尽量塞fragment,那也就意味着有多个页面,ViwePager 的缓存机制会默认缓存旁边的页面,是为了让页面更加流畅。但是同样,我们知道明显带来以下两个问题:

1)在缓存旁边页面的时候会执行到onCreateView方法,如果你两个碎片的onCreateView 中都有执行请求数据的时候,旁边的页面也会发送请求,这样就会造成网络请求的一些问题
2)由上面几节知识和实践可知,当ViewPager缓存旁边的页面之后,会执行它的一系列的生命周期方法(onCreate->...->onResume),执行完成之后,如果切换过来,此时生命周期不会再次执行,可是如果我们仅仅根据fragment是否可见,去在相应生命周期去判断,然后去加载网络数据,明显也是存在问题的,因为等fragment从缓存切换为展示的时候,生命周期并不会重新走一遍。除非不可见后,再一次被缓存。

为了解决以上两个明显存在的业务逻辑问题,我们必然要去实现懒加载。

5.2 如何实现懒加载?

5.2.1 错误的懒加载实现方式1

有人会直接说,第四节不是说了,setUserVisibleHint函数是在fragment可见和不可见的时候,会被分别调用吗?我自己保存这个状态,然后在onResume、onCreateView里面的数据加载前,做一下判断不就行了,如下面的代码:
//错误的懒加载实现方式1

package com.itbird.fragment;

import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import com.itbird.R;

/**
 * A simple {@link Fragment} subclass.
 * Use the {@link ErrorLazyBlankFragment2#newInstance} factory method to
 * create an instance of this fragment.
 */
public class ErrorLazyBlankFragment2 extends Fragment {

    // TODO: Rename parameter arguments, choose names that match
    // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";

    // TODO: Rename and change types of parameters
    private String mParam1;
    private String mParam2;
    private String TAG = ErrorLazyBlankFragment2.class.getSimpleName();

    public ErrorLazyBlankFragment2() {
        // Required empty public constructor
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @param param1 Parameter 1.
     * @param param2 Parameter 2.
     * @return A new instance of fragment BlankFragment.
     */
    // TODO: Rename and change types and number of parameters
    public static ErrorLazyBlankFragment2 newInstance(String param1, String param2) {
        ErrorLazyBlankFragment2 fragment = new ErrorLazyBlankFragment2();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate");
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }

    @Override
    public void onStart() {
        Log.d(TAG, "onStart");
        super.onStart();
    }

    @Override
    public void onStop() {
        Log.d(TAG, "onStop");

        super.onStop();
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        Log.d(TAG, "onViewCreated");
        super.onViewCreated(view, savedInstanceState);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        Log.d(TAG, "onActivityCreated");
        super.onActivityCreated(savedInstanceState);
    }

    @Override
    public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
        super.onViewStateRestored(savedInstanceState);
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy");

        super.onDestroy();
    }

    @Override
    public void onDestroyView() {
        Log.d(TAG, "onDestroyView");

        super.onDestroyView();
    }

    @Override
    public void onDetach() {
        Log.d(TAG, "onDetach");

        super.onDetach();
    }

    @Override
    public void onAttach(@NonNull Context context) {
        Log.d(TAG, "onAttach");

        super.onAttach(context);
    }

    @Override
    public void onHiddenChanged(boolean hidden) {
        Log.d(TAG, "onHiddenChanged");

        super.onHiddenChanged(hidden);
    }

    @Override
    public void onResume() {
        Log.d(TAG, "onResume");
        super.onResume();


        //错误的懒加载方式
        if (isVisibleToUser) {
            loadNetData();
        }
    }

    private void loadNetData() {
        Log.d(TAG, "loadNetData");
        //TODO :网络加载数据
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        Log.d(TAG, "setUserVisibleHint =" + isVisibleToUser);
        this.isVisibleToUser = isVisibleToUser;
        super.setUserVisibleHint(isVisibleToUser);
    }

    private boolean isVisibleToUser;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        Log.d(TAG, "onCreateView");
        View view = inflater.inflate(R.layout.fragment_blank, container, false);
        textView = view.findViewById(R.id.textview);
        textView.setText(mParam1 + "       " + mParam2 + "    BlankFragment2");
        return view;
    }

    TextView textView;
}

但是运行之后,我们发现fragment2的loadNetData并未执行,因为它的生命周期在缓存阶段,已经执行完成。

2022-03-23 16:48:00.118 30959-30959/com.itbird D/BlankFragment1: setUserVisibleHint =false
2022-03-23 16:48:00.118 30959-30959/com.itbird D/BlankFragment2: setUserVisibleHint =false
2022-03-23 16:48:00.118 30959-30959/com.itbird D/BlankFragment1: setUserVisibleHint =true
2022-03-23 16:48:00.121 30959-30959/com.itbird D/BlankFragment1: onAttach
2022-03-23 16:48:00.131 30959-30959/com.itbird D/BlankFragment1: onCreate
2022-03-23 16:48:00.132 30959-30959/com.itbird D/BlankFragment1: onCreateView
2022-03-23 16:48:00.160 30959-30959/com.itbird D/BlankFragment1: onViewCreated
2022-03-23 16:48:00.161 30959-30959/com.itbird D/BlankFragment1: onActivityCreated
2022-03-23 16:48:00.161 30959-30959/com.itbird D/BlankFragment1: onStart
//fragment2缓存,生命周期执行完成
2022-03-23 16:48:00.162 30959-30959/com.itbird D/BlankFragment2: onAttach
2022-03-23 16:48:00.162 30959-30959/com.itbird D/BlankFragment2: onCreate
2022-03-23 16:48:00.163 30959-30959/com.itbird D/BlankFragment2: onCreateView
2022-03-23 16:48:00.165 30959-30959/com.itbird D/BlankFragment2: onViewCreated
2022-03-23 16:48:00.165 30959-30959/com.itbird D/BlankFragment2: onActivityCreated
2022-03-23 16:48:00.167 30959-30959/com.itbird D/BlankFragment1: onResume
//fragment1的loadNetData正常执行,因为它是默认展示的页面
2022-03-23 16:48:00.167 30959-30959/com.itbird D/BlankFragment1: loadNetData
2022-03-23 16:48:00.172 30959-30959/com.itbird D/BlankFragment2: onStart
2022-03-23 16:48:00.173 30959-30959/com.itbird D/BlankFragment2: onResume
//切换为fragment2时,fragment2的生命周期并没有再次执行,只是回调了setUserVisibleHint方法
2022-03-23 16:48:14.578 30959-30959/com.itbird D/BlankFragment1: setUserVisibleHint =false
2022-03-23 16:48:14.579 30959-30959/com.itbird D/BlankFragment1: setUserVisibleHint =false
2022-03-23 16:48:14.579 30959-30959/com.itbird D/BlankFragment2: setUserVisibleHint =true

5.2.2 错误的懒加载实现方式2

经过上面的实践,也有人会说,我还有实现方式,既然生命周期由于缓存机制不会再次执行,那么必然数据加载不能放在原先最爱的onResume等函数中,那我直接放在setUserVisibleHint 函数中不就行了,根据可见还是不可见,去是否加载网络数据不就行了,代码如下:

package com.itbird.fragment;

import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import com.itbird.R;

/**
 * 错误的懒加载实现方式2
 */
public class ErrorLazyBlankFragment3 extends Fragment {

    // TODO: Rename parameter arguments, choose names that match
    // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";

    // TODO: Rename and change types of parameters
    private String mParam1;
    private String mParam2;
    private String TAG = ErrorLazyBlankFragment3.class.getSimpleName();

    public ErrorLazyBlankFragment3() {
        // Required empty public constructor
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @param param1 Parameter 1.
     * @param param2 Parameter 2.
     * @return A new instance of fragment BlankFragment.
     */
    // TODO: Rename and change types and number of parameters
    public static ErrorLazyBlankFragment3 newInstance(String param1, String param2) {
        ErrorLazyBlankFragment3 fragment = new ErrorLazyBlankFragment3();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate");
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }

    @Override
    public void onStart() {
        Log.d(TAG, "onStart");
        super.onStart();
    }

    @Override
    public void onStop() {
        Log.d(TAG, "onStop");

        super.onStop();
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        Log.d(TAG, "onViewCreated");
        super.onViewCreated(view, savedInstanceState);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        Log.d(TAG, "onActivityCreated");
        super.onActivityCreated(savedInstanceState);
    }

    @Override
    public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
        super.onViewStateRestored(savedInstanceState);
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy");

        super.onDestroy();
    }

    @Override
    public void onDestroyView() {
        Log.d(TAG, "onDestroyView");

        super.onDestroyView();
    }

    @Override
    public void onDetach() {
        Log.d(TAG, "onDetach");

        super.onDetach();
    }

    @Override
    public void onAttach(@NonNull Context context) {
        Log.d(TAG, "onAttach");

        super.onAttach(context);
    }

    @Override
    public void onHiddenChanged(boolean hidden) {
        Log.d(TAG, "onHiddenChanged");

        super.onHiddenChanged(hidden);
    }

    @Override
    public void onResume() {
        Log.d(TAG, "onResume");
        super.onResume();
    }

    private void loadNetData() {
        Log.d(TAG, "loadNetData");
        //TODO :网络加载数据
        //网络数据加载完成,更新view
        textView.setText("xxx");
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        Log.d(TAG, "setUserVisibleHint =" + isVisibleToUser);
        if (isVisibleToUser) {
            loadNetData();
        }
        super.setUserVisibleHint(isVisibleToUser);
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        Log.d(TAG, "onCreateView");
        View view = inflater.inflate(R.layout.fragment_blank, container, false);
        textView = view.findViewById(R.id.textview);
        textView.setText(mParam1 + "       " + mParam2 + "    BlankFragment2");
        return view;
    }

    TextView textView;
}

一运行,崩溃了

    --------- beginning of crash
2022-03-23 17:00:54.891 31621-31621/com.itbird E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.itbird, PID: 31621
    java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
        at com.itbird.fragment.ErrorLazyBlankFragment3.loadNetData(ErrorLazyBlankFragment3.java:139)
        at com.itbird.fragment.ErrorLazyBlankFragment3.setUserVisibleHint(ErrorLazyBlankFragment3.java:146)
        at androidx.fragment.app.FragmentPagerAdapter.setPrimaryItem(FragmentPagerAdapter.java:231)
        at androidx.viewpager.widget.ViewPager.populate(ViewPager.java:1234)
        at androidx.viewpager.widget.ViewPager.populate(ViewPager.java:1092)
        at androidx.viewpager.widget.ViewPager.onMeasure(ViewPager.java:1622)
        at android.view.View.measure(View.java:22071)
        at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:715)
        at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:461)
        at android.view.View.measure(View.java:22071)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6602)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
        at android.view.View.measure(View.java:22071)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6602)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1514)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:806)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:685)
        at android.view.View.measure(View.java:22071)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6602)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
        at com.android.internal.policy.DecorView.onMeasure(DecorView.java:724)
        at android.view.View.measure(View.java:22071)
        at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:2422)
        at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1504)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1761)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1392)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6752)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:911)
        at android.view.Choreographer.doCallbacks(Choreographer.java:723)
        at android.view.Choreographer.doFrame(Choreographer.java:658)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:897)
        at android.os.Handler.handleCallback(Handler.java:790)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

查看日志,是这是的textview还是空的,从上面的源码分析章节中,我们知道,setUserVisibleHint函数的调用发生在生命周期之前,显然此时就算fragment可见了,但是相应生命周期还未执行,此时界面还未加载、绘制,当然获取不到界面元素了。

5.2.3 正确的懒加载实现方式2

    public void setUserVisibleHint(boolean isVisibleToUser) {
        Log.d(TAG, "setUserVisibleHint =" + isVisibleToUser);
        super.setUserVisibleHint(isVisibleToUser);
        this.isVisibleToUser = isVisibleToUser;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        Log.d(TAG, "onViewCreated");
        super.onViewCreated(view, savedInstanceState);
        isViewCreated = true;
        loadNetData();
    }

    private boolean isViewCreated;
    private boolean isVisibleToUser;

    private void loadNetData() {
        if (isViewCreated && isVisibleToUser) {
            Log.d(TAG, "loadNetData");
            //TODO :网络加载数据
        }
    }

运行日志

2022-03-23 17:32:13.762 742-742/? I/zygote: Late-enabling -Xcheck:jni
2022-03-23 17:32:13.845 742-742/? W/zygote: Unexpected CPU variant for X86 using defaults: x86
2022-03-23 17:32:14.264 742-742/com.itbird E/FragmenViewPagerTestActivity: FragmenViewPagerTestActivity onCreate
2022-03-23 17:32:14.270 742-742/com.itbird D/FragmenViewPagerTestActivity: onStart
2022-03-23 17:32:14.275 742-742/com.itbird D/FragmenViewPagerTestActivity: onResume
2022-03-23 17:32:14.286 742-798/com.itbird D/OpenGLRenderer: HWUI GL Pipeline
2022-03-23 17:32:14.307 742-742/com.itbird D/BlankFragment1: setUserVisibleHint =false
2022-03-23 17:32:14.307 742-742/com.itbird D/BlankFragment1: setUserVisibleHint =false
2022-03-23 17:32:14.307 742-742/com.itbird D/BlankFragment1: setUserVisibleHint =true
//页面1加载完成
2022-03-23 17:32:14.308 742-742/com.itbird D/BlankFragment1: onAttach
2022-03-23 17:32:14.309 742-742/com.itbird D/BlankFragment1: onCreate
2022-03-23 17:32:14.309 742-742/com.itbird D/BlankFragment1: onCreateView
2022-03-23 17:32:14.321 742-742/com.itbird D/BlankFragment1: onViewCreated
2022-03-23 17:32:14.321 742-742/com.itbird D/BlankFragment1: loadNetData
//页面2缓存完成
2022-03-23 17:32:14.322 742-742/com.itbird D/BlankFragment1: onActivityCreated
2022-03-23 17:32:14.324 742-742/com.itbird D/BlankFragment1: onStart
2022-03-23 17:32:14.325 742-742/com.itbird D/BlankFragment1: onAttach
2022-03-23 17:32:14.325 742-742/com.itbird D/BlankFragment1: onCreate
2022-03-23 17:32:14.326 742-742/com.itbird D/BlankFragment1: onCreateView
2022-03-23 17:32:14.329 742-742/com.itbird D/BlankFragment1: onViewCreated
2022-03-23 17:32:14.331 742-742/com.itbird D/BlankFragment1: onActivityCreated
2022-03-23 17:32:14.334 742-742/com.itbird D/BlankFragment1: onResume
2022-03-23 17:32:14.335 742-742/com.itbird D/BlankFragment1: onStart
2022-03-23 17:32:14.336 742-742/com.itbird D/BlankFragment1: onResume
//页面2加载数据
2022-03-23 17:32:26.642 742-742/com.itbird D/BlankFragment1: setUserVisibleHint =false
2022-03-23 17:32:26.643 742-742/com.itbird D/BlankFragment1: setUserVisibleHint =false
2022-03-23 17:32:26.643 742-742/com.itbird D/BlankFragment1: setUserVisibleHint =true
2022-03-23 17:32:26.643 742-742/com.itbird D/BlankFragment1: loadNetData

总结

1.Fragment与Activity通信(数据传递&接口调用)的方式有:BrocastReceiver、Handler、ContentProvider、File、Socket、IPC、setArguments、共用Model、接口回调。
2.ViewPager是Google Android官方提供的一种多页面滑动组合的控件,我们经常填充fragment来实现多页面的滑动方案。
3.ViewPager+Fragment由于默认的缓存机制,必然会加载至少两个页面。
4.ViewPager+Fragment使用懒加载可以解决,缓存机制带来的数据加载和生命周期不匹配的问题。
5.ViewPager实现懒加载重在两个状态,一个fragment是否可见,一个view是否创建完成,两者缺一不可,除非您的数据加载和view是完全没关系的,不过反过来思考,如果Data与View完全没关系,那说明就不是必须在Fragment前期生命周期中调用了,反而带来界面卡顿等问题。

Demo

你可能感兴趣的:(Android组件之Fragment(二)---实战知识)