onStart()
:
表示 Activity 正在被启动,这时候的 Activity 已经被创建好了,完全过了准备阶段,但是没有出现在前台,需要执行 onResume()
函数才可以进入到前台与用户进行交互。
onResume()
:
表示 Activitiy 已经可见了,并且 Activity 处于运行状态,也就是 Activity 不止出现在了前台,而且还可以让用户点击,滑动等等操作与它进行交互。
onPause()
:
表示 Activity 正在暂停,大多数情况下,Activity 执行完 onPause()
函数后会继续执行onStop()
函数,造成这种函数调用的原因是当前的 Activity 启动了另外一个 Activity 或者回切到上一个 Activity。还有一种情况就是 onPause()
函数被单独执行了,并没有附带执行 onStop()
方法,造成这种函数调用的原因很简单,就是当前 Activity 里启动了类似于对话框的东东。
onStop()
:
表示 Activity 即将停止,我们程序员应该在此函数中做一些不那么耗时的轻量级回收操作。
onRestart()
:
表示 Activity 正在重新启动。一般情况下,一个存在于后台不可见的 Activity 变为可见状态,都会去执行 onRestart()
函数,然后会继续执行 onStart()
函数,onResume()
函数出现在前台并且处于运行状态。
onDestory()
:
表示 Activity 要被销毁了。这是 Activity 生命中的最后一个阶段,我们可以在onDestory()
函数中做一些回收工作和资源释放等,比如:广播接收器的注销等。
异常情况下的生命周期:
什么是异常情况呢?
情况 1: 资源相关的系统配置发生改变导致 Activity 被杀死并重新创建。
可以从图中看出当 Activity 发生意外的情况的时候,这里的意外指的就是系统配置发生改变,Activity 会被销毁,其onPause
,OnStop
,onDestory
函数均会被调用,同时由于Actiivty
是在异常情况下终止的,系统会调用onSaveInstanceState
来保存当前 Activity状态。调用 onSaveInstanceState
的时机总会发生在 onStop
之前,至于会不会调用时机发生在 onPause
方法之前,那就说不定了,这个没有固定的顺序可言,正常情况下一般onSaveInstanceState
不会被调用。当 Activity 被重新创建后,系统会调用onRestoreInstanceState
,并且把 Actiivty
销毁时 onSaveInstanceState
方法所保存的Bundle 对象作为参数传递给 onRestoreInstanceState
和 onCreate
方法。所以我们可以通过 onRestoreInstanceState
和 onCreate
方法来判断 Actiivty
是否被重建了,如果被重建了,那么我们就可以取出之前保存的数据并恢复,从时序上来看,onRestoreInstanceState
的调用时机发生在 onStart
之后。
同时,在 onSaveInstanceState
和 onRestoreInstanceState
方法中,系统自动为我们做了一定的恢复工作。当 Activity 在异常情况下需要重新创建时,系统会默认为我们保存当前 Activity 的视图结构。当 Activity 在异常情况下需要重新创建时,系统会默认为我们保存当前 Activity 的视图结构,并且在 Activity 重启后为我们恢复这些数据,比如:文本框中用户输入的数据,ListView
滚动的位置等,这些 View 相关的状态系统都能够默认为我们恢复。具体针对某一个特定的 View 系统 能为我们恢复哪些数据,我们可以查看 View 的源码。和 Activity 一样,每个 View 都有 onSaveInstanceState
和onRestoreInstanceState
这两个方法,看一下它们的具体实现,就能知道系统能够自动为每个 View 恢复哪些数据
关于保存和恢复 View 层次结构,系统的工作流程是这样的:
首先 Activity 被意外终止时,Activity 会调用 onSaveInstanceState
去保存数据,然后Activity 会委托 Window 去保存数据,接着 Window 在委托它上面的顶级容器去保存数据。顶级容器是一个 ViewGroup
,一般来说它很可能是 DecorView
。最后顶层容器再去一一通知它的子元素来保存数据,这样整个数据保存过程就完成了。可以发现,这是一个典型的委托思想,上层委托下层,父容器去委托子元素去处理一件事情,这种思想在Android 中有很多应用,比如:View 的绘制过程,事件分发等都是采用类似的思想。至于数据恢复过程也是类似的,这样就不再重复介绍了。
情况 2: 资源内存不足导致低优先级的 Activity 被杀死。
首先,Activity 有优先级?你肯定怀疑,代码中都没设置过啊!优先级从何而来,其实这里的 Activity 的优先级是指一个 Activity 对于用户的重要程度,比如:正在与用户进行交互的 Activity 那肯定是最重要的。我们可以按照重要程度将 Activity 分为以下等级:
优先级最高: 与用户正在进行交互的 Activity,即前台 Activity。
优先级中等: 可见但非前台的 Activity,比如:一个弹出对话框的 Activity,可见但是非前台运行。
优先级最低: 完全存在与后台的 Activity,比如:执行了onStop
。
当内存严重不足时,系统就会按照上述优先级去 kill 掉目前 Activity 所在的进程,并在后续通过 onSaveInstanceState
和 onRestoreInstanceState
来存储和恢复数据。如果一个进程中没有四大组件的执行,那么这个进程将很快被系统杀死,因此,一些后台工作不适合脱离四大组件独立运行在后台中,这样进程更容易被杀死。比较好的方法就是将后台工作放入 Service 中从而保证进程有一定的优先级,这样就不会轻易地被系统杀死
总结:
上面分析了系统的数据存储和恢复机制,我们知道,当系统配置发生改变之后,Activity会被重新创建,那么有没有办法不重新创建呢?答案是有的,接下来我们就来分析这个问题。系统配置中有很多内容,如果某项内容发生了该变后,我们不想系统重新创建Activity 可以给 Activity 指定 configChanges
属性。比如我们不想让 Actiivty
在屏幕旋转的时候重新创建,就可以给configChanges
属性添加一些值,请继续往下看。
2.3 一些特殊情况下的生命周期分析
2.3.1 Activity 的横竖屏切换
与横竖屏生命周期函数有关调用的属性是"android:configChanges"
,关于它的属性值设置影响如下:
orientation
:消除横竖屏的影响keyboardHidden
:消除键盘的影响screenSize
:消除屏幕大小的影响
当我们设置 Activity 的 android:configChanges
属性为 orientation
或者orientation
|keyboardHidden
或者不设置这个属性的时候,它的生命周期会走如下流程:
1.刚刚启动 Activity 的时候:
2. onCreate
3. onStart
4. onResume
5. 由竖屏切换到横屏:
6. onPause
7. onSaveInstanceState //这里可以用来横竖屏切换的保存数据
8. onStop
9. onDestroy
10. onCreate
11. onStart
12. onRestoreInstanceState//这里可以用来横竖屏切换的恢复数据
13. onResume
14. 横屏切换到竖屏:
15. onPause
16. onSaveInstanceState
17. onStop
18. onDestroy
19. onCreate
20. onStart
21. onRestoreInstanceState
22. onResume
当我们设置 Activity 的 android:configChanges
属性为 orientation
|screenSize
或者orientation
|screenSize
|keyboardHidden
- 刚刚启动 Activity 的时候:
- onCreate
- onStart
- onResume
- 由竖屏切换到横屏:
- 什么也没有调用
- 横屏切换到竖屏:
- 什么也没有调用
而且需要注意一点的是设置了 orientation
|screenSize
属性之后,在进行横竖屏切换的时候调用的方法是 onConfigurationChanged()
,而不会回调 Activity 的各个生命周期函数;
当然在显示中我们可以屏蔽掉横竖屏的切换操作,这样就不会出现切换的过程中 Activity生命周期重新加载的情况了,具体做法是,在 Activity 中加入如下语句:
android:screenOrientation="portrait"
始终以竖屏显示
android:screenOrientation="landscape"
始终以横屏显示
如果不想设置整个软件屏蔽横竖屏切换,只想设置屏蔽某个 Activity 的横竖屏切换功能的话,只需要下面操作:
Activity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
;以竖屏显示
Activity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
;以横屏显示
最后提一点,当你横竖屏切换的时候,如果走了销毁 Activity 的流程,那么需要保存当前和恢复当前 Activity 的状态的话,我们可以灵活运用 onSaveInstanceState()
方法和onRestoreInstanceState()
方法。
2.3.2 什么时候 Activity 单独走 onPause()不走 onStop()?
关于这个特殊情况,笔者在上面的生命周期图解析的时候,贴了一个链接,这里主要是检验你是否会了这个问题的答案,这里笔者就不贴答案了,答案全在那个链接里,你会了吗?
2.3.3 什么时候导致 Activity 的 onDestory()不执行?
当用户后台强杀应用程序时,当前返回栈仅有一个 activity 实例时,这时候,强杀,是会执行 onDestroy
方法的;当返回栈里面存在多个 Activity 实例时,栈里面的第一个没有销毁的 activity 执行会 ondestroy
方法,其他的不会执行;比如说:从 mainactivity
跳转到activity-A(或者继续从 activity-A 再跳转到 activity-B),这时候,从后台强杀,只会执行 mainactivity
的 onDestroy
方法,activity-A(以及 activity-B)的 onDestroy
方法都不会执行;
2.4 进程的优先级
前台>可见>服务>后台>空
前台: 与当前用户正在交互的 Activity 所在的进程。
可见: Activity 可见但是没有在前台所在的进程。
服务: Activity 在后台开启了 Service 服务所在的进程。
后台: Activity 完全处于后台所在的进程。
空: 没有任何 Activity 存在的进程,优先级也是最低的。
三.Android 任务栈
任务栈与 Activity 的启动模式密不可分,它是用来存储 Activity 实例的一种数据结构,Activity 的跳转以及回跳都与这个任务栈有关。详情请看下面的 Activity 的启动模式。
四.Activity 的启动模式
Activity 的启动模式,你在初学期间一定很熟悉了吧!不管你是否熟悉还是不熟悉,跟随笔者的思路把 Activity 的启动模式整理一遍:
问题 1: Activity 为什么需要启动模式?
问题 2: Activity 的启动模式有哪些?特性如何
问题 3: 如何给 Activity 选择合适的启动模式
问题 1:Activity 为什么需要启动模式?
我们都知道启动一个 Activity 后,这个 Activity 实例就会被放入任务栈中,当点击返回键的时候,位于任务栈顶层的 Activity 就会被清理出去,当任务栈中不存在任何 Activity 实例后,系统就回去回收这个任务栈,也就是程序退出了。这只是对任务栈的基本认识,深入学习,笔者会在之后文章中提到。那么问题来了,既然每次启动一个 Activity 就会把对应的要启动的 Activity 的实例放入任务栈中,假如这个 Activity 会被频繁启动,那岂不是会生成很多这个 Activity 的实例吗?对内存而言这可不是什么好事,明明可以一个Activity 实例就可以应付所有的启动需求,为什么要频繁生成新的 Activity 实例呢?杜绝这种内存的浪费行为,所以 Activity 的启动模式就被创造出来去解决上面所描述的问题。
问题 2:Activity 的启动模式有哪些?特性如何
Activity 的启动模式有 4 种,分别是:standard,singleTop,singleTask 和singleInstance。
下面一一作介绍:
1.系统默认的启动模式:Standard
标准模式,这也是系统的默认模式。每次启动一个 Activity 都会重新创建一个新的实例,不管这个实例是否存在。被创建的实例的生命周期符合典型情况下的 Activity 的生命周期。在这种模式下,谁启动了这个 Activity,那么这个 Activity 就运行在启动它的那个Activity 的任务栈中。比如 Activity A 启动了 Activity B(B 是标准模式),那么 B 就会进入到 A 所在的任务栈中。有个注意的地方就是当我们用 ApplicationContext
去启动standard 模式的 Activity 就会报错,这是因为 standard 模式的 Actiivty
默认会进入启动它的 Activity 所属的任务栈中,但是由于非 Activity 类型的 Context(ApplicationContext)
并没有所谓的任务栈,所以这就会出现错误。解决这个问题的方法就是为待启动的 Activity 指定 FLAG_ACTIVITY_NEW_TASK 标记位,这样启动的时候就会为它创建一个新的任务栈,这个时候启动 Activity 实际上以singleTask 模式启动的,读者可以自己仔细体会。
2.栈顶复用模式:SingleTop
在这种模式下,如果新的 Activity 已经位于任务栈的栈顶,那么此 Activity 不会被重新创建,同时它的 onNewIntent
方法被回调,通过此方法的参数我们可以取出当前请求的信息。需要注意的是,这个 Activity 的 onCreate
,onStart
不会被系统调用,因为它并没有发生改变。如果新的 Activity 已经存在但不是位于栈顶,那么新的 Activity 仍然会重新重建。举个例子,假设目前栈内的情况为 ABCD,其中 ABCD 为四个 Activity,A 位于栈低,D 位于栈顶,这个时候假设要再次启动 D,如果 D 的启动模式为 singleTop
,那么栈内的情况依然为 ABCD;如果 D 的启动模式为 standard,那么由于 D 被重新创建,导致栈内的情况为 ABCDD。
3.栈内复用模式:SingTask
这是一种单例实例模式,在这种模式下,只要 Activity 在一个栈中存在,那么多次启动此Activity 都不会重新创建实例,和 singleTop
一样,系统也会回调其 onNewIntent
。具体一点,当一个具有 singleTask
模式的 Activity 请求启动后,比如 Activity A,系统首先寻找任务栈中是否已存在 Activity A 的实例,如果已经存在,那么系统就会把 A 调到栈顶并调用它的 onNewIntent
方法,如果 Activity A 实例不存在,就创建 A 的实例并把 A 压入栈中。举几个栗子:
- 比如目前任务栈 S1 的情况为 ABC,这个时候 Activity D 以 singleTask 模式请求启动,其所需的任务栈为 S2,由于 S2 和 D 的实例均不存在,所以系统会先创建任务栈S2,然后再创建 D 的实例并将其投入到 S2 任务栈中。
- 另外一种情况是,假设 D 所需的任务栈为 S1,其他情况如同上面的例子所示,那么由于 S1 已经存在,所以系统会直接创建 D 的实例并将其投入到 S1。
- 如果 D 所需的任务栈为 S1,并且当前任务栈 S1 的情况为 ADBC,根据栈内复用的原则,此时 D 不会重新创建,系统会把 D 切换到栈顶并调用其
onNewIntent
方法,同时由于singleTask
默认具有clearTop
的效果,会导致栈内所有在 D 上面的 Activity全部出栈,于是最终 S1 中的情况为 AD。
通过以上 3 个例子,你应该能比较清晰地理解 singleTask 的含义了。
4.单实例模式:SingleInstance
这是一种加强的 singleTask
模式,它除了具有 singleTask
模式所有的特性外,还加强了一点,那就是具有此种模式的 Activity 只能单独位于一个任务栈中,换句话说,比如Activity A 是 singleInstance
模式,当 A 启动后,系统会为它创建一个新的任务栈,然后A 独自在这个新的任务栈中,由于栈内复用的特性,后续的请求均不会创建新的 Activity,除非这个独特的任务栈被系统销毁了。
对于 SingleInstance
,面试时你有说明它的以下几个特点:
(1) 以 singleInstance
模式启动的 Activity 具有全局唯一性,即整个系统中只会存在一个这样的实例。
(2) 以 singleInstance
模式启动的 Activity 在整个系统中是单例的,如果在启动这样的Activiyt
时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。
(3) 以 singleInstance
模式启动的 Activity 具有独占性,即它会独自占用一个任务,被他开启的任何 activity 都会运行在其他任务中。
(4) 被 singleInstance
模式的 Activity 开启的其他 activity,能够在新的任务中启动,但不一定开启新的任务,也可能在已有的一个任务中开启。换句话说,其实 SingleInstance
就是我们刚才分析的 SingleTask
中,分享 Activity 为栈底元素的情况。
总结
上面介绍了 4 种启动模式,这里需要指出一种情况,我们假设目前有 2 个任务栈,前台任务栈的情况为 AB,而后台任务栈的情况为 CD,这里假设 CD 的启动模式均为singleTask
。现在请求启动 D,那么整个后台任务栈都会被切换到前台,这个时候整个后退列表变成了 ABCD。当用户按 back 键的时候,列表中的 Activity 会一一出栈,如下图 1所示:
注意:
前台任务栈: 就是指和用户正在交互的应用程序所在的任务栈。
后台任务栈: 就是指处于后台的应用程序所在的任务栈。
如果不是请求的 D 而是请求的 C,那么情况就不一样了,如下图 2 所示:
如何指定活动的启动模式呢?在 AndroidManifest.xml 文件当注册活动的代码中去指定
比如: 我要把 MainActivity
活动的启动模式指定为 singleInstance
模式
<activity
android:name=“.MainActivity”
android:label=“@string/app_name”
android:launchMode="singlelnstance">
<intent-filter>
<action android:name=“android.intent.action.MAIN”/>
<category android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
也可以在代码中指定:
Intent pack = new Inten(MCPersonalCenterActivity.this,MCGiftsCenterActivity.class);
pack.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(pack);
五.Activity组件之间的通信
六.scheme 跳转协议
Android 中的 scheme 是一种页面内跳转协议,通过自定义 scheme 协议,可以非常方便的跳转到 app 中的各个页面,通过 scheme 协议,服务器可以定制化告诉 app 跳转到哪个页面,可以通过通知栏消息定制化跳转页面,可以通过 H5 页面跳转到相应页面等等。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://img-blog.csdnimg.cn/img_convert/870a28d174d4f7e39d2efdfff47f9b72.jpeg)
最后
感觉现在好多人都在说什么安卓快凉了,工作越来越难找了。又是说什么程序员中年危机啥的,为啥我这年近30的老农根本没有这种感觉,反倒觉得那些贩卖焦虑的都是瞎j8扯谈。当然,职业危机意识确实是要有的,但根本没到那种草木皆兵的地步好吗?
Android凉了都是弱者的借口和说辞。虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。
所以,最后这里放上我耗时两个月,将自己8年Android开发的知识笔记整理成的Android开发者必知必会系统学习资料笔记,上述知识点在笔记中都有详细的解读,里面还包含了腾讯、字节跳动、阿里、百度2019-2021面试真题解析,并且把每个技术点整理成了视频和PDF(知识脉络 + 诸多细节)。
以上全套学习笔记面试宝典,吃透一半保你可以吊打面试官,只有自己真正强大了,有核心竞争力,你才有拒绝offer的权力,所以,奋斗吧!骚年们!千里之行,始于足下。种下一颗树最好的时间是十年前,其次,就是现在。
最后,赠与大家一句诗,共勉!
不驰于空想,不骛于虚声。不忘初心,方得始终。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
有拒绝offer的权力,所以,奋斗吧!骚年们!千里之行,始于足下。种下一颗树最好的时间是十年前,其次,就是现在。
最后,赠与大家一句诗,共勉!
不驰于空想,不骛于虚声。不忘初心,方得始终。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!