# Android APP性能优化–白屏启动速度优化
一、界面优化
设置启动activity主题,背景设置为图片或背景色一致的颜色值。启动速度未改变,视觉上不再有白屏。代码如下:
1.style文件添加主题(主主题,动画主题,启动页主题)
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<!-- 隐藏状态栏 -->
<item name="android:windowFullscreen">true</item>
<!-- 隐藏标题栏 -->
<item name="android:windowNoTitle">true</item>
<item name="android:windowAnimationStyle">@style/noAnimation</item>
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="noAnimation">
<item name="android:activityOpenEnterAnimation">@null</item>
<item name="android:activityOpenExitAnimation">@null</item>
<item name="android:activityCloseEnterAnimation">@null</item>
<item name="android:activityCloseExitAnimation">@null</item>
<item name="android:taskOpenEnterAnimation">@null</item>
<item name="android:taskOpenExitAnimation">@null</item>
<item name="android:taskCloseEnterAnimation">@null</item>
<item name="android:taskCloseExitAnimation">@null</item>
<item name="android:taskToFrontEnterAnimation">@null</item>
<item name="android:taskToFrontExitAnimation">@null</item>
<item name="android:taskToBackEnterAnimation">@null</item>
<item name="android:taskToBackExitAnimation">@null</item>
</style>
<style name="LaunchTheme" parent="AppTheme">
<item name="android:windowBackground">@drawable/splash_layer</item>
<item name="android:windowFullscreen">true</item>
</style>
2.修改mainfest中启动activity的主题为上述LaunchTheme
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.why.project.androidstartingwindowdemo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!--将首页的them设置成自定义的样式-->
<activity
android:name="MainActivity"
android:screenOrientation="landscape"
android:theme="@style/LaunchTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.HOME.PEITE" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>
二、App启动分类
1.冷启动 Cold start
在启动应用前,系统还没有App的任何进程。比如设备开机后应用的第一次启动,系统杀掉应用进程 (如:系统内存吃紧引发的 kill 和 用户主动产生的 kill) 后 的再次启动等。那么自然这种方式下,应用的启动时间最长。
2.热启动 Warm start
当应用中的 Activities 被销毁,但在内存中常驻时,应用的启动方式就会变为暖启动。相比冷启动,暖启动过程减少了对象初始化、UI的布局和渲染。启动时间更短。但启动时,系统依然会展示一个空白背景,直到第一个 Activity 的内容呈现为止。
3.温启动 Lukewarm start
用户退出您的应用,但随后重新启动。该过程可能已继续运行,但应用程序必须通过调用onCreate()从头开始重新创建活动。系统从内存中驱逐您的应用程序,然后用户重新启动它。进程和Activity需要重新启动,但任务可以从保存的实例状态包传递到onCreate()中。
启动速度优化主要是针对冷启动方式
三、启动时间获取&分析
获取App启动时间的几种方法:
1.adb命令获取
adb shell am start -S -R 5 -W 包名/完整的Activity名
这个命令的输出日志如下:
Stopping: 包名
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=包名/完整的Activity名 }
Status: ok
Activity: 包名/完整的Activity名
ThisTime: 2808
TotalTime: 2808
WaitTime: 2832
**ThisTime:**一般和TotalTime时间一样,除非在应用启动时开了一个透明的Activity预先处理一些事再显示出主Activity,这样将比TotalTime小。
**TotalTime:**应用的启动时间,包括创建进程+Application初始化+Activity初始化到界面显示。
**WaitTime:**一般比TotalTime大点,包括系统影响的耗时。
2.用TraceView分析启动时间
打开方式:
TraceView 位于 Android Devices Monitor 里面。可以在 Android Studio ——>Tools ——>moniter 中找到 Android Devices Monitor。在 Monitor 中就可以打开对应的 Trace 文件,或者进行 Trace 操作。
分析启动时间
1.在application或者mainActivity的onCreate的始末位置添加打印trace语句(“”中为trace文件的文件名):
Debug.startMethodTracing("SysTime");
...
Debug.stopMethodTracing();
2.App启动运行后,会在sd卡中生成SysTime.trace文件,或者手机Android——>data——>"包名"路径下,用moniter打开,可以进行分析
分析面板含义:
名字 | 含义 |
Incl Cpu Time | Cpu执行该方法该方法及其子方法所花费的时间 |
Name | 方法的详细信息,包括包名和参数信息 |
Incl Cpu Time % | Cpu执行该方法该方法及其子方法所花费占Cpu总执行时间的百分比 |
Excl Cpu Time | Cpu执行该方法所话费的时间 |
Excl Cpu Time % | Cpu执行该方法所话费的时间占Cpu总时间的百分比 |
Incl Real Time | 该方法及其子方法执行所话费的实际时间,从执行该方法到结束一共花了多少时间 |
Incl Real Time % | 上述时间占总的运行时间的百分比Incl Real Time % |
Excl Real Time % | 该方法自身的实际允许时间 |
Excl Real Time | 上述时间占总的允许时间的百分比 |
Calls+Recur | 调用次数+递归次数,只在方法中显示,在子展开后的父类和子类方法这一栏被下面的数据代替 |
Calls/Total | 调用次数和总次数的占比 |
Cpu Time/Call | Cpu执行时间和调用次数的百分比,代表该函数消耗cpu的平均时间 |
Real Time/Call | 实际时间于调用次数的百分比,该表该函数平均执行时间 |
分析面板截图如下:按照耗时时间长短排列,点击可查看父类函数中各子类耗时长短,根据耗时针对性进行优化。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ijOi45Rg-1572172419637)(C:\Users\IvyZhao\Desktop\Image_20191009194240.png)]
四、优化方案
1.避免application中onCreat进行太多初始化
Application里面的初始化操作不结束,其他任意的程序操作都无法进行。Application的onCreate中会做大量第三方组件的初始化工作,其实很多组件是需要做区别对待的,有些可以做延迟加载,有些可以放到其他的地方做初始化操作,特别需要留意包含Disk IO操作,网络访问等严重耗时的任务,他们会严重阻塞程序的启动。
实现方法:
-异步加载、延时加载、懒加载
-判断第三方库初始化能否在子线程中加载
-项目是多进程架构,只在主进程执行Application的onCreate();
2.MainActivity的onCreate避免执行过多初始化
2.1使用延迟加载。
确保在Activity的页面显示出来之后再进行加载数据,避免过早或过晚的加载导致页面空白时间过长。可采用以下 代码实现延迟加载。在Activity的onCreate方法中:
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
myHandler.post(mLoadingRunnable);
}
});
2.2 异步加载
将不需要首次加载的初始化内容放入子线程进行初始化。
3.UI布局优化
-避免多级嵌套,采用constraintlayout布局代替嵌套的LinearLayout;
-复杂布局,使用include
-删除无用布局
4.MultiDex初次启动优化
4.1问题
随着代码数量的膨胀,工程本身的代码加上引用的第三方库的代码中方法数量会超过65536的限制。是由于DEX文件格式限制,一个DEX文件中method个数采用使用原生类型short来索引文件中的方法,也就是4个字节共计最多表达65536个method,field/class的个数也均有此限制。 Google为构建超过65K方法数的应用提供官方支持的方案:MultiDex。
但是在Dalvik下MultiDex有个问题:5.0以下某些低端机会出现ANR或者长时间卡顿不进入引导页,而罪魁祸首是MultiDex.install(Context context)的dexopt过程耗时过长。因此需要在初次启动时做特别处理。
而5.0以上会使用ART,在ART下MultiDex是不存在这个问题的,这主要是因为ART下采用Ahead-of-time (AOT) compilation技术,系统在APK的安装过程中会使用自带的dex2oat工具对APK中可用的DEX文件进行编译并生成一个可在本地机器上运行的文件,这样能提高应用的启动速度,只是在安装过程中进行了处理这样会影响应用的安装速度。
4.2解决思路
1、在Application.attachBaseContext(Context base)中,判断是否初次启动,以及系统版本是否小于5.0,如果是,跳到2;否则,直接执行MultiDex.install(Context context)。
2、开启一个新进程,在这个进程中执行MultiDex.install(Context context)。执行完毕,唤醒主进程,自身结束。主进程在开启新进程后,自身是挂起的,直到被唤醒。
1、在Application.attachBaseContext(Context base)中,判断是否初次启动,以及系统版本是否小于5.0,如果是,跳到2;否则,直接执行MultiDex.install(Context context)。
2、开启一个新进程,在这个进程中执行MultiDex.install(Context context)。执行完毕,唤醒主进程,自身结束。主进程在开启新进程后,自身是挂起的,直到被唤醒。
3、唤醒的主进程继续执行初始化操作。