应用程序组件-------Activity之Fragment(三)

来源:互联网 发布:windows 8.1 10 whql 编辑:程序博客网 时间:2024/05/17 08:45
自从Android 3.0中引入fragments 的概念,中文翻译为:碎片、片段。fragment的出现解决不同屏幕分辩率的动态和灵活UI设计。大屏幕如平板小屏幕如手机,平板电脑的设计使得其有更多的空间来放更多的UI组件,而多出来的空间存放UI使其会产生更多的交互,从而诞生了fragments。如图所示:
同样的界面Activity占用内存比Fragment要多,响应速度Fragment比Activty在中低端手机上快了很多,甚至能达到好几倍!如果你的app当前或以后有移植平板等平台时,可以让你节省大量时间和精力。

一、Fragment生命周期
Fragments依托于Activity,每一个Activity管理着一个Fragments的堆栈,而每一个fragments 都有自己的一套生命周期回调方法和处理自己的用户输入事件。并且与Activity的生命周期绑在一起。如下图所示:

onCreate
   系统创建Fragments 时调用,可做执行初始化工作或者当程序被暂停或停止时用来恢复状态,跟Activity 中的onCreate相当。 
onCreateView
   用于首次绘制用户界面的回调方法,必须返回要创建的Fragments 视图UI。假如你不希望提供Fragments 用户界面则可以返回NULL。 
onPause 
    当用户离开这个Fragments 的时候调用,这时你要提交任何应该持久的变化,因为用户可能不会回来。更多的事件可以参考上图的生命周期关系图。
管理fragment的生命周期, 大多数地方和管理activity生命周期很像.和activity一样, fragment可以处于3种状态:
   Resumed
        在运行中的activity中fragment可见。
   Paused
        另一个activity处于前台并拥有焦点,但是这个fragment所在的activity仍然可见(前台activity局部透明或者没有覆盖整个屏幕)。
   Stopped
        要么是宿主activity已经被停止, 要么是fragment从activity被移除但被添加到后台堆栈中。
        停止状态的fragment仍然活着(所有状态和成员信息被系统保持着)。然而,它对用户不再可见,并且如果activity被干掉,他也会被干掉。
其对应关系图如下:
和activity一样, 你可以使用Bundle保持fragment的状态,万一activity的进程被干掉,并且当activity被重新创建的时候, 你需要恢复fragment的状态时就可以用到. 你可以在fragment的 onSaveInstanceState() 期间保存状态,并可以在 onCreate(),onCreateView() 或 onActivityCreated() 期间恢复它。
生命周期方面activity和fragment之间最重要的区别是各自如何在它的后台堆栈中储存。 在默认情况下,activity在停止后,它会被放到一个由系统管理的用于保存activity的后台堆栈。(因此用户可以使用BACK按键导航回退到它)。
然而,仅当你在一个事务期间移除fragment时,显式调用addToBackStack()请求保存实例时,才被放到一个由宿主activity管理的后台堆栈。
另外,管理fragment的生命周期和管理activity生命周期非常类似。因此, "managing the activitylifecycle"中的相同实践也同样适用于fragment。你需要理解的是,activity的生命如何影响fragment的生命。
二、Fragments 的类别
DialogFragment
 对话框式的Fragments,可以将一个fragments 对话框并到activity 管理的fragments back stack 中,允许用户回到一个前曾摒弃fragments.
ListFragments
   类似于ListActivity 的效果,并且还提供了ListActivity 类似的onListItemCLick和setListAdapter等功能。
PreferenceFragments
   类似于PreferenceActivity .可以创建类似IPAD的设置界面。

三、Fragment与Activity交互
1、为Activity创建事件回调方法
在一些情况下, 你可能需要一个fragment与activity分享事件。 一个好的方法是在fragment中定义一个回调的interface, 并要求宿主activity实现它。当activity通过interface接收到一个回调, 必要时它可以和在layout中的其他fragment分享信息。
例如, 如果一个新的应用在activity中有2个fragment – 一个用来显示文章列表(framgent A), 另一个显示文章内容(fragment B) – 然后 framgent A必须告诉activity何时一个list item被选中,然后它可以告诉fragmentB去显示文章。
代码如下:

public static class FragmentA extends ListFragment
{
    OnArticleSelectedListener mListener;
    //...
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnArticleSelectedListener) activity;
         } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implementOnArticleSelectedListener");
        }
    }
      //...
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        // Append the clicked item's row ID with the content provider Uri
         Uri noteUri =ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
        // Send the event and Uri to the host activity
       mListener.onArticleSelected(noteUri);
    //...
   public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }
}
如果activity没有实现接口,fragment会抛出 ClassCastException 异常。正常情形下,mListener成员会保持一个到activity的OnArticleSelectedListener实现的引用,因此fragment A可以通过调用在OnArticleSelectedListener接口中定义的方法分享事件给activity。例如,如果fragment A是一个 ListFragment的子类, 每次用户点击一个列表项,系统调用在fragment中的onListItemClick(),然后后者调用 onArticleSelected() 来分配事件给activity。

2、添加项目到ActionBar
Fragment可以通过实现 onCreateOptionMenu() 提供菜单项给activity的选项菜单(以此类推, Action Bar也一样)。为了使这个方法接收调用,无论如何,你必须在 onCreate() 期间调用setHasOptionsMenu(true) 来指出fragment愿意添加item到选项菜单(否则, fragment将接收不到对 onCreateOptionsMenu()的调用)。
随后从fragment添加到Option菜单的任何项,都会被追加到现有菜单项的后面。当一个菜单项被选择,fragment也会接收到 对 onOptionsItemSelected() 的回调。也可以在你的fragment layout中通过调用registerForContextMenu() 注册一个view来提供一个环境菜单。当用户打开环境菜单,fragment接收到一个对 onCreateContextMenu() 的调用.当用户选择一个项目, fragment接收到一个对onContextItemSelected() 的调用。
publicstaticclass DetailsFragmentextends Fragment { @Overridepublicvoid onCreate(Bundle savedInstanceState) {// TODO Auto-generated method stubsuper.onCreate(savedInstanceState); setHasOptionsMenu(true); }
@Overridepublicvoid onCreateOptionsMenu(Menu menu, MenuInflater inflater) {// TODO Auto-generated method stubsuper.onCreateOptionsMenu(menu, inflater); menu.add("Menu 1a").setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); menu.add("Menu 1b").setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); } @Overridepublicboolean onOptionsItemSelected(MenuItem item) {// TODO Auto-generated method stub Toast.makeText(getActivity(), "index is"+getShownIndex()+" && menu text is "+item.getTitle(), 1000).show(); returnsuper.onOptionsItemSelected(item); }
注意:尽管你的fragment会接收到它所添加的每一个菜单项被选择后的回调,但实际上当用户选择一个菜单项时,activity会首先接收到对应的回调。如果activity的on-item-selected回调函数实现并没有处理被选中的项目,然后事件才会被传递到fragment的回调。
这个规则适用于选项菜单和环境菜单。

四、Fragment踩过的坑
1、getActivity()空指针
大多数情况下的原因:你在调用了getActivity()时,当前的Fragment已经onDetach()了宿主Activity。比如:你在pop了Fragment之后,该Fragment的异步任务仍然在执行,并且在执行完成后调用了getActivity()方法,这样就会空指针。比较安全的办法:
protected Activity mActivity;@OverridepublicvoidonAttach(Activity activity) {super.onAttach(activity);this.mActivity = activity;}
在Fragment基类里设置一个Activity mActivity的全局变量,在onAttach(Activity activity)里赋值,使用mActivity代替getActivity(),保证Fragment即使在onDetach后,仍持有Activity的引用(有引起内存泄露的风险,但是相比空指针闪退,这种做法“安全”些)。
2、Fragment重叠异常-----正确使用hide、show的姿势
如果你add()了几个Fragment,使用show()、hide()方法控制,在“内存重启”后回到前台,app的这几个Fragment界面会重叠。
原因是FragmentManager帮我们管理Fragment,当发生“内存重启”,他会从栈底向栈顶的顺序一次性恢复Fragment;
但是因为没有保存Fragment的mHidden属性,默认为false,即show状态,所以所有Fragment都是以show的形式恢复,我们看到了界面重叠。
(如果是replace,恢复形式和Activity一致,只有当你pop之后上一个Fragment才开始重新恢复,所有使用replace不会造成重叠现象)
还有一种场景,addreplace都有可能造成重叠: 在onCreate中加载Fragment,并且没有判断saveInstanceState==null,导致重复加载了同一个Fragment导致重叠。(PS:replace情况下,如果没有加入回退栈,则不判断也不会造成重叠,但建议还是统一判断下)
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {// 在页面重启时,Fragment会被保存恢复,而此时再加载Fragment会重复加载,导致重叠 ; if(saveInstanceState == null){ // 正常情况下去 加载根Fragment } }
FragmentState没帮我们保存Hidden状态,那就我们自己来保存,在页面重启后,我们自己来决定Fragment是否显示!
publicclassBaseFragmentextendsFragment {privatestaticfinal String STATE_SAVE_IS_HIDDEN ="STATE_SAVE_IS_HIDDEN"; @OverridepublicvoidonCreate(@Nullable Bundle savedInstanceState) { ...if (savedInstanceState !=null) {boolean isSupportHidden = savedInstanceState.getBoolean(STATE_SAVE_IS_HIDDEN); FragmentTransaction ft = getFragmentManager().beginTransaction();if (isSupportHidden) { ft.hide(this); } else { ft.show(this); } ft.commit(); } @Override publicvoidonSaveInstanceState(Bundle outState) { ... outState.putBoolean(STATE_SAVE_IS_HIDDEN, isHidden()); }}
3、异常:Can not perform this action after onSaveInstanceState
在你离开当前Activity等情况下,系统会调用onSaveInstanceState()帮你保存当前Activity&Fragment的一些状态、数据等,而在离开后(onSaveInstanceState()已经被执行),你又去执行Fragment的相关事务方法后,就会抛出该异常!
解决方法2个:
1、(不推荐)该事务使用commitAllowingStateLoss()方法提交,但是有可能导致该次提交无效
2、(推荐)在重新回到该Activity的时候(比如onStart里),再执行该事务!

4、Fragment嵌套的那些坑
其实一些小伙伴遇到的很多嵌套的坑,大部分都是由于对嵌套的栈视图产生混乱,只要理清栈视图关系,做好恢复相关工作以及正确选择是使用getFragmentManager()还是getChildFragmentManager()就可以避免这些问题。

还有一些平时也可能出现的bug.看看这个博文,说的比较详细:
http://www.jianshu.com/p/c12a98a36b2b
所有的问题在他的Fragmentation库中都有封装,处理各种复杂的一个Activity——多fragment,以及少Activity+多Fragment的框架。
0 0
原创粉丝点击