性能优化之fragment的懒加载

你如果拿到公司的一个大型app,首先肯定是研究一下它的技术,这个App到底用到什么框架,用什么方式来实现的,如果开发一款高性能app,考虑使用什么框架来构建这个app,现在大多数app都是在用MVVM,这里的MVVM是M-V-VM,UI上的框架大多使用viewPage+Fragment,头条、QQ等都是这么干,都是使用双层的ViewPage+Fragment,这个已经成为了app的标配,左右滑动,滑动切换界面,切换内容,符合我们用户的操作需求。
认真观察头条的app,在它栏目里左右滑动的时候会出现空白,这是使用了懒加载,这是对性能的考虑。

了解懒加载之前,先了解一下什么是预加载?

预加载就是预先加载,先加载,除了当前页面,前后两个页面也要加载出来。让界面可以加快显示出来。
数据结构中有两个知识点,空间复杂度、时间复杂度。
那么这里让界面能加快展示到用户面前,是有代价的。有句话是“用空间换时间”,“用时间换空间”,虽然我只显示了一个页面,当前页面,但是我已经在背后默默地在当前页面左右都缓存了两个页面的数据,者就是预加载,将它加载到内存里面,当我们从当前页面滑到另外一个页面时候,直接那个页面的数据就显示出来了,它不需要再去进行渲染这方面的操作,这动作已经完成了,所以这就能提升我们用户左右滑动这个效果的感觉,但是这个代价在,预加载都要放到内存中,内存的使用增加,数据加载增加,本来只加载1页页面的数据,现在要加载5页的数据,内存本来是缓存一个页面的,现在要加载5页的数据,内存5M->25M ,数据加载0.1s->0.5s

懒加载

预加载在数据量少的时候,确实能满足app的需求,但是随着用户的需求,页面的展示越来越高要求,页面需要展示高清图,视频等占内存大,请求数据大的。预加载占的内存太大,而且一次性请求的数据量也会随之而几倍增长。
懒加载,用一个空白页面,有页面没数据,节省内存,预加载一个空白页面只占5k内存,减少内存的使用,当滑动为当前页面时,加载数据。


e7277b65bd4d0294bc353947e77ce80.jpg

a82bb8d2773f035ccbfcb710d807031.jpg

都知道方法tansaction.attach(fragment)到这里时,Fragment的生命周期是不会马上执行的,等事务执行commit的时候才会走生命周期,这和数据库的管理很类似,都是用触发器去管理的,触发器管理有一个特征,它需要commit,你只有提交了,你的事件才会执行。
所以这里setUserVisibleHint会先于生命周期执行。
懒加载跟UI可见有关系
加载数据的操作要放到与UI相关的函数里面去,跟UI相关的函数是:
onCreateView -- >创建UI fragment页面的创建
onResume -- >界面可见的
onPause --> 是界面可见,但是不可交互
onDestroyView --> 界面的销毁


public abstract class LazyFragment extends Fragment
{
    
    private static final String TAG = "LazyFragment";
    
    /**
     * Fragment生命周期 onAttach -> onCreate -> onCreatedView -> onActivityCreated
     * -> onStart -> onResume -> onPause -> onStop -> onDestroyView -> onDestroy
     * -> onDetach 对于 ViewPager + Fragment 的实现我们需要关注的几个生命周期有: onCreatedView +
     * onActivityCreated + onResume + onPause + onDestroyView
     */
    
    protected View rootView = null;
    
    /**
     * 布局是否创建完成
     */
    protected boolean isViewCreated = false;
    
    /**
     * 当前可见状态
     */
    protected boolean currentVisibleState = false;
    
    /**
     * 是否第一次可见
     */
    protected boolean mIsFirstVisible = true;
    
    @Override
    public void onAttach(Context context)
    {
        super.onAttach(context);
    }
    
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
    }
    
    /**
     * 修改Fragment的可见性 setUserVisibleHint 被调用有两种情况:
     * 1)在切换tab的时候,会先于所有fragment的其他生命周期,先调用这个函数,可以看log 2)
     * 对于之前已经调用过setUserVisibleHint方法的fragment后,让fragment从可见到不可见之间状态的变化
     */
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser)
    {
        super.setUserVisibleHint(isVisibleToUser);
        Logger.d("setUserVisibleHint: " + isVisibleToUser);
        // 对于情况1)不予处理,用 isViewCreated 进行判断,如果isViewCreated false,说明它没有被创建
        if (isViewCreated)
        {
            // 对于情况2,需要分情况考虑,如果是不可见 -> 可见 2.1
            // 如果是可见 -> 不可见 2.2
            // 对于2.1)我们需要如何判断呢?首先必须是可见的(isVisibleToUser
            // 为true)而且只有当可见状态进行改变的时候才需要切换,否则会出现反复调用的情况
            // 从而导致事件分发带来的多次更新
            if (isVisibleToUser && !currentVisibleState)
            {
                // 从不可见 -> 可见
                dispatchUserVisibleHint(true);
            }
            else if (!isVisibleToUser && currentVisibleState)
            {
                dispatchUserVisibleHint(false);
            }
        }
    }
    
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater,
        @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
    {
        super.onCreateView(inflater, container, savedInstanceState);
        if (null == rootView)
        {
            rootView = inflater.inflate(getLayoutRes(), container, false);
        }
        initView(rootView);
        Logger.d("onCreateView: ");
        // 初始化的时候,判断当前Fragment可见状态
        // isHidden在使用FragmentTransaction的show/hidden时会调用,可见返回的是false
        if (!isHidden() && getUserVisibleHint())
        {
            // 可见状态,进行事件分发
            dispatchUserVisibleHint(true);
        }
        return rootView;
    }
    
    /**
     * 用FragmentTransaction来控制fragment的hide和show时,
     * 那么这个方法就会被调用。每当你对某个Fragment使用hide 或者是show的时候,那么这个Fragment就会自动调用这个方法。
     */
    
    @Override
    public void onHiddenChanged(boolean hidden)
    {
        Logger.d("onHiddenChanged: " + hidden);
        super.onHiddenChanged(hidden);
        // 这里的可见返回为false
        if (hidden)
        {
            dispatchUserVisibleHint(false);
        }
        else
        {
            dispatchUserVisibleHint(true);
        }
    }
    
    /**
     * 统一处理用户可见事件分发
     */
    private void dispatchUserVisibleHint(boolean isVisible)
    {
        Logger.d("dispatchUserVisibleHint: " + isVisible);
        
        // 首先考虑一下fragment嵌套fragment的情况(只考虑2层嵌套)
        if (isVisible && isParentInvisible())
        {
            // 父Fragmnet此时不可见,直接return不做处理
            return;
        }
        // 为了代码严谨,如果当前状态与需要设置的状态本来就一致了,就不处理了
        if (currentVisibleState == isVisible)
        {
            return;
        }
        currentVisibleState = isVisible;
        if (isVisible)
        {
            if (mIsFirstVisible)
            {
                mIsFirstVisible = false;
                // 第一次可见,进行全局初始化
                onFragmentFirstVisible();
            }
            onFragmentResume();
            // 分发事件给内嵌的Fragment
            dispatchChildVisibleState(true);
        }
        else
        {
            onFragmentPause();
            dispatchChildVisibleState(false);
        }
        
    }
    
    /**
     * 在双重ViewPager嵌套的情况下,第一次滑到Frgment 嵌套ViewPager(fragment)的场景的时候
     * 此时只会加载外层Fragment的数据,而不会加载内嵌viewPager中的fragment的数据,因此,我们
     * 需要在此增加一个当外层Fragment可见的时候,分发可见事件给自己内嵌的所有Fragment显示
     */
    private void dispatchChildVisibleState(boolean visible)
    {
        FragmentManager fragmentManager = getChildFragmentManager();
        List fragments = fragmentManager.getFragments();
        if (null != fragments)
        {
            for (Fragment fragment : fragments)
            {
                if (fragment instanceof LazyFragment && !fragment.isHidden()
                    && fragment.getUserVisibleHint())
                {
                    ((LazyFragment)fragment).dispatchUserVisibleHint(visible);
                }
            }
        }
    }
    
    /**
     * Fragment真正的Pause,暂停一切网络耗时操作
     */
    protected void onFragmentPause()
    {
        Logger.d("onFragmentResume " + " 真正的resume,开始相关操作耗时");
        
    }
    
    /**
     * Fragment真正的Resume,开始处理网络加载等耗时操作
     */
    protected void onFragmentResume()
    {
        Logger.d("onFragmentPause" + " 真正的Pause,结束相关操作耗时");
    }
    
    private boolean isParentInvisible()
    {
        Fragment parentFragment = getParentFragment();
        if (parentFragment instanceof LazyFragment)
        {
            LazyFragment fragment = (LazyFragment)parentFragment;
            return !fragment.isSupportVisible();
        }
        return false;
    }
    
    private boolean isSupportVisible()
    {
        return currentVisibleState;
    }
    
    /**
     * 在滑动或者跳转的过程中,第一次创建fragment的时候均会调用onResume方法
     */
    @Override
    public void onResume()
    {
        super.onResume();
        // 如果不是第一次可见
        if (!mIsFirstVisible)
        {
            // 如果此时进行Activity跳转,会将所有的缓存的fragment进行onResume生命周期的重复
            // 只需要对可见的fragment进行加载,
            if (!isHidden() && !currentVisibleState && getUserVisibleHint())
            {
                dispatchUserVisibleHint(true);
            }
        }
        
    }
    
    /**
     * 只有当当前页面由可见状态转变到不可见状态时才需要调用 dispatchUserVisibleHint currentVisibleState &&
     * getUserVisibleHint() 能够限定是当前可见的 Fragment 当前 Fragment 包含子 Fragment 的时候
     * dispatchUserVisibleHint 内部本身就会通知子 Fragment 不可见 子 fragment 走到这里的时候自身又会调用一遍
     */
    @Override
    public void onPause()
    {
        super.onPause();
        if (currentVisibleState && getUserVisibleHint())
        {
            dispatchUserVisibleHint(false);
        }
    }
    
    @Override
    public void onDestroyView()
    {
        super.onDestroyView();
        Logger.d("onDestroyView");
        isViewCreated = false;
        mIsFirstVisible = false;
    }
    
    @Override
    public void onDestroy()
    {
        super.onDestroy();
    }
    
    @Override
    public void onDetach()
    {
        super.onDetach();
    }
    
    /**
     * 第一次可见,根据业务进行初始化操作
     */
    protected abstract void onFragmentFirstVisible();
    
    /**
     * 初始化页面,如果不需要进行懒加载则可直接在此处进行网络处理
     * 
     * @param rootView
     */
    protected abstract void initView(View rootView);
    
    /**
     * 获取布局资源
     */
    protected abstract int getLayoutRes();
    
}

initView()和getLayoutRes()都声明为抽象函数,让子类去实现,这里是布局和findViewbyId等操作,交给具体的fragment去实现

为什么rootView要去判空创建呢?rootView不是一定会是空吗?

它的生命周期肯定不会只调用一次,当它重复去走onCreateView的时候,这个生命周期我们是控制不了的,是Activity去回调给我们的。

为什么onFragmentResume()和onFragmentPause()不是抽象方法?

这个LazyFragment是一个抽象类,如果你把它做成一个模板类BaseFragment,你就可以把它当父类,你把它当父类又不需要实现懒加载,那你就不需要实现这两个函数,是没关系的没这是正常的fragment,为了扩展,这是一个需求,面向对象的思路,选择和不选择懒加载都可以共用 这个父类。

加入标志位 isViewCreate

这个值为true时,才会分发事件
在首次加载的时候也要补发一句

       // 初始化的时候,判断当前Fragment可见状态
        // isHidden在使用FragmentTransaction的show/hidden时会调用,可见返回的是false
        if (!isHidden() && getUserVisibleHint())
        {
            // 可见状态,进行事件分发
            dispatchUserVisibleHint(true);
        }

添加变量currentVisibleState

为了代码严谨,如果当前状态与需要设置的状态本来就一致了,就不处理了

fragment要显示数据,哪里来的?

fragment要显示数据,哪里来的?一般情况下在onResume里面,在onResume里面做数据加载操作,网络操作,线程,启动线程,去viewmodel里面取数据,但是懒加载不允许你数据加载在onResume里面,因为onRsume有种情况不需要加载,那就是预加载的情况,如果预加载也在onRsume里面,那么懒加载就没意义了,所以懒加载是用于控制数据加载的时机,所以不能在onResume里面加载,应该在UI可见的时候去setUserVisibleHint传入true,说明当前可见,就加载数据调用dispathUserVisibleHint(true);否者停止加载,调用dispatchUserVisibleHint(false),根据传进去的visibleState去判断选择使用onFragmentLoad还是OnFragmentLoadStop。

在onResume()和onPause()里面加处理

当这个Activity跳到另一个Activity的时候,不会去修改可见性,只会走生命周期的onPause(),而回来的时候也只会走onResume(),所以我们要主动去调用可见方法。dispatchUserVisibleHint(XXX);

什么是性能优化?

性能优化的本质是,做法是节省内存,减少流量,目的是在软件放牧减少不必要的CPU的浪费,减少对内存的不合理使用,这是根本。

懒加载是怎么优化的?

懒加载是针对预加载进行优化的,对ArrayList的优化,它其实根本是对ArrayList,以前保存的是一个fragment,这个fragment中有大量的view,现在我们通过arrayList里面保存的也是一个fragment,只不过这个fragment是一个空白的fragment,这就减少了内存,那空白的fragment的数据哪里来的呢?所以就出现了可见的时候加载,在可见的时候将空白的数据,把它替换掉,形成一个可见的漂亮的界面,然后放到ArrayList里面去,这是一个延迟加载,懒加载,优化的根本是对ArrayList原理里面的优化,ArrayList保存的是一个大量数据的,肯定会很多内存,如果保存的是一个空白页面,那么我们保存的数据就会越少。

预加载可以避免吗?

当然不可以,这里虽然加载的是一个空白页面,但是预加载这个模式依旧是存在的。

你可能感兴趣的:(性能优化之fragment的懒加载)