Fragment 全面使用及懒加载初试 -- 指北

4,137 阅读7分钟
原文链接: www.jianshu.com

随着Fragment的使用越来越简便好用,项目中使用Fragment越来越频繁。这不仅可以减少频繁创建销毁Activity时的开销,也可以通过统一管理Fragment让我们的代码更加清晰,更方便别人接手二次开发。

1.Fragment简介

这里简单根据官方文档说下什么是Fragment?

A Fragment is a piece of an application's user interface or behavior that can be placed in an Activity. 
Interaction with fragments is done through FragmentManager, 
which can be obtained via Activity.getFragmentManager() and Fragment.getFragmentManager().

api文档已经说的很清楚了,Fragment是依附在Activity中的一个表现用户界面或者行为的片段,你可以将片段视为 Activity 的模块化组成部分,它具有自己的生命周期,能接收自己的输入事件,并且可以在 Activity 运行时添加或移除片段(有点像可以在不同 Activity 中重复使用的“子 Activity”)。当然,我们可以通过getFragmentManager()获取FragmentManager来管理Fragment。

2.Fragment使用

在学习使用前,我们先罗列下Fragment的生命周期:


fragment lifecycle from google guide

这里举个最简单的使用Fragment和验证生命周期的栗子:
activity_main.xml布局:




    


MainActiviy:

getSupportFragmentManager().beginTransaction().add(R.id.frag_container,BlankFragment.getInstance()).commit();

fragment_blank.xml布局:



    


当我们启动MainActiviy时,在onCreate时就把Fragment中add进来了(实际使用中当然是根据场景add、hide、show、remove),这个Fragment的生命周期如下,可以结合上图一起理解:

01-11 00:32:42.912 8838-8838/com.blink.dagger D/com.blink.dagger.BlankFragment: onAttach
01-11 00:32:42.912 8838-8838/com.blink.dagger D/com.blink.dagger.BlankFragment: onCreate
01-11 00:32:42.912 8838-8838/com.blink.dagger D/com.blink.dagger.BlankFragment: onCreateView
01-11 00:32:42.927 8838-8838/com.blink.dagger D/com.blink.dagger.BlankFragment: onViewCreated
01-11 00:32:47.823 8838-8838/com.blink.dagger D/com.blink.dagger.BlankFragment: onDestroyView
01-11 00:32:47.832 8838-8838/com.blink.dagger D/com.blink.dagger.BlankFragment: onDestroy
01-11 00:32:47.832 8838-8838/com.blink.dagger D/com.blink.dagger.BlankFragment: onDetach

所以一些关于init的操作我们最好放进onCreateView、onViewCreated中操作。

当然,还有一种不常用的方法。我们可以直接把Fragment通过标签添加至Activity的布局文件中:

    

接着通过getFragmentManager().findFragmentById或者getFragmentManager().findFragmentByTag来实例化该Fragment。不过个人觉得通过这种方式添加Fragment会提高代码的维护成本,说不定还会给自己挖坑!

3. Fragment 通信

在使用Fragment的时候,肯定需要交互传递不同的数据信息,我们可以把这部分的交互分为两种类型: 1.Fragment && Activity , 2. Fragment && Fragment 。

  • Fragment && Activity

这里首先说个小技巧,当我们需要通过Activity向目标Fragment传递一些简单数据时,完全可以通过Bundle和Arguments将我们的Activity与Fragment解耦(如果通过Intent传递那就将宿主Activity、Fragment绑定到一起了,看实际情况选择吧)。不知道的同学可以通过代码理解,如下:

    public static BlankFragment getInstance(String argument) {
        Bundle bundle = new Bundle();
        bundle.putString("extra", argument);
        if (blankFragment == null) {
            blankFragment = new BlankFragment();
            blankFragment.setArguments(bundle);
        }
        return blankFragment;
    }

在Activity中通过上述getInstance()实例化Fragment时,就可以传入string。在Fragment的onCreateView时,可以将该string取出:

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        Log.d(TAG,"onCreateView");
        Bundle bundle = getArguments();
        if (bundle != null) {
            String extra = bundle.getString("extra");
        }
        return inflater.inflate(R.layout.fragment_blank, container, false);
    }

另外一种常见的Fragment && Activity交互场景就是当我们在Fragment中处理事件后,要通知Activity也要做出相应表现。我们先做一个简单的假设场景,当我们点击Fragment中的view时,要在宿主Activity中将该操作展示给用户哪个view被点击了,我们可以这样实现(通过接口回调的方法):

首先在Fragment中定义一个接口,用于在Activity中回调:

    // Container Activity must implement this interface
    public interface OnFragmentSelectedListener {
        public void showText(String content);
    }

在我们点击不同view的时候触发showText()方法 :

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.mText:
                mListener.showText("TextView is clicked !");
                break;
            case R.id.mButton:
                mListener.showText("Button is clicked !");
                break;
        }
    }

不要忘了将实现该接口的Activity转化为我们的"listener":

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        try {
            mListener = (OnFragmentSelectedListener) getActivity();
        } catch (ClassCastException e) {
            throw new ClassCastException("activity must implement OnFragmentSelectedListener");
        }
        Log.d(TAG, "onAttach");
    }

最后在Activity中实现我们的接口就大功告成了~:

    @Override
    public void showText(String content) {
        mTextView.setText(content);
    }

运行效果如下(屏幕最上方的TextView属于Activity,而不是写在Fragment的布局中):


  • Fragment && Fragment :
    Fragment和Fragment间数据交互,应该也是会经常用到的。第一时间,我们可能本能地会想到使用宿主Activity做传递媒介。原理其实也是通过使用onActivityResult回调,完成Fragment && Fragment 的数据交互,这其中有两个比较重要的方法:Fragment.setTargetFragment、getTargetFragment()。代码也是比较简单的~这里只给出一小部分了:

通过setTargetFragment来连接需要交互的Fragment

requstFragment.setTargetFragment(blankFragment.this, REQUEST_CODE);

接着实现onActivityResult处理传递过来的数据

    //实现回调处理数据(一般数据都封装进intent中了)
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_CODE)
        {
            String evaluate = data
                    .getStringExtra("");
            //dosomething();
        }
    }

在另一个需要发送数据的requsetFragment中回调即可

    // 判断是否设置了targetFragment  
    if (getTargetFragment() == null)
            return;

    Intent intent = new Intent();
    intent.putExtra("key", "value"]);
    getTargetFragment().onActivityResult(BlankFragment.REQUEST_CODE,
                                         Activity.RESULT_OK, intent);
4.Fragment懒加载

Fragment懒加载的使用场景便是在一个ViewPager管理多个Fragment时候,由于ViewPager"出色"的缓存机制会在显示一个Fragment的同时,预先加载好左右相邻两个Fragment的部分资源(会出发相邻Fragment的onCreate生命周期)。所谓的懒加载就是让我们加载数据的操作仅在Fragment可见的时候执行,可以节省不必要的开销。

看"懒加载"这个名称挺唬人的,其实就是通过系统提供的setUserVisibleHint(boolean isVisibleToUser)方法,在这其中进行数据的加载。

setUserVisibleHint:

Set a hint to the system about whether this fragment's UI is currently visible to the user. 
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.

唯一需要值得注意的是,setUserVisibleHint是在onCreateView周期前调用,此时布局中各View还未初始化,所以只能在setUserVisibleHint中进行纯数据的加载。那么牵涉到ui的操作(比如为某个view设置数据)该放在那里呢?栗:

/**
 * created by blink
 */
public abstract class LazyFragment extends Fragment {

    private boolean isVisible;
    private boolean isViewCreated;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(getLayoutID(), container, false);
        Log.e("blink","onCreateView");
        isViewCreated = true;
        return view;
    }


    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if(getUserVisibleHint()) {
            isVisible = true;
            loadForData();
        } else {
            isVisible = false;
            onInvisible();
        }
        if (isViewCreated && isVisible)
            loadForUI();
    }

    protected abstract void onInvisible();

    protected abstract void loadForData();

    protected abstract void loadForUI();

    // provide layout id
    public abstract int getLayoutID();

}

可以看到纯数据加载的操作,我们重写在loadForData()中就可以了。而涉及到ui的数据加载操作,我们必须放在loadForUI()中,此方法仅当用户可见,并且onCreata后才会调用,就可以避免view还未创建就去使用的NullPoint(不过这样封装挖了一个小小坑~不知道大家看出来没~)。具体封装就看具体需求了~为了验证以上推论,我把打印log的时间点附一下:

1-12 23:20:33.765 5845-5845/com.example.viewpager E/blink: onCreateView
01-12 23:20:47.924 5845-5845/com.example.viewpager D/blink: onInvisible_Third
01-12 23:20:47.925 5845-5845/com.example.viewpager D/blink: onInvisible_First
01-12 23:20:47.925 5845-5845/com.example.viewpager D/blink: loadForData_Second
01-12 23:20:47.925 5845-5845/com.example.viewpager D/blink: loadForUI_Second
01-12 23:20:47.925 5845-5845/com.example.viewpager D/blink: onAttach_Third
01-12 23:20:47.933 5845-5845/com.example.viewpager E/blink: onCreateView
01-12 23:20:55.475 5845-5845/com.example.viewpager D/blink: onInvisible_Second
01-12 23:20:55.476 5845-5845/com.example.viewpager D/blink: loadForData_Third
01-12 23:20:55.476 5845-5845/com.example.viewpager D/blink: loadForUI_Third

具体的封装Fragment和ViewPager等以后有时间补充~(996忙得真心伤不起!!!)

Over,欢迎指出错误,互相学习~~~

最后求赞求关注~