Fragment_关于Fragment你要知道的一切 偏执的太偏执、 2022-07-16 12:56 244阅读 0赞 本文将从 **fragment的基础** **/**\--/ [**嵌套Fragments** 的使用及常见错误][Fragments_] /--/ [Activity, Fragment, **WebView的状态保存和恢复**][Activity_ Fragment_WebView] /--/ [Toolbar使用及**Fragment中的Toolbar**处理][Toolbar_Fragment_Toolbar] 到应用及常见问题的解析,干货奉上;参考自 [Dandan Meng][]. **-------------------------------------------------------------------------------------------------** # 首先,Fragment使用基础 # ## Fragment添加 ## 方法一: 布局里的标签 标识符: tag, id, 如果都没有, container的id将会被使用. 方法二: 动态添加 动态添加利用了一个transaction: FragmentManager fragmentManager = getFragmentManager(); Fragment fragment = fragmentManager.findFragmentByTag(FragmentB.TAG); if (null == fragment) { FragmentB fragmentB = new FragmentB(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.add(R.id.fragment_container, fragmentB, FragmentB.TAG) .commit(); } `commit()`方法并不立即执行transaction中包含的动作,而是把它加入到UI线程队列中. 如果想要立即执行,可以在commit之后立即调用FragmentManager的[`executePendingTransactions()`][executePendingTransactions]方法. `commit()`方法必须在状态存储之前调用,否则会抛出异常,如果觉得状态丢失没关系,可以调用`commitAllowingStateLoss()`. 但是除非万不得已, 一般不推荐用这个方法, 会掩盖很多错误. ## Back Stack ## Activity的back stack: 系统维护, 每个task一个back stack. Fragment的back stack: 宿主activity掌管, 每个activity一个. 通过调用`addToBackStack()`,commit()的一系列转换作为一个transaction被存储在back stack中, 用户按Back键, 从栈中pop出一个transaction, 逆转操作, 可以返回上一个转换前的状态. 一个transaction可以包含多种操作, 并且不局限于对同一个Fragment, 所以每一个transaction实际上可以是一系列对多个fragment的操作的组合. 加入到back stack中去的时候, 是把这一系列的组合作为一个原子, 加入到back stack中. ## 构造和参数传递 ## 所有的Fragment都必须有一个`public的无参构造函数`, 因为framework经常会在需要的时候重新创建实例(状态恢复时), 它需要的就是这个构造. 如果无参构造没有提供,会有异常. 所以`不要给Fragment写有参数的构造函数, 也不要企图搞个什么单例的Fragment`. 这些都是反设计的. 参数传递的正确姿势: public static FragmentWithParameters newInstance(int num) { FragmentWithParameters fragmentWithParameter = new FragmentWithParameters(); Bundle args = new Bundle(); args.putInt(NUM, num); fragmentWithParameter.setArguments(args); return fragmentWithParameter; } @Overridepublic void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); num = getArguments() != null ? getArguments().getInt(NUM) : 0; } 这里是提供了一个静态方法, 也可以new出对象后自己set Bundle参数. ## Fragment的通信 ## 除了DialogFragment和嵌套Fragment需要与自己的parent fragment通信以外, 一般的fragment是不与其他fragment有任何通信的. 因为要求应尽量独立, 模块化, 可复用. fragment与自己的parent activity (除了嵌套和dialog的情况外, 这个parent通常是activity) 有直接通信, 一般以这三种方式: 1. 在构造fragment的时候, 通过Bundle传递参数. 2. parent可以直接调用fragment的public方法, 这里也可以传递一些参数. 3. Listener, 也即parent实现的callback接口, fragment可以在自己内部调用, 这里fragment也可以传递参数出去. 对于DialogFragment来说, 可以通过一个public的set方法将外面的target设置进去. 比如用Fragment的这个方法: [setTargetFragment()][setTargetFragment] [例子][Link 1] 对于嵌套(nested)Fragment, 通信方式与上面普通的fragment类似, 只不过parent此时不是activity而是一个fragment. 后面会单独有一个文章说嵌套Fragment的使用, 敬请期待. # Fragment的生命周期 # Fragment的生命周期首先和Activity的生命周期密切相关, 如果activity stopped,其中所有的fragment都不能start; 如果activity destroyed, 其中所有的fragment都会被destroyed. 只有activity在resumed状态下,fragment的生命周期可以独立改变,否则它被activity控制. ![Fragment Lifecycle][] ![Fragment Lifecycle 2][] ![Activity-Fragment Lifecycle][] ![Fragment more callbacks lifecycle][] 上面这个图来自于: [https://corner.squareup.com/2014/10/advocating-against-android-fragments.html][https_corner.squareup.com_2014_10_advocating-against-android-fragments.html] 这里还有一个更吊的图: [https://github.com/xxv/android-lifecycle][https_github.com_xxv_android-lifecycle] # FragmentTransaction基础操作 # ## 操作类型 ## [FragmentTransaction][] 中对Fragment有如下几种操作: attach(), detach()add(), remove(), show(), hide(), replace() 除了`replace()`以外其他都是成对的. 其中`attach()`和`detach()`不是很常用. 调用`detach()`之后, fragment实际的生命周期会走到onDestroyView(), 但不会走onDestroy()和onDetach(), 也即fragment本身并没有被销毁, 只是view被销毁了. 这和addToBackStack()的情况一样, 尽管调用detach()的时候没有addToBackStack(), 仍然只是走到view被销毁的阶段. `add()`和`remove()`是将fragment添加和移除. remove()比detach()要彻底一些, 如果不加入到back stack, remove()的时候, fragment的生命周期会一直走到onDetach(). `show()`和`hide()`是用来设置fragment的显示和隐藏状态, 这两个方法并不对应fragment的状态变化,只是将view设置为visible和gone,然后调用onHiddenChanged()的回调. 实际上`replace() == remove() + add()`, 所以它的反操作也是replace(), 只不过把add和remove的东西交换一下. 关于replace()和show(), hide()的选择, 要根据实际使用情形来定. `replace()`的好处是会减少内存占用, 但是返回时需要重新走完初始化的过程. `show()`和`hide()`只是控制了fragment的显示和隐藏, 不会改变生命周期状态, 也即fragment始终是处于running状态的, 被保持在内存中, 适用于频繁切换的情形. ## remove(), replace()是否加到back stack对生命周期的影响 ## 前面说过, `replace() == remove() + add()` 新的fragment将取代在容器布局中的fragment, 如果没有,将直接添加新的fragment. 是否添加到back stack对fragment的生命周期是有影响的. `remove()`或者`replace()`的时候,如果`commit()`之前没有调用`addToBackStack()`,那个旧fragment将会被destroyed和detach; 即完全销毁和移除. 如果调用了`addToBackStack()`,旧的fragment会处在stopped状态,调用到`onDestroyView()`, 可以通过返回键来resume. 这个时候对于旧的Fragment来说, 成员变量依然在,但是View被销毁了. 所以返回时它的生命周期从`onCreateView()`开始重建View. **-------------------------------------------------------------------------------------------------** # 二.嵌套Fragment的使用及常见错误 # 嵌套Fragments (Nested Fragments), 是在Fragment内部又添加Fragment. 使用时, 主要要依靠宿主Fragment的 `getChildFragmentManager()` 来获取FragmentManger. 虽然看起来和在activity中添加fragment差不多, 但因为fragment生命周期及管理恢复模式不同, 其中有一些需要特别注意的地方. 本文内容还包括了从Fragment迁移到v4.Fragment代码中需要改动的一些地方. ## 嵌套Fragments ## 嵌套Fragments [Nested Fragments][] 是Android 4.2 API 17 引入的. 目的: 进一步增强动态复用. 如果要在Android 4.2之前使用, 可以用support library v4的版本, 后面会有详细的迁移过程介绍. ### 嵌套Fragment的动态添加 ### 在宿主fragment里调用[getChildFragmentManager()][getChildFragmentManager] 即可用它来向这个fragment内部添加fragments. Fragment videoFragment = new VideoPlayerFragment(); FragmentTransaction transaction = getChildFragmentManager().beginTransaction(); transaction.add(R.id.video_fragment, videoFragment).commit(); 同样, 对于内部的fragment来说, [getParentFragment()][getParentFragment] 方法可以获取到fragment的宿主fragment. ### getChildFragmentManager() 和 getFragmentManager() ### `getChildFragmentManager()`是fragment中的方法, 返回的是管理当前fragment内部子fragments的manager. `getFragmentManager()`在activity和fragment中都有. 在activity中, 如果用的是v4 support库, 方法应该用`getSupportFragmentManager()`, 返回的是管理activity中fragments的manager. 在fragment中, 还叫getFragmentManager(), 返回的是把自己加进来的那个manager. 也即, 如果fragment在activity中, fragment.getFragmentManager()得到的是activity中管理fragments的那个manager. 如果fragment是嵌套在另一个fragment中, fragment.getFragmentManager()得到的是它的parent的getChildFragmentManager(). 总结就是: **getFragmentManager()是本级别管理者, getChildFragmentManager()是下一级别管理者**. 这实际上是一个树形管理结构. ## 使用Support library ## ### 为什么要使用support library? 有两种原因: ### 1. 要在API level11之前使用fragment. 2. 要在API Level 17之前使用`getChildFragmentManager()`, 即使用嵌套Fragment. ### 迁移到support library需要改动哪些地方? ### 把Fragment迁移到v4版本, 需要改动如下地方: import android.app.Fragment; -> import android.support.v4.app.Fragment; Activity -> FragmentActivity / AppCompatActivity activity.getFragmentManager() -> getSupportFragmentManager() Loader, LoaderManager, LoaderCursor也需要改成v4包的. activity.getLoaderManager() -> getSupportLoaderManager() Fragment中onTrimMemory()方法不见了 以前是这个方法 @Overridepublic void onTrimMemory(int level) { super.onTrimMemory(level); imageLoader.trimMemory(level); } v4版本需要改成这个 @Overridepublic void onLowMemory() { super.onLowMemory(); imageLoader.trimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE); } ## 嵌套Fragment使用常见错误 ## ### 错误情形1: 把嵌套Fragment放在布局里 ### 把嵌套Fragment放在布局里 -> `InflateException in Binary XML` 看起来嵌套fragment的使用除了要用`getChildFragmentManager()`以外, 其他跟之前似乎没什么区别. 如果嵌套的fragment不需要太多控制, 固定地占据了一块地方, 你可能想当然地为了省事就把它放进了xml布局文件里, 写个标签. 运行一下初看起来似乎没什么错, run一下也能显示出来, 但是千万不要这样做, 多玩两下更复杂的你就知道了. 上面[官网介绍时][Nested Fragments]就有这么一句: Note: You cannot inflate a layout into a fragment when that layout includes a <fragment>. Nested fragments are only supported when added to a fragment dynamically. 人家这么说肯定是有原因的哇, 下面我来告诉你我知道的问题: 如果Fragment被嵌套写在了布局里, inflate到这个标签的时候就相当于将它加进了FragmentManager里. 如果嵌套的parent fragment因为需要重建View而重新走了`onCreateView()`方法, 再次inflate, 此时就会抛出异常: `InflateException in Binary XML` 之前为什么可以呢? 非嵌套的情况, fragment直接加在activity里, 如果需要重新inflate, 必定是在onCreate()里, activity是重新建的, 所以没有问题, 因为不存在fragmentManager中已经持有同一个fragment的问题. 举一个例子: 在嵌套的情况下, 如果FragmentE布局里有FragmentA, 这时候我们需要叠加一个FragmentD. 用了`replace()`, 并且`addToBackStack()`. 当D显示的时候, E实际上View是被销毁的, 然后back回来, 重建View, 即FragementE需要重新从onCreateView ()开始走生命周期, 走到inflate的时候又看到了fragmentA的标签. 但是这时候A实际上还在FragmentManager里面, 所以就会抛出如下的异常: `android.view.InflateException: Binary XML file line # XX: Binary XML file line #XX: Error inflating class fragment` 崩溃的位置就在parent fragment(FragmentE) inflate的时候. 打印具体的异常栈信息可以看到: at com.example.ddmeng.helloactivityandfragment.fragment.FragmentE.onCreateView(FragmentE.java:35)at android.app.Fragment.performCreateView(Fragment.java:2220)at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:973)at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1148)at android.app.FragmentManagerImpl.popBackStackState(FragmentManager.java:1587)at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:578)at android.support.v4.app.BaseFragmentActivityEclair.onBackPressedNotHandled(BaseFragmentActivityEclair.java:27)at android.support.v4.app.FragmentActivity.onBackPressed(FragmentActivity.java:189)Caused by: java.lang.IllegalArgumentException: Binary XML file line #16: Duplicate id 0x7f0c0059, tag null, or parent id 0xffffffff with another fragment for com.example.ddmeng.helloactivityandfragment.fragment.FragmentAat android.app.FragmentManagerImpl.onCreateView(FragmentManager.java:2205) [实验例子代码][Link 2] #### Solution 1: 动态添加child fragment #### 解决上面的问题有各种方法, 最常规的做法是, 使用动态添加: Fragment fragmentA = getChildFragmentManager().findFragmentByTag(NESTED_FRAGMENT_TAG); if (fragmentA == null) { Log.i(LOG_TAG, "add new FragmentA !!"); fragmentA = new FragmentA(); FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction(); fragmentTransaction.add(R.id.fragment_container, fragmentA, NESTED_FRAGMENT_TAG).commit(); } else { Log.i(LOG_TAG, "found existing FragmentA, no need to add it again !!"); } #### Solution 2: 在异常之前remove child fragment #### 如果你的子fragment非要加在布局里不可, 而你的程序确实会有重建父fragment view的情形. 为了避免上面的异常, 你也可以这样做(tricky and not recommended): public void removeChildFragment(Fragment parentFragment) { FragmentManager fragmentManager = parentFragment.getChildFragmentManager(); Fragment child = fragmentManager.findFragmentById(R.id.child); if (child != null) { fragmentManager.beginTransaction() .remove(child) .commitAllowingStateLoss(); } } 在parentFragment的`onCreateView()`方法中inflate之前和`onSaveInstanceState()`方法中做save工作之前调用它. 这两个地方是发生异常的地方, 只要在其之前remove就好. ### 错误情形2: 把fragment放在一个动态布局里 ### 把fragment放在一个动态布局里 -> `java.lang.IllegalArgumentException: No view found for id` 发现这个错误是因为项目中的一个子Fragment是添加在RecyclerView里面的一块的. RecyclerView要等到Loader的数据取到了之后再populate每一块的布局. 还是上面的流程, 启动父fragment, load数据, 添加子fragment, 这都没有问题. 但是一旦如果是上面的`replace()`加`addToBackStack()` , 并且再次返回, 就会出现异常. 因为当重建View的时候, fragmentManager其中是持有child fragment的, 但是找不到它的container, 于是就会抛出异常. 我也同样做了一个小实验, 在我的demo程序里: [HelloActivityAndFragment][Link 2] Nested Fragment in Dynamic Container: 在Fragment F中, 先添加一个FrameLayout, 再把child fragment A加进去. 然后在Activity中, 用D replace F, 按back键返回, 就会有crash: java.lang.IllegalArgumentException: No view found for id 0x7f0c0062 (com.example.ddmeng.helloactivityandfragment:id/frame_container) for fragment FragmentA{b37763 #0 id=0x7f0c0062 FragmentA} at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:965) at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1148) at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1130) at android.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManager.java:1953) at android.app.Fragment.performActivityCreated(Fragment.java:2234) at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:992) at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1148) at android.app.BackStackRecord.popFromBackStack(BackStackRecord.java:1670) at android.app.FragmentManagerImpl.popBackStackState(FragmentManager.java:1587) at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:578) at android.app.Activity.onBackPressed(Activity.java:2503) 这是因为返回的时候FragmentManager找不到对应的container了. 所以应该避免这种做法, 尽量把fragment加进parent的根布局里, 而不是某个动态添加的布局. **另附大神工具类[Fragmentation][]**.:[https://github.com/YoKeyword/Fragmentation][Fragmentation] 有关于fragment的一些坑及解决版本,推荐start \------------------------------------------------------------------------------------------------- # 三.Activity fragment,webview的状态保存与恢复 # 总结来说, 就是Activity的销毁, 分为彻底销毁和留下数据的销毁两种. **彻底销毁**是指用户主动去关闭或退出这个Activity. 此时是不需要状态恢复的, 因为下次回来又是重新创建全新的实例. **留下数据的销毁**是指系统销毁了activity, 但是当用户返回来时, 会重新创建它, 让用户觉得它一直都在. 屏幕旋转重建可以归结为第二种情况, 打开Do not keep activities开关, 切换activities也是会出现第二种情况. 打开**Do not keep activities**开关就是为了模拟内存不足时的系统行为, 这里有一篇[分析][Link 3] ## 如何恢复 ## 实际上系统已经帮我们做好了View层面基本的恢复工作, 主要是依靠下面两个方法: @Overrideprotected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // 在onStop()之前调用, 文档中说并不保证在onPause()的之前还是之后// 我的试验中一般是在onPause()之后 } @Overrideprotected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); // 在onStart() 之后 } Bundle其中包含了activity中的view和fragment的各种信息, 所以调用基类的方法就可以完成基本的view层面的恢复工作. **注意这两个方法并不是activity的生命周期回调, 对于activity来说它们不是一定会发生的. 另外需要注意的是, View必须要有id才能被恢复.** 举一个实例来说明: Activity A start B, 那么A的`onSaveInstanceState()`会在onStop()之前调用, 以防A被系统销毁. 但是在B中按下back键finish()了自己后, B被销毁的过程中, 并没有调用`onSaveInstanceState()`, 是因为B并没有被压入task的back stack中, 也即系统知道B并不需要储存自己的状态. 正常情况下, 返回到A, A没有被销毁, 也不会调用`onRestoreInstanceState()`, 因为所有的状态都还在, 并不需要重建. 如果我们打开了**Do not keep activities**开关, 模拟系统内存不足时的行为, 从A到B, 可以看到当B resume的时候A会一路走到onDestroy(), 而关掉B之后, A会从onCreate()开始走, 此时onCreate()的参数bundle就不为空了, onStart()之后会调用`onRestoreInstanceState()`方法, 其参数bundle中内容类似于如下: Bundle[{android:viewHierarchyState=Bundle[mParcelledData.dataSize=272]}] 其中包含了View的状态, 如果有Fragment, 也会包含Fragment的状态, 其实质是保存了FragmentManagerState, 内容类似于如下: Bundle[{ android:viewHierarchyState=Bundle[{ android:views={ 16908290=android.view.AbsSavedState$1@bc382e7, 2131492950=CompoundButton.SavedState{ 4034f96 checked=true}, 2131492951=android.view.AbsSavedState$1@bc382e7}}], android:fragments=android.app.FragmentManagerState@bacc717}] 对于上面的例子来说, B什么时候会调用`onSaveInstanceState()`呢? 当从A打开B之后, 按下Home键, B就会调用`onSaveInstanceState()`. 因为这时候系统不知道用户什么时候会返回, 有可能会把B也销毁了, 所以保存一下它的状态. 如果下次回来它没有被重建, `onRestoreInstanceState()`就不会被调用, 如果它被重建了, `onRestoreInstanceState()`才会被调用. ### Activity保存方法的调用时机 ### **activity的`onSaveInstanceState()`和`onRestoreInstanceState()`方法在如下情形下会调用:** 1. 屏幕旋转重建: 先save再restore. 2. 启动另一个activity: 当前activity在离开前会save, 返回时如果因为被系统杀死需要重建, 则会从onCreate()重新开始生命周期, 调用onRestoreInstanceState(); 如果没有重建, 则不会调用onCreate(), 也不会调用onRestoreInstanceState(), 生命周期从onRestart()开始, 接着onStart()和onResume(). 3. 按Home键的情形和启动另一个activity一样, 当前activity在离开前会save, 用户再次点击应用图标返回时, 如果重建发生, 则会调用onCreate()和onRestoreInstanceState(); 如果activity不需要重建, 只是onRestart(), 则不会调用onRestoreInstanceState(). ### Activity恢复方法的调用时机 ### **activity的`onSaveInstanceState()`和`onRestoreInstanceState()`方法在如下情形下不会调用:** 1. 用户主动finish()掉的activity不会调用onSaveInstanceState(), 包括主动按back退出的情况. 2. 新建的activity, 从onCreate()开始, 不会调用onRestoreInstanceState(). ## Activity中还需要手动恢复什么 ## 如上, 系统已经为我们恢复了activity中的各种view和fragment, 那么我们自己需要保存和恢复一些什么呢? 答案是**成员变量值**. 因为系统并不知道你的各种成员变量有什么用, 哪些值需要保存, 所以需要你自己覆写上面两个方法, 然后把自己需要保存的值加进bundle里面去. 具体例子, 这里[Activity的重新创建][Activity]有, 我就不重复了. 重要的是不要忘记调用super的方法, 那里有系统帮我们恢复的工作. # 工具类Icepick介绍 # 在介绍下面的内容之前, 先介绍一个小工具: [Icepick][] 这个工具的作用是, 在你想保存和重建自己的成员变量数据时, 帮你省去那些put和get方法的调用, 你也不用为每一个字段起一个常量key. 你需要做的就是简单地在你想要保存状态的字段上面加上一个`@State` 注解. 然后在保存和恢复的时候分别加上一句话: @Overridepublic void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Icepick.restoreInstanceState(this, savedInstanceState); } @Overridepublic void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Icepick.saveInstanceState(this, outState); } 然后你的成员变量就有了它应该有的值了, DONE! # Fragment的状态保存和恢复 # Fragment的状态比Activity的要复杂一些, 因为它的生命周期状态比较多. ## Fragment状态保存和恢复的相关方法 ## 按照上面的思路, 我先去查找Fragment中保存和恢复的回调方法了. Fragment的状态保存回调是这个方法: public void onSaveInstanceState(Bundle outState) { // may be called any time before onDestroy() } 这个方法和之前activity的情况大体是类似的, 它不是生命周期的回调, 所以只在有需要的时候会调到. onSaveInstanceState()在activity调用onSaveInstanceState()的时候发生, 用于保存实例状态.(看它的方法名: instance state). `onSaveInstanceState()`方法保存的bundle会返回给几个生命周期回调: `onCreate()`, `onCreateView()`, `onViewCreated()`和`onActivityCreated()`. Fragment并没有对应的onRestoreInstanceState()方法. 也即没有实例状态的恢复回调. Fragment只有一个onViewStateRestored()的回调方法: public void onViewStateRestored(@Nullable Bundle savedInstanceState) { // 在onActivityCreated()和onStart()之间调用 mCalled = true; } onViewStateRestored()每次新建Fragment都会发生. 它并不是实例状态恢复的方法, 只是一个View状态恢复的回调. **这里需要注意, Fragment的状态分两个类型: 实例状态和View状态**. 这里有个最佳实践: [The Real Best Practices to Save/Restore Activity's and Fragment's state][The Real Best Practices to Save_Restore Activity_s and Fragment_s state] **不要把Fragment的实例状态和View状态混在一起处理.** 在这里我先上个结论, 把查看源码中Fragment状态保存和恢复的相关方法列出来: Fragment状态保存入口: ![Fragment状态保存][Fragment] Fragment的状态保存入口有三个: 1. Activity的状态保存, 在Activity的`onSaveInstanceState()`里, 调用了FragmentManger的`saveAllState()`方法, 其中会对mActive中各个Fragment的实例状态和View状态分别进行保存. 2. FragmentManager还提供了public方法: `saveFragmentInstanceState()`, 可以对单个Fragment进行状态保存, 这是提供给我们用的, 后面会有例子介绍这个. 其中调用的`saveFragmentBasicState()`方法即为情况一中所用, 图中已画出标记. 3. FragmentManager的`moveToState()`方法中, 当状态回退到`ACTIVITY_CREATED`, 会调用`saveFragmentViewState()`方法, 保存View的状态. `moveToState()`方法中有很长的switch case, 中间不带break, 基本是根据新状态和当前状态的比较, 分为正向创建和反向销毁两个方向, 一路沿着多个case走下去. Fragment状态恢复入口: ![Fragment状态恢复][Fragment 1] 三个恢复的入口和三个保存的入口刚好对应. 1. 在Activity重新创建的时候, 恢复所有的Fragment状态. 2. 如果调用了FragmentManager的方法: `saveFragmentInstanceState()`, 返回值得到的状态可以用Fragment的`setInitialSavedState()`方法设置给新的Fragment实例, 作为初始状态. 3. FragmentManager的`moveToState()`方法中, 当状态正向创建到`CREATED`时, Fragment自己会恢复View的状态. 这三个入口分别对应的情况是: 入口1对应系统销毁和重建新实例. 入口2对应用户自定义销毁和创建新Fragment实例的状态传递. 入口3对应同一Fragment实例自身的View状态重建. ## Fragment状态保存恢复和Activity的联系 ## 这里对应的是入口1的情况. 当Activity在做状态保存和恢复的时候, 在它其中的fragment自然也需要做状态保存和恢复. 所以Fragment的onSaveInstanceState()在activity调用onSaveInstanceState()的时候一定会发生. 同样的, 如果Fragment中有一些成员变量的值在此时需要保存, [也可以用@State标记][State], 处理方法和上面一样. 也即, 在Activity需要保存状态的时候, 其中的Fragments的**实例状态**自动被处理保存. ## Fragment同一实例的View状态恢复 ## 这里对应的是入口3的情况. 前面介绍过, activity在保存状态的时候, 会将所有View和Fragment的状态都保存起来等待重建的时候使用. 但是如果是单个Activity对应多个Fragments的架构, Activity永远是resume状态, 多个Fragments在切换的过程中, 没有activity的帮助, 如何保存自己的状态? 首先, 取决于你的多个Fragments是如何初始化的. 我做了一个实验, 在activity的onCreate()里面初始化两个Fragment: private void initFragments() { tab1Fragment = getFragmentManager().findFragmentByTag(Tab1Fragment.TAG); if (tab1Fragment == null) { tab1Fragment = new Tab1Fragment(); } tab2Fragment = getFragmentManager().findFragmentByTag(Tab2Fragment.TAG); if (tab2Fragment == null) { tab2Fragment = new Tab2Fragment(); } } 然后点击两个按钮来切换它们, replace(), 并且不加入到back stack中: @OnClick(R.id.tab1) void onTab1Clicked() { getFragmentManager().beginTransaction() .replace(R.id.content_container, tab1Fragment, Tab1Fragment.TAG) .commit(); } @OnClick(R.id.tab2) void onTab2Clicked() { getFragmentManager().beginTransaction() .replace(R.id.content_container, tab2Fragment, Tab2Fragment.TAG) .commit(); } 可以看到, 每一次的切换, 都是一个Fragment的完全destroy, detach和另一个fragment的attach, create, 但是当我在这两个fragment中各自加上EditText, 发现只要EditText有id, 切换过程中EditText的内容是被保存的. 这是谁在什么时候保存并恢复的呢? 我在TextChange的回调里打了断点, 发现调用栈如下: ![Fragment restore view][] 在`FragmentManagerImpl`中, `moveToState()`方法的case Fragment.CREATED中: 调用了: `f.restoreViewState(f.mSavedFragmentState);` 此时我没有做任何保存状态的处理, 但是断点中可以看出: ![Fragment states][] 虽然mSavedFragmentState是null, 但是mSavedViewState却有值. 所以这个View状态保存和恢复对应的入口即是上面两个图中的入口三. 这是因为我的两个fragment只new了一次, 然后保存了成员变量, 即便是Fragment重新onCreate(), 但是对应的实例仍然是同一个. 这和Activity是不同的, 因为你是无法new一个Activity的. 在上面的例子中, 如果不保存Fragment的引用, 每次都new Fragment, 那么View的状态是不会被保存的, 因为不同实例间的状态传递只有在系统销毁恢复的情况下才会发生(入口一). 如果我们需要在不同的实例间传递状态, 就需要用到下面的方法. ## 不同Fragment实例间的状态保存和恢复 ## 这里对应的是入口2, 不同于入口1和3, 它们是自动的, 入口2是用户主动保存和恢复的情形. 自己主动保存Fragment的状态, 可以调用FragmentManager的这个方法: public abstract Fragment.SavedState saveFragmentInstanceState(Fragment f); 它的实现是这样的: @Overridepublic Fragment.SavedState saveFragmentInstanceState(Fragment fragment) { if (fragment.mIndex < 0) { throwException(new IllegalStateException("Fragment " + fragment + " is not currently in the FragmentManager")); } if (fragment.mState > Fragment.INITIALIZING) { Bundle result = saveFragmentBasicState(fragment); return result != null ? new Fragment.SavedState(result) : null; } return null; } 返回的数据类型是: Fragment.SavedState, 这个state可以通过Fragment的这个方法设置给自己: public void setInitialSavedState(SavedState state) { if (mIndex >= 0) { throw new IllegalStateException("Fragment already active"); } mSavedFragmentState = state != null && state.mState != null ? state.mState : null; } 但是注意只能在Fragment被加入之前设置, 这是一个初始状态. 利用这两个方法可以更加自由地保存和恢复状态, 而不依赖于Activity. 这样处理以后, 不必保存Fragment的引用, 每次切换的时候虽然都new了新的实例, 但是旧的实例的状态可以设置给新实例. 例子代码: @State SparseArray<Fragment.SavedState> savedStateSparseArray = new SparseArray<>(); void onTab1Clicked() { // save current tab Fragment tab2Fragment = getSupportFragmentManager().findFragmentByTag(Tab2Fragment.TAG); if (tab2Fragment != null) { saveFragmentState(1, tab2Fragment); } // restore last state Tab1Fragment tab1Fragment = new Tab1Fragment(); restoreFragmentState(0, tab1Fragment); // show new tabgetSupportFragmentManager().beginTransaction() .replace(R.id.content_container, tab1Fragment, Tab1Fragment.TAG) .commit(); } private void saveFragmentState(int index, Fragment fragment) { Fragment.SavedState savedState = getSupportFragmentManager().saveFragmentInstanceState(fragment); savedStateSparseArray.put(index, savedState); } private void restoreFragmentState(int index, Fragment fragment) { Fragment.SavedState savedState = savedStateSparseArray.get(index); fragment.setInitialSavedState(savedState); } 注意这里用了SparseArray来存储Fragment的状态, 并且加上了`@State`, 这样在Activity重建的时候其中的内容也能够被恢复. ## Back stack中的fragment ## 有一点很特殊的是, 当Fragment从back stack中返回, 实际上是经历了一次View的销毁和重建, 但是它本身并没有被重建. 即View状态需要重建, 实例状态不需要重建. 举个例子说明这种情形: Fragment被另一个Fragment replace(), 并且压入back stack中, 此时它的View是被销毁的, 但是它本身并没有被销毁. 也即, 它走到了onDestroyView(), 却没有走`onDestroy()`和`onDetact()`. 等back回来的时候, 它的view会被重建, 重新从onCreateView()开始走生命周期. 在这整个过程中, 该Fragment中的成员变量是保持不变的, 只有View会被重新创建. 在这个过程中, instance state的saving并没有发生. **所以, 很多时候Fragment还需要考虑的是在没有Activity帮助的情形下(Activity并没有可能重建的情形), 自身View状态的保存.** 此时要注意一些不容易发现的错误, 比如List的新实例需要重新setAdapter等. ## Fragment setRetainInstance ## Fragment有一个相关方法: [setRetainInstance][] 这个方法设置为true的时候表示, 即便activity重建了, 但是fragment的实例并不被重建. 注意此方法只对没有放在back stack中的fragment生效. 什么时候要用这个方法呢? 处理configuration change的时候: [Handling Configuration Changes with Fragments][] 这样, 当屏幕旋转, Activity重建, 但是其中的fragment和fragment正在执行的任务不必重建. 更多解释可以参见: [http://stackoverflow.com/questions/11182180/understanding-fragments-setretaininstanceboolean][http_stackoverflow.com_questions_11182180_understanding-fragments-setretaininstanceboolean] [http://stackoverflow.com/questions/11160412/why-use-fragmentsetretaininstanceboolean][http_stackoverflow.com_questions_11160412_why-use-fragmentsetretaininstanceboolean] 注意这个方法只是针对**configuration change**, 并不影响用户主动关闭和系统销毁的情况: 当activity被用户主动finish, 其中的所有fragments仍然会被销毁. 当activity不在最顶端, memory不够了, 系统仍然可能会销毁activity和其中的fragments. # View的状态保存和恢复 # View的状态保存和恢复主要是依赖于下面几个方法: 保存: `saveHierarchyState()` -> `dispatchSaveInstanceState()` -> `onSaveInstanceState()` 恢复: `restoreHierarchyState()` -> `dispatchRestoreInstanceState()` -> `onRestoreInstanceState()` 还有两个重要的前提条件是View要有id, 并且`setSavedEnabled()`为true.(这个值默认为true). 在系统的widget里(比如TextView, EditText, Checkbox等), 这些都是已经被处理好的, 我们只需要给View赋予id, Activity和Fragment重建的时候会自动恢复其中的状态. (这里的Fragment恢复对应入口一和入口三, 入口二属于跨实例新建的情况). 但是如果你要使用第三方的自定义View, 就需要确认一下它们内部是否有状态保存和恢复的代码. 如果不行你就需要继承该自定义View, 然后实现这两个方法: //// Assumes that SomeSmartButton is a 3rd Party view that// View State Saving/Restoring are not implemented internally//public class SomeBetterSmartButton extends SomeSmartButton { ... @Overridepublic Parcelable onSaveInstanceState() { Bundle bundle = new Bundle(); // Save current View's state herereturn bundle; } @Overridepublic void onRestoreInstanceState(Parcelable state) { super.onRestoreInstanceState(state); // Restore View's state here } ... } # WebView的状态保存和恢复 # WebView的状态保存和恢复不像其他原生View一样是自动完成的. WebView不是继承自View的. 如果我们把WebView放在布局里, 不加处理, 那么Activity或Fragment重建的过程中, WebView的状态就会丢失, 变成初始状态. 在Fragment的onSaveInstanceState()里面可以加入如下代码来保存WebView的状态: @Overridepublic void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); webView.saveState(outState); } 然后在初始化的时候, 增加判断, 不必每次都打开初始链接: if (savedInstanceState != null) { webView.restoreState(savedInstanceState); } else { webView.loadUrl(TEST_URL); } 这样处理以后, 在重新建立的时候, WebView的状态就能恢复到离开前的页面. 不论WebView是放在Activity里还是Fragment里, 这个方法都适用. 但是Fragment还有另一种情况, 即Fragment被压入back stack, 此时它没有被destroy(), 所以没有调用onSavedInstanceState()这个方法. 这种情况返回的时候, 会从onCreateView()开始, 并且savedInstanceState为null, 于是其中WebView之前的状态在此时丢失了. 解决这种情况可以利用Fragment实例并未销毁的条件, 增加一个成员变量bundle, 保存WebView的状态, 最终解决如下: private Bundle webViewState; @Overridepublic void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); ButterKnife.bind(this, view); initWebView(); if (webViewState != null) { //Fragment实例并未被销毁, 重新create view webView.restoreState(webViewState); } else if (savedInstanceState != null) { //Fragment实例被销毁重建 webView.restoreState(savedInstanceState); } else { //全新Fragment webView.loadUrl(TEST_URL); } } @Overridepublic void onPause() { super.onPause(); webView.onPause(); //Fragment不被销毁(Fragment被加入back stack)的情况下, 依靠Fragment中的成员变量保存WebView状态 webViewState = new Bundle(); webView.saveState(webViewState); } @Overridepublic void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); //Fragment被销毁的情况, 依靠outState保存WebView状态if (webView != null) { webView.saveState(outState); } } \------------------------------------------------------------------------------------------------- # 四.fragment Toolbar处理 # ## 使用support library的Toolbar ## Android的ActionBar每个版本都会做一些改变, 所以原生的ActionBar在不同的系统上看起来可能会不一样. 使用support library版本的[Toolbar][]可以让你的应用在多种设备类型上保持一致. support library中总是包含了最新的features. Android从5.0 (API Level 21)开始提供[Material Design][], 使用v7版本的Toolbar后, 在任何Android 2.1(API Level 7)以上的机器上都可以看到Material Design风格的Toolbar. ## 在Activity中使用Toolbar ## 1.首先项目gradle中添加: compile 'com.android.support:appcompat-v7:23.4.0' 2.确保Activity继承`AppCompatActivity` 3.在application设置中使用NoActionBar的主题: <application android:theme="@style/Theme.AppCompat.Light.NoActionBar"/> 4.把Toolbar写在布局中 <android.support.v7.widget.Toolbar android:id="@+id/my_toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:elevation="4dp" android:theme="@style/ThemeOverlay.AppCompat.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/> 5.在Activity里面把Toolbar设置成为ActionBar 首先把Toolbar find出来, 然后调用[setSupportActionBar方法][setSupportActionBar] 把Toolbar设置为自己的ActionBar即可. public class ToolbarDemoActivity extends AppCompatActivity { @BindView(R.id.toolbar) Toolbar toolbar; @Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_toolbar_demo); ButterKnife.bind(this); setSupportActionBar(toolbar); } } 然后就可以随意使用啦, 用[getSupportActionBar][]可以获取ActionBar类型的对象, 从而使用[ActionBar][]的方法. ### 添加Action Buttons ### 定义menu: <?xml version="1.0" encoding="utf-8"?><menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"><item android:id="@+id/action_android" android:icon="@drawable/ic_android_black_24dp" android:title="@string/action_android" app:showAsAction="always" /><item android:id="@+id/action_favourite" android:icon="@drawable/ic_favorite_black_24dp" android:title="@string/action_favourite" app:showAsAction="ifRoom" /><item android:id="@+id/action_settings" android:title="@string/action_settings" app:showAsAction="never" /></menu> 然后在代码中inflate和处理它的点击事件: @Overridepublic boolean onCreateOptionsMenu(Menu menu) { Log.i(TAG, "onCreateOptionsMenu()"); getMenuInflater().inflate(R.menu.menu_activity_main, menu); return super.onCreateOptionsMenu(menu); } @Overridepublic boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_android: Log.i(TAG, "action android selected"); return true; case R.id.action_favourite: Log.i(TAG, "action favourite selected"); return true; case R.id.action_settings: Log.i(TAG, "action settings selected"); return true; default: return super.onOptionsItemSelected(item); } } ### 添加向上返回的action ### 添加向上返回parent的action: @Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_toolbar_demo); ButterKnife.bind(this); setSupportActionBar(toolbar); // add a left arrow to back to parent activity,// no need to handle action selected event, this is handled by supergetSupportActionBar().setDisplayHomeAsUpEnabled(true); } 然后只需要在manifest中指定parent: <activity android:name=".toolbar.ToolbarDemoActivity" android:parentActivityName=".MainActivity"></activity> ## 在Fragment中使用Toolbar ## 在Fragment中使用Toolbar的步骤和Activity差不多. 在Fragment布局中添加一个Toolbar, 然后find它, 然后调用Activity的方法来把它设置成ActionBar: ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar); 注意此处有一个强转, 必须是AppCompatActivity才有这个方法. 但是此时运行到Fragment之后, 发现Toolbar上的文字和按钮全是Activity传过来的, 这是因为只有Activity的`onCreateOptionsMenu()`被调用了, 但是Fragment的并没有被调用. 在Fragment中加上这句: setHasOptionsMenu(true); 此时Fragment的`onCreateOptionsMenu()`回调会被调到了, 但是inflate出的按钮和Activity中的actions加在一起显示出来了. 因为Activity的`onCreateOptionsMenu()`会在之前调用到. 于是Fragment中的写成这样: @Overridepublic void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { Log.e(TAG, "onCreateOptionsMenu()"); menu.clear(); inflater.inflate(R.menu.menu_parent_fragment, menu); } 即先clear()一下, 这样按钮就只有Fragment中设置的自己的了, 不会有Activity中的按钮. ## 在嵌套的子Fragment中使用Toolbar ## 前面已经介绍过, Fragment可以嵌套使用: [Android Fragment使用(二) 嵌套Fragments (Nested Fragments) 的使用及常见错误][Fragments_]. 那么在前面的Fragment中再显示一个子Fragment, 并且又带有一个不一样的Toolbar, 还需要哪些处理呢? 首先, java代码中还是需要有: setHasOptionsMenu(true) ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar); 然后根据是否需要菜单按钮, 覆写onCreateOptionsMenu()方法来inflate自己的menu文件即可. 感觉和在普通的Fragment中使用Toolbar作为ActionBar并没有什么区别. 但是如果你的多个Fragment有不同的Toolbar菜单选项, 如果你没有懂得其中的原理, 可能就会出现一些混乱. 下面来解说一下相关的方法. ### onCreateOptionsMenu()方法的调用 ### 一旦调用 ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar); 就会导致Activity`onCreateOptionsMenu()`方法的调用, 而Activity会根据其中Fragment是否设置了setHasOptionsMenu(true)来调用Fragment的 `onCreateOptionsMenu()`方法, 调用顺序是树形的, 按层级调用, 中间如果有false则跳过. 假设当前Activity, Parent Fragment和Child Fragment中都设置了自己的Toolbar为ActionBar. 在打开Child fragment的时候, `onCreateOptionsMenu()`的调用顺序是. `Activity -> Parent -> Child.` 此时parent和child fragment都设置了setHasOptionsMenu(true). 关于这个, 还有以下几种情况: - 如果Parent的`setHasOptionsMenu(false)`, Child为true, 则Parent的`onCreateOptionsMenu()`不会调用, 打开Child的时候Activity -> Child. - 如果Child的`setHasOptionsMenu(false)`, Parent为true, 则打开Child的时候仍然会调用Activity和Parent的onCreateOptionsMenu()方法. - 如果Parent和Child都置为false, 打开Parent和Child Fragment的时候都会调用Activity的onCreateOptionsMenu()方法. **仅仅是child Fragment的show() hide()的切换, activity和parent Fragment的onCreateOptionsMenu()也会重新进入.** 这一点我还没有想明白, 是项目中遇到的, 初步推测可能是menu的显隐变化invalidate了menu, 改天有空再试试. 上面的机制常常是导致Toolbar上面的按钮混淆错乱的原因. 举个例子: 如果我们现在Activity和Parent Fragment有不同的Toolbar按钮, 但是Child只有文字, 没有按钮. 很显然我们不需要给child写menu文件, 也不需要覆写child里的`onCreateOptionsMenu()`方法. 但是此时不管怎样, parent的`onCreateOptionsMenu()`方法都会被调用, 这样我们打开child的时候, toolbar上就神奇地出现了parent里的按钮. 这种情况如何解决呢? 可以在parent中加一个条件, 当没有child fragment的时候才做inflate的工作: @Overridepublic void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { Log.e(TAG, "onCreateOptionsMenu()"); menu.clear(); if (getChildFragmentManager().getBackStackEntryCount() == 0) { inflater.inflate(R.menu.menu_parent_fragment, menu); } } 另外, 除了`setSupportActionBar()`之外, 如果我们想**主动触发** `onCreateOptionsMenu()`方法的调用, 可以用 `invalidateOptionsMenu()`方法. ### onOptionsItemSelected()方法的调用 ### 在Activity和其中的Fragment都有options menu的时候, 需要注意menu item的id不要重复. 以为点击事件的分发也是从Activity开始分发下去的, 如果child fragment中有个选项的id和Activity中一个选项的id重复了, 则在Activity中就会将其处理, 不会继续分发. ### 有嵌套Fragment时 Back键处理 ### 之前没有嵌套Fragment的情况下, 只要将Fragment加入到Back Stack中, 那么按下Back键的时候pop动作是系统自动做好的. 虽然在添加child fragment的时候将其加入到back stack中, 但是按back键的时候仍然是将parent fragment弹出, 只剩下Activity. 这是因为back键只检查第一层Fragment的back stack, 对于child fragment, 需要在其parent中自己处理. 比如这样处理: 在Activity中 @Overridepublic void onBackPressed() { Fragment fragment = getSupportFragmentManager().findFragmentById(android.R.id.content); if (fragment instanceof ToolbarFragment) { if (((ToolbarFragment) fragment).onBackPressed()) { return; } } super.onBackPressed(); } 其中ToolbarFragment是直接加在Activity中作为parent fragment的. 在parent fragment中(即ToolbarFragment中): public boolean onBackPressed() { return getChildFragmentManager().popBackStackImmediate(); } \------------------------------------------------------------------------------------------------- <table style="margin:8px 0px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:1142.2222900390625px"> <tbody> <tr> <td style="padding:4px 8px; border-collapse:collapse; border:1px solid rgb(187,187,187); width:1125.138916015625px"> 参考资料:<br> <a href="https://developer.android.com/reference/android/app/Fragment.html" style="color:rgb(51,153,255); text-decoration:none; font-family:Helvetica,arial,sans-serif; font-size:14px" rel="nofollow"></a><a href="https://guides.codepath.com/android/Creating-and-Using-Fragments" style="color:rgb(51,153,255); text-decoration:none; font-family:Helvetica,arial,sans-serif; font-size:14px" rel="nofollow">CodePath Guides: Creating and Using Fragments</a><br> <a href="https://guides.codepath.com/android/Creating-and-Using-Fragments#nesting-fragments-within-fragments" style="color:rgb(51,153,255); outline:0px; text-decoration:none; font-family:Helvetica,arial,sans-serif; font-size:14px" rel="nofollow">Guide: Nested Fragments</a><br> <span style="font-family:Helvetica,arial,sans-serif; color:#3399ff; color:rgb(51,153,255); font-family:Helvetica,arial,sans-serif; font-size:14px"><a href="http://www.jianshu.com/p/180d2cc0feb5" rel="nofollow">从源码角度剖析Fragment核心知识点</a></span><br style="color:rgb(68,68,68); font-family:Helvetica,arial,sans-serif; font-size:14px"> <a href="http://www.jianshu.com/p/bd4a8be309c8" style="color:rgb(51,153,255); text-decoration:none; font-family:Helvetica,arial,sans-serif; font-size:14px" rel="nofollow">Fragment源码阅读笔记</a><br style="color:rgb(68,68,68); font-family:Helvetica,arial,sans-serif; font-size:14px"> <a href="http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0327/2648.html" style="color:rgb(51,153,255); text-decoration:none; font-family:Helvetica,arial,sans-serif; font-size:14px" rel="nofollow">Android中保存和恢复Fragment状态的最好方法</a><br> <a href="http://www.cnblogs.com/mengdd/p/5582244.html" style="color:rgb(51,153,255); text-decoration:none; font-family:Helvetica,arial,sans-serif; font-size:14px" rel="nofollow"> WebView的状态保存和恢复</a><br> <a href="https://inthecheesefactory.com/blog/fragment-state-saving-best-practices/en" style="color:rgb(51,153,255); text-decoration:none; font-family:Helvetica,arial,sans-serif; font-size:14px" rel="nofollow">The Real Best Practices to Save/Restore Activity's and Fragment's state</a></td> </tr> </tbody> </table> [Fragments_]: http://www.cnblogs.com/mengdd/p/5552721.html [Activity_ Fragment_WebView]: http://www.cnblogs.com/mengdd/p/5582244.html [Toolbar_Fragment_Toolbar]: http://www.cnblogs.com/mengdd/p/5590634.html [Dandan Meng]: http://www.cnblogs.com/mengdd/tag/Android/default.html?page=1 [executePendingTransactions]: https://developer.android.com/reference/android/app/FragmentManager.html#executePendingTransactions%28%29 [setTargetFragment]: https://developer.android.com/reference/android/app/Fragment.html#setTargetFragment%28android.app.Fragment,%20int%29 [Link 1]: https://guides.codepath.com/android/Using-DialogFragment#passing-data-to-parent-fragment [Fragment Lifecycle]: /images/20220715/314c7b72b38e443ca74b1325b979357e.png [Fragment Lifecycle 2]: /images/20220715/7e9bdce1c3b34fa2af656a542d1a64cd.png [Activity-Fragment Lifecycle]: /images/20220715/ef3e7cadaa05456192ff2bc53d0214f2.png [Fragment more callbacks lifecycle]: /images/20220715/82fe0f848c0f4327a58e94002f52fc83.png [https_corner.squareup.com_2014_10_advocating-against-android-fragments.html]: https://corner.squareup.com/2014/10/advocating-against-android-fragments.html [https_github.com_xxv_android-lifecycle]: https://github.com/xxv/android-lifecycle [FragmentTransaction]: https://developer.android.com/reference/android/app/FragmentTransaction.html [Nested Fragments]: https://developer.android.com/about/versions/android-4.2.html#NestedFragments [getChildFragmentManager]: https://developer.android.com/reference/android/app/Fragment.html#getChildFragmentManager%28%29 [getParentFragment]: https://developer.android.com/reference/android/app/Fragment.html#getParentFragment%28%29 [Link 2]: https://github.com/mengdd/HelloActivityAndFragment [Fragmentation]: https://github.com/YoKeyword/Fragmentation [Link 3]: http://www.cnblogs.com/mengdd/p/4528417.html [Activity]: http://www.cnblogs.com/mengdd/archive/2012/12/17/2822291.html [Icepick]: https://github.com/frankiesardo/icepick [The Real Best Practices to Save_Restore Activity_s and Fragment_s state]: https://inthecheesefactory.com/blog/fragment-state-saving-best-practices/en [Fragment]: /images/20220715/7fcf6829060a4407b0c3109be5e9b46c.png [Fragment 1]: /images/20220715/3837c9ddcba9446c9d872ade00af8ec6.png [State]: mailto:%E4%B9%9F%E5%8F%AF%E4%BB%A5%E7%94%A8@State%E6%A0%87%E8%AE%B0 [Fragment restore view]: /images/20220715/9f92a328034b4f1883e3ddc28e4a08e3.png [Fragment states]: /images/20220715/9166fe7295484d008c9ae5c00a307053.png [setRetainInstance]: https://developer.android.com/reference/android/app/Fragment.html#setRetainInstance%28boolean%29 [Handling Configuration Changes with Fragments]: http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html [http_stackoverflow.com_questions_11182180_understanding-fragments-setretaininstanceboolean]: http://stackoverflow.com/questions/11182180/understanding-fragments-setretaininstanceboolean [http_stackoverflow.com_questions_11160412_why-use-fragmentsetretaininstanceboolean]: http://stackoverflow.com/questions/11160412/why-use-fragmentsetretaininstanceboolean [Toolbar]: https://developer.android.com/reference/android/support/v7/widget/Toolbar.html [Material Design]: https://developer.android.com/design/material/index.html [setSupportActionBar]: https://developer.android.com/reference/android/support/v7/app/AppCompatActivity.html#setSupportActionBar%28android.support.v7.widget.Toolbar%29 [getSupportActionBar]: https://developer.android.com/reference/android/support/v7/app/AppCompatActivity.html#getSupportActionBar%28%29 [ActionBar]: https://developer.android.com/reference/android/support/v7/app/ActionBar.html
还没有评论,来说两句吧...