应用的启动速度缓慢是我们在开发过程中经常会遇到的问题,比如启动缓慢导致的黑屏,白屏问题
一、应用的启动方式:应用的启动方式有冷启动和热启动两种,
1、冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。
2、热启动:当启动应用时,后台已有该应用的进程(例:按back键、home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动。
本篇指的启动过程的优化主要是指冷启动。
二、1、App的启动过程:
1.点击Launcher,启动程序,通知ActivityManagerService
2.ActivityManagerService通知zygote进程孵化出应用进程,分配内存空间等
在Android中,zygote是整个系统创建新进程的核心进程。zygote进程在内部会先启动Dalvik虚拟机,继而加载一些必要的系统资源和系统类,最后进入一种监听状态。在之后的运作中,当其他系统模块(比如AMS)希望创建新进程时,只需向zygote进程发出请求,zygote进程监听到该请求后,会相应地fork出新的进程,于是这个新进程在初生之时,就先天具有了自己的Dalvik虚拟机以及系统资源。zygote进程是由init进程启动起来,由init.rc 脚本中关于zygote的描述可知:zygote对应的可执行文件就是/system/bin/app_process,也就是说系统启动时会执行到这个可执行文件的main()函数里。
关于更多zygote的解释,参照 https://www.cnblogs.com/bRAyKpoyNt/p/3649417.html
3.执行该应用ActivityThread的main()方法
4.应用程序通知ActivityManagerService它已经启动,ActivityManagerService保存一个该应用的代理对象,ActivityManagerService通过它可以控制应用进程
5.ActivityManagerService通知应用进程创建入口的Activity实例,执行它的生命周期
2、启动过程中Application和入口Activity的生命周期方法按如下顺序调用:
1.Application 构造方法
2.attachBaseContext()
3.onCreate()
4.入口Activity的对象构造
5.setTheme() 设置主题等信息
6.入口Activity的onCreate()
7.入口Activity的onStart()
8.入口Activity的onResume()
9.入口Activity的onAttachToWindow()
10.入口Activity的onWindowFocusChanged()
3、启动时间统计
而view绘制的真正过程measure layout draw过程并没有,所以不是真正意义上的“可见”。 在onWindowFocusChanged()回调处更接近事实。
所以我们确认启动时间可以从Application的构造方法记录开始时间,然后到入口Activity的onWindowFocusChanged()再记录一个时间,两者之差就是App的启动时间。
当然这是比较笨的方法,adb命令里有相应的记录命令 :adb shell am start -W 包名/入口类全路径名
adb shell am start -W [PackageName]/[PackageName.MainActivity]
在onCreate开始和结尾打上trace.
Debug.startMethodTracing("TestApp"); ... Debug.stopMethodTracing();运行程序, 会在sdcard上生成一个”TestApp.trace”的文件. 注意: 需要给程序加上写存储的权限: <uses-permission android:name= "android.permission.WRITE_EXTERNAL_STORAGE"/>通过adb pull将其导出到本地
adb pull /sdcard/TestApp.trace ~/testSpeed.trace打开DDMS分析trace文件,。很重要的指标:Calls + Recur Calls / Total , 最重要的指标: Cpu Time / Call ,超过500ms的都是耗时的,需要重点关注因为我们最关心的有两点,一是调用次数不多,但每次调用却需要花费很长时间的函数。这个可以从Cpu Time / Call反映出来。另外一个是那些自身占用时间不长,但调用却非常频繁的函数。这个可以从Calls + Recur Calls / Total 反映出来。
然后我们可以通过这样的分析,来查看启动的时候,哪些进行了耗时操作,然后进行相应的处理,比如能不在主线程中做的,放入子线程中,还有一些懒加载处理,比如有些Application中做了支付SDK的初始化,用户又不会一打开App就要支付,对其进行懒加载。
三、启动页优化
优点:启动速度有所加快
缺点:最终还是要进入首页,在进入首页的时候,首页复杂的View渲染以及必须在UI线程执行的业务逻辑,仍然拖慢了启动速度。启动页简单执行快,首页复杂执行慢,前轻后重。
思路:能否在启动页的展示的同时,首页的View就能够被加载,首页的业务逻辑就能够被执行?
优化方向:
把SplashActivity改成SplashFragment,应用程序的入口仍然是MainActivity,在MainActivity中先展示SplashFragment
当SplashFragment显示完毕后再将它remove,同时在SplashFragment的2S的友好时间内进行网络数据缓存,在窗口加载完毕后,我们加载activity_main的布局,考虑到这个布局有可能比较复杂,耽误View的解析时间,采用ViewStub的形式进行懒加载。
这样一开始只要加载SplashFragment所展示的布局就Ok了。
思路二:
不妨调整下Application的onCreate实现, 一般来说我们可以将这些初始化工作放在一个单独的线程中处理, 为了方便今后管理, 这里我用了一个InitializeService的IntentService来做初始化工作.明确一点:IntentService不同于service,它是工作在后台线程中的
InitializeService.java代码如下:
package com.anly.githubapp.compz.service; import android.app.IntentService; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; import android.net.Uri; import android.widget.ImageView; import com.anly.githubapp.common.wrapper.AppLog; import com.anly.githubapp.common.wrapper.CrashHelper; import com.anly.githubapp.common.wrapper.FeedbackPlatform; import com.anly.githubapp.common.wrapper.ImageLoader; import com.anly.githubapp.common.wrapper.PushPlatform; import com.anly.githubapp.common.wrapper.SharePlatform; import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader; import com.mikepenz.materialdrawer.util.DrawerImageLoader; /** * Created by ping. */ public class InitializeService extends IntentService { private static final String ACTION_INIT_WHEN_APP_CREATE = "com.anly.githubapp.service.action.INIT"; public InitializeService() { super("InitializeService"); } public static void start(Context context) { Intent intent = new Intent(context, InitializeService.class); intent.setAction(ACTION_INIT_WHEN_APP_CREATE); context.startService(intent); } @Override protected void onHandleIntent(Intent intent) { if (intent != null) { final String action = intent.getAction(); if (ACTION_INIT_WHEN_APP_CREATE.equals(action)) { performInit(); } } } private void performInit() { AppLog.d("performInit begin:" + System.currentTimeMillis()); // init Drawer image loader DrawerImageLoader.init(new AbstractDrawerImageLoader() { @Override public void set(ImageView imageView, Uri uri, Drawable placeholder) { ImageLoader.loadWithCircle(getApplicationContext(), uri, imageView); } }); // init crash helper CrashHelper.init(this.getApplicationContext()); // init Push PushPlatform.init(this.getApplicationContext()); // init Feedback FeedbackPlatform.init(this.getApplication()); // init Share SharePlatform.init(this.getApplicationContext()); AppLog.d("performInit end:" + System.currentTimeMillis()); } }GithubApplication的onCreate改成:
public class GithubApplication extends MultiDexApplication { @Override public void onCreate() { super.onCreate(); // init logger. AppLog.init(); InitializeService.start(this); } }这样就会提升了很多, 然后还有一点瑕疵, 就是起来的时候会有一个白屏, 如果手机较慢的话, 这个白屏就会持续一段时间, 不太友好.
白屏的优化:给我们的应用窗口弄一个placeHolder
Android最新的Material Design有这么个建议的. 建议我们使用一个placeholder UI来展示给用户直至App加载完毕.
这个空白的窗口展示跟主题相关, 那么我们是不是可以从首屏的主题入手呢? 恰好有一个windowBackground的主题属性, 我们来给Splash界面加上一个主题, 带上我们想要展示的背景.
做一个logo_splash的背景:
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 底层白色 --> <item android:drawable="@color/white" /> <!-- 顶层Logo居中 --> <item> <bitmap android:gravity="center" android:src="@drawable/ic_github" /> </item> </layer-list>弄一个主题:
<style name="SplashTheme" parent="AppTheme"> <item name="android:windowBackground">@drawable/logo_splash</item> </style>将一个什么布局都不做的activity作为启动屏
写一个什么都不做的LogoSplashActivity.
public class LogoSplashActivity extends BaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 注意, 这里并没有setContentView, 单纯只是用来跳转到相应的Activity. // 目的是减少首屏渲染 if (AppPref.isFirstRunning(this)) { IntroduceActivity.launch(this); } else { MainActivity.launch(this); } finish(); } }在AndroidManifest.xml中设置其为启动屏, 并加上主题:
<activity android:name=".ui.module.main.LogoSplashActivity" android:screenOrientation="portrait" android:theme="@style/SplashTheme"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity>
相比之前, 呈现给用户的不再是一个白屏了, 带上了logo, 当然这个背景要显示什么, 我们可以根据实际情况来自定义.
这种优化, 对于有些Application内的初始化工作不能移到子线程做的情况, 是非常友好的. 可以避免我们的App长时间的呈现给用户一个空白的窗口.
四、启动优化的一些思路:
1、避免启动页UI的过度绘制,减少UI重复绘制时间,打开设置中的GPU过度绘制开关,界面整体呈现浅色,特别复杂的界面,红色区域也不应该超过全屏幕的四分之一;
2、主线程中的所有SharedPreference能否在非UI线程中进行,SharedPreferences的apply函数需要注意,因为Commit函数会阻塞IO,这个函数虽然执行很快,但是系统会有另外一个线程来负责写操作,当apply频率高的时候,该线程就会比较占用CPU资源。类似的还有统计埋点等,在主线程埋点但异步线程提交,频率高的情况也会出现这样的问题。
3、对于首次启动的黑屏问题,对于“黑屏”是否可以设计一个.9图片替换掉,间接减少用户等待时间。
4、对于网络错误界面,友好提示界面,使用ViewStub的方式,减少UI一次性绘制的压力。
5、通过下面这种方式进行懒加载
getWindow().getDecorView() .post(new Runnable() { @Override public void run() { myHandler.post(mLoadingRunnable); } });6、Multidex的使用,也是拖慢启动速度的元凶,必须要做优化
