一、Activity的生命周期
activity应该是我们初学android就接触的,而它的生命周期更是重中之重,掌握activity的生命周期也是学好android的基础
这张图片可谓是非常经典,应该学习android的人都见过,下面讲一一讲解各个方法:
(1) onCreate():在activity创建时会被调用,通常会在其中加载布局,初始化控件。
(2) onRestart():当当前界面重新启动时会被调用,可是能是用户回到桌面又切换回来了,也可能是用户打开了一个新的Activity又切换回来了。
(3) onStart(): Activity正在被启动,即将开始,这时Activity已经出现了,但是还没有出现在前台,无法与用户交互(与onResume的主要区别)
(4) onResume():界面可见
(5) onPause(): Activity正在停止,仍可见。onPause中不能进行耗时操作,会影响到新Activity的显示。因为onPause必须执行完,新的Activity的onResume才会执行。
(6) onStop():Activity即将停止,不可见,位于后台。可以做稍微重量级的回收工作,同样不能太耗时。
(7) onDestory():Activity即将销毁,这是Activity生命周期的最后一个回调,可以做一些回收工作和最终的资源回收。
从ActivityA跳转到ActivityB的过程
ActivityA onPause() -->ActivityB onCreate() -->ActivityB onStart() --> ActivityB onResume() --> ActivityA onStop()
onPostCreate:一般不实现这个方法,当程序的代码开始运行时,它调用系统做最后的初始化工作。
特别注意:当ActivityB为透明界面或者Dialog样式时不会调用ActivityA的onStop()
异常生命周期:
1.横竖屏切换
如果没有在AndroidManifest中配置以下属性,Activity将会在屏幕旋转时重建
android:configChanges = "orientation| screenSize"
当Activity异常销毁时会调用onSaveInstanceState()方法来保存界面状态,会将之存储到Bundle对象中,系统会根据该界面是否会重现来决定调不调用该方法,该方法的调用时机不确定,但是如果要调用就一定发生在onStop方法之前,但并不保证发生在onPause的前面还是后面。
当重建Activity重建时会回调onRestoreInstanceState()方法,传入onSaveInstanceState()保存的Bundle对象。这个方法在onStart 和 onPostCreate之间调用,在onCreate中也可以状态恢复,但是有时候需要布局初始化完成后再恢复状态。Google官方建议在onRestoreInstanceState()中恢复,因为此方法一旦被调用就可以保证Bundle对象不为null。
当出现下列情况时会调用保存和恢复方法:
- 当用户按下HOME键时
- 长按HOME键,选择运行其他的程序时
- 锁屏时
- 从activity A中启动一个新的activity时
- 屏幕方向切换时
2.资源内存不足导致优先级低的Activity被杀死
Activity优先级的划分和下面的Activity的三种运行状态是对应的。
(1) 前台Activity——正在和用户交互的Activity,优先级最高。
(2) 可见但非前台Activity——比如Activity中弹出了一个对话框,导致Activity可见但是位于后台无法和用户交互。
(3) 后台Activity——已经被暂停的Activity,比如执行了onStop,优先级最低。
当系统内存不足时,会按照上述优先级从低到高去杀死目标Activity所在的进程。这种情况下界面存储和恢复过程和上述情况一致,生命周期情况也一样。
Activity的三种运行状态
①Resumed(活动状态)
又叫Running状态,正在显示的界面
②Paused(暂停状态)
这是一个比较不常见的状态。这个Activity在屏幕上是可见的,但是并不是在屏幕最前端的那个Activity。比如有另一个非全屏或者透明的Activity是Resumed状态,没有完全遮盖这个Activity。
③Stopped(停止状态)
当Activity完全不可见时,此时Activity还在后台运行。
二、Activity的启动模式与特点
(1)标准模式(Standard)
系统的默认模式,每次启动一个Activity都会重新创建一个新的Activity实例,不管是否已经存在。并且谁启动了这个Activity,那么这个Activity就会运行在启动它的Activity所在的任务栈中。
特别注意:如果我们用ApplicationContext来启动标准模式的Activity就会报错,因为它没有所谓的任务栈,必须给Activity添加FLAG_ACTIVITY_NEW_TASK标志位。
(2)栈顶复用模式(singleTop)
在这个模式中,Activity还是会创建在启动它的Activity的任务栈中,但是如果它已经位于栈顶,那么就不会重复创建,并且它的onNewIntent()会被调用,可用于传递消息,但是要注意它的生命周期方法onCreate()等等不会被调用。如果它并不位于栈顶,如ABC,这时启动B,还是会创建一个新的实例,和standard一样。
可以用于避免手抖打开多个界面
(3)栈内复用模式(singleTask)
在这个模式中,比如启动Activity A,首先系统会寻找是否存在A想要的任务栈:如果存在,就看任务栈中是否有A,如果有就清除A上面的所有Activity,把A推到栈顶。
特别注意:如果存在A的任务栈,那么任务栈自动回切换到前台
singleTask和taskAffinity配合使用,指定开启的Activity加入到哪个栈中
<activity android:name=".Activity1"
android:launchMode="singleTask"
android:taskAffinity="com.liuyue.task"
android:label="@string/app_name">
</activity>
每个Activity都有taskAffinity属性,这个属性指出了它希望进入的Task。如果一个Activity没有显式的指明该Activity的taskAffinity,那么它的这个属性就等于Application指明的taskAffinity,如果Application也没有指明,那么该taskAffinity的值就等于包名。
应用场景:
singleTask适合作为程序入口点。例如浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走onNewIntent(),并且会清空主界面上面的其他页面。之前打开过的页面,打开之前的页面就ok,不再新建。
(4)单实例模式(singleInstance)
加强版的singleTAsk,启动时,无论从哪里启动都会给A创建一个唯一的任务栈,后续的创建都不会再创建新的A,除非A被销毁了。singleInstance不要用于中间页面,如果用于中间页面,跳转会有问题,比如:A -> B (singleInstance) -> C,完全退出后,再次启动,首先打开的是B。
应用场景:
singleInstance适合需要与程序分离开的页面。例如闹铃提醒,将闹铃提醒与闹铃设置分离。
Activity的Flags
Activity的Flags很多,这里介绍几种常用的,用于设定Activity的启动模式。可以在启动Activity时,通过Intent的addFlags()方法设置。
(1)FLAG_ACTIVITY_NEW_TASK 其效果与指定Activity为singleTask模式一致。
(2)FLAG_ACTIVITY_SINGLE_TOP 其效果与指定Activity为singleTop模式一致。
(3)FLAG_ACTIVITY_CLEAR_TOP 具有此标记位的Activity,当它启动时,在同一个任务栈中所有位于它上面的Activity都要出栈。如果和singleTask模式一起出现,若被启动的Activity已经存在栈中,则清除其之上的Activity,并调用该Activity的onNewIntent方法。如果被启动的Activity采用standard模式,那么该Activity连同之上的所有Activity出栈,然后创建新的Activity实例并压入栈中。
三、Activity卡顿原因
(1)内存泄漏导致内存占用较高,导致JVM频繁触发GC。
导致内存泄漏的原因有一下几个
1.错误的单例
单例的生命周期和应用一样长,如果持有一个短生命周期对象的引用的话就会造成内存泄漏
2.创建了非静态内部类的静态实例
非静态内部类隐性持有外部类的对象,且静态示例存在周期较长
3.资源忘记关闭
BraodcastReceiver、ContentObserver、File、Cursor、Stream、Bitmap
(2)加载大数据,占用太多内存,同样导致JVM频繁GC。
解决办法:主要是Bitmap占用太多内存。可以通过根据需要显示的Bitmap宽高设定采样率来压缩图片。另外,通过采用LRUCache方法避免。如果是其他的大数据比如3D模型数据,可以通过使用Native空间,使用ByteBuffer.allocate(size);
(3)UI线程做耗时任务(数据库操作,数据计算等),1秒绘制60帧才不会卡顿,即16.6ms要刷新一次才不会卡顿。
解决方法:另起线程做耗时任务。
(4)UI OverDraw。
(5)在包含ImageView的ListView中,等滑动停止后加载图片。
四、Activity的启动过程
可将其分为6个过程:
1 使用代理模式启动到ActivityManagerService中执行;
2 创建ActivityRecord到mHistory记录中;
3 通过socket通信到Zgote相关类创建process;
4 通过ApplicatonThread与ActivityManagerService建立通信;
5 ActivityManagerService通知ActivityThread启动Activity的创建;
6 ActivityThread创建Activity加入到mActivities中并开始调度Activity执行
注: 整个应用程序的启动过程要执行很多步骤,但是整体来看,主要分为以下五个阶段:
应用程序是由Launcher启动起来的,其实,Launcher本身也是一个应用程序,其它的应用程序安装后,就会Launcher的界面上出现一个相应的图标,点击这个图标时,Launcher就会对应的应用程序启动起来。
一. Launcher通过Binder进程间通信机制通知ActivityManagerService,它要启动一个Activity;
二. ActivityManagerService通过Binder进程间通信机制通知Launcher进入Paused状态;
三. :Launcher通过Binder进程间通信机制通知ActivityManagerService,它已经准备就绪进入Paused状态,于是ActivityManagerService就创建一个新的进程,用来启动一个ActivityThread实例,即将要启动的Activity就是在这个ActivityThread实例中运行;
四. ActivityThread通过Binder进程间通信机制将一个ApplicationThread类型的Binder对象传递给ActivityManagerService,以便以后ActivityManagerService能够通过这个Binder对象和它进行通信;
五. ActivityManagerService通过Binder进程间通信机制通知ActivityThread,现在一切准备就绪,它可以真正执行Activity的启动操作了。
如何加速Activity的启动
1.视图优化
- 尽量精简布局,减少布局层数,使用viewStub、include、merge
- 谨慎选用布局 如相对布局和线性布局的选用
- 避免太繁复的动画,onDraw中不要创建大量临时对象
- 列表中的视图复用,异步加载,分页加载
- 使用Hierarchy Viewer、Layoutopt
- 页面分模块加载
2.网络优化
- 页面开始时先取用缓存数据再进行网络请求,更新数据
- 使用线程池管理线程
- 不要短时间内创建大量线程,尽量流出更多时间先把界面显示出来
- Fragment懒加载