为了账号安全,请及时绑定邮箱和手机立即绑定

Fragment详解(一)

标签:
Android


android fragment的一个简单写法是:

public class WebViewFragment extends Fragment {

    private WebView mWebView;
    private View mRootView;
    private String mUrl;

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mUrl = getArguments().getString("url");
        setUrl();
    }


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

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        mRootView = inflater.inflate(R.layout.tp_webview_main,null);
        mWebView = (WebView) mRootView.findViewById(R.id.webview);
        return mRootView;
    }
  }

Fragment有很多可以复写的方法,其中最常用的就是onCreateView(),该方法返回Fragment的UI布局,需要注意的是inflate()的第三个参数是false,因为在Fragment内部实现中,会把该布局添加到container中,如果设为true,那么就会重复做两次添加,则会抛如下异常:

Caused by: java.lang.IllegalStateException: The specified child already 
has a parent. You must call removeView() on the child's parent first.

如果在创建Fragment时要传入参数,必须要通过setArguments(Bundle bundle)方式添加,而不建议通过为Fragment添加带参数的构造函数,因为通过setArguments()方式添加,在由于内存紧张导致Fragment被系统杀掉并恢复(re-instantiate)时能保留这些数据

我们可以在Fragment的onAttach()中通过getArguments()获得传进来的参数,并在之后使用这些参数。如果要获取Activity对象,不建议调用getActivity(),而是在onAttach()中将Context对象强转为Activity对象。

创建完Fragment后,接下来就是把Fragment添加到Activity中。在Activity中添加Fragment的方式有两种:

  • 静态添加:通过xml的方式添加,缺点是一旦添加就不能在运行时删除。

  • 动态添加:运行时添加,这种方式比较灵活,因此建议使用这种方式。

这里只给出动态添加的方式。首先Activity需要有一个容器存放Fragment,一般是FrameLayout,因此在Activity的布局文件中加入FrameLayout:

<FrameLayout
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

然后在onCreate()中,通过以下代码将Fragment添加进Activity中。

if (bundle == null) {
    getSupportFragmentManager().beginTransaction()
        .add(R.id.container, Fragment1.newInstance("hello world"), "f1")        //.addToBackStack("fname")
        .commit();
}

这里需要注意几点:

  • 因为我们使用了support库的Fragment,因此需要使用getSupportFragmentManager()获取FragmentManager。

  • add()是对Fragment众多操作中的一种,还有remove(), replace()等,第一个参数是根容器的id(FrameLayout的id,即”@id/container”),第二个参数是Fragment对象,第三个参数是fragment的tag名,指定tag的好处是后续我们可以通过Fragment1 frag = getSupportFragmentManager().findFragmentByTag("f1")从FragmentManager中查找Fragment对象。

  • 在一次事务中,可以做多个操作,比如同时做add().remove().replace()

  • commit()操作是异步的,内部通过mManager.enqueueAction()加入处理队列。对应的同步方法为commitNow()commit()内部会有checkStateLoss()操作,如果开发人员使用不当(比如commit()操作在onSaveInstanceState()之后),可能会抛出异常,而commitAllowingStateLoss()方法则是不会抛出异常版本的commit()方法,但是尽量使用commit(),而不要使用commitAllowingStateLoss()

  • addToBackStack("fname")是可选的。FragmentManager拥有回退栈(BackStack),类似于Activity的任务栈,如果添加了该语句,就把该事务加入回退栈,当用户点击返回按钮,会回退该事务(回退指的是如果事务是add(frag1),那么回退操作就是remove(frag1));如果没添加该语句,用户点击返回按钮会直接销毁Activity。

  • Fragment有一个常见的问题,即Fragment重叠问题,这是由于Fragment被系统杀掉,并重新初始化时再次将fragment加入activity,因此通过在外围加if语句能判断此时是否是被系统杀掉并重新初始化的情况。

Fragment有个常见的异常:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
    at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
    at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
    at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
    at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)

该异常出现的原因是:commit()onSaveInstanceState()后调用。首先,onSaveInstanceState()onPause()之后,onStop()之前调用。onRestoreInstanceState()onStart()之后,onResume()之前。

因此避免出现该异常的方案有:
  • 不要把Fragment事务放在异步线程的回调中,比如不要把Fragment事务放在AsyncTask的onPostExecute(),因此onPostExecute()可能会在onSaveInstanceState()之后执行。

  • 逼不得已时使用commitAllowingStateLoss()


  Fragment的生命周期如下:

 https://img1.sycdn.imooc.com//5c076ef20001bce808341330.jpg

  • onAttach():Fragment和Activity相关联时调用。可以通过该方法获取Activity引用,还可以通过getArguments()获取参数。

  • onCreate():Fragment被创建时调用。

  • onCreateView():创建Fragment的布局。

  • onActivityCreated():当Activity完成onCreate()时调用。

  • onStart():当Fragment可见时调用。

  • onResume():当Fragment可见且可交互时调用。

  • onPause():当Fragment不可交互但可见时调用。

  • onStop():当Fragment不可见时调用。

  • onDestroyView():当Fragment的UI从视图结构中移除时调用。

  • onDestroy():销毁Fragment时调用。

  • onDetach():当Fragment和Activity解除关联时调用。

上面的方法中,只有onCreateView()在重写时不用写super方法,其他都需要。

常见的Fragment面试题:

  1. Fragment与view有啥区别?

  Fragment是有生命周期的

  2. Fragment特点:

  • Fragment可以作为Activity界面的一部分组成出现;

  • 可以在一个Activity中同时出现多个Fragment,并且一个Fragment也可以在多个Activity中使用;

  • 在Activity运行过程中,可以添加、移除或者替换Fragment;

  • Fragment可以响应自己的输入事件,并且有自己的生命周期,它们的生命周期会受宿主Activity的生命周期影响。

3. 为什么引入Fragment?

Android在api11中加入了Fragment,主要是给大屏设备上进行动态灵活的UI设计提供支持,更多的情况下我们把Fragment作为一个可重复利用的模块化组件,利用它自身的生命周期来对功能模块进行分离。

4. FragmentPagerAdapter与FragmentStatePagerAdapter的区别?

FragmentPagerAdapter一般用于少量界面的ViewPager,划过的Fragment会一直保存在内存中不会被销毁。而FragmentStatePagerAdapter适用于界面较多的ViewPager,它会保存当前的界面以及下一个界面和上一个界面,最多保存三个,其他的会在destroyItem()方法中被销毁调,可以节省内存占用。

5. Fragment通信

在Fragment中调用Activity中的方法

通过getActivity()方法获取所附属的Activity后调用当中的方法

在Activity中调用Fragment中的方法

采用接口回调的形式

在Fragment中调用Fragment中的方法

findFragmentById()

6. 无法在activity调用了onSaveInstanceState之后再执行commit进行添加Fragment

Activity被系统回收(界面已经不存在了),为了能在下次打开的时候恢复原来的样子,系统为我们保存界面的所有状态,这个时候再去修改界面理论上肯定是不被允许的,为了避免这种异常可以使用:

transaction.commitAllowingStateLoss();

来提交添加Fragment到Activity的事务,与commit()不同的是使用这种方法允许丢失界面的状态和信息。

7. ViewPager与Fragment结合使用时的懒加载问题

所谓的 “懒加载” 就是数据只有在Fragment对于用户可见的时才进行加载。因为ViewPager会帮我们预先初始化Fragment。由于这个特性,我们不能把数据的加载放到onCreateView方法或者onCreate方法中。

因此,我们需要判定Fragment在什么时候是处于可见的状态。一般我们通常是通过Fragment中的生命周期方法onResume来判断Fragment是否可见,但是由于ViewPager预加载的特性,Fragment即便不可见也会执行onResume方法,因此使用这个方法进行可见性的判断就行不通了。这个时候我们需要用到下面的这个方法来进行Fragment可见性的判断:

setUserVisibleHint()方法:

什么时候被调用?

当fragment被创建的时,setUserVisibleHint(boolean isVisibleToUser)方法会被调用,且传入参数值为false。

当fragment可见时,setUserVisibleHint(boolean isVisibleToUser)方法会被调用,且传入参数值为true。

当fragment由 可见 -> 不可见 时,setUserVisibleHint(boolean isVisibleToUser)方法会被调用,且传入参数值为false。

了解了这个方法之后,我们应该比较清楚懒加载该如何实现了吧,只需要当setUserVisibleHint(boolean isVisibleToUser)方法中的 isVisibleToUser 参数的值为true的时候我们才开始进行数据的加载。但是有一点需要需要注意的是 setUserVisibleHint(boolean isVisibleToUser)方法在Fragment的生命周期方法onCreate 之前调用的,也就是说他并不在Fragment的生命周期中。既然是在 onCreate 方法之前被调用,这样就存在许多不确定因素,如果Fragmnet的View还没有完成初始化之前,就在setUserVisibleHint()方法中进行UI的操作,这样显然会导致空指针的出现。因此我们需要对Fragment创建的View进行缓存,确保缓存的View不为空的情况下我们才可以在setUserVisibleHint方法中进行UI操作。


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消