@color/colorPrimary@color/colorPrimaryDark@color/colorAccent本文多张图片是GooglePerformanceOptimizationGuide第六季的一些截图Google给townbuilding的优化指南https://developer.android.com...splashscreen定义了Android性能优化的官方模式,从第六季开始,推出了一系列针对App启动的优化实践,地址如下:https://www.youtube.com/watch...可以想象,App的启动性能非常重要。同时,谷歌对于App启动画面也给出了非常详细的设计定义,如下图。https://material.google.com/p...其实最早的时候,启动画面是在app还没有完全启动的时候加入的设计,让用户不至于对app是否启动感到困惑开始了。但是现在很多APP基本把启动画面当成了广告宣传页,好像已经失去了本来的意义,但是启动画面,不管怎么说,在一个APP启动的时候是非常重要的。让设计工作交给UE。开发需要做的是让app的启动体验完美。App启动流程App启动的整个过程可以分解为以下几个过程:用户点击Launcher上的AppIcon系统为App创建一个进程并显示启动窗口App在进程中创建自己的组件这个过程可以可以用下图来说明说明:我们可以优化的,也就是下面Application的创建部分,系统的进程分配,还有一些窗口切换的动画效果等等,都是跟ROM相关的,并且我们无法处理它们。因此,我们需要重点关注Application的创建过程。以上是官方的描述,我们用更通俗的语言来解释一下。当用户点击桌面上的图标时,系统就绪,进程空间分配给App,就像去酒店开房一样,但是不能直接进入房间,必须拿电梯到房间,然后你坐电梯的时间,其实就是系统的准备时间,所以系统的准备时间一般不会太长,但是如果你开的是总统套房,系统会占用一个很多时间要照顾,所以系统为所有用户准备了一个过渡界面,这个界面,是开机黑屏白屏,也就是你在电梯里看的小广告,看完小广告advertisement,你会在房间里,然后你可以做任何你想做的事,这就是你想做的事。速度完全取决于你开门的速度。开门快,自然就这么快,所以这是开发者可以优化的地方。有的开发者需要几秒钟才能拔出一把钥匙,有的只需要几百毫秒。完全影响了后者的效率。一般来说,故事到这里就结束了。但是,系统,也就是酒店,并不是野鸡酒店。它还想尽最大努力让客户满意,这样才会有回头客。因此,酒店做了一个优化,让每位客人自己定义乘坐电梯时想看到什么!也就是说,系统在加载App的时候,首先加载的是资源文件,里面包含了要启动的Activity的Theme,而这个Theme嘛,是可以自定义的,就是客户什么时候想看到什么乘坐电梯,不再是同样的白屏或黑屏,他可以自定义很多东西,比如ActionBar、background、StatBar等等。启动时间的度量Activity启动时间的定义对于一个Activity来说,当它启动时,首先执行的是onCreate()、onStart()、onResume()等生命周期函数,但是即使这些生命周期方法回调结束,应用程序仍然还不算完全启动,还需要等待View树完全构建完成。一般认为setContentView中的所有View都显示完毕,应用才算完全启动。DisplayTime在API19之后,Android在系统Log中增加了DisplayLog信息。通过过滤ActivityManager和Display这两个关键字,可以在系统中找到这个Log:$adblogcat|grep"ActivityManager"ActivityManager:Displayedcom.example.launcher/.LauncherActivity:+999ms抓取的Log如图:然后这个时间其实就是从Activity启动到Layout全部显示的过程,但是需要注意的是这里不包括数据的加载,因为很多app在加载的时候都会采用懒加载模式,即拉取数据后,会刷新默认的UI。reportFullyDrawn前面提到过,系统日志中的DisplayTime只是布局的显示时间,不包括延迟加载某些数据所消耗的时间。因此,系统为我们定义了一个类似的“自定义报告时间”——reportFullyDrawn。也借用google一张图来说明:reportFullyDrawn是我们自己调用的。一般在所有数据加载完成后,我们手动调用,这样会在Log中添加一条日志:$adblogcat|grep"ActivityManager"ActivityManager:Displayedcom.example.launcher/.LauncherActivity:+999msActivityManager:Fullydrawncom.example。launcher/.LauncherActivity:+1s999ms一般来说,使用的场景如下:publicclassMainActivityextendsAppCompatActivityimplementsLoaderManager.LoaderCallbacks{@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.Loaderadactivity_vomain();}loader,Voiddata){//loaddata//...//reportreportFullyDrawnreportFullyDrawn();}@OverridepublicLoaderonCreateLoader(intid,Bundleargs){returnnull;}@OverridepublicvoidonLoaderReset(Loaderloader){}}但需要注意的是,该方法需要API19+,所以这里需要判断SDK版本。计算启动时间——ADB可以通过ADB命令统计应用程序的启动时间。命令如下:?~adbshel??lamstart-Wcom.xys.preferencetest/.MainActivityStarting:Intent{act=android.intent.action.MAINcat=[android.intent.category.LAUNCHER]cmp=com.xys.preferencetest/.MainActivity}Status:okActivity:com.xys.preferencetest/.MainActivityThisTime:1047TotalTime:1047WaitTime:1059Complete这条指令一共给出了3次:ThisTime:***one已启动Activity的启动时间TotalTime:所有自身的启动时间activitiesWaitTime:ActivityManagerService启动App的Activity的总时间(包括当前Activity的onPause()和自身Activity的启动)这三个时间不太好理解,我们可以分解整个流程1.OnPause()的previousActivity-2.耗时的系统调用AMS-3.启动第一个Activity的耗时(可能是闪屏页面)-4.**第一个Activity的耗时onPause()—5。启动第二个Activity的耗时。然后,ThisTime代表5(启动最后一个Activity的耗时)。TotalTime表示3.4.5的总耗时(如果启动时只有一个Activity,那么ThisTime和TotalTime应该是一样的)。WaitTime表示所有操作的耗时,即1.2.3.4.5的所有耗时。每次给的时间可能不一样,每次从应用程序安装启动到正常启动的时间都会不一样,跟系统是否需要分配进程空间是不一样的。计算启动时间-ScreenRecord是一种通过录制屏幕来分析启动的好方法。在API21+中,Android为我们提供了更方便准确的方式:?~adbshel??lscreenrecord--bugreport/sdcard/test.mp4Android在screenrecord中添加了一个新的参数-bugreport,那么添加这个参数后,录制的视频会显示一行数字在左上角,如图所示。视频开始前,会显示设备信息和一些参数:视频开始后,左上角会有一行数字:比如图中:15:31:22.261f=171(0)其中,前4位数字是时间戳,即15:31:22:261,f=后面的数字是当前帧数,注意不是帧率,是当前帧数,和括号中的数字代表“Droppedframescount”,即丢帧数。有了这个东西,再结合视频,信息就可以看得很清楚了。调试模拟启动延时在测试时,我们可以通过以下方式模拟启动延时:SystemClock.sleep(2000)或者直接通过:try{Thread.sleep(2000);}catch(InterruptedExceptione){e.printStackTrace();}或通过:newHandler().postDelayed(newRunnable(){@Overridepublicvoidrun(){//Delay}},2000);这些方案可以模拟启动延迟。强制冷启动在“开发者选项”的后台进程限制中设置为无后台进程。StaticBlock很多代码中的StaticBlock就是做一些初始化工作,尤其是在ContentProvider中对StaticBlock中的一些UriMatcher进行初始化。这些东西可以做成懒加载模式。ApplicationApplication是程序的主要入口,尤其是很多第三方SDK会需要在Application的onCreate中做大量的初始化操作。不得不说,各种第三方SDK特别喜欢这个“战场”,再加上自己的一些库的初始化,会让整个Application不堪重负。优化方法无非是以下几个方面:后台任务延迟初始化接口预加载阻塞阻塞的情况有很多种,比如磁盘IO阻塞(读写文件,SharedPerfences),网络阻塞(现在应该不行)和高CPU占用的代码(加解密、渲染、解析等)。参见《Android群英传》View级别的耗时方法使用TraceView&&Systrace&&MethodTracing工具进行排查,参见《Android群英传:神兵利器》App启动优化大体流程通过TraceView和Systrace分析耗时方法和组件。整理启动时加载的每个库和组件。根据功能和需求对梳理出的库进行划分,设计库的启动时间。沟通交互,设计闪屏,按照之前的方法进行优化。解决方案主题当系统加载一个Activity时,onCreate()是一个耗时的过程。在这个过程中,为了让用户有更好的体验,系统实际上会先绘制一些初始界面,类似于PlaceHolder。系统会先读取当前活动的主题,然后根据主题中的配置进行绘制。Activity加载完成后,将被替换为真实的界面。所以Google官方提供的解决方案是通过android:windowBackground属性来配置预加载配置。同时,这里不仅可以配置颜色,还可以配置图片。例如,我们可以使用layer-list作为android:windowBackgroundFigure来显示:start_window.xml 可以看到图片是通过layer-listOverlay实现的,可以让开发者自由组合。配置中的android:opacity="opaque"参数是为了防止启动时背景闪烁。接下来可以设置一个新的Style,就是Activity预加载的Style。@color/colorPrimary@color/colorPrimaryDark@color/colorAccent??@drawable/start_windowOK,下面在Mainifest中给Activity指定需要预加载的Style:这里需要注意,必须是Activity的Theme,而不是Application的Theme***,我们可以在Activity加载真正的界面前,将Theme设置回普通的Theme:R.style.AppTheme);super.onCreate(savedInstanceState);SystemClock.sleep(2000);setContentView(R.layout.activity_main);}}在这个Activity中,我使用SystemClock.sleep(2000)来模拟耗时加载Activity进程,在调用super.onCreate(savedInstanceState)之前将主题重置为原始主题。这样设置的效果如下:启动时先显示一个画面。这个画面就是系统解析出来的Style。Activity完全加载后,会加载Activity的界面。在Activity的界面中,我们将主题重置为普通主题,以达到友好的启动体验。事实上,这种方式并没有真正加快启动过程,而是通过交互体验优化了显示效果。异步初始化非常简单。就是让App在onCreate中做的事情尽量少,利用手机的多核特性,尽量利用多线程。比如一些第三方框架的初始化。进入线程,最简单的,直接newThread()即可,当然你也可以通过公共线程池进行异步初始化工作,这是最能压缩启动时间的方式延迟初始化延迟初始化并没有减少启动时间,但是让耗时操作让位,让资源绘制到UI上,延迟耗时操作,直到UI加载完成。所以推荐使用mDecoView.post方法进行延迟加载。代码如下:getWindow().getDecorView().post(newRunnable(){@Overridepublicvoidrun(){……}});我们的ContentView是通过mDecoView.addView添加到根布局的,这样,延迟加载的内容就可以在ContentView初始化完成后,再次执行,保证UI绘制的流畅。IntentServiceIntentService是一个继承自Service的类,处理异步请求。在IntentService内部,有一个工作线程来处理耗时操作。IntentService的启动方法与传统Service相同。同时,当任务执行完毕后,IntentService会自动停止,无需人工控制。publicclassInitIntentServiceextendsIntentService{privatestaticfinalStringACTION="com.xys.startperformancedemo.action";publicInitIntentService(){super("InitIntentService");}publicstaticvoidstart(Contextcontext){Intentintent=newIntent(context,InitIntentService.class);intent.setAction(ACTION);context.startService(intent);}@OverrideprotectedvoidonHandleIntent(Intentintent){SystemClock.sleep(2000);Log.d(TAG,"onHandleIntent:");}}我们把耗时的任务丢进IntentService处理,系统会自动开启Thread进行处理,同时任务结束后,可以自行结束Service,多么人性化啊!OK,在Application或Activity的onCreate中启动IntentService即可:@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);InitIntentService.start(this);}***不要忘记在Mainifest中注册服务。使用ActivityLifecycleCallbacksFramework提供的这个方法可以监控所有Activity的生命周期。这里,我们可以使用onActivityCreated之类的回调,将一些UI相关的初始化操作放在这里。同时通过unregisterActivityLifecycleCallbacks避免重复初始化。同时这里的onActivityCreated回调的参数Bundle可以用来区分是否是被系统回收的Activity。publicclassMainApplicationextendsApplication{@OverridepublicvoidonCreate(){super.onCreate();//初始化基础内容//...registerActivityLifecycleCallbacks(newActivityLifecycleCallbacks(){@OverridepublicvoidonActivityCreated(Activityactivity,BundlesavedInstanceState){unregisterActivity/allbacks相关的UI/allbacks/allbacks……}@OverridepublicvoidonActivityStarted(活动活动){}@OverridepublicvoidonActivityResumed(活动活动){}@OverridepublicvoidonActivityPaused(活动活动){}@OverridepublicvoidonActivityStopped(活动活动){}@OverridepublicvoidonActivitySaveInstanceState(活动活动,BundleoutState){}@OverridepublicvoidonActivityDestroyed(活动活动){}});}}资源优化有几个方面。一是优化布局和布局层次,二是优化资源,尽可能精简资源,避免垃圾资源。这些可以通过混淆和tinyPNG等工具来实现。下面是两种不同的方案,都是在Style中配置的:true和truetrue先来看看这个的效果:设置效果类似,就是通过取消和透明化系统统一加载页面来实现启动的“加速”。其实就是一个“甩锅”的过程。强烈建议开发者不要用这种方式做“所谓的启动加速”。这种方法虽然看起来启动app非常快,瞬间完成,但实际上隐藏了真正的启动界面。系统说:我们不怪你!Nosolution对应5.0以下的65535问题,目前只能通过Multidex处理,5.0以下的机器上,加载前合并Dex的过程可能会很长,这也是暂时没有解决的问题存在,只能寄希望于后面对Multidex进行优化。OK,App的启动优化就基本如上。关键过程仍然是分析耗时操作以及如何设计合理的启动顺序。希望大家可以通过本文介绍的方法优化App的启动。