一、应用的启动方式
通常来说,启动方式分为两种:冷启动和热启动。
1、冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。
2、热启动:当启动应用时,后台已有该应用的进程(例:按back键、home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动。
特点
1、冷启动:冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。
2、热启动:热启动因为会从已有的进程中来启动,所以热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个MainActivity就行了,而不必创建和初始化Application,
因为一个应用从新进程的创建到进程的销毁,Application只会初始化一次。
二、应用的启动过程
冷启动启动流程:当点击app的启动图标时,安卓系统会从Zygote进程中fork创建出一个新的进程分配给该应用:
应用的第一次启动才算完成,这时候我们看到的界面也就是所说的第一帧。所以,总结一下,应用的启动流程如下:
Application的构造器方法——>attachBaseContext()——>onCreate()——>Activity的构造方法——>onCreate()——>配置主题中背景等属性——>onStart()——>onResume()——>测量布局绘制显示在界面上。
大致流程如下:
1、点击桌面图标,Launcher会启动程序默认的Acticity,之后再按照程序的逻辑启动各种Activity
2、启动Activity都需要借助应用程序框架层的ActivityManagerService服务进程(Service也是由ActivityManagerService进程来启动的);在Android应用程序框架层中,ActivityManagerService是一个非常重要的接口,
它不但负责启动Activity和Service,还负责管理Activity和Service。
Step 1. 无论是通过Launcher来启动Activity,还是通过Activity内部调用startActivity接口来启动新的Activity,都通过Binder进程间通信进入到ActivityManagerService进程中,并且调用ActivityManagerService.startActivity接口;
Step 2. ActivityManagerService调用ActivityStack.startActivityMayWait来做准备要启动的Activity的相关信息;
Step 3. ActivityStack通知ApplicationThread要进行Activity启动调度了,这里的ApplicationThread代表的是调用ActivityManagerService.startActivity接口的进程,对于通过点击应用程序图标的情景来说,这个进程就是Launcher了,
而对于通过在Activity内部调用startActivity的情景来说,这个进程就是这个Activity所在的进程了;
Step 4. ApplicationThread不执行真正的启动操作,它通过调用ActivityManagerService.activityPaused接口进入到ActivityManagerService进程中,看看是否需要创建新的进程来启动Activity;
Step 5. 对于通过点击应用程序图标来启动Activity的情景来说,ActivityManagerService在这一步中,会调用startProcessLocked来创建一个新的进程,而对于通过在Activity内部调用startActivity来启动新的Activity来说,这一步是不需要执行的,
因为新的Activity就在原来的Activity所在的进程中进行启动;
Step 6. ActivityManagerServic调用ApplicationThread.scheduleLaunchActivity接口,通知相应的进程执行启动Activity的操作;
Step 7. ApplicationThread把这个启动Activity的操作转发给ActivityThread,ActivityThread通过ClassLoader导入相应的Activity类,然后把它启动起来。 链接
三、消除启动时的白屏/黑屏
1、将背景图设置成我们APP的Logo图,作为APP启动的引导,现在市面上大部分的APP也是这么做的:
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.NoActionBar.Splash">
<item name="android:windowBackground">@drawable/splash_image_1</item>
</style>
复制代码
2、将背景颜色设置为透明色,这样当用户点击桌面APP图片的时候,并不会"立即"进入APP,而且在桌面上停留一会,其实这时候APP已经是启动的了,只是我们原来黑/白屏windowBackground的颜色设置成透明的:
<style name="AppTheme.NoActionBar.Transparent">
<item name="android:windowBackground">@color/transparent</item>
<item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>
<item name="android:windowIsTranslucent">true</item>
</style>
复制代码
四、启动耗时统计
adb shell am start -W [-D] [PackageName]/[PackageName.MainActivity] 加-D可等待调试器附加到进程 执行成功后将返回三个测量到的时间:
五、启动耗时优化
一般而言,热启动时时间较长是因为在MainActivity的onCreate()函数中做了太多UI耗时操作,而冷启动时间较长主要跟Application的onCreate()函数以及MainActivity的onCreate()函数的执行时间有关。 工欲善其事,必先利其器,首先来介绍一下Android测量函数耗时的几个方法:
点击record录制app启动过程中执行每个函数及其子函数的CPU耗时,非常直观的可以找到启动过程中耗时最多的地方。为了捕获到完整的冷启动流程,在开始录制之前先到Android系统设置菜单中选择带调试的应用:
Debug.startMethodTracing(“launch”);
....
代码
.....
Debug.stopMethodTracing();
复制代码
随着程序的运行,在/sdcard/文件夹下会自动创建一个launch.trace的文件,这个文件可以通过Android Studio profiler和eclipse traceView打开,然后进行分析。
2、使用eclipse traceView打开trace文件:
Parents:调用该方法的父类方法
Children:该方法调用的子类方法
如果该方法含有递归调用,可能还会多出两个类别:
Parents while recursive:递归调用时所涉及的父类方法
Children while recursive:递归调用时所涉及的子类方法
至于数据分析面板红色框中,各个字段的含义如下:
3、使用StrictMode线程策略
StrictMode意思为严格模式,是用来检测程序中违例情况的开发者工具,线程策略(ThreadPolicy)检测的内容有
1)自定义的耗时调用 使用detectCustomSlowCalls()开启
2)磁盘读取操作 使用detectDiskReads()开启
3)磁盘写入操作 使用detectDiskWrites()开启
4)网络操作 使用detectNetwork()开启
复制代码
严格模式的开启可以放在Application或者Activity以及其他组件的onCreate方法。为了更好地分析应用中的问题,建议放在Application的onCreate方法中:
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectAll()//开启所有的detectXX系列方法
.penaltyDialog()//弹出违规提示框
.penaltyLog()//在Logcat中打印违规日志
.build());
复制代码
setThreadPolicy()将对当前线程应用该策略。如果不指定检测函数,也可以用detectAll()来替代。penaltyLog()表示将警告输出到LogCat,你也可以使用其他或增加新的惩罚(penalty)函数,例如使用penaltyDeath()的话,一旦StrictMode消息被写到LogCat后应用就会崩溃。另外虚拟机策略(VmPolicy)不能通过一个对话框提供警告。
在线程策略(ThreadPolicy)检测的时候,有几个penalty系列方法。
1)penaltyDeath(),当触发违规条件时,直接Crash掉当前应用程序。
2)penaltyDeathOnNetwork(),当触发网络违规时,Crash掉当前应用程序。
3)penaltyDialog(),触发违规时,显示对违规信息对话框。
4)penaltyFlashScreen(),会造成屏幕闪烁,不过一般的设备可能没有这个功能
复制代码
4、使用Android Systrace
Systrace 允许在系统级别收集和检查设备上运行的所有进程的计时信息。 它将来自Android内核的数据(例如CPU调度程序,磁盘活动和应用程序线程)组合起来,以生成HTML报告。如果想分析Android系统或者app的问题,首先我们需要抓取Systrace文件分析并找出引起系统卡顿,或者app反应慢的原因,然后在源码上解决引起慢的问题。首先链接手机,打开Android Device Monitor,选择要分析的进程,点击Capture system wide trace using Android。
六、优化建议
1、页面布局:
1)当根布局文件最外层标签为FrameLayout以及使用include标签引用的布局文件的最外层标签
可以用merge标签来替换,减少一层布局深度。
2)对于页面中不需要立即展示的部件使用ViewStub实现延迟加载的方式
3)合理运用LinearLayout和RelativeLayout,减少布局嵌套,推荐使用ConstraintLayout
4)当时用自定义控件时,onDraw()中及不要做耗时操作
复制代码
2、应用初始化:
在大部分app开发中,我们都会去重写Application类,然后在onCreate()里进行一些初始化操作,比如创建一些全局单例,第三方SDK以及一些功能模块的初始化等,如果初始化函数耗时过长,应该放到线程池或者intentService中去操作;涉及到数据库和文件等I/O操作,放到真正需要的地方再执行。同样在MainActivity的onCreate()函数中也要注意上述问题,因为只有当Activity生命周期执行到onResume()函数的时候界面才会真正显示出来,onCreate()函数执行时间越长,界面等待显示的时间越长,如果有些初始化流程必须要放在onCreate()当中,可以使用DelayLoad的机制:
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
myHandler.post(mLoadingRunnable);
}
});
复制代码
ViewRootImpl 的 performTraversals 方法是一个很核心的方法,每一帧绘制都会走一遍,调用各种 measure / layout / draw 等 ,最终将要显示的数据交给 hwui 去进行绘制。Activity 在启动时,会在第二次执行 performTraversals 才会去真正的绘制,原因在于第一次执行 performTraversals 的时候,会走到 Egl 初始化的逻辑,然后会重新执行一次 performTraversals 。在 onCreate 中 Post 的 runnable 对象,在第一个 performTraversals 方法执行的时候被调用,mLoadingRunnable在界面真正绘制的时候才会执行。