Fragment-踩坑

Posted wzgiceman

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Fragment-踩坑相关的知识,希望对你有一定的参考价值。

背景

Fragment已经成为android开发界面设计中不可或缺的一部分,同时也发挥着越来越重要的角色,虽然Fragment已经能出色的项目开发,但是在使用过程中也暴露了越来越多的问题,虽然google也一直在及时的修复,但是还是有很多坑,所以决定记录Fragment使用过程中的使用问题,避免小伙伴们重复踩坑。

在了解踩坑之前,我们需要先了解Fragment的使用要点和使用方法


Fragment介绍

作为 view 界面的一部分,Fragment 的存在必须依附于 FragmentActivit使用,并且与 FragmentActivit 一样,拥有自己的独立的生命周期,同时处理用户的交互动作。同一个 FragmentActivit 可以有一个或多个 Fragment 作为界面内容,同样Fragment也可以拥有多个子Fragment,并且可以动态添加、删除 Fragment,让UI的重复利用率和易修改性得以提升,同样可以用来解决部分屏幕适配问题。

另一方面,support v4 包中也提供了 Fragment,兼容 Android 3.0 之前的系统,使用兼容包需要注意两点:

  • 宿主Activity 必须继承自 FragmentActivity

  • 使用getSupportFragmentManager() 方法获取 FragmentManager 对象;


生命周期

Fragment同样是具备了独立的生命周期,但是和Activity的生命周期还有不一样的地方,如图:原图地址

Fragment初始化

Fragment默认有两种初始化的方法,一种new另一种是嵌入xml

  • new
FirstFragment firstFragment=new FirstFragment();
  • xml
<fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        class="com.wzgiceman.fragmentpit.Fragment.FirstFragment"/>

上面两种方法都可以初始得到一个Fragment对象,但是前者比后者的有点在于,前者更加的灵活,所以推荐使用第一种方式。


ActivityFragment传参

默认创建Fragment系统已经给我们初始了传参的代码

 /**
     * 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 FirstFragment.
     */
    // TODO: Rename and change types and number of parameters
    public static FirstFragment newInstance(String param1, String param2) 
        FirstFragment fragment = new FirstFragment();
        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);
        if (getArguments() != null) 
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        
    

这无疑是最好的选择


回调

Fragment 类提供有startActivityForResult()方法用于 Activity 间的页面跳转和数据回传,其实内部也是调用 Activity 的对应方法。但是在页面返回时需要注意 Fragment 没有提供 setResult() 方法,可以通过宿主 Activity 实现。


FragmentManagerFragmentTransaction使用

FragmentManager

Activity中使用Fragment可以使用getSupportFragmentManager获取一个FragmentManager对象,但是在Fragment中显示子Fragment需要调用FragmentgetChildFragmentManager()

源码如下:

public final FragmentManager getChildFragmentManager() 
        throw new RuntimeException("Stub!");
    

FragmentTransaction

Fragment 的动态添加、删除等操作都需要借助于 FragmentTransaction 类来完成,比如上面提到的 commit() 操作,下面是几种常用的方法:

  • add() 系列:添加 Fragment 到 Activity 界面中;

  • remove():移除 Activity 中的指定 Fragment;

  • replace() 系列:通过内部调用 remove() 和 add() 完成 Fragment 的修改;

  • hide() 和 show():隐藏和显示 Activity 中的 Fragment;

  • addToBackStack():添加当前事务到回退栈中,即当按下返回键时,界面回归到当前事物状态;

  • commit():提交事务,所有通过上述方法对 Fragment 的改动都必须通过调用 commit() 方法完成提交

replace()hide()区别

replace()hide()都可以动态的在Activity中显示多个Fragment,并且可以来回灵活的切换,但是它们有很大的区别,replace() 方法不会保留 Fragment 的状态,也就是说诸如 EditText 内容输入等用户操作在 remove() 时会消失;但是hide()却不会,能完整的保留用户的处理信息。

addToBackStack()退栈
当用户按下返回键时,如果回退栈中保存有之前的事务,会先执行事务回退,然后再执行Activityfinish()方法 。

简单使用

通过FragmentManagerFragmentTransaction结合使用,我们可以将第一种初始化的Fragment动态的显示到界面中,这里使用replace()演示:

 FragmentManager fm = getSupportFragmentManager();
 FragmentTransaction ft = fm.beginTransaction();
 ft.replace(R.id.fl_fragment, FirstFragment.newInstance("A","B"));
 ft.commit();

踩坑

在了解了Fragment的基础使用后,可以开始使用过程中的踩坑了

getActivity() 引用问题

Fragment中常常需要使用到content对象,比如网络加载现在一个progress等等,这时候可能你遇到过getActivity()返回null,或者平时运行完好的代码,在“内存重启”之后,调用getActivity()的地方却返回null,报了空指针异常。

大多数情况下的原因:你在调用了getActivity()时,当前的Fragment已经onDetach()了宿主Activity
比如:你在popFragment之后,该Fragment的异步任务仍然在执行,并且在执行完成后调用了getActivity()方法,这样就会空指针;

解决办法

  • getContext()替代getActivity()

  • 定义全局变量,在FragmentonAttach(Activity activity)准备废弃或者onAttach(Content content)方法中初始化

   Content content;
    @Override
    public void onAttach(Content content) 
        super.onAttach(content);
        this.content=content;
    

显然第一种方法更加灵活方便了。


高耦合

当子Fragment需要调用宿主Acitivity的方法时,比如子Fragment需要发送一个广播,但是Fragment没有改方法,所以需要借助宿主Activity去发送,这时候常常需要强制转换content对象,然后调用宿主Acitivity发方发送广播,这种直接使用的方式违背了高聚低耦的设计原则;

解决办法

通过接口抽象的方法,通过接口去调用宿主Activity的方法。

  • 定义接口
/**
 * 发送广播
 * Created by WZG on 2016/12/31.
 */

public interface SendBListener 
    void send();
  • 实现接口
public class FirstFragment extends Fragment 
    SendBListener listener;

    public void setListener(SendBListener listener) 
        this.listener = listener;
    

    @OnClick(value = R.id.tv)
    void onTvClick(View view) 
        listener.send();
    
    
  • 调用
public class MainActivity extends AppCompatActivity implements SendBListener
    @BindView(R.id.fl_fragment)
    FrameLayout mFlFragment;

    @Override
    public void send() 
        sendBroadcast(new Intent("xxxxxx"));
    

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        FirstFragment firstFragment=new FirstFragment();
        firstFragment.setListener(this);
      
 

重叠

由于采用创建对象的方式去初始化Fragment对象,当宿主Activity在界面销毁或者界面重新执行onCreate()方法时,就有可能再一次的执行Fragment的创建初始,而之前已经存在的 Fragment 实例也会销毁再次创建,这不就与 Activity 中 onCreate() 方法里面第二次创建的 Fragment 同时显示从而发生 UI 重叠的问题。

如果宿主界面Acitivity可以横竖屏切换,导致的生命周期重新刷新也同理可导致界面的重叠问题。

解决办法

  • 推荐:利用savedInstanceState判断
  @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();

        FirstFragment firstFragment;
        if (savedInstanceState==null) 
            firstFragment=new FirstFragment();
            ft.add(R.id.fl_fragment, firstFragment, "FirstFragment");
        else 
            firstFragment = (FirstFragment) fm.findFragmentByTag("FirstFragment");
        

    
  • Activity 提供的 onAttachFragment() 方法中处理
 @Override
    public void onAttachFragment(Fragment fragment) 
        super.onAttachFragment(fragment);
        if (fragment instanceof  FirstFragment)
            firstFragment = (FirstFragment) fragment;
        
   
  • 创建Fragment时判断

        Fragment fragment = getSupportFragmentManager().findFragmentByTag("FirstFragment");
        if (fragment==null) 
            firstFragment =new FirstFragment();
            ft.add(R.id.fl_fragment, firstFragment, "FirstFragment");
        else 
            firstFragment = (FirstFragment) fragment;
        

Fragment转场动画

如果你想给下一个Fragment设置进栈动画和出栈动画,setCustomAnimations(enter, exit)只能设置进栈动画,第二个参数并不是设置出栈动画;
请使用setCustomAnimations(enter, exit, popEnter, popExit),这个方法的第1个参数对应进栈动画,第4个参数对应出栈动画,所以是setCustomAnimations(进, exit, popEnter, 出))


Fragment状态监听

很多时候,我们需要在多Fragment中刷新界面,当然由于Fragment有自己独立的生命周期但是也依赖宿主Activity存在,所以在刷新界面的时候需要注意如:

当宿主Activity A进入B中,又冲B返回到A,这时候宿主A执行onResume()方法,当然这时候的Fragment也会执行onResume()

当宿主Activity A中的Fragment全部初始完成显示过,在切换Fragment的时候不会再一次触发onResume()方法,但是却可以触发Fragment的onHiddenChanged(boolean hidden)方法

所以当我们需要实时刷新Fragment界面的时候,需要同时结合onResume()onHiddenChanged(boolean hidden)方法去刷新当前显示Fragment而避免刷新hide()Fragment

使用

    Override
    public void onResume() 
        super.onResume();
        //当前是否是现实状态
        if (isVisible())
            //刷新界面
            updateUI();
        
    

    @Override
    public void onHiddenChanged(boolean hidden) 
        super.onHiddenChanged(hidden);
        //方法重复发起刷新界面
        if (isVisible() && isResumed())
            updateUI();
        
    

交流

QQ交流群,谈谈梦想,聊聊人生!

以上是关于Fragment-踩坑的主要内容,如果未能解决你的问题,请参考以下文章

Weex线上踩坑实录

踩坑 Windows 服务来宿主 .NET 程序

Android Fragment生命周期及静态加载

Android中Fragment与Activity之间的交互(两种实现方式)

Fragment总结

Fragment 使用详解