1. Fragment的生命周期
2、Fragment如何与Activity交互,Acitivity和Fragment的通信
3. 如何管理Fragment回退栈
4. Fragment的状态保存
5、Fragment 的startActivityForResult
6、Fragment 替换和隐藏的区别
7、使用Fragment创建对话框
8、Fragment常报的一些错
Fragment的产生与介绍:一个App可以同时适应手机和平板
Fragment是可以让你的app纵享丝滑的设计,如果你的app想在现在基础上性能大幅度提高,并且占用内存降低,同样的界面Activity占用内存比Fragment要多,
响应速度Fragment比Activty在中低端手机上快了很多,甚至能达到好几倍!如果你的app当前或以后有移植平板等平台时,可以让你节省大量时间和精力。
Fragment的生命周期:
Fragment必须是依存与Activity而存在的,因此Activity的生命周期会直接影响到Fragment的生命周期
好处: 更为重要的是,你可以动态的添加、替换和移除某个Fragment
2、Fragment与Activity通信
因为所有的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()。
Fragment的状态保存:
Fragment也有onSaveInstanceState的方法
Activity的Fragment的之间的传值:Fragment Arguments,比传统的Intent可以达到更好的解耦标准
方法一:
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添加newInstance方法,将需要的参数传入,设置到bundle中,然后setArguments(bundle),最后在onCreate中进行获取;
这样就完成了Fragment和Activity间的解耦。当然了这里需要注意:
Fragment的startActivityForResult
我们点击跳转到对应Activity的Fragment中,并且希望它能够返回参数,那么我们肯定是使用Fragment.startActivityForResult ;
在Fragment中存在startActivityForResult()以及onActivityResult()方法,
但是呢,没有setResult()方法,用于设置返回的intent,这样我们就需要通过调用getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent);。
没有serResult的方法,就需要调用Activity的serResult方法!
案列:一个Activity里面包含一个fragment,然后fragment调整到另外一个Activity中,这个Activity里面也包含一个fragment。
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 List<String> mTitles = Arrays.asList("Hello", "World", "Android");
private int mCurrentPos ;
private ArrayAdapter<String> mAdapter ;
@Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
setListAdapter(mAdapter = new ArrayAdapter<String>(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();
}
}
}
跳转的Acitivy和fragment
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接收返回问题
在support 23.2.0以下的支持库中,对于在嵌套子Fragment的
startActivityForResult
()
,会发现无论如何都不能在onActivityResult()
中接收到返回值,只有最顶层的父Fragment才能接收到,这是一个support v4库的一个BUG,不过在前两天发布的support 23.2.0库中,已经修复了该问题,嵌套的子Fragment也能正常接收到返回数据了!onActivityResult
Activity里面的onActivityResult方法 还有Fragment里面的OnActivityResult的方法
@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;
}
}
}
add(), show(), hide(), replace()替换和隐藏Fragment的区别:
1、区别
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切换
http://www.yrom.net/blog/2013/03/10/fragment-switch-not-restart/
多个fragment问题呢http://www.tuicool.com/articles/eua2mm
不错的demohttp://blog.csdn.net/guolin_blog/article/details/13171191
fragment 特点
Fragment可以作为Activity界面的一部分组成出现;
可以在一个Activity中同时出现多个Fragment,并且一个Fragment也可以在多个Activity中使用;
在Activity运行过程中,可以添加、移除或者替换Fragment;
-
Fragment可以响应自己的输入事件,并且有自己的生命周期,它们的生命周期会受宿主Activity的生命周期影响。
Menu菜单问题:fragment有菜单,activity有菜单的话,会把菜单综合在一起如果名称相同的话,也不会覆盖的
Fragment中menu菜单注意事项 以前一般都是在Activity中添加menu菜单,一般是重写onCreateOptionsMenu和onOptionsItemSelected方法。
现在用fragment用的多了,就在fragment里面添加menu菜单,也是重写了onCreateOptionsMenu和onOptionsItemSelected方法,但是发现没有效果。
好吧,看了下源代码,原来跟一个mHasMenu的boolean变量有关系1// If set this fragment has menu items to contribute.2 boolean mHasMenu;这个变量控制fragment的menu菜单添加:01boolean performCreateOptionsMenu(Menu menu, MenuInflater inflater) {02 boolean show = false;03 if (!mHidden) {04 if (mHasMenu && mMenuVisible) {05 show = true;06 onCreateOptionsMenu(menu, inflater);07 }08 if (mChildFragmentManager != null) {09 show |= mChildFragmentManager.dispatchCreateOptionsMenu(menu, inflater);10 }11 }12 return show;13 }上面代码说明,如果mHasMenu为false,那么是不会执行onCreateOptionsMenu(menu, inflater)方法的,也就是不会添加fragment的menu菜单。
所以,在fragment中使用menu菜单,需要在onCreate()方法里面添加语句setHasOptionsMenu(true);
也就是这样:1<a href="http://home.51cto.com/index.php?s=/space/5017954" target="_blank">@Override</a>2 public void onCreate(Bundle savedInstanceState) {3 super.onCreate(savedInstanceState);4 setHasOptionsMenu(true);5 }嗯,很简单的东西,总结下,希望大家以后不要跟我一样犯错误哈。
=============================================================
Fragment滑动FragmentPagerAdapter与FragmentStatePagerAdapter
相信这两个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。
fragment添加全部的还是添加2页(和viewpager有关)=====用的适配器不一样
有点连oncreatView方法都没有执行
两种适配器,一个返回的是fragment一个返回的view
一个Activity里面嵌套多个Fragment,滑动加载的流程和生命周期1.只加载前面2个,没滑动一次加载一个以后什么方法都不执行
2.只加载前面连个,没滑动加载一个再滑动执行onpause也执行oncreat方法
3.只加载前面连个,没滑动加载一个
第一个页面不为空的话,会执行下面的一些列的方法.以后的话就不会再执行里面的任何方法了
如果页面为空或者重写的话,不会执行下面的方法
如果第一个为空,第2个不为空的话,也没有问题
如果 前面两个都为空的话就有问题了,是viewPager的问题
2.onCreateView---这样每次都用更新数据
private View rootView;@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;
}
Fragment之间切换时每次都会调用onCreateView方法,导致每次Fragment的布局都重绘,无法保持Fragment原有状态。
解决办法:在Fragment onCreateView方法中缓存View.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队列,从而恢复之前的状态。
DialogFragment的用法和setTargetFragment用法
例如,点击当前Fragment中按钮,弹出一个对话框(DialogFragment),在对话框中的操作需要返回给触发的Fragment中,那么如何数据传递呢?对于对话框的使用推荐:Android 官方推荐 : DialogFragment 创建对话框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的缺点http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0605/2996.htmlSquare:从今天开始抛弃Fragment吧!Fragment transactions
Transaction的出现:
在AIDL里面生成的java文件中会有!Fragment 的 transaction 允许你执行一系列的 Fragment 操作,但不幸的是,提交 transaction 是异步操作,并且在 UI 线程的 Handler 队列的队尾被提交。
这会在接收多个点击事件或配置发生改变时让你的 App 处在未知的状态。
class BackStackRecord extends FragmentTransaction { int commitInternal(boolean allowStateLoss) { if (mCommitted) throw new IllegalStateException("commit already called"); mCommitted = true; if (mAddToBackStack) { mIndex = mManager.allocBackStackIndex(this); } else { mIndex = -1; } mManager.enqueueAction(this, allowStateLoss); return mIndex; }}创建 Fragment 可能带来的问题
Fragment 的实例能够通过 Fragment Manager 创建,例如下面的代码看起来没有什么问题:
DialogFragment dialogFragment = new DialogFragment() { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { ... }};dialogFragment.show(fragmentManager, tag);然而,当我们需要存储 Activity 实例的状态时,Fragment Manager 可能会通过反射机制重新创建该 Fragment 的实例,又因为这是一个匿名内部类,该类有一个隐藏的构造器的参数正是外部类的引用,如果大家有看过这篇博文的话就会知道,拥有外部引用可能会带来内存泄漏的问题。
结论:1.我们遇到的大多数难以解决的 Bug 都与 Fragment 的生命周期有关。2.我们只需要 View 创建响应式 UI,实现回退栈以及屏幕事件的处理,不用 Fragment 也能满足实际开发的需求。3.MVVP模式看待的话,解耦性不好!看了上面的介绍,你可能会觉得Fragment有点可怕。
但是我想说,如果你只是浅度使用,比如一个Activity容器包含列表Fragment+详情Fragment这种简单情景下,
不涉及到
popBackStack/Immediate(tag/id)
这些的方法,还是比较轻松使用的,出现的问题,网上都可以找到解决方案。getActivity()空指针
可能你遇到过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;
}异常:Can not perform this action after onSaveInstanceState
大致意思是说我使用的 commit方法是在Activity的onSaveInstanceState()之后调用的,这样会出错,因为onSaveInstanceState
方法是在该Activity即将被销毁前调用,来保存Activity数据的,如果在保存玩状态后再给它添加Fragment就会出错。解决办法就
是把commit()方法替换成 commitAllowingStateLoss()就行了,其效果是一样的。
有很多小伙伴遇到这个异常,这个异常产生的原因是:
在你离开当前Activity等情况下,系统会调用
onSaveInstanceState()
帮你保存当前Activity的状态、数据等,直到再回到该Activity之前(onResume()
之前),你执行Fragment事务,就会抛出该异常!(一般是其他Activity的回调让当前页面执行事务的情况,会引发该问题)解决方法:
- 1、该事务使用
commitAllowingStateLoss()
方法提交,但是有可能导致该次提交无效!(在此次离开时恰巧Activity被强杀时) - 2、在重新回到该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;
}
}Fragment重叠异常-----正确使用hide、show的姿势
在类
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();
}
}(1)每个Fragment以及宿主Activity(继承自FragmentActivity)都会在创建时,初始化一个FragmentManager对象,处理好Fragment嵌套问题的关键,就是理清这些不同阶级的栈视图。
(2)对于宿主Activity,
getSupportFragmentManager()
获取的FragmentActivity的FragmentManager对象;对于Fragment,
getFragmentManager()
是获取的是父Fragment(如果没有,则是FragmentActivity)的FragmentManager对象,而getChildFragmentManager()
是获取自己的FragmentManager对象。5、是使用单Activity+多Fragment的架构,还是多模块Activity+多Fragment的架构?
单Activity+多Fragment:
一个app仅有一个Activity,界面皆是Frament,Activity作为app容器使用。优点:性能高,速度最快。参考:新版知乎 、google系app
缺点:逻辑比较复杂,尤其当Fragment之间联动较多或者嵌套较深时,比较复杂。
多模块Activity+多Fragment:
一个模块用一个Activity,比如
1、登录注册流程:
LoginActivity + 登录Fragment + 注册Fragment + 填写信息Fragment + 忘记密码Fragment
2、或者常见的数据展示流程:
DataActivity + 数据列表Fragment + 数据详情Fragment + ...优点:速度快,相比较单Activity+多Fragment,更易维护。
我的观点:
权衡利弊,我认为多模块Activity+多Fragment是最合适的架构,开发起来不是很复杂,app的性能又很高效。
抽象的Activity是不需要注册的。只有启动的才需要!那些年踩过的坑
http://www.jianshu.com/p/d9143a92ad94
别人写的框架:Fragmentation
fragment的demo地址:
- 1、该事务使用
2、Fragment如何与Activity交互,Acitivity和Fragment的通信
3. 如何管理Fragment回退栈
4. Fragment的状态保存
5、Fragment 的startActivityForResult
6、Fragment 替换和隐藏的区别
7、使用Fragment创建对话框
8、Fragment常报的一些错
Fragment的产生与介绍:一个App可以同时适应手机和平板
Fragment是可以让你的app纵享丝滑的设计,如果你的app想在现在基础上性能大幅度提高,并且占用内存降低,同样的界面Activity占用内存比Fragment要多,
响应速度Fragment比Activty在中低端手机上快了很多,甚至能达到好几倍!如果你的app当前或以后有移植平板等平台时,可以让你节省大量时间和精力。
Fragment的生命周期:
Fragment必须是依存与Activity而存在的,因此Activity的生命周期会直接影响到Fragment的生命周期
好处:更为重要的是,你可以动态的添加、替换和移除某个Fragment
2、Fragment与Activity通信
因为所有的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()。
Fragment的状态保存:
Fragment也有onSaveInstanceState的方法
Activity的Fragment的之间的传值:Fragment Arguments,比传统的Intent可以达到更好的解耦标准
方法一:
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添加newInstance方法,将需要的参数传入,设置到bundle中,然后setArguments(bundle),最后在onCreate中进行获取;
这样就完成了Fragment和Activity间的解耦。当然了这里需要注意:
Fragment的startActivityForResult
我们点击跳转到对应Activity的Fragment中,并且希望它能够返回参数,那么我们肯定是使用Fragment.startActivityForResult ;
在Fragment中存在startActivityForResult()以及onActivityResult()方法,
但是呢,没有setResult()方法,用于设置返回的intent,这样我们就需要通过调用getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent);。
没有serResult的方法,就需要调用Activity的serResult方法!
案列:一个Activity里面包含一个fragment,然后fragment调整到另外一个Activity中,这个Activity里面也包含一个fragment。
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 List<String> mTitles = Arrays.asList("Hello", "World", "Android");
private int mCurrentPos ;
private ArrayAdapter<String> mAdapter ;
@Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
setListAdapter(mAdapter = new ArrayAdapter<String>(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();
}
}
}
跳转的Acitivy和fragment
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接收返回问题
在support 23.2.0以下的支持库中,对于在嵌套子Fragment的
startActivityForResult
()
,会发现无论如何都不能在onActivityResult()
中接收到返回值,只有最顶层的父Fragment才能接收到,这是一个support v4库的一个BUG,不过在前两天发布的support 23.2.0库中,已经修复了该问题,嵌套的子Fragment也能正常接收到返回数据了!onActivityResult
Activity里面的onActivityResult方法还有Fragment里面的OnActivityResult的方法
@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;
}
}
}
add(), show(), hide(), replace()替换和隐藏Fragment的区别:
1、区别
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切换
http://www.yrom.net/blog/2013/03/10/fragment-switch-not-restart/
多个fragment问题呢http://www.tuicool.com/articles/eua2mm
不错的demohttp://blog.csdn.net/guolin_blog/article/details/13171191
fragment 特点
Fragment可以作为Activity界面的一部分组成出现;
可以在一个Activity中同时出现多个Fragment,并且一个Fragment也可以在多个Activity中使用;
在Activity运行过程中,可以添加、移除或者替换Fragment;
-
Fragment可以响应自己的输入事件,并且有自己的生命周期,它们的生命周期会受宿主Activity的生命周期影响。
Menu菜单问题:fragment有菜单,activity有菜单的话,会把菜单综合在一起如果名称相同的话,也不会覆盖的
Fragment中menu菜单注意事项 以前一般都是在Activity中添加menu菜单,一般是重写onCreateOptionsMenu和onOptionsItemSelected方法。
现在用fragment用的多了,就在fragment里面添加menu菜单,也是重写了onCreateOptionsMenu和onOptionsItemSelected方法,但是发现没有效果。
好吧,看了下源代码,原来跟一个mHasMenu的boolean变量有关系1// If set this fragment has menu items to contribute.2 boolean mHasMenu;这个变量控制fragment的menu菜单添加:01boolean performCreateOptionsMenu(Menu menu, MenuInflater inflater) {02 boolean show = false;03 if (!mHidden) {04 if (mHasMenu && mMenuVisible) {05 show = true;06 onCreateOptionsMenu(menu, inflater);07 }08 if (mChildFragmentManager != null) {09 show |= mChildFragmentManager.dispatchCreateOptionsMenu(menu, inflater);10 }11 }12 return show;13 }上面代码说明,如果mHasMenu为false,那么是不会执行onCreateOptionsMenu(menu, inflater)方法的,也就是不会添加fragment的menu菜单。
所以,在fragment中使用menu菜单,需要在onCreate()方法里面添加语句setHasOptionsMenu(true);
也就是这样:1<a href="http://home.51cto.com/index.php?s=/space/5017954" target="_blank">@Override</a>2 public void onCreate(Bundle savedInstanceState) {3 super.onCreate(savedInstanceState);4 setHasOptionsMenu(true);5 }嗯,很简单的东西,总结下,希望大家以后不要跟我一样犯错误哈。
=============================================================
Fragment滑动FragmentPagerAdapter与FragmentStatePagerAdapter
相信这两个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。
fragment添加全部的还是添加2页(和viewpager有关)=====用的适配器不一样
有点连oncreatView方法都没有执行
两种适配器,一个返回的是fragment一个返回的view
一个Activity里面嵌套多个Fragment,滑动加载的流程和生命周期1.只加载前面2个,没滑动一次加载一个以后什么方法都不执行
2.只加载前面连个,没滑动加载一个再滑动执行onpause也执行oncreat方法
3.只加载前面连个,没滑动加载一个
第一个页面不为空的话,会执行下面的一些列的方法.以后的话就不会再执行里面的任何方法了
如果页面为空或者重写的话,不会执行下面的方法
如果第一个为空,第2个不为空的话,也没有问题
如果 前面两个都为空的话就有问题了,是viewPager的问题
2.onCreateView---这样每次都用更新数据
private View rootView;@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;
}
Fragment之间切换时每次都会调用onCreateView方法,导致每次Fragment的布局都重绘,无法保持Fragment原有状态。
解决办法:在Fragment onCreateView方法中缓存View.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队列,从而恢复之前的状态。
DialogFragment的用法和setTargetFragment用法
例如,点击当前Fragment中按钮,弹出一个对话框(DialogFragment),在对话框中的操作需要返回给触发的Fragment中,那么如何数据传递呢?对于对话框的使用推荐:Android 官方推荐 : DialogFragment 创建对话框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的缺点http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0605/2996.htmlSquare:从今天开始抛弃Fragment吧!Fragment transactions
Transaction的出现:
在AIDL里面生成的java文件中会有!Fragment 的 transaction 允许你执行一系列的 Fragment 操作,但不幸的是,提交 transaction 是异步操作,并且在 UI 线程的 Handler 队列的队尾被提交。
这会在接收多个点击事件或配置发生改变时让你的 App 处在未知的状态。
class BackStackRecord extends FragmentTransaction { int commitInternal(boolean allowStateLoss) { if (mCommitted) throw new IllegalStateException("commit already called"); mCommitted = true; if (mAddToBackStack) { mIndex = mManager.allocBackStackIndex(this); } else { mIndex = -1; } mManager.enqueueAction(this, allowStateLoss); return mIndex; }}创建 Fragment 可能带来的问题
Fragment 的实例能够通过 Fragment Manager 创建,例如下面的代码看起来没有什么问题:
DialogFragment dialogFragment = new DialogFragment() { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { ... }};dialogFragment.show(fragmentManager, tag);然而,当我们需要存储 Activity 实例的状态时,Fragment Manager 可能会通过反射机制重新创建该 Fragment 的实例,又因为这是一个匿名内部类,该类有一个隐藏的构造器的参数正是外部类的引用,如果大家有看过这篇博文的话就会知道,拥有外部引用可能会带来内存泄漏的问题。
结论:1.我们遇到的大多数难以解决的 Bug 都与 Fragment 的生命周期有关。2.我们只需要 View 创建响应式 UI,实现回退栈以及屏幕事件的处理,不用 Fragment 也能满足实际开发的需求。3.MVVP模式看待的话,解耦性不好!看了上面的介绍,你可能会觉得Fragment有点可怕。
但是我想说,如果你只是浅度使用,比如一个Activity容器包含列表Fragment+详情Fragment这种简单情景下,
不涉及到
popBackStack/Immediate(tag/id)
这些的方法,还是比较轻松使用的,出现的问题,网上都可以找到解决方案。getActivity()空指针
可能你遇到过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;
}异常:Can not perform this action after onSaveInstanceState
大致意思是说我使用的 commit方法是在Activity的onSaveInstanceState()之后调用的,这样会出错,因为onSaveInstanceState
方法是在该Activity即将被销毁前调用,来保存Activity数据的,如果在保存玩状态后再给它添加Fragment就会出错。解决办法就
是把commit()方法替换成 commitAllowingStateLoss()就行了,其效果是一样的。
有很多小伙伴遇到这个异常,这个异常产生的原因是:
在你离开当前Activity等情况下,系统会调用
onSaveInstanceState()
帮你保存当前Activity的状态、数据等,直到再回到该Activity之前(onResume()
之前),你执行Fragment事务,就会抛出该异常!(一般是其他Activity的回调让当前页面执行事务的情况,会引发该问题)解决方法:
- 1、该事务使用
commitAllowingStateLoss()
方法提交,但是有可能导致该次提交无效!(在此次离开时恰巧Activity被强杀时) - 2、在重新回到该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;
}
}Fragment重叠异常-----正确使用hide、show的姿势
在类
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();
}
}(1)每个Fragment以及宿主Activity(继承自FragmentActivity)都会在创建时,初始化一个FragmentManager对象,处理好Fragment嵌套问题的关键,就是理清这些不同阶级的栈视图。
(2)对于宿主Activity,
getSupportFragmentManager()
获取的FragmentActivity的FragmentManager对象;对于Fragment,
getFragmentManager()
是获取的是父Fragment(如果没有,则是FragmentActivity)的FragmentManager对象,而getChildFragmentManager()
是获取自己的FragmentManager对象。5、是使用单Activity+多Fragment的架构,还是多模块Activity+多Fragment的架构?
单Activity+多Fragment:
一个app仅有一个Activity,界面皆是Frament,Activity作为app容器使用。优点:性能高,速度最快。参考:新版知乎 、google系app
缺点:逻辑比较复杂,尤其当Fragment之间联动较多或者嵌套较深时,比较复杂。
多模块Activity+多Fragment:
一个模块用一个Activity,比如
1、登录注册流程:
LoginActivity + 登录Fragment + 注册Fragment + 填写信息Fragment + 忘记密码Fragment
2、或者常见的数据展示流程:
DataActivity + 数据列表Fragment + 数据详情Fragment + ...优点:速度快,相比较单Activity+多Fragment,更易维护。
我的观点:
权衡利弊,我认为多模块Activity+多Fragment是最合适的架构,开发起来不是很复杂,app的性能又很高效。
抽象的Activity是不需要注册的。只有启动的才需要!那些年踩过的坑
http://www.jianshu.com/p/d9143a92ad94
别人写的框架:Fragmentation
fragment的demo地址:
- 1、该事务使用