Activity 的生命周期和启动模式

一、Activity 生命周期

生命周期图示

生命周期方法

  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,再返回生命周期流程:

几个循环

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

可见循环

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

不可见循环

从 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) 当打开一个全屏非透明 Activity时,打开再关闭的日志打印如下

重走生命周期

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

二、Activity 启动模式

静态启动模式

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

standard

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

入栈动画

back 出栈动画

singleTop

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

入栈 Activity 不在栈顶时:

入栈 Activity 在栈顶时:

singleTask

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

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

动画示意图:

singleInstance

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

singleInstancePerTask

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

taskAffinity 属性

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

动态 FLAG

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

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类中我们可以定义一些公共的操作和管理

kotlin 复制代码
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 栈的维护

kotlin 复制代码
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()
    }
} 
kotlin 复制代码
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 就行。

相关推荐
yngsqq10 分钟前
c#使用高版本8.0步骤
java·前端·c#
Myli_ing44 分钟前
考研倒计时-配色+1
前端·javascript·考研
余道各努力,千里自同风1 小时前
前端 vue 如何区分开发环境
前端·javascript·vue.js
软件小伟1 小时前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js
醉の虾1 小时前
Vue3 使用v-for 渲染列表数据后更新
前端·javascript·vue.js
张小小大智慧1 小时前
TypeScript 的发展与基本语法
前端·javascript·typescript
hummhumm2 小时前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j
asleep7012 小时前
第8章利用CSS制作导航菜单
前端·css
hummhumm2 小时前
第 28 章 - Go语言 Web 开发入门
java·开发语言·前端·python·sql·golang·前端框架
幼儿园的小霸王2 小时前
通过socket设置版本更新提示
前端·vue.js·webpack·typescript·前端框架·anti-design-vue