Android基础之Fragment常用技巧(懒加载)

1、简介

      该篇博客是以记录Android开发中Fragment的使用技巧。作为开发中的大头,这一块还是有必要好好了解一下的。包括其生命周期包括其重要特性等,主要研究了fragment的懒加载以及fragment生命周期的使用和调用过程。

 优雅的懒加载实现

       开发也有一段时间了,不能说一直忽略懒加载,但是由于之前一直没有很好的掌握,故而希望借这次机会真真正正的把懒加载做好,因为不然性能真的很差,真的很难忍受初始化的时候明明只是进入了Fragment A 而 Fragment B  C  D 确得到了加载。这样真的非常的不好,因为每一个Fragment主页可能都会有很多的资源和网络请求需要进行加载,可想而知是非常的缓慢的,而且很容易造成内存不够的问题。说了这么多,有吐槽自己的不足点,也阐明了懒加载的必要性,下面就有有必要整理下实际项目中的懒加载应该如何做。OK,阐述一下懒加载主要存在于类似于TabLayout+ViewPager这种包含有缓存中,普通的Fragment切换是不需要懒加载的。

2 、知识储备

相信对于Fragment的生命周期大家都有所了解吧,下面是图例。

Android基础之Fragment常用技巧(懒加载)_第1张图片

 值得留意的是不同于Activity被操作系统管理,Fragment是依附于Activity的同样其也是托管于Activity的。注意这里能发现Activity的方法类似,是的,对于Fragement而言,其调用时机也是和活动相同步的。

案例1展示:活动-fragment-fragment切换生命周期

 如下方log所展示,详细的记录了生命周期的切换。

//启动活动并且显示add FragmentA
Activity      onCreate
FragmentA     onAttach
              onCreate
              onCreateView
              onStart
Activity      onStart
              onResume
FragmentA     onResume
//先隐藏FragmentA  然后再add FragmentB
FragmentB     onAttach
              onCreate
              onCreateView
              onStart
              onResume               
// 退出活动
FragmentA     onPause
FragmentB     onPause
Activity      onPause
FragmentA     onStop
FragmentB     onStop
Activity      onStop
FragmentA     onDestroyView
              onDestroy
              onDetach
FragmentB     onDestroyView
              onDestroy
              onDetach
Activity      onDestroy
          

  案例展示2:验证setUserVisibleHint的调用时机

// 活动中展示Fragment 
    private void showFragment() {
        // 开启事务 ------
        FragmentTransaction fragmentTransaction =
                getSupportFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.fragment,new FragmentA());
        // 进行提交 -------
        fragmentTransaction.commit();
    }
  

//  Fragment中打印Log日志
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        Log.e(TAG, "onAttach: ");
    }



    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        Log.e(TAG, "setUserVisibleHint: "+ isVisibleToUser);
        super.setUserVisibleHint(isVisibleToUser);
    }

    @Override
    public void onDetach() {
        super.onDetach();
        Log.e(TAG, "onDetach: ");
    }

观察启动界面的日志:  

09-12 11:29:06.173 3577-3577/com.example.mydairytestproject E/FragmentA: onAttach: 
09-12 11:29:08.585 3577-3577/com.example.mydairytestproject E/FragmentA: onDetach: 

值得注意的是,当仅仅是启动或者关闭一个Fragment时,其实并不会调用setUserVisibleHint()方法的。  该方法调用的时机是在:不同fragment切换时才会调用

那我们接下来完成一个ViewPager+Fragment的案例(FragmentA + FragmentB),看看其生命周期调用的情况吧。

这次我们打印Fragment的生命周期方法调用吧,说明一下,我是这样操作的: 首先自然是打开Activity显示的是FragmentA,然后切换到FragmentB  之后再切换回 FragmentA.

16467-16467/com.example.mydairytestproject E/FragmentA: setUserVisibleHint: false
16467-16467/com.example.mydairytestproject E/FragmentB: setUserVisibleHint: false
16467-16467/com.example.mydairytestproject E/FragmentA: setUserVisibleHint: true
16467-16467/com.example.mydairytestproject E/FragmentA: onCreate: 
16467-16467/com.example.mydairytestproject E/FragmentB: onCreate: 
16467-16467/com.example.mydairytestproject E/FragmentA: onCreateView: 
16467-16467/com.example.mydairytestproject E/FragmentB: onCreateView: 

6467-16467/com.example.mydairytestproject E/FragmentA: setUserVisibleHint: false
16467-16467/com.example.mydairytestproject E/FragmentB: setUserVisibleHint: true

16467-16467/com.example.mydairytestproject E/FragmentB: setUserVisibleHint: false
6467-16467/com.example.mydairytestproject E/FragmentA: setUserVisibleHint: true

以上案例很重要,对于我们后面鉴定判别懒加载活动很重要,我们执行一些规避判断等也是基于以上的。 

关于fragment生命˙周期这里说明下:

      只有viewpager+fragment形式方会执行setUserVisibleHint()

      且使用fragment hide()和show() 才会调用onHideChanged() 

       暂时对于fragment可见生命周期比较难把控。

3、懒加载案例

a.未做处理的运行效果

 我在每个加载的Fragmet中的initVIew( ) 和 initData( ) 中都加入了log。

可以发现,只要一进入,那么就会加载全部的四个Fragment的VIew 和 数据,假设每个界面都需要进行大额的网络请求,可想而知那肯定是灾难的啊,卡顿不说,性能还不好,用户明明没点进去啊,怎么数据就加载了呢,非常非常的不好。

08-17 17:20:36.410 8621-8621/com.example.myapplication22 E/initView:  ---- 第三个Fragment-----
08-17 17:20:36.410 8621-8621/com.example.myapplication22 E/initData:  ---- 第三个Fragment-----
08-17 17:20:36.412 8621-8621/com.example.myapplication22 E/initView:  ---- 第二个Fragment-----
08-17 17:20:36.412 8621-8621/com.example.myapplication22 E/initData:  ---- 第二个Fragment-----
08-17 17:20:36.413 8621-8621/com.example.myapplication22 E/initView:  ---- 第四个Fragment-----
08-17 17:20:36.413 8621-8621/com.example.myapplication22 E/initData:  ---- 第四个Fragment-----
08-17 17:20:36.415 8621-8621/com.example.myapplication22 E/initView:  ---- 第一个Fragment-----
08-17 17:20:36.415 8621-8621/com.example.myapplication22 E/initData:  ---- 第一个Fragment-----

b. 进行改进

   优秀的开源框架,含有懒加载  Fragmentation

Android基础之Fragment常用技巧(懒加载)_第2张图片

08-17 17:44:24.014 15253-15253/com.example.myapplication22 E/initView:  ---- 第二个Fragment-----
08-17 17:44:24.017 15253-15253/com.example.myapplication22 E/initView:  ---- 第三个Fragment-----
08-17 17:44:24.018 15253-15253/com.example.myapplication22 E/initView:  ---- 第四个Fragment-----
08-17 17:44:24.019 15253-15253/com.example.myapplication22 E/initView:  ---- 第一个Fragment-----
08-17 17:44:24.062 15253-15253/com.example.myapplication22 E/initData:  ---- 第一个Fragment-----
08-17 17:44:25.232 15253-15253/com.example.myapplication22 E/initData:  ---- 第二个Fragment-----
08-17 17:44:26.519 15253-15253/com.example.myapplication22 E/initData:  ---- 第三个Fragment-----
08-17 17:44:27.255 15253-15253/com.example.myapplication22 E/initData:  ---- 第四个Fragment-----

好了这样明显是在显示界面完了之后执行了initData()方法的。真的是完美!

只需要将您的activity基类继承自Fragmentation 实现的SupportActivty然后重写 onLazyInitView() 在这里面进行数据的加载即可。

    @Override
    public void onLazyInitView(@Nullable Bundle savedInstanceState) {
        super.onLazyInitView(savedInstanceState);
        initData();
        initListener();
    }

添加Fragment供FragmentManager管理时,onAttach(Activity)、onCreate(Bundle)以及onCreateView(…)方法会被调用。

4. 自己动手实现懒加载

public abstract class LazyFragment extends Fragment {
    /**
     * 标记已加载完成,保证懒加载只能加载一次
     */
    private boolean hasLoaded = false;
    /**
     * 标记Fragment是否已经onCreate
     */
    private boolean isCreated = false;
    /**
     * 界面对于用户是否可见
     */
    private boolean isVisibleToUser = false;
    private View view;

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


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        init(getView(inflater, getLayoutId(), container), savedInstanceState);
        return view;
    }

    private View getView(LayoutInflater inflater, int layoutId, ViewGroup container) {
        return inflater.inflate(layoutId, container, false);
    }


    public void init(View view, Bundle savedInstanceState) {
        isCreated = true;
        this.view = view;
        initViews(this.view,savedInstanceState);
        lazyLoad(this.view,savedInstanceState);
    }
    /**
     * 监听界面是否展示给用户,实现懒加载
     * 这个方法也是网上的一些方法用的最多的一个,我的思路也是这个,不过把整体思路完善了一下
     */
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        Log.i("TAG", "setUserVisibleHint: ");
        //注:关键步骤
        this.isVisibleToUser = isVisibleToUser;
        lazyLoad(view, null);
    }

    public abstract void initViews(View view,Bundle savedInstanceState);


    /**
     * 懒加载方法,获取数据什么的放到这边来使用,在切换到这个界面时才进行网络请求
     */
    private void lazyLoad(View view, Bundle savedInstanceState) {

        //如果该界面不对用户显示、已经加载、fragment还没有创建,
        //三种情况任意一种,不获取数据
        if (!isVisibleToUser || hasLoaded || !isCreated) {
            return;
        }
        lazyInit(view, savedInstanceState);
        //注:关键步骤,确保数据只加载一次
        hasLoaded = true;
    }

    /**
     * 子类必须实现的方法,这个方法里面的操作都是需要懒加载的
     */
    public abstract void lazyInit(View view, Bundle savedInstanceState);

    public abstract int getLayoutId();


    @Override
    public void onDestroyView() {
        super.onDestroyView();
        isCreated = false;
        hasLoaded = false;
    }
}

运行结果如下图所示:经测试是确实有效果的。 

Android基础之Fragment常用技巧(懒加载)_第3张图片

5.优雅的切换Fragment

 下面的代码介绍了,合理的通过使用fragmentTransaction实现Fragement的添加与切换的效果,故而记录下

 这里我要说明一下,这里我们不推荐用单例方式获取Fragment,这里会产生一些问题的

    private void  showWhichFragment(int index){
        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        hideAllFragment(fragmentTransaction);
        switch (index){
            case 0:
                if (mDetFragment == null){
                    mDetFragment = LaunchFgFactory.getFactoryInstance().getDetFgInstance();
                    fragmentTransaction.add(R.id.fgMain,mDetFragment);
                }else {
                    fragmentTransaction.show(mDetFragment);
                }
                break;
            case 1:
                if (mNewsFragment == null){
                    mNewsFragment = LaunchFgFactory.getFactoryInstance().getNewFgInstance();
                    fragmentTransaction.add(R.id.fgMain,mNewsFragment);
                }else {
                    fragmentTransaction.show(mNewsFragment);
                }
                break;
            case 2:
                if (mMsgFragment == null){
                    mMsgFragment = LaunchFgFactory.getFactoryInstance().getMsgInstance();
                    fragmentTransaction.add(R.id.fgMain,mMsgFragment);
                }else {
                    fragmentTransaction.show(mMsgFragment);
                }
                break;
            case 3:
                if (mMeFragment == null){
                    mMeFragment =  LaunchFgFactory.getFactoryInstance().getMeFgInstacne();
                    fragmentTransaction.add(R.id.fgMain,mMeFragment);
                }else {
                    fragmentTransaction.show(mMeFragment);
                }
                break;
                default:
                    break;
        }
        fragmentTransaction.commit();
    }

    private void  hideAllFragment(FragmentTransaction fragmentTransaction){
        if (mDetFragment != null){
            fragmentTransaction.hide(mDetFragment);
        }

        if (mNewsFragment != null){
            fragmentTransaction.hide(mNewsFragment);
        }

        if (mMsgFragment != null){
            fragmentTransaction.hide(mMsgFragment);
        }

        if (mMeFragment != null){
            fragmentTransaction.hide(mMeFragment);
        }
    }

    @Override
    protected void initListener() {
        mBottomBar.setOnTabSelectListener(tabId -> {
            switch (tabId){
                case R.id.tabDet:
                    showWhichFragment(0);
                    break;
                case R.id.tabNews:
                    showWhichFragment(1);
                    break;
                case R.id.tabMsg:
                    showWhichFragment(2);
                    break;
                case R.id.tabMe:
                    showWhichFragment(3);
                    break;
                default:
                    break;
            }
        });
    }

增加isAdded()判断

    private void showWhichDialog(int index){
        if (mFragmentManager == null){
            mFragmentManager = getSupportFragmentManager();
        }

        FragmentTransaction mFragmentTransaction = mFragmentManager.beginTransaction();

        if (mCategoryFragment != null){
            mFragmentTransaction.hide(mCategoryFragment);
        }

        if (mCenterFragment != null){
            mFragmentTransaction.hide(mCenterFragment);
        }

        if (mHomeFragment != null){
            mFragmentTransaction.hide(mHomeFragment);
        }

        switch (index){
            case 0:
                if (mHomeFragment == null){
                    mHomeFragment = new HomeFragment();
                    mFragmentTransaction.add(R.id.container,mHomeFragment);
                }else {
                    if (mHomeFragment.isAdded()){
                        if (mHomeFragment.isHidden()){
                            mFragmentTransaction.show(mHomeFragment);
                        }
                    }else {
                        mFragmentTransaction.add(R.id.container,mHomeFragment);
                    }
                }
                break;
            case 1:
                if (mCategoryFragment == null){
                    mCategoryFragment = new CategoryFragment();
                    mFragmentTransaction.add(R.id.container,mCategoryFragment);
                }else {
                    if (mCategoryFragment.isAdded()){
                        if (mCategoryFragment.isHidden()){
                            mFragmentTransaction.show(mCategoryFragment);
                        }
                    }else {
                        mFragmentTransaction.add(R.id.container,mCategoryFragment);
                    }
                }
                break;
            case 2:
                if (mCenterFragment == null){
                    mCenterFragment = new CenterFragment();
                    mFragmentTransaction.add(R.id.container,mCenterFragment);
                }else {
                    if (mCenterFragment.isAdded()){
                        if (mCenterFragment.isHidden()){
                            mFragmentTransaction.show(mCenterFragment);
                        }
                    }else {
                        mFragmentTransaction.add(R.id.container,mCenterFragment);
                    }
                }
                break;
        }
        mFragmentTransaction.commit();
    }

6. activity中清除老的fragment

    // 清除老的Fragment
    private void clearOldFragment() {
        if (mSupportFragmentManager == null){
            mSupportFragmentManager = getSupportFragmentManager();
        }
        FragmentTransaction transaction = mSupportFragmentManager.beginTransaction();
        List fragments = mSupportFragmentManager.getFragments();
        if (transaction == null || fragments == null || fragments.size() == 0) {
            return;
        }
        boolean doCommit = false;
        for (Fragment fragment : fragments) {
            if (fragment != null) {
                transaction.remove(fragment);
                doCommit = true;
            }
        }
        if (doCommit) {
            transaction.commitNow();
        }
    }

7. 其他细节记录

1. Fragment嵌套Fragment当使用TabLayout+ViewPager构建界面时需要使用:getChildFragmentManager(),让当前的Fragemnt去管理,如果越界给Activity去管理会出现加载不全的问题。

2. Fragment之间的传值问题

明确: Fragment之间的桥梁就是Activity,也就是说可以通过Activity这个载体去传递值。

样例: (xxxActivity)getActivity.getFragemtManager().findFragmentBuTag("tag");

这样就能获得设置了具体Tag的Fragment的实例,那么也就能够通过实例方法进行传值刷新UI了。

当然:像rxbus eventBus 等组件间通信咱们就不提了,也是基本功之一。

3. activity中创建fragment时,传递数值

如下所示,当该Fragment时存在多种类型,进而去做不同的网络请求时可以通过该方式传递数值。

    public static DetFragment newInstance(String type) {
        DetFragment fragment = new DetFragment();
        Bundle args = new Bundle();
        args.putString(TYPE, type);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (null != getArguments()){
            type = getArguments().getString(TYPE);
            Log.e(TAG, "TYPE = "+type);
        }
    }

4. activity 与 DialogFragment传值问题,通过回调函数方式

// activity中
// 显示fragment
   ShowEnjoyDialogFragment.showFragment(MainActivity.this,mEnjoyBeanList,this);
// 接收fragment传值
@Override
 public void onEnsureData(List mList) {
     mEnjoyBeanList.clear();
     mEnjoyBeanList.addAll(mList);
}

// dialogFragment中
    public static void showFragment(Activity activity,List enjoys,EnsureListener ensureListener){
        ShowEnjoyDialogFragment enjoyDialogFragment = new ShowEnjoyDialogFragment();
        enjoyDialogFragment.setEnsureListener(ensureListener);
        Bundle bundle = new Bundle();
        bundle.putSerializable("list", (Serializable) enjoys);
        enjoyDialogFragment.setArguments(bundle);
        enjoyDialogFragment.show(activity.getFragmentManager(),"enjoy");
    }

// 接收数值
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 设置数据...
        _initData();
        Bundle arguments = getArguments();
        if (arguments != null) {
            List enjoyBeans = (List) arguments.getSerializable("list");
            if (enjoyBeans.size() == 0){
                return;
            }
            for (int i=0;i

5. fragment初始化

   fragment切换保存崩溃时的状态,注意点,添加时附带名称并且注意使用onSaveInstanceState()保存崩溃前的状态。

    private void initFragments(Bundle savedInstanceState) {
        if (savedInstanceState != null){
            mDetFragment = (DetFragment) getSupportFragmentManager().findFragmentByTag("det");
            mMsgFragment = (MsgFragment) getSupportFragmentManager().findFragmentByTag("msg");
            mNewsFragment = (NewsFragment) getSupportFragmentManager().findFragmentByTag("news");
            mMeFragment = (MeFragment) getSupportFragmentManager().findFragmentByTag("me");
            // 假如异常结束了,那么可以从原来中获取到
            currentTabPosition = savedInstanceState.getInt(HOME_CURRENT_TAB_POSITION);
        }
        showSwitchFragment(currentTabPosition);
        // 设置默认
        mBottomBar.setDefaultTab(R.id.tabDet);
    }

    /**
     * fragment崩溃时会进行调用
     * @param outState
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        //奔溃前保存位置
        if (mBottomBar != null) {
            outState.putInt(HOME_CURRENT_TAB_POSITION, mBottomBar.getCurrentTabPosition());
        }
    }


    if (null == mDetFragment){
        mDetFragment = DetFragment.newInstance("android");
         fragmentTransaction.add(R.id.fg_main,mDetFragment,"det");
       }else {
          fragmentTransaction.show(mDetFragment);
     }

6. addToBackStack

官方的说法是取决于你是否要在回退的时候显示上一个Fragment,当添加该方法时按返回键会返回到上一个页面。

你可能感兴趣的:(五大组件)