2023001 重学安卓笔记

446 阅读13分钟

文章中绝大部分内容来源于付费专栏 重学安卓

1. 视图控制器

1. Activity角色

  1. 对系统而言,Activity本质上仍然是被管理的窗口,系统能管理窗口间的切换和通信.
  2. 对开发者而言,Activity本质上是视图控制器,控制View的排版,特殊情况下View的保存及恢复.
  3. Activity的存在是为了兼顾"多窗口管理"和"傻瓜式视图管理".
  4. Activity的要点在于 生命周期, Activity重建, 多窗口跳转, 视图加载及优化.

2. 进程优先级

  1. Android系统为不进程设置不同的优先级,以便在系统资源不足时杀死优先级较低的进程释放资源.
  2. 按照进程优先级从高到低,将进程分为5种
    1. 前景进程
      • 1个APP有组件(如Activity)处于可交互状态,比如Activity进入onResume,则该APP属于前景进程
      • 对Activity而言,进入onResume代表该APP属于前景进程.
    2. 可见进程
      • 1个APP有组件(如Activity)处于可见但不可交互状态,比如Activity进入onStart,则该APP属于可见进程
      • 比如PC上可以同时显示多个不同应用的窗口,但只有1个应用A当前可获取焦点,则A属于前景进程,其他显示的窗口所属应用属于可见进程
      • 对Activity而言,进入onStart,onPause代表该APP属于可见进程
    3. 服务进程
    4. 背景进程
      • 对Activity而言,进入onStop代表该APP属于背景进程
      • 当APP属于背景进程,类似于PC上的窗口被最小化,随时可能被系统回收.
    5. 空白进程
      • 系统为回收内存,将背景进程回收为空白进程.
      • 被回收时,背景进程该保留的状态数据会保留,等再次变成前景进程,系统可以为APP恢复状态数据.
  3. APP进程优先级,由其"活跃的或处于栈顶的组件"的状态(或生命周期)决定.
  4. 系统回收资源时,针对的是APP进程,而非APP进程中的某个组件/如Activity.

3. Activity的生命周期

  1. 为何在onCreate中执行setContentView和UI控件初始化,因为onCreate和onDestroy仅执行一次,其他生命周期函数会多次调用,为了避免反复的初始化.
  2. Activity A跳转到Activity B.生命周期函数调用顺序
    • A.onPasuse -> B.onCreate -> B.onStart -> B.onResume -> A.onStop
    • 不能在onPause中执行耗时逻辑,因为两个Activity生命周期是串行的,上一个Activity的onPause耗时,会导致下一个Activity过晚进入onResume,给用户的感觉就是反应迟钝.
    • 短暂耗时逻辑可以在onStop中执行,但也不能过于耗时.因为onStop状态APP若属于背景进程,随时可能被系统回收,导致耗时逻辑无法执行完.
  3. 为了保证数据被及时保存,应该定时将数据从内存备份到磁盘,避免在onStop中未保存完APP就被系统回收.
  4. Activity处于不同生命周期,其APP进程优先级
    • Activity处于onStart,onPause,APP属于可见进程.
    • Activity处于onResume,APP属于前景进程
    • Activity处于onStop,APP属于背景进程
  5. 前景进程的Activity也可能被回收
    • 该情况是因为Activity所属的task被虚拟机回收
    • 所以回收包括:
      • 系统回收某进程
      • 虚拟机回收某进程的某个task
      • 上面2种回收机制针对的都不是某个Activity.

4. Activity的数据保存及恢复

  1. Activity重建产生的原因
    • APP变成背景进程,进程被系统回收.再次打开应用,触发该Activity重建
    • 系统配置发生变更(旋转屏幕,语言变更)
  2. Activity重建涉及的2个方法 : onSaveInstanceState(@NonNull Bundle outState) 及 onRestoreInstanceState(@NonNull Bundle savedInstanceState)
    • onSaveInstanceState
      • 从Android P开始, onSaveInstanceState 在 onStop之后执行
      • 在Android P之前, onSaveInstanceState 在 onStop之前执行,但和onPause顺序不定
    • onRestoreInstanceState
      • 在 onStart 之后执行
  3. Activity需要保存的数据有两类
    • View的数据,原生View已实现了数据保存及恢复方法.第三方及开源控件不一定实现.
    • Activity其他数据,需要在 onSaveInstanceState 及 onRestoreInstanceState 中手动保存恢复.
  4. Activity数据保存及恢复的步骤
    1. 用到的所有View都已经实现了数据保存及恢复的方法.目前原生的View均已实现.
    2. 布局中每个View都设置id
    3. 非View数据,要手动在 onSaveInstanceState 及 onRestoreInstanceState 中保存恢复.
    4. 将数据托管给Jetpack ViewModel,可直接实现数据的 暂存及恢复. 后续介绍.

5. Activity任务, 返回栈, 启动模式

  1. 任务和返回栈

    1. ActivityRecord
      • 描述1个Activity的信息,对应着1个Activity界面
    2. TaskRecord/任务
      • 1个栈结构,内部有多个ActivityRecord.
      • 栈顶的ActivityRecord可能就是当前获得焦点的Activity
    3. ActivityStack/返回栈
      • 1个栈结构,内部有多个TaskRecord
      • 栈顶的TaskRecord'可能'就是当前获得焦点的任务
    4. ActivityStackSupervisior
      • 管理多个ActivityStack.
      • 同一时间只会有1个获得焦点的ActivityStack.

    上述结构的图示: ActivityStackSupervisior结构.png

  2. 1个Activity所属的TaskRecord

  3. Activity启动模式(TaskA:TA, ActivityA:AA)

    1. standard
      • TA中的AA启动standard的AB. 会直接创建AB实例,放到TA的栈顶
    2. singleTop
      • TA中的AA启动singleTop的AB.
        • 若AB就在TA的栈顶,则不创建AB新实例,直接执行其onNewIntent
        • 若AB不在TA的栈顶,则创建其实例放到TA的栈顶
    3. singleTask
      • TA中的AA启动singleTask的AB.
        • 若AB所属的T中已存在AB实例,则清空其上面的Activity,并执行其onNewIntent
        • 若AB所属的T中不存在AB实例,则创建其实例,放到T的栈顶
    4. singleInstance
      • 该Activity会独占1个T.
      • 整个系统中只有1个该Activity单例,多个APP公用.
  4. FLAG

    1. FLAG_ACTIVITY_NO_HISTORY
      • 从该Activity跳转至其他Activity,则该Activity会从TaskRecord中被移除
      • 例如该Activity启动AB,或者点击HOME,该Activity就会被销毁
    2. FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
      • 该Activity不会出现在'最近任务'列表中
    3. Flag和启动模式的区别
      • 启动模式配置在manifest中,是固定的,而设置Flag是灵活的,可以多个flag随时组合.
      • 例如 singleTask 是固定的、而 ClearTop 是可组合的:当你多数场景下不合适使用 SingleTask,但个别场景又需要它的这种清空栈中上方 Activity 的特性,那么此时你就选用 singleTop + clearTop FLAG 的组合,如此,在清单中你配置的是 singleTop,仅在你需要清空效果的场景下,在代码中动态附加 FLAG 来组合使用
  5. 1个TaskRecord TT在哪个ActivityStack中创建,取决于该TaskRecord中的Activity A是被哪个Activity B所启动. 若B所属的TaskRecord T所属的ActivityStack是S. 则 TT 就会在 S 中创建.

  6. allowTaskReparenting

    1. 用于activity的迁移,即可以从一个task迁移到另一个task
    2. 比如现在有两个应用A和B,A启动了B的一个ActivityC(其allowTaskReparenting的值为true),然后按Home键回到桌面,再单击B应用时,那么这个时候并不会启动B的主Activity,而是直接显示已被应用A启动的ActivityC,我们也可以认为ActivityC从A的任务栈转移到了B的任务栈中。
    3. 代码实例
    APP1的manifest配置:
    <?xml version="1.0" encoding="utf-8"?>
    <manifest ***>
        <application
            ***
            //以下3个Activity都可以打开APP2中的一个设置了android:allowTaskReparenting="true"的Activity
            <activity
                android:name=".MainActivity"
                android:exported="true">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            <activity android:name=".JumpAllowTaskReparentingActivity"/>
            <activity android:name=".JumpToJumpAllowTaskReparentingActivity"/>
        </application>
    </manifest>
    
    APP2的manifest配置:
    <?xml version="1.0" encoding="utf-8"?>
    <manifest ***>
        <application
            ***
            <activity
                android:name=".MainActivity"
                android:exported="true">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            //该Activity就是可以从1个TaskRecord迁移到另一个TaskRecord的Activity
            <activity
                android:name=".AllowTaskReparentingActivity"
                android:exported="true"
                android:allowTaskReparenting="true"
                />
        </application>
    </manifest>
    
    日志:
    //通过APP1中的Activity(3个中任意1个)打开APP2中的AllowTaskReparentingActivity.
    D/AllowTaskReparentingActivity: AllowTaskReparentingActivity. onResume. AllowTaskReparentingActivity -- taskId:92 -- processId:27266 内存地址:com.example.app02.AllowTaskReparentingActivity@5bcbbc0
    //点击HOME回到桌面
    //点击APP2
    D/AllowTaskReparentingActivity: AllowTaskReparentingActivity. onResume. AllowTaskReparentingActivity -- taskId:93 -- processId:27266 内存地址:com.example.app02.AllowTaskReparentingActivity@5bcbbc0
    //点击回退
    APP2从 AllowTaskReparentingActivity 回到了其启动页: MainActivity
    
  7. Activity在manifest中可以配置的几个属性 来源于知乎

    1. 系统默认行为
      • 当一个APP被系统回收,长时间属于空白进程,会被系统的其他机制彻底销毁.其TaskRecord仅仅保留最底层的Activity,其他Activity全部清除.
    2. alwaysRetainTaskState
      • 设置给TaskRecord中的根Activity才有效.
      • TaskRecord中所有的Activity过了很久也不会被清除.
    3. clearTaskOnLaunch
      • 设置给TaskRecord中的根Activity才有效.
      • 只要用户离开了该任务,再次回到该任务,只有根Activity被保留,其余全部被清除
    4. excludeFromRecents
      • 设置给TaskRecord中的根Activity才有效.
      • 该根Activity所属的TaskRecord不会在Recent中显示.
    5. autoRemoveFromRecents
      • 设置给TaskRecord中的根Activity才有效.
      • 当该TaskRecord中的Activity都被移除(比如一直BACK),该TaskRecord自动从Recent中移除
    6. finishOnTaskLaunch
      • 和clearTaskOnLaunch类似,但是其仅针对设置了该属性的Activity生效.再次回到该任务,对应Activity就被清除.
      • 根Activity和其他Activity都可以设置.
    7. Google Activity在manifest中所有可配置的属性
  8. standard和singleTop的ActivityRecord在哪个TaskRecord,完全由启动它的Activity所在的TaskRecord决定.

    • APP1中的A启动APP1中的B(standard或singleTop),B就在A所属的TaskRecord中
    • APP1中的A启动APP2中的B(standard或singleTop),B依然在A所属的TaskRecord中
  9. singleTask和singleInstance的ActivityRecord,一定在其所属的APP的TaskRecord中,无论是新建还是复用.

  10. 在Android 9及以后,每新建1个TaskRecord,就会同时新建一个ActivityStack. 在Android 9之前,哪怕是 SingleInstance,也不过是独享了一个 TaskRecord,而不是新建和独享一个 ActivityStack.

  11. Recent中展示的最小单位是TaskRecord. 所以1个APP可以在RECENT中展示多个预览图.

  12. 几个问题

    1. 从SDK 20起,startActivityForResult且requestCode>=0,启动Activity A,若A是singleTask或singleInstance,则不会为A新建1个TaskRecord.
    2. 系统为何不直接使用ActivityStack管理ActivityRecord.加入TaskRecord的原因:为了页面的连续跳转 及 窗口多开.
      • 1个TaskRecord相当于1个窗口,其持有的多个ActivityRecord就是多个页面,在窗口中可以实现前进,后退
      • 多个TaskRecord同时存在,只有1个可以走到前台获得焦点.

5. Intent

  1. 显式Intent
    • 通过setComponent等指定了目标组件的Intent
  2. 隐式Intent
    • 非显示Intent即隐式Intent.通过一些条件过滤目标组件.
    • 当存在多个符合条件的目标组件,会弹出弹窗将多个组件列表展示出来供用户选择.

6. Fragment

  1. 执行replace和add的差别
  • replace会执行prev的onDestroyView, add不会.

  • 回退时,replace会执行prev的onCreateView+onViewCreated,以及curr的onDestroyView; add只会执行curr的onDestroyView.

    private void loadFirstFragment() {
        OneTestFragment fragment = new OneTestFragment();
        fragment.setListener(() -> loadSecondFragment());
    
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.fragment_container, fragment,
                        OneTestFragment.class.getSimpleName())
                                //这里没有执行addToBackStack
                .commit();
    }
    2023-01-25 14:11:35.328 9805-9805/com.kunminx.relearn_android_2 D/TagFragment: OneTestFragment. onCreateView
    2023-01-25 14:11:35.328 9805-9805/com.kunminx.relearn_android_2 D/TagFragment: OneTestFragment. onViewCreated
    
    private void loadSecondFragment() {
        TwoTestFragment fragment = new TwoTestFragment();
        fragment.setListener(() -> loadThirdFragment());
    
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.fragment_container, fragment,
                        TwoTestFragment.class.getSimpleName())
                .addToBackStack(null)
                .commit();
    }
    2023-01-25 14:11:40.617 9805-9805/com.kunminx.relearn_android_2 D/TagFragment: OneTestFragment. onDestroyView
    2023-01-25 14:11:40.655 9805-9805/com.kunminx.relearn_android_2 D/TagFragment: TwoTestFragment. onViewCreated
    
    private void loadThirdFragment() {
        ThreeTestFragment fragment = new ThreeTestFragment();
    
        getSupportFragmentManager().beginTransaction()
                .add(R.id.fragment_container, fragment,
                        ThreeTestFragment.class.getSimpleName())
                .addToBackStack(null)
                .commit();
    }
    2023-01-25 14:11:46.440 9805-9805/com.kunminx.relearn_android_2 D/TagFragment: ThreeTestFragment. onViewCreated
    

    //回退

    2023-01-25 14:11:56.675 9805-9805/com.kunminx.relearn_android_2 D/TagFragment: ThreeTestFragment. onDestroyView
    

    //回退

    2023-01-25 14:12:03.444 9805-9805/com.kunminx.relearn_android_2 D/TagFragment: TwoTestFragment. onDestroyView
    2023-01-25 14:12:03.530 9805-9805/com.kunminx.relearn_android_2 D/TagFragment: OneTestFragment. onCreateView
    2023-01-25 14:12:03.536 9805-9805/com.kunminx.relearn_android_2 D/TagFragment: OneTestFragment. onViewCreated
    

    //回退

    2023-01-25 14:12:12.158 9805-9805/com.kunminx.relearn_android_2 D/TagFragment: OneTestFragment. onDestroyView
    
  1. Fragment返回栈实现了Fragment状态的自动保存.
  • 2020 年起,fragment 就算 replace,返回后仍然能恢复之前的状态.
  • 即使页面跳转时视图被销毁,也要让页面回退时状态被恢复.
    • 比如farg1被frag2执行replace. frag1中输入框中的文字,列表滚动的位置都会被保留,再次回到frag1,上述状态都会恢复.
  1. FragmentManager.popBackStack
  • 一直跳转到新Fragment
    2023-01-25 14:32:50.139 11667-11667/com.kunminx.relearn_android_2 D/TagFragment: OneTestFragment. onCreateView
    2023-01-25 14:32:50.139 11667-11667/com.kunminx.relearn_android_2 D/TagFragment: OneTestFragment. onViewCreated
    
    2023-01-25 14:32:53.872 11667-11667/com.kunminx.relearn_android_2 D/TagFragment: OneTestFragment. onDestroyView
    2023-01-25 14:32:53.923 11667-11667/com.kunminx.relearn_android_2 D/TagFragment: TwoTestFragment. onViewCreated
    
    2023-01-25 14:32:56.140 11667-11667/com.kunminx.relearn_android_2 D/TagFragment: ThreeTestFragment. onViewCreated
    
  • 一直执行FragmentTransaction addToBackStack
    //ThreeTestFragment: mActivity.getSupportFragmentManager().popBackStack()
    2023-01-25 14:32:58.355 11667-11667/com.kunminx.relearn_android_2 D/TagFragment: ThreeTestFragment. onDestroyView
    //TwoTestFragment: mActivity.getSupportFragmentManager().popBackStack()
    2023-01-25 14:33:02.511 11667-11667/com.kunminx.relearn_android_2 D/TagFragment: TwoTestFragment. onDestroyView
    2023-01-25 14:33:02.550 11667-11667/com.kunminx.relearn_android_2 D/TagFragment: OneTestFragment. onCreateView
    2023-01-25 14:33:02.553 11667-11667/com.kunminx.relearn_android_2 D/TagFragment: OneTestFragment. onViewCreated
    //OneTestFragment: mActivity.getSupportFragmentManager().popBackStack()
    无变化, OneTestFragment 依然在显示.
    
  1. 修改 OneTestFragment . FragmentTransaction addToBackStack. 即可将该Fragment实例添加至返回栈.

    private void loadFirstFragment() {
        OneTestFragment fragment = new OneTestFragment();
        fragment.setListener(() -> loadSecondFragment());
    
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.fragment_container, fragment,
                        OneTestFragment.class.getSimpleName())
                //添加addToBackStack,即可将OneTestFragment实例添加至返回栈
                .addToBackStack(null)
                .commit();
    }
    
    //ThreeTestFragment: mActivity.getSupportFragmentManager().popBackStack()
    2023-01-25 14:45:47.813 12107-12107/com.kunminx.relearn_android_2 D/TagFragment: ThreeTestFragment. onDestroyView
    //TwoTestFragment: mActivity.getSupportFragmentManager().popBackStack()
    2023-01-25 14:45:50.823 12107-12107/com.kunminx.relearn_android_2 D/TagFragment: TwoTestFragment. onDestroyView
    2023-01-25 14:45:50.860 12107-12107/com.kunminx.relearn_android_2 D/TagFragment: OneTestFragment. onCreateView
    2023-01-25 14:45:50.863 12107-12107/com.kunminx.relearn_android_2 D/TagFragment: OneTestFragment. onViewCreated
    //OneTestFragment: mActivity.getSupportFragmentManager().popBackStack()
    //因为之前已经将OneTestFragment添加至返回栈,所以这里执行popBackStack,会触发其View的销毁.
    2023-01-25 14:45:54.272 12107-12107/com.kunminx.relearn_android_2 D/TagFragment: OneTestFragment. onDestroyView
    
  2. setArugments 和 getArguments 用于向Fragment传递数据.

  • 在执行Fragment替换之前执行setArguments传递数据, 在Fragment的onAttach通过getArgumments获取数据
    class TestFragmentActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_test_fragment)
        }
    
        fun testSetArguments(view: View) {
            val fragment: Fragment1 = Fragment1()
            val bundle: Bundle = Bundle()
            bundle.putFloat("1", 1.00F)
            fragment.arguments = bundle
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, fragment, fragment.javaClass.simpleName)
                .addToBackStack(null)
                .commit()
        }
    }
    
    class Fragment1 : Fragment() {
        override fun onAttach(context: Context) {
            super.onAttach(context)
            Log.i(TagConstants.TAG_FRAGMENT, "Fragment1. onAttach.")
            arguments?.apply {
                val content: Float = getFloat("1")
                Log.i(
                    TagConstants.TAG_FRAGMENT, "Fragment1. onAttach. arguments exist. content:$content"
                )
            }
        }
    }
    
    2023-01-25 15:30:32.777 I/TagFragment: Fragment1. onAttach.
    2023-01-25 15:30:32.777 I/TagFragment: Fragment1. onAttach. arguments exist. content:1.0
    ***
    
  1. Activity重建对应的数据保存和恢复方法
  • Activity重建前:
    • Activity中每个UI控件都设置ID
    • Activity中 onSaveInstanceState 保存Activity的成员变量
    • Fragment中每个UI控件都设置ID
    • Fragment中 onSaveInstanceState 保存Fragment的成员变量
  • Activity重建后:
    • Activity中 onRestoreInstanceState 获取之前保存的数据
    • Fragment中 onActivityCreated 获取之前保存的数据
    //正常进入界面
    2023-01-25 16:41:35.243 I/TagActivity: TestFragmentActivity. onCreate.
    2023-01-25 16:41:37.333 I/TagFragment: Fragment1. onAttach.
    2023-01-25 16:41:37.333 I/TagFragment: Fragment1. onAttach. arguments exist. content:1.0
    2023-01-25 16:41:37.333 I/TagFragment: Fragment1. onCreate.
    2023-01-25 16:41:37.334 I/TagFragment: Fragment1. onCreateView.
    2023-01-25 16:41:37.395 I/TagFragment: Fragment1. onViewCreated.
    2023-01-25 16:41:37.400 I/TagFragment: Fragment1. onActivityCreated.
    
    //点击HOME
    //触发Activity和Fragment的onSaveInstanceState
    2023-01-25 16:42:00.842 I/TagFragment: Fragment1. onSaveInstanceState.
    2023-01-25 16:42:00.846 I/TagActivity: TestFragmentActivity. onSaveInstanceState. c1:冻久了 c2:阿鲁日本五六万
    
    //调整字体大小后,重新进入该APP,触发Activity重建
    //这里可见,之前通过setArguments传给Fragment的数据,Activity重建后,Fragment实例依然可以获取到
    2023-01-25 16:42:30.532 I/TagFragment: Fragment1. onAttach.
    2023-01-25 16:42:30.532 I/TagFragment: Fragment1. onAttach. arguments exist. content:1.0
    2023-01-25 16:42:30.532 I/TagFragment: Fragment1. onCreate.
    2023-01-25 16:42:30.626 I/TagActivity: TestFragmentActivity. onCreate.
    2023-01-25 16:42:30.628 I/TagFragment: Fragment1. onCreateView.
    2023-01-25 16:42:30.635 I/TagFragment: Fragment1. onViewCreated.
    //在Fragment的onActivityCreated中,可以获取之前保存的数据
    2023-01-25 16:42:30.635 I/TagFragment: Fragment1. onActivityCreated.
    2023-01-25 16:42:30.635 I/TagFragment: Fragment1. onActivityCreated. s1:string s1.
    2023-01-25 16:42:30.641 I/TagActivity: TestFragmentActivity. onRestoreInstanceState. c1:冻久了 c2:阿鲁日本五六万
    
    class TestFragmentActivity : AppCompatActivity() {
        private var c1: String? = null
        private var c2: String? = null
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_test_fragment)
            Log.i(TagConstants.TAG_ACTIVITY, "TestFragmentActivity. onCreate.")
        }
    
        override fun onSaveInstanceState(outState: Bundle) {
            super.onSaveInstanceState(outState)
            c1 = (findViewById<View>(R.id.et1) as EditText).text.toString()
            c2 = (findViewById<View>(R.id.et2) as EditText).text.toString()
            outState.putString("c1", c1)
            outState.putString("c2", c2)
            Log.i(TagConstants.TAG_ACTIVITY, "TestFragmentActivity. onSaveInstanceState. c1:$c1 c2:$c2")
        }
    
        override fun onRestoreInstanceState(savedInstanceState: Bundle) {
            super.onRestoreInstanceState(savedInstanceState)
            c1 = savedInstanceState.getString("c1")
            c2 = savedInstanceState.getString("c2")
            Log.i(
                TagConstants.TAG_ACTIVITY, "TestFragmentActivity. onRestoreInstanceState. c1:$c1 c2:$c2"
            )
        }
    
        fun testSetArguments(view: View) {
            val fragment: Fragment1 = Fragment1()
            val bundle: Bundle = Bundle()
            bundle.putFloat("1", 1.00F)
            fragment.arguments = bundle
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, fragment, fragment.javaClass.simpleName).addToBackStack(null)
                .commit()
        }
    }
    
    class Fragment1 : Fragment() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            Log.i(TagConstants.TAG_FRAGMENT, "Fragment1. onCreate.")
        }
    
        override fun onActivityCreated(savedInstanceState: Bundle?) {
            super.onActivityCreated(savedInstanceState)
            Log.i(TagConstants.TAG_FRAGMENT, "Fragment1. onActivityCreated.")
            savedInstanceState?.apply {
                val str: String? = getString("s1")
                Log.i(TagConstants.TAG_FRAGMENT, "Fragment1. onActivityCreated. s1:$str")
            }
        }
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
        ): View? {
            Log.i(TagConstants.TAG_FRAGMENT, "Fragment1. onCreateView.")
            return inflater.inflate(R.layout.layout_frag1, container, false)
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            Log.i(TagConstants.TAG_FRAGMENT, "Fragment1. onViewCreated.")
        }
    
        override fun onAttach(context: Context) {
            super.onAttach(context)
            Log.i(TagConstants.TAG_FRAGMENT, "Fragment1. onAttach.")
            arguments?.apply {
                val content: Float = getFloat("1")
                Log.i(
                    TagConstants.TAG_FRAGMENT, "Fragment1. onAttach. arguments exist. content:$content"
                )
            }
        }
    
        override fun onSaveInstanceState(outState: Bundle) {
            super.onSaveInstanceState(outState)
            Log.i(TagConstants.TAG_FRAGMENT, "Fragment1. onSaveInstanceState.")
            outState.putString("s1", "string s1.")
        }
    }
    
  1. 避免Fragment重叠
  • Fragment重叠是因为Activity触发了重建,重建会自动恢复之前添加的Fragment实例.若不加判断直接创建Fragment实例添加就会触发重叠.
  • 正确做法是在FragmentActivity中判断是否能获取到之前加载过的Fragment实例,找不到再创建加载.
    //进入页面
    2023-01-25 17:05:45.313 I/TagActivity: TestFragmentActivity. onCreate.
    2023-01-25 17:05:47.875 I/TagFragment: Fragment1. onAttach.
    ***
    2023-01-25 17:05:47.960 I/TagFragment: Fragment1. onActivityCreated.
    //HOME
    2023-01-25 17:06:02.156 I/TagFragment: Fragment1. onSaveInstanceState.
    2023-01-25 17:06:02.160 I/TagActivity: TestFragmentActivity. onSaveInstanceState. c1:222 c2:333
    //Activity重建,发现之前已经加载过Fragment实例
    ***
    2023-01-25 17:06:15.755 I/TagActivity: TestFragmentActivity. onCreate.
    2023-01-25 17:06:15.755 I/TagActivity: TestFragmentActivity. onCreate. frag : Fragment1{9d9fde2} (fe3e0b11-ce9f-4b09-8288-4d61db5b6908 id=0x7f08007f tag=Fragment1)
    ***
    2023-01-25 17:06:15.783 I/TagActivity: TestFragmentActivity. onRestoreInstanceState. c1:222 c2:333
    
    class TestFragmentActivity : AppCompatActivity() {
        private var c1: String? = null
        private var c2: String? = null
        private var frag: Fragment1? = null
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_test_fragment)
            Log.i(TagConstants.TAG_ACTIVITY, "TestFragmentActivity. onCreate.")
            savedInstanceState?.apply {
                frag =
                    supportFragmentManager.findFragmentByTag(Fragment1::class.java.simpleName) as Fragment1
                Log.i(TagConstants.TAG_ACTIVITY, "TestFragmentActivity. onCreate. frag : $frag")
            }
        }
    
        ***
    }
    
  1. 建议使用单Activity多Fragment原因
  • Fragment可以被混淆,Activity不能被混淆
    • 饿了么的开源工具可以混淆Activity和View. Mess
  • Fragment专用于视图控制,占用资源少,切换速度比Activity快5-10倍
  • Fragment重建及恢复比Activity更快
  • 建议用DialogFragment替换Dialog
    • 可以很方便的承载复杂的布局
    • 可以跟随Activity自动完成重建及状态恢复,避免Activity重建后对话框消失
  • 什么情况不适合单Activity多Fragment结构
    • 每个 Activity 持有一个 Window,申请的资源有限,单Activity 架构的多 Fragment 在转场动画时,容易存在不可预期的绘制错误,如果 fragment 中采用 毛玻璃等重量级绘制任务,容易出问题.

2. 视图系统

1. Activity角色