Activity 的生命周期和启动模式

1,213 阅读7分钟

一、Activity 生命周期

生命周期图示

image.png

生命周期方法

  1. onCreate() : Activity 首次创建时会触发此方法,如果 Activity 触发了 onDestory() 方法后如果再次打开这个 Activity 则又会重走此方法创建。它的参数 savedInstanceState在 Activity 完全新建时是个空的,但是当我们因为某些情况打断了 Activity 的显示,比如说突然来电进入到接电话的界面。如果接完电话我们的 Activity 没被销毁则直接回到接电话前的状态了,如果接完电话因为长时间处于后台,我们的 Activity 已经被系统回收了那我们回来就会重新触发 onCreate 重建,之前我们正在填写的内容就会被清除掉。为了解决这个问题所以引入了这个参数,我们可以重写 onSaveInstanceState 这个方法来在系统非用户退出销毁 Activity 时触发保存值,然后 onCreate 时就可以拿到对应数据进行恢复了
  2. onRestart(): 这个方法不是 Activity 生命周期主线方法,他在 ActivityB 返回 ActivityA时,如果ActivityA 在栈内未被回收,ActivityA 会被触发其 onRestart 然后再触发 onStart
  3. onStart() : Activity 在屏幕上对用户可见时调用,这个时候 Activity 是还没拿到焦点的
  4. onResume(): Activity 获得焦点时调用
  5. onPause(): Activity 失去焦点时调用,当打开新的 Activity 或者打开弹窗抢夺了焦点时当前 Activity 会先调用 onPause 触发失去焦点
  6. onStop(): Activity 跳转新的Activity后,对用户完全不可见。新的 Activity走完 onCreate、onStart 方法后
  7. onDestory: Activity 被销毁时触发,走了这个方法 Activity的生命周期就宣告结束了

打开一个Activity,再返回生命周期流程: image.png

几个循环

Activity 的生命周期大致可以分为以下几个循环场景

可见循环

可见循环如下图所示,ActivityA 上面弹出 Dialog 然后,关闭 Dialog ActivityA 重新获得焦点回到可交互状态,这种情况 ActivityA 的生命周期将走 onPause -> onResume 的循环。 image.png image.png

不可见循环

从 MainActivity 中打开 RedActivity,Main(onPause)-> Red(onCreate)->Red(onStart)->Red(onResume)->Main(onStop) 从 RedActivity 中返回 MainActivity,Red(onPause)-> Main(onResetart)->Main(onStart)->Main(onResume)->Red(onStop)->Red(onDestory) image.png 当打开一个全屏非透明 Activity时,打开再关闭的日志打印如下 image.png

重走生命周期

当 Activity 处于停止状态是,系统会根据需要进行回收。当非栈顶的 Activity 被回收后,如果要再返回到那个 Activity,那么系统就会重新创建一个新的 Activity。这里又跟前面的 onCreate 中恢复状态进行了闭环。

二、Activity 启动模式

静态启动模式

Android 中的启动模式在 Android 12 之前有四种,standardsingleTopsingleInstancesingleTask,Android 12之后新增了 singleInstancePerTask 启动模式 image.png

standard

standard 启动模式是 Activity 的默认启动模式,每次都会创建一个 Activity 实例入栈

入栈动画 standard.gif

back 出栈动画 standard_out.gif

singleTop

singelTop 模式分为两种情况,如果 ActivityB 是 singleTop 模式,当要启动的 activity正好又是 ActivityB,那么不会创建新的实例而是会复用当前栈顶的实例。如果当前栈顶的实例不是ActivityB,则会穿件一个新的 ActivityB进行入栈,栈内就会有两个 ActivityB

入栈 Activity 不在栈顶时: singleTop_notTop.gif

入栈 Activity 在栈顶时: singleTop_Top.gif

singleTask

栈内单例模式表示栈内只能存在一个当前 ActivityA 的实例,当栈内无 ActivityA 时,他跟 standard 一样,正常创建新实例并入栈。当栈内有 ActivityA 时,他会把栈内 ActivityA 上面的 Activity全部出栈然后复用 ActivityA

栈内单例模式的入栈逻辑如下: image.png

动画示意图:

singleTask.gif

singleInstance

singleInstance 模式可以理解为全局的单例模式,当 Activity 被标记为 singleInstance 模式启动时系统会把它放入一个独立的栈内,全局维护一个唯一的 Activity。当需要打开这个 Activity 时会直接栈内复用这个 Activity。

singleInstancePerTask

改模式为 Android 12 增加,具有以下特点: 1、当目标栈的底部为当前Activity则其栈与生命周期的变化与singleTask模式一样。 2、当目标栈不存在此 Activity 时其栈与生命周期的变化与 singleInstance模式一样,创建一个新栈把 Activity 作为根 Activity。

taskAffinity 属性

image.png

用于指定 Activity 的任务栈,多个应用程序使用同样的 taskAffinity + singleInstance 模式可以实现多个应用共享 Activity页面。singleInstance 模式启动,不管是否指定 taskAffinity 都会创建新的 task。

动态 FLAG

通过在启动 Activity 时给 intent 添加 Flag 方式动态指定启动模式 image.png

FLAG_ACTIVITY_CLEAR_TOP

同于 mainfest 中配置的 singleTask,如果栈内存在,会清空上面的 Activity。

FLAG_ACTIVITY_SINGLE_TOP

同于manifest 中配置的 singleTop

FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

等同于 AndroidManifest 中设置了 excludeFromRecents="true",Task 不会出现在最近任务列表中

FLAG_ACTIVITY_NO_HISTORY

等同于 AndroidManifest 中设置了noHistory="true",这个Activity 不会放入栈中,从这个页面离开了就销毁了

FLAG_ACTIVITY_NEW_TASK

这个属性,需要 taskAffinity 与 APP 的包名不一样才能生效。会启动一个新的 Task 来放入 Activity

Activity 的最佳实践

封装 BaseActivity

我们在开发应用时一般会创建一个继承自AndroidSDK 提供的 Activity、FragmentActivity、AppCompatActivity 等的 BaseActivity类,在这个 Base类中我们可以定义一些公共的操作和管理

abstract class BaseActivity<P : BasePresenter<*>, VB : ViewBinding> : BaseSkinActivity(), BaseView {

    protected lateinit var mPresenter: P

    protected lateinit var viewBinding: VB

    private var clazzVB: Class<VB>

    init {
        val type = javaClass.genericSuperclass as java.lang.reflect.ParameterizedType
        clazzVB = type.actualTypeArguments[1] as Class<VB>
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O && isTranslucentOrFloating(this)) {
            fixOrientation()
        }
        super.onCreate(savedInstanceState)
        setWindowSkinBackgroundDrawable(R.drawable.color_bg_img_1)

        viewBinding = inflateViewBinding()
        setContentView(viewBinding.root)
        AutoUtils.auto(window.decorView)
        bindViews()
        mPresenter = createPresenter()
        mPresenter.attachView(this)
        initEventAndData()
    }

    private fun isTranslucentOrFloating(context: Context): Boolean {
        var isTranslucentOrFloating = false
        try {
            val styleableRes = Class.forName("com.android.internal.R$styleable").getField("Window")[null] as IntArray
            val ta: TypedArray = context.obtainStyledAttributes(styleableRes)
            val m: Method = ActivityInfo::class.java.getMethod("isTranslucentOrFloating", TypedArray::class.java)
            m.isAccessible = true
            isTranslucentOrFloating = m.invoke(null, ta) as Boolean
            m.isAccessible = false
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return isTranslucentOrFloating
    }

    private fun fixOrientation(): Boolean {
        try {
            val field: Field = Activity::class.java.getDeclaredField("mActivityInfo")
            field.isAccessible = true
            val o: ActivityInfo = field[this] as ActivityInfo
            o.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
            field.isAccessible = false
            return true
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return false
    }

    override fun onResume() {
        super.onResume()
        DataReporter.appop.onResume(this)
    }

    override fun onStart() {
        super.onStart()
        window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
    }

    override fun onStop() {
        super.onStop()
        window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
        DataReporter.appop.onStop(this)
    }

    override fun onDestroy() {
        super.onDestroy()
        mPresenter.detachView()
    }

    protected abstract fun initEventAndData()
    protected abstract fun createPresenter(): P

    /**
     * 原有 ButterKnife结构,有一些自定义的注解因此不能直接替换,保留原来的 View 属性定义和自定义注解
     * 在此方法统一使用 ViewBinding 的方式进行 View 的绑定
     */
    protected abstract fun bindViews()

    private fun inflateViewBinding(): VB {
        val inflateMethod = clazzVB.getMethod("inflate", LayoutInflater::class.java)
        return inflateMethod.invoke(null, layoutInflater) as VB
    }
}

也可以在这个 Activity 的各种生命周期方法中去实现公用的日志打印

维护 Activity 栈

如果目前你手机的界面还停留在ThirdActivity,你会发现当前想退出程序是非常不方便的,需 要连按3次Back键才行。按Home键只是把程序挂起,并没有退出程序。如果我们的程序需要 注销或者退出的功能该怎么办呢?看来要有一个随时随地都能退出程序的方案才行。上面已经说了可以在 BaseActivity 的生命周期中添加公共方法,那么我们也可以在 BaseActivity 的生命周期中进行入栈、出栈操作来完成自己 Activity 栈的维护

object ActivityCollector {
    private val activities = ArrayList<Activity>()
    fun addActivity(activity: Activity) {
        activities.add(activity)
    }
    
    fun removeActivity(activity: Activity) {
        activities.remove(activity)
    }
    fun finishAll() {
        for (activity in activities) {
            if (!activity.isFinishing) {
                activity.finish()
            }
        }
        activities.clear()
    }
} 
open class BaseActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("BaseActivity", javaClass.simpleName)
        ActivityCollector.addActivity(this)
    }
    
    override fun onDestroy() {
        super.onDestroy()
        ActivityCollector.removeActivity(this)
    }
}

当然我们也可以直接定义一个父 Activity 来标识这些 Activity 都是某某业务的,在完成之后直接循环判断是这个父 Activity 的子类全部出栈,方法多种多样。

启动实践

写 Activity 时对外提供静态的启动方法,把自己 Activity 启动需要的参数条件暴露出去,这样的好处是不需要关心外部启动时约定的 key 对不对得上。别人只需要调用你提供的方法来启动 Activity 就行。

image.png