Android Activity和Fragment通信 切换 数据传值



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问题呢




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菜单添加:
    01
    boolean 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的问题



    fragment生命周期.png

    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的缺点
    Square:从今天开始抛弃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==nullif(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是不需要注册的。只有启动的才需要!

    那些年踩过的坑



    别人写的框架:
    Fragmentation


    fragment的demo地址:



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问题呢




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菜单添加:
    01
    boolean 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的问题



    fragment生命周期.png

    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的缺点
    Square:从今天开始抛弃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==nullif(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是不需要注册的。只有启动的才需要!

    那些年踩过的坑



    别人写的框架:
    Fragmentation


    fragment的demo地址:



猜你喜欢

转载自blog.csdn.net/WHB20081815/article/details/76854099