Android Fragment 填坑

01.getActivity()空指针

  • 遇到的问题

    • 可能你遇到过getActivity()返回null,或者平时运行完好的代码,在“内存重启”之后,调用getActivity()的地方却返回null,报了空指针异常。
  • 出现的原因分析

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

    • 在Fragment基类里设置一个Activity mActivity的全局变量,在onAttach(Activity activity)里赋值,使用mActivity代替getActivity(),保证Fragment即使在onDetach后,仍持有Activity的引用(有引起内存泄露的风险,但是异步任务没停止的情况下,本身就可能已内存泄漏,相比Crash,这种做法“安全”些),即:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Override
    public void onAttach(Context context) {
    super.onAttach(context);
    activity = (MainActivity) context;
    }

    @Override
    public void onDetach() {
    super.onDetach();
    activity = null;
    }

02.Fragment发生重叠

  • 发生重叠的原因

    • 发生了页面重启(旋转屏幕、内存不足等情况被强杀重启)。
      • 由于采用创建对象的方式去初始化Fragment对象,当宿主Activity在界面销毁或者界面重新执行onCreate()方法时,就有可能再一次的执行Fragment的创建初始,而之前已经存在的 Fragment 实例也会销毁再次创建,这不就与 Activity 中 onCreate() 方法里面第二次创建的 Fragment 同时显示从而发生 UI 重叠的问题。
      • 如果宿主界面Acitivity可以横竖屏切换,导致的生命周期重新刷新也同理可导致界面的重叠问题。
    • 重复replace|add Fragment 或者 使用show , hide控制Fragment;
  • 通过源码分析重叠原因

    • Activity中有个onSaveInstanceState()方法,该方法会在Activity将要被kill的时候回调(例如进入后台、屏幕旋转前、跳转下一个Activity等情况下会被调用)。
    • 当Activity只执行onPause方法时(透明Activity),这时候如果App设置的targetVersion大于11则不会执行onSaveInstanceState方法。
    • 此时系统帮我们保存一个Bundle类型的数据,我们可以根据自己的需求,手动保存一些例如播放进度等数据,而后如果发生了页面重启,我们可以在onRestoreInstanceState()或onCreate()里get该数据,从而恢复播放进度等状态。
    • 产生Fragment重叠的原因就与这个保存状态的机制有关,大致原因就是系统在页面重启前,帮我们保存了Fragment的状态,但是在重启后恢复时,视图的可见状态没帮我们保存,而Fragment默认的是show状态,所以产生了Fragment重叠现象。
  • 解决办法:推荐利用savedInstanceState判断

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @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() 方法中处理
    1
    2
    3
    4
    5
    6
    7
    @Override
    public void onAttachFragment(Fragment fragment) {
    super.onAttachFragment(fragment);
    if (fragment instanceof FirstFragment){
    firstFragment = (FirstFragment) fragment;
    }
    }
    • 创建Fragment时判断
    1
    2
    3
    4
    5
    6
    7
    Fragment fragment = getSupportFragmentManager().findFragmentByTag("FirstFragment");
    if (fragment==null) {
    firstFragment = new FirstFragment();
    ft.add(R.id.fl_fragment, firstFragment, "FirstFragment");
    }else {
    firstFragment = (FirstFragment) fragment;
    }

03.Fragment高耦合性

  • 那些场景体现fragment高耦合性

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    /**
    * 发送广播
    * 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);
    }
    }

04.处理返回键逻辑

4.1 使用transaction.addToBackStack(null)添加fragment

  • 先看下代码

    • 出现的问题是:在某个activity上添加fragment,不处理宿主activity中返回键逻辑,点击返回键,关闭了fragment同时也关闭了activity。
    1
    2
    3
    4
    5
    6
    OrderStatesFragment fragment = new OrderStatesFragment();
    FragmentManager fragmentManager = activity.getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.add(android.R.id.content, fragment, "OrderStatesFragment");
    transaction.addToBackStack(null);
    transaction.commitAllowingStateLoss();
  • 应该符合的逻辑

    • 在某个activity上添加fragment,如果不处理宿主activity中返回键逻辑,点击返回键,依次关闭了fragment直到没有,回到宿主activity页面。再次点击返回键,则关闭activity!
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
    List<Fragment> fragments = getSupportFragmentManager().getFragments();
    // fragmentManager里默认就有1个fragment,如果fragments大于1 说明activity添加里新的fragment
    if (fragments.size() > 2) {
    onBackPressed();
    }
    return true;
    }
    return super.onKeyDown(keyCode, event);
    }
  • 然后更改一下代码,发现返回键逻辑就行不通了。

    1
    2
    3
    4
    5
    OrderStatesFragment fragment = new OrderStatesFragment();
    FragmentManager fragmentManager = activity.getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.add(android.R.id.content, fragment, "OrderStatesFragment");
    transaction.commitAllowingStateLoss();