4. Fragment的状态保存
5、Fragment 的startActivityForResult
6、Fragment 替换和隐藏的区别
7、使用Fragment创建对话框
8、Fragment常报的一些错
因为所有的Fragment都是依附于Activity的,所以通信起来并不复杂,大概归纳为:
a、如果你Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法
b、如果Activity中未保存任何Fragment的引用,那么没关系,每个Fragment都有一个唯一的TAG或者ID,可以通过getFragmentManager.findFragmentByTag()或者findFragmentById()获得任何Fragment实例,然后进行操作。
c、在Fragment中可以通过getActivity得到当前绑定的Activity的实例,然后进行操作。
注:如果在Fragment中需要Context,可以通过调用getActivity(),如果该Context需要在Activity被销毁后还存在,则使用getActivity().getApplicationContext()。
public class ContentActivity extends SingleFragmentActivity { private ContentFragment mContentFragment; @Override protected Fragment createFragment() { String title = getIntent().getStringExtra(ContentFragment.ARGUMENT); mContentFragment = ContentFragment.newInstance(title);
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle bundle = getArguments(); if (bundle != null) { mArgument = bundle.getString(ARGUMENT); } } public static ContentFragment newInstance(String argument) { Bundle bundle = new Bundle(); bundle.putString(ARGUMENT, argument); ContentFragment contentFragment = new ContentFragment(); contentFragment.setArguments(bundle); return contentFragment; }
这样就完成了Fragment和Activity间的解耦。当然了这里需要注意:
我们点击跳转到对应Activity的Fragment中,并且希望它能够返回参数,那么我们肯定是使用Fragment.startActivityForResult ;
在Fragment中存在startActivityForResult()以及onActivityResult()方法,
但是呢,没有setResult()方法,用于设置返回的intent,这样我们就需要通过调用getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent);。
没有serResult的方法,就需要调用Activity的serResult方法!
public class ListTitleActivity extends SingleFragmentActivity { private ListTitleFragment mListFragment; @Override protected Fragment createFragment() { mListFragment = new ListTitleFragment(); return mListFragment; } }
public class ListTitleFragment extends ListFragment { public static final int REQUEST_DETAIL = 0x110; private ListmTitles = Arrays.asList("Hello", "World", "Android"); private int mCurrentPos ; private ArrayAdapter mAdapter ; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setListAdapter(mAdapter = new ArrayAdapter (getActivity(), android.R.layout.simple_list_item_1, mTitles)); } @Override public void onListItemClick(ListView l, View v, int position, long id) { mCurrentPos = position ; Intent intent = new Intent(getActivity(),ContentActivity.class); intent.putExtra(ContentFragment.ARGUMENT, mTitles.get(position)); startActivityForResult(intent, REQUEST_DETAIL); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { Log.e("TAG", "onActivityResult"); super.onActivityResult(requestCode, resultCode, data); if(requestCode == REQUEST_DETAIL) { mTitles.set(mCurrentPos, mTitles.get(mCurrentPos)+" -- "); mAdapter.notifyDataSetChanged(); } } }
public class ContentActivity extends SingleFragmentActivity { private ContentFragment mContentFragment; @Override protected Fragment createFragment() { String title = getIntent().getStringExtra(ContentFragment.ARGUMENT); mContentFragment = ContentFragment.newInstance(title); return mContentFragment; } }
public class ContentFragment extends Fragment { private String mArgument; public static final String ARGUMENT = "argument"; public static final String RESPONSE = "response"; public static final String EVALUATE_DIALOG = "evaluate_dialog"; public static final int REQUEST_EVALUATE = 0X110; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle bundle = getArguments(); if (bundle != null) { mArgument = bundle.getString(ARGUMENT); } } public static ContentFragment newInstance(String argument) { Bundle bundle = new Bundle(); bundle.putString(ARGUMENT, argument); ContentFragment contentFragment = new ContentFragment(); contentFragment.setArguments(bundle); return contentFragment; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Random random = new Random(); TextView tv = new TextView(getActivity()); ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); tv.setLayoutParams(params); tv.setText(mArgument); tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 30); tv.setGravity(Gravity.CENTER); tv.setBackgroundColor(Color.argb(random.nextInt(100), random.nextInt(255), random.nextInt(255), random.nextInt(255))); // set click tv.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { EvaluateDialog dialog = new EvaluateDialog(); //注意setTargetFragment dialog.setTargetFragment(ContentFragment.this, REQUEST_EVALUATE); dialog.show(getFragmentManager(), EVALUATE_DIALOG); } }); return tv; } //接收返回回来的数据 @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_EVALUATE) { String evaluate = data .getStringExtra(EvaluateDialog.RESPONSE_EVALUATE); Toast.makeText(getActivity(), evaluate, Toast.LENGTH_SHORT).show(); Intent intent = new Intent(); intent.putExtra(RESPONSE, evaluate); getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent); } }
startActivityForResult ()
,会发现无论如何都不能在
onActivityResult()
中接收到返回值,只有最顶层的父Fragment才能接收到,这是一个support v4库的一个BUG,不过在前两天发布的support 23.2.0库中,已经修复了该问题,嵌套的子Fragment也能正常接收到返回数据了!
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (mAppManageFragment != null) { mAppManageFragment.onActivityResult(requestCode, resultCode, data); } }
@Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if(requestCode==0){ adapter.refreshDeleteList(); } }
我们成功实现了向活动中动态添加碎片的功能,不过你尝试一下就会发现,通过点击按钮添加了一个碎片之后,这时按下Back键程序就会直接退出。如果这里我们想模仿类似于返回栈的效果,按下Back键可以回到上一个碎片,该如何实现呢?
其实很简单,FragmentTransaction中提供了一个addToBackStack()方法,可以用于将一个事务添加到返回栈中,修改MainActivity中的代码,如下所示:
public class MainActivity extends Activity implements OnClickListener { …… @Override public void onClick(View v) { switch (v.getId()) { case R.id.button: AnotherRightFragment fragment = new AnotherRightFragment(); FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction transaction = fragmentManager. beginTransaction(); transaction.replace(R.id.right_layout, fragment); transaction.addToBackStack(null); transaction.commit(); break; default: break; } } }
show()
,
hide()
最终是让Fragment的View
setVisibility
(true还是false),不会调用生命周期;
replace()
的话会销毁视图,即调用onDestoryView、onCreateView等一系列生命周期;
add()
和 replace()
不要在同一个阶级的FragmentManager里混搭使用。
使用场景
如果你有一个很高的概率会再次使用当前的Fragment,建议使用show()
,hide()
,可以提高性能。
在我使用Fragment过程中,大部分情况下都是用show()
,hide()
,而不是replace()
。
注意:如果你的app有大量图片,这时更好的方式可能是replace,配合你的图片框架在Fragment视图销毁时,回收其图片所占的内存
在多个 Fragment 之间进行切换的时候
replace()和hide的区别
首先replace 是代替,之前的Fragment会被清空掉,在再次切换回来的时候会进行重新加载。而hide是将之前的一个fragment 进行隐藏,将新的fragment 叠在上面进行显示.等在次调用的时候进行显示。不需要重新加载。测试这个最简单的方法就是 设计两个fragment 在同一位置上放一个按钮,一个有监听一个没有,但现实没有监听的按钮点击按钮的时候会响应一号按钮的监听事件。
另外一个对于 FragmentTransaction对象在commit之后就会失效。所以建议直接使用fragmentManager.beginTransaction() 这样就不会产生失效的问题了。
commit 于commitAllowingStateLoss();的区别
简单来说就是避免报错。
他们都调用了commitInternal
public int commit() {
return commitInternal(false);
}
public int commitAllowingStateLoss() {
return commitInternal(true);
}
这个两个方法区别就在传参判断后的处理方法checkStateLoss
当使用commit方法时,系统将进行状态判断,如果状态(mStateSaved)已经保存,将发生”Can not perform this action after onSaveInstanceState”错误。
如果mNoTransactionsBecause已经存在,将发生”Can not perform this action inside of ” + mNoTransactionsBecause错误
fragment 特点
Fragment可以作为Activity界面的一部分组成出现;
可以在一个Activity中同时出现多个Fragment,并且一个Fragment也可以在多个Activity中使用;
在Activity运行过程中,可以添加、移除或者替换Fragment;
Fragment可以响应自己的输入事件,并且有自己的生命周期,它们的生命周期会受宿主Activity的生命周期影响。
相信这两个PagerAdapter的子类,大家都不陌生吧~~自从Fragment问世,使用ViewPager再结合上面任何一个实例的制作APP主页的案例特别多~~~
那么这两个类有何区别呢?
主要区别就在与对于fragment是否销毁,下面细说:
FragmentPagerAdapter:对于不再需要的fragment,选择调用detach方法,仅销毁视图,并不会销毁fragment实例。
FragmentStatePagerAdapter:会销毁不再需要的fragment,当当前事务提交以后,会彻底的将fragmeng从当前Activity的FragmentManager中移除,state标明,销毁时,会将其onSaveInstanceState(Bundle outState)中的bundle信息保存下来,当用户切换回来,可以通过该bundle恢复生成新的fragment,也就是说,你可以在onSaveInstanceState(Bundle outState)方法中保存一些数据,在onCreate中进行恢复创建。
如上所说,使用FragmentStatePagerAdapter当然更省内存,但是销毁新建也是需要时间的。一般情况下,如果你是制作主页面,就3、4个Tab,那么可以选择使用FragmentPagerAdapter,如果你是用于ViewPager展示数量特别多的条目时,那么建议使用FragmentStatePagerAdapter。
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (null != rootView) { ViewGroup parent = (ViewGroup) rootView.getParent(); if (null != parent) { parent.removeView(rootView); } } else { rootView = inflater.inflate(layoutId, null); initView(rootView);// 控件初始化 } return rootView; }
public abstract class SingleFragmentActivity extends FragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_single_fragment); FragmentManager fm = getSupportFragmentManager(); Fragment fragment =fm.findFragmentById(R.id.id_fragment_container); if(fragment == null ) { fragment = createFragment() ; fm.beginTransaction().add(R.id.id_fragment_container,fragment).commit(); } }
为什么需要判null呢?
主要是因为,当Activity因为配置发生改变(屏幕旋转)或者内存不足被系统杀死,造成重新创建时,
我们的fragment会被保存下来,但是会创建新的FragmentManager,新的FragmentManager会首先会去获取保存下来的fragment队列,重建fragment队列,从而恢复之前的状态。
tv.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { EvaluateDialog dialog = new EvaluateDialog(); //注意setTargetFragment dialog.setTargetFragment(ContentFragment.this, REQUEST_EVALUATE); dialog.show(getFragmentManager(), EVALUATE_DIALOG); } }); return tv; } //接收返回回来的数据() @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_EVALUATE) { String evaluate = data .getStringExtra(EvaluateDialog.RESPONSE_EVALUATE); Toast.makeText(getActivity(), evaluate, Toast.LENGTH_SHORT).show(); Intent intent = new Intent(); intent.putExtra(RESPONSE, evaluate); getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent); } }
public class EvaluateDialog extends DialogFragment { private String[] mEvaluteVals = new String[] { "GOOD", "BAD", "NORMAL" }; public static final String RESPONSE_EVALUATE = "response_evaluate"; @Override public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle("Evaluate :").setItems(mEvaluteVals, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { setResult(which); } }); return builder.create(); } // 设置返回数据 protected void setResult(int which) { // 判断是否设置了targetFragment if (getTargetFragment() == null) return; Intent intent = new Intent(); intent.putExtra(RESPONSE_EVALUATE, mEvaluteVals[which]); getTargetFragment().onActivityResult(ContentFragment.REQUEST_EVALUATE, Activity.RESULT_OK, intent); } }
Fragment 的 transaction 允许你执行一系列的 Fragment 操作,但不幸的是,提交 transaction 是异步操作,并且在 UI 线程的 Handler 队列的队尾被提交。
这会在接收多个点击事件或配置发生改变时让你的 App 处在未知的状态。
Fragment 的实例能够通过 Fragment Manager 创建,例如下面的代码看起来没有什么问题:
看了上面的介绍,你可能会觉得Fragment有点可怕。
但是我想说,如果你只是浅度使用,比如一个Activity容器包含列表Fragment+详情Fragment这种简单情景下,
不涉及到popBackStack/Immediate(tag/id)
这些的方法,还是比较轻松使用的,出现的问题,网上都可以找到解决方案。
可能你遇到过getActivity()返回null,或者平时运行完好的代码,在“内存重启”之后,调用getActivity()的地方却返回null,报了空指针异常。
大多数情况下的原因:你在调用了getActivity()时,当前的Fragment已经onDetach()
了宿主Activity。
比如:你在pop了Fragment之后,该Fragment的异步任务仍然在执行,并且在执行完成后调用了getActivity()方法,这样就会空指针。
解决办法:
更"安全"的方法:(对于Fragment已经onDetach这种情况,我们应该避免在这之后再去调用宿主Activity对象,比如取消这些异步任务,但我们的团队可能会有粗心大意的情况,所以下面给出的这个方案会保证安全)
在Fragment基类里设置一个Activity mActivity的全局变量,在onAttach(Activity activity)
里赋值,使用mActivity代替getActivity()
,保证Fragment即使在onDetach
后,仍持有Activity的引用(有引起内存泄露的风险,但是异步任务没停止的情况下,本身就可能已内存泄漏,相比Crash,这种做法“安全”些),即:
protected Activity mActivity;
@Overridepublic void onAttach(Activity activity) {
super.onAttach(activity);
this.mActivity = activity;
}
/**
* 如果你用了support 23的库,上面的方法会提示过时,有强迫症的小伙伴,可以用下面的方法代替
*/@Overridepublic void onAttach(Context context) {
super.onAttach(context);
this.mActivity = (Activity)context;
}
大致意思是说我使用的 commit方法是在Activity的onSaveInstanceState()之后调用的,这样会出错,因为onSaveInstanceState
方法是在该Activity即将被销毁前调用,来保存Activity数据的,如果在保存玩状态后再给它添加Fragment就会出错。解决办法就
是把commit()方法替换成 commitAllowingStateLoss()就行了,其效果是一样的。
有很多小伙伴遇到这个异常,这个异常产生的原因是:
在你离开当前Activity等情况下,系统会调用onSaveInstanceState()
帮你保存当前Activity的状态、数据等,直到再回到该Activity之前(onResume()
之前),你执行Fragment事务,就会抛出该异常!(一般是其他Activity的回调让当前页面执行事务的情况,会引发该问题)
解决方法:
commitAllowingStateLoss()
方法提交,但是有可能导致该次提交无效!(在此次离开时恰巧Activity被强杀时)onResumeFragments()
或onPostResume()
),再执行该事务,配合数据保存,可以做到事务的完整性,不会丢失事务。示例代码 (以EventBus通知执行事务为例,其他场景思路一致):
@Override// 如果是在Fragment内, 则复写onResumeFragments()改为onResume()即可protected void onResumeFragments() {
super.onResumeFragments();
mIsSaved = true;
if (mTransactionEvent != null) {
// 这里执行事务
mTransactionEvent = null;
}
}
@Overrideprotected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mIsSaved = false;
}
@Subscribe(sticky = true) // sticky事件可以保证即使Activity被强杀,也会在恢复后拿到数据public void onEvent(TransactionEvent event) {
if (mIsSaved) {
// 这里执行事务
} else {
mTransactionEvent = event;
}
}
在类onCreate()
的方法加载Fragment,并且没有判断saveInstanceState==null
或if(findFragmentByTag(mFragmentTag) == null)
,导致重复加载了同一个Fragment导致重叠。(PS:replace
情况下,如果没有加入回退栈,则不判断也不会造成重叠,但建议还是统一判断下)
@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {
// 在页面重启时,Fragment会被保存恢复,而此时再加载Fragment会重复加载,导致重叠 ;if(saveInstanceState == null){
// 或者 if(findFragmentByTag(mFragmentTag) == null)// 正常情况下去 加载根Fragment
}
}
即在add()
或者replace()
时绑定一个tag,一般我们是用fragment的类名作为tag,然后在发生“内存重启”时,通过findFragmentByTag
找到对应的Fragment,并hide()
需要隐藏的fragment。
下面是个标准恢复写法:
@Overrideprotected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
TargetFragment targetFragment;
HideFragment hideFragment;
if (savedInstanceState != null) { // “内存重启”时调用
targetFragment = getSupportFragmentManager().findFragmentByTag(TargetFragment.class.getName);
hideFragment = getSupportFragmentManager().findFragmentByTag(HideFragment.class.getName);
// 解决重叠问题
getFragmentManager().beginTransaction()
.show(targetFragment)
.hide(hideFragment)
.commit();
}else{ // 正常时
targetFragment = TargetFragment.newInstance();
hideFragment = HideFragment.newInstance();
getFragmentManager().beginTransaction()
.add(R.id.container, targetFragment, targetFragment.getClass().getName())
.add(R.id,container,hideFragment,hideFragment.getClass().getName())
.hide(hideFragment)
.commit();
}
}
(2)对于宿主Activity,getSupportFragmentManager()
获取的FragmentActivity的FragmentManager对象;
对于Fragment,getFragmentManager()
是获取的是父Fragment(如果没有,则是FragmentActivity)的FragmentManager对象,而getChildFragmentManager()
是获取自己的FragmentManager对象。
优点:性能高,速度最快。参考:新版知乎 、google系app
缺点:逻辑比较复杂,尤其当Fragment之间联动较多或者嵌套较深时,比较复杂。
多模块Activity+多Fragment:
一个模块用一个Activity,比如
1、登录注册流程:
LoginActivity + 登录Fragment + 注册Fragment + 填写信息Fragment + 忘记密码Fragment
2、或者常见的数据展示流程:
DataActivity + 数据列表Fragment + 数据详情Fragment + ...
优点:速度快,相比较单Activity+多Fragment,更易维护。
我的观点:
权衡利弊,我认为多模块Activity+多Fragment是最合适的架构,开发起来不是很复杂,app的性能又很高效。
4. Fragment的状态保存
5、Fragment 的startActivityForResult
6、Fragment 替换和隐藏的区别
7、使用Fragment创建对话框
8、Fragment常报的一些错
因为所有的Fragment都是依附于Activity的,所以通信起来并不复杂,大概归纳为:
a、如果你Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法
b、如果Activity中未保存任何Fragment的引用,那么没关系,每个Fragment都有一个唯一的TAG或者ID,可以通过getFragmentManager.findFragmentByTag()或者findFragmentById()获得任何Fragment实例,然后进行操作。
c、在Fragment中可以通过getActivity得到当前绑定的Activity的实例,然后进行操作。
注:如果在Fragment中需要Context,可以通过调用getActivity(),如果该Context需要在Activity被销毁后还存在,则使用getActivity().getApplicationContext()。
public class ContentActivity extends SingleFragmentActivity { private ContentFragment mContentFragment; @Override protected Fragment createFragment() { String title = getIntent().getStringExtra(ContentFragment.ARGUMENT); mContentFragment = ContentFragment.newInstance(title);
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle bundle = getArguments(); if (bundle != null) { mArgument = bundle.getString(ARGUMENT); } } public static ContentFragment newInstance(String argument) { Bundle bundle = new Bundle(); bundle.putString(ARGUMENT, argument); ContentFragment contentFragment = new ContentFragment(); contentFragment.setArguments(bundle); return contentFragment; }
这样就完成了Fragment和Activity间的解耦。当然了这里需要注意:
我们点击跳转到对应Activity的Fragment中,并且希望它能够返回参数,那么我们肯定是使用Fragment.startActivityForResult ;
在Fragment中存在startActivityForResult()以及onActivityResult()方法,
但是呢,没有setResult()方法,用于设置返回的intent,这样我们就需要通过调用getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent);。
没有serResult的方法,就需要调用Activity的serResult方法!
public class ListTitleActivity extends SingleFragmentActivity { private ListTitleFragment mListFragment; @Override protected Fragment createFragment() { mListFragment = new ListTitleFragment(); return mListFragment; } }
public class ListTitleFragment extends ListFragment { public static final int REQUEST_DETAIL = 0x110; private ListmTitles = Arrays.asList("Hello", "World", "Android"); private int mCurrentPos ; private ArrayAdapter mAdapter ; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setListAdapter(mAdapter = new ArrayAdapter (getActivity(), android.R.layout.simple_list_item_1, mTitles)); } @Override public void onListItemClick(ListView l, View v, int position, long id) { mCurrentPos = position ; Intent intent = new Intent(getActivity(),ContentActivity.class); intent.putExtra(ContentFragment.ARGUMENT, mTitles.get(position)); startActivityForResult(intent, REQUEST_DETAIL); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { Log.e("TAG", "onActivityResult"); super.onActivityResult(requestCode, resultCode, data); if(requestCode == REQUEST_DETAIL) { mTitles.set(mCurrentPos, mTitles.get(mCurrentPos)+" -- "); mAdapter.notifyDataSetChanged(); } } }
public class ContentActivity extends SingleFragmentActivity { private ContentFragment mContentFragment; @Override protected Fragment createFragment() { String title = getIntent().getStringExtra(ContentFragment.ARGUMENT); mContentFragment = ContentFragment.newInstance(title); return mContentFragment; } }
public class ContentFragment extends Fragment { private String mArgument; public static final String ARGUMENT = "argument"; public static final String RESPONSE = "response"; public static final String EVALUATE_DIALOG = "evaluate_dialog"; public static final int REQUEST_EVALUATE = 0X110; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle bundle = getArguments(); if (bundle != null) { mArgument = bundle.getString(ARGUMENT); } } public static ContentFragment newInstance(String argument) { Bundle bundle = new Bundle(); bundle.putString(ARGUMENT, argument); ContentFragment contentFragment = new ContentFragment(); contentFragment.setArguments(bundle); return contentFragment; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Random random = new Random(); TextView tv = new TextView(getActivity()); ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); tv.setLayoutParams(params); tv.setText(mArgument); tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 30); tv.setGravity(Gravity.CENTER); tv.setBackgroundColor(Color.argb(random.nextInt(100), random.nextInt(255), random.nextInt(255), random.nextInt(255))); // set click tv.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { EvaluateDialog dialog = new EvaluateDialog(); //注意setTargetFragment dialog.setTargetFragment(ContentFragment.this, REQUEST_EVALUATE); dialog.show(getFragmentManager(), EVALUATE_DIALOG); } }); return tv; } //接收返回回来的数据 @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_EVALUATE) { String evaluate = data .getStringExtra(EvaluateDialog.RESPONSE_EVALUATE); Toast.makeText(getActivity(), evaluate, Toast.LENGTH_SHORT).show(); Intent intent = new Intent(); intent.putExtra(RESPONSE, evaluate); getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent); } }
startActivityForResult ()
,会发现无论如何都不能在
onActivityResult()
中接收到返回值,只有最顶层的父Fragment才能接收到,这是一个support v4库的一个BUG,不过在前两天发布的support 23.2.0库中,已经修复了该问题,嵌套的子Fragment也能正常接收到返回数据了!
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (mAppManageFragment != null) { mAppManageFragment.onActivityResult(requestCode, resultCode, data); } }
@Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if(requestCode==0){ adapter.refreshDeleteList(); } }
我们成功实现了向活动中动态添加碎片的功能,不过你尝试一下就会发现,通过点击按钮添加了一个碎片之后,这时按下Back键程序就会直接退出。如果这里我们想模仿类似于返回栈的效果,按下Back键可以回到上一个碎片,该如何实现呢?
其实很简单,FragmentTransaction中提供了一个addToBackStack()方法,可以用于将一个事务添加到返回栈中,修改MainActivity中的代码,如下所示:
public class MainActivity extends Activity implements OnClickListener { …… @Override public void onClick(View v) { switch (v.getId()) { case R.id.button: AnotherRightFragment fragment = new AnotherRightFragment(); FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction transaction = fragmentManager. beginTransaction(); transaction.replace(R.id.right_layout, fragment); transaction.addToBackStack(null); transaction.commit(); break; default: break; } } }
show()
,
hide()
最终是让Fragment的View
setVisibility
(true还是false),不会调用生命周期;
replace()
的话会销毁视图,即调用onDestoryView、onCreateView等一系列生命周期;
add()
和 replace()
不要在同一个阶级的FragmentManager里混搭使用。
使用场景
如果你有一个很高的概率会再次使用当前的Fragment,建议使用show()
,hide()
,可以提高性能。
在我使用Fragment过程中,大部分情况下都是用show()
,hide()
,而不是replace()
。
注意:如果你的app有大量图片,这时更好的方式可能是replace,配合你的图片框架在Fragment视图销毁时,回收其图片所占的内存
在多个 Fragment 之间进行切换的时候
replace()和hide的区别
首先replace 是代替,之前的Fragment会被清空掉,在再次切换回来的时候会进行重新加载。而hide是将之前的一个fragment 进行隐藏,将新的fragment 叠在上面进行显示.等在次调用的时候进行显示。不需要重新加载。测试这个最简单的方法就是 设计两个fragment 在同一位置上放一个按钮,一个有监听一个没有,但现实没有监听的按钮点击按钮的时候会响应一号按钮的监听事件。
另外一个对于 FragmentTransaction对象在commit之后就会失效。所以建议直接使用fragmentManager.beginTransaction() 这样就不会产生失效的问题了。
commit 于commitAllowingStateLoss();的区别
简单来说就是避免报错。
他们都调用了commitInternal
public int commit() {
return commitInternal(false);
}
public int commitAllowingStateLoss() {
return commitInternal(true);
}
这个两个方法区别就在传参判断后的处理方法checkStateLoss
当使用commit方法时,系统将进行状态判断,如果状态(mStateSaved)已经保存,将发生”Can not perform this action after onSaveInstanceState”错误。
如果mNoTransactionsBecause已经存在,将发生”Can not perform this action inside of ” + mNoTransactionsBecause错误
fragment 特点
Fragment可以作为Activity界面的一部分组成出现;
可以在一个Activity中同时出现多个Fragment,并且一个Fragment也可以在多个Activity中使用;
在Activity运行过程中,可以添加、移除或者替换Fragment;
Fragment可以响应自己的输入事件,并且有自己的生命周期,它们的生命周期会受宿主Activity的生命周期影响。
相信这两个PagerAdapter的子类,大家都不陌生吧~~自从Fragment问世,使用ViewPager再结合上面任何一个实例的制作APP主页的案例特别多~~~
那么这两个类有何区别呢?
主要区别就在与对于fragment是否销毁,下面细说:
FragmentPagerAdapter:对于不再需要的fragment,选择调用detach方法,仅销毁视图,并不会销毁fragment实例。
FragmentStatePagerAdapter:会销毁不再需要的fragment,当当前事务提交以后,会彻底的将fragmeng从当前Activity的FragmentManager中移除,state标明,销毁时,会将其onSaveInstanceState(Bundle outState)中的bundle信息保存下来,当用户切换回来,可以通过该bundle恢复生成新的fragment,也就是说,你可以在onSaveInstanceState(Bundle outState)方法中保存一些数据,在onCreate中进行恢复创建。
如上所说,使用FragmentStatePagerAdapter当然更省内存,但是销毁新建也是需要时间的。一般情况下,如果你是制作主页面,就3、4个Tab,那么可以选择使用FragmentPagerAdapter,如果你是用于ViewPager展示数量特别多的条目时,那么建议使用FragmentStatePagerAdapter。
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (null != rootView) { ViewGroup parent = (ViewGroup) rootView.getParent(); if (null != parent) { parent.removeView(rootView); } } else { rootView = inflater.inflate(layoutId, null); initView(rootView);// 控件初始化 } return rootView; }
public abstract class SingleFragmentActivity extends FragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_single_fragment); FragmentManager fm = getSupportFragmentManager(); Fragment fragment =fm.findFragmentById(R.id.id_fragment_container); if(fragment == null ) { fragment = createFragment() ; fm.beginTransaction().add(R.id.id_fragment_container,fragment).commit(); } }
为什么需要判null呢?
主要是因为,当Activity因为配置发生改变(屏幕旋转)或者内存不足被系统杀死,造成重新创建时,
我们的fragment会被保存下来,但是会创建新的FragmentManager,新的FragmentManager会首先会去获取保存下来的fragment队列,重建fragment队列,从而恢复之前的状态。
tv.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { EvaluateDialog dialog = new EvaluateDialog(); //注意setTargetFragment dialog.setTargetFragment(ContentFragment.this, REQUEST_EVALUATE); dialog.show(getFragmentManager(), EVALUATE_DIALOG); } }); return tv; } //接收返回回来的数据() @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_EVALUATE) { String evaluate = data .getStringExtra(EvaluateDialog.RESPONSE_EVALUATE); Toast.makeText(getActivity(), evaluate, Toast.LENGTH_SHORT).show(); Intent intent = new Intent(); intent.putExtra(RESPONSE, evaluate); getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent); } }
public class EvaluateDialog extends DialogFragment { private String[] mEvaluteVals = new String[] { "GOOD", "BAD", "NORMAL" }; public static final String RESPONSE_EVALUATE = "response_evaluate"; @Override public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle("Evaluate :").setItems(mEvaluteVals, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { setResult(which); } }); return builder.create(); } // 设置返回数据 protected void setResult(int which) { // 判断是否设置了targetFragment if (getTargetFragment() == null) return; Intent intent = new Intent(); intent.putExtra(RESPONSE_EVALUATE, mEvaluteVals[which]); getTargetFragment().onActivityResult(ContentFragment.REQUEST_EVALUATE, Activity.RESULT_OK, intent); } }
Fragment 的 transaction 允许你执行一系列的 Fragment 操作,但不幸的是,提交 transaction 是异步操作,并且在 UI 线程的 Handler 队列的队尾被提交。
这会在接收多个点击事件或配置发生改变时让你的 App 处在未知的状态。
Fragment 的实例能够通过 Fragment Manager 创建,例如下面的代码看起来没有什么问题:
看了上面的介绍,你可能会觉得Fragment有点可怕。
但是我想说,如果你只是浅度使用,比如一个Activity容器包含列表Fragment+详情Fragment这种简单情景下,
不涉及到popBackStack/Immediate(tag/id)
这些的方法,还是比较轻松使用的,出现的问题,网上都可以找到解决方案。
可能你遇到过getActivity()返回null,或者平时运行完好的代码,在“内存重启”之后,调用getActivity()的地方却返回null,报了空指针异常。
大多数情况下的原因:你在调用了getActivity()时,当前的Fragment已经onDetach()
了宿主Activity。
比如:你在pop了Fragment之后,该Fragment的异步任务仍然在执行,并且在执行完成后调用了getActivity()方法,这样就会空指针。
解决办法:
更"安全"的方法:(对于Fragment已经onDetach这种情况,我们应该避免在这之后再去调用宿主Activity对象,比如取消这些异步任务,但我们的团队可能会有粗心大意的情况,所以下面给出的这个方案会保证安全)
在Fragment基类里设置一个Activity mActivity的全局变量,在onAttach(Activity activity)
里赋值,使用mActivity代替getActivity()
,保证Fragment即使在onDetach
后,仍持有Activity的引用(有引起内存泄露的风险,但是异步任务没停止的情况下,本身就可能已内存泄漏,相比Crash,这种做法“安全”些),即:
protected Activity mActivity;
@Overridepublic void onAttach(Activity activity) {
super.onAttach(activity);
this.mActivity = activity;
}
/**
* 如果你用了support 23的库,上面的方法会提示过时,有强迫症的小伙伴,可以用下面的方法代替
*/@Overridepublic void onAttach(Context context) {
super.onAttach(context);
this.mActivity = (Activity)context;
}
大致意思是说我使用的 commit方法是在Activity的onSaveInstanceState()之后调用的,这样会出错,因为onSaveInstanceState
方法是在该Activity即将被销毁前调用,来保存Activity数据的,如果在保存玩状态后再给它添加Fragment就会出错。解决办法就
是把commit()方法替换成 commitAllowingStateLoss()就行了,其效果是一样的。
有很多小伙伴遇到这个异常,这个异常产生的原因是:
在你离开当前Activity等情况下,系统会调用onSaveInstanceState()
帮你保存当前Activity的状态、数据等,直到再回到该Activity之前(onResume()
之前),你执行Fragment事务,就会抛出该异常!(一般是其他Activity的回调让当前页面执行事务的情况,会引发该问题)
解决方法:
commitAllowingStateLoss()
方法提交,但是有可能导致该次提交无效!(在此次离开时恰巧Activity被强杀时)onResumeFragments()
或onPostResume()
),再执行该事务,配合数据保存,可以做到事务的完整性,不会丢失事务。示例代码 (以EventBus通知执行事务为例,其他场景思路一致):
@Override// 如果是在Fragment内, 则复写onResumeFragments()改为onResume()即可protected void onResumeFragments() {
super.onResumeFragments();
mIsSaved = true;
if (mTransactionEvent != null) {
// 这里执行事务
mTransactionEvent = null;
}
}
@Overrideprotected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mIsSaved = false;
}
@Subscribe(sticky = true) // sticky事件可以保证即使Activity被强杀,也会在恢复后拿到数据public void onEvent(TransactionEvent event) {
if (mIsSaved) {
// 这里执行事务
} else {
mTransactionEvent = event;
}
}
在类onCreate()
的方法加载Fragment,并且没有判断saveInstanceState==null
或if(findFragmentByTag(mFragmentTag) == null)
,导致重复加载了同一个Fragment导致重叠。(PS:replace
情况下,如果没有加入回退栈,则不判断也不会造成重叠,但建议还是统一判断下)
@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {
// 在页面重启时,Fragment会被保存恢复,而此时再加载Fragment会重复加载,导致重叠 ;if(saveInstanceState == null){
// 或者 if(findFragmentByTag(mFragmentTag) == null)// 正常情况下去 加载根Fragment
}
}
即在add()
或者replace()
时绑定一个tag,一般我们是用fragment的类名作为tag,然后在发生“内存重启”时,通过findFragmentByTag
找到对应的Fragment,并hide()
需要隐藏的fragment。
下面是个标准恢复写法:
@Overrideprotected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
TargetFragment targetFragment;
HideFragment hideFragment;
if (savedInstanceState != null) { // “内存重启”时调用
targetFragment = getSupportFragmentManager().findFragmentByTag(TargetFragment.class.getName);
hideFragment = getSupportFragmentManager().findFragmentByTag(HideFragment.class.getName);
// 解决重叠问题
getFragmentManager().beginTransaction()
.show(targetFragment)
.hide(hideFragment)
.commit();
}else{ // 正常时
targetFragment = TargetFragment.newInstance();
hideFragment = HideFragment.newInstance();
getFragmentManager().beginTransaction()
.add(R.id.container, targetFragment, targetFragment.getClass().getName())
.add(R.id,container,hideFragment,hideFragment.getClass().getName())
.hide(hideFragment)
.commit();
}
}
(2)对于宿主Activity,getSupportFragmentManager()
获取的FragmentActivity的FragmentManager对象;
对于Fragment,getFragmentManager()
是获取的是父Fragment(如果没有,则是FragmentActivity)的FragmentManager对象,而getChildFragmentManager()
是获取自己的FragmentManager对象。
优点:性能高,速度最快。参考:新版知乎 、google系app
缺点:逻辑比较复杂,尤其当Fragment之间联动较多或者嵌套较深时,比较复杂。
多模块Activity+多Fragment:
一个模块用一个Activity,比如
1、登录注册流程:
LoginActivity + 登录Fragment + 注册Fragment + 填写信息Fragment + 忘记密码Fragment
2、或者常见的数据展示流程:
DataActivity + 数据列表Fragment + 数据详情Fragment + ...
优点:速度快,相比较单Activity+多Fragment,更易维护。
我的观点:
权衡利弊,我认为多模块Activity+多Fragment是最合适的架构,开发起来不是很复杂,app的性能又很高效。