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
7Fragment 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
6OrderStatesFragment 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
5OrderStatesFragment fragment = new OrderStatesFragment();
FragmentManager fragmentManager = activity.getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(android.R.id.content, fragment, "OrderStatesFragment");
transaction.commitAllowingStateLoss();