Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What is the method for Android to start optimization

2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article introduces the relevant knowledge of "what is the method of Android startup optimization". In the operation of actual cases, many people will encounter such a dilemma, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!

Start classification

The start-up of App is mainly divided into cold start, hot start and warm start.

Cold start:

It takes the most time and is a measure of the startup time of the entire application. Let's take a look at the process of the cold start experience through a diagram:

Hot start:

The startup is the fastest, and the application switches directly from the background to the foreground.

Warm start:

Startup is fast, which is a startup mode between cold startup and hot startup. Warm startup only executes Activity-related lifecycle methods, not process creation and other operations.

The direction and focus of our optimization is mainly cold start. Because it represents all the time it takes for the application to be clicked by the user until the final page is drawn. Let's take a look at the cold start-up related task flow through a flowchart:

Looking at the flow chart of the above task, what do readers think is the direction of our optimization? In fact, all we can do is the lifecycle phase of Application and Activity, because everything else is created by the system and we can't intervene, such as starting App, loading blank Window, creating processes, and so on. This loading blank Window we can actually do a false optimization is to use a boot diagram to replace the blank Window, the specific operation we will introduce below.

Starting measurement mode

Here are two main ways: ADB command and manual management. Let's take a look at the use of the two as well as their advantages and disadvantages.

ADB command:

Enter the following command in the Terminal of Android Studio

Adb shell am start-W packagename/ [packagename]. First screen Activity

After execution, the following is output in the console:

Starting: Intent {act=android.intent.action.MAIN cat= [android.intent.category.LAUNCHER] cmp=com.optimize.performance/.MainActivity} Status: ok Activity: com.optimize.performance/.MainActivity ThisTime: 563 TotalTime: 563 WaitTime: 575 Complete

There are three main endings: ThisTime, TotalTime and WaitTime. Explain the meaning of these three endings respectively:

ThisTime: the last Activity takes time to start

TotalTime: all Activity starts up in time

Total time it takes for WaitTime:AMS to start Activity

The time of ThisTime and TotalTime are the same because there is no Splash interface in our Demo. After the application executes Application, it starts MainActivity directly. So the normal startup time should be like this: ThisTime

< TotalTime < WaitTime 这就是ADB方式统计的启动时间,细心的读者应该能想到了就是这种方式在线下使用很方便,但是却不能带到线上,而且这种统计的方式是非严谨、精确的时间。 手动打点方式: 手动打点方式就是启动时埋点,启动结束埋点,取二者差值即可。 我们首先需要定义一个统计时间的工具类: class LaunchRecord { companion object { private var sStart: Long = 0 fun startRecord() { sStart = System.currentTimeMillis() } fun endRecord() { endRecord("") } fun endRecord(postion: String) { val cost = System.currentTimeMillis() - sStart println("===$postion===$cost") } } } 启动时埋点我们直接在Application的attachBaseContext中进行打点。那么启动结束应该在哪里打点呢?这里存在一个误区:网上很多资料建议是在Activity的onWindowFocusChange中进行打点,但是onWindowFocusChange这个回调只是表示首帧开始绘制了,并不能表示用户已经看到页面数据了,我们既然做启动优化,那么就要切切实实的得出用户从点击应用图标到看到页面数据之间的时间差值。所以结束埋点建议是在页面数据展示出来进行埋点。比如页面是个列表那就是第一条数据显示出来,或者其他的任何view的展示。 class MyApplication : Application() { override fun attachBaseContext(base: Context?) { super.attachBaseContext(base) //开始打点 LaunchRecord.startRecord() } } 我们分别监听页面view的绘制完成时间和onWindowFocusChanged回调两个值进行对比。 class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) mTextView.viewTreeObserver.addOnDrawListener { LaunchRecord.endRecord("onDraw") } } override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) LaunchRecord.endRecord("onWindowFocusChanged") } } 打印的数据为: ===onWindowFocusChanged===322 ===onDraw===328 可以很明显看到onDraw所需要的时长是大于onWindowFocusChanged的时间的。因为我们这个只是简单的数据展示没有进行网络相关请求和复杂布局所以差别不大。 这里需要说明下:addOnDrawListener 需要大于API 16才可以使用,如果为了兼顾老版本用户可以使用addOnPre DrawListener来代替。 手动打点方式统计的启动时间比较精确而且可以带到线上使用,推荐这种方式。但在使用的时候要避开一个误区就是启动结束的埋点我们要采用Feed第一条数据展示出来来进行统计。同时addOnDrawListener要求API 16,这两点在使用的时候需要注意的。 优化工具的选择 在做启动优化的时候我们可以借助三方工具来更好的帮助我们理清各个阶段的方法或者线程、CPU的执行耗时等情况。主要介绍以下两个工具,我在这里就简单介绍下,读者朋友们可以线下自己取尝试下。 TraceView: TraceView是以图形的形式展示执行时间、调用栈等信息,信息比较全面,包含所有线程。 使用: 开始:Debug.startMethodTracing("name" ) 结束:Debug.stopMethodTracing("" ) 最后会生成一个文件在SD卡中,路径为:Andrid/data/packagename/files。 因为traceview收集的信息比较全面,所以会导致运行开销严重,整体APP的运行会变慢,这就有可能会带偏我们优化的方向,因为我们无法区分是不是traceview影响了启动时间。 SysTrace: Systrace是结合Android内核数据,生成HTML报告,从报告中我们可以看到各个线程的执行时间以及方法耗时和CPU执行时间等。API 18以上使用,推荐使用TraceCompat,因为这是兼容的API。 使用: 开始:TraceCompat.beginSection("tag ") 结束:TraceCompat.endSection() 然后执行脚本: python systrace.py -b 32768 -t 10 -a packagename -o outputfile.html sched gfx view wm am app 给大家解释下各个字端的含义: -b 收集数据的大小 -t 时间 -a 监听的应用包名 -o 生成文件的名称 Systrace开销较小,属于轻量级的工具,并且可以直观反映CPU的利用率。这里需要说明下在生成的报告中,当你看某个线程执行耗时时会看到两个字端分别好似walltime和cputime,这两个字端给大家解释下就是walltime是代码执行的时间,cputime是代码真正消耗cpu的执行时间,cputime才是我们优化的重点指标。这点很容易被大家忽视。 优雅获取方法耗时 上文中主要是讲解了如何监听整体的应用启动耗时,那么我们如何识别某个方法所执行的耗时呢? 我们常规的做法和上文中一样也是打点,如: public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); initFresco(); initBugly(); initWeex(); } private void initWeex(){ LaunchRecord.Companion.startRecord(); InitConfig config = new InitConfig.Builder().build(); WXSDKEngine.initialize(this, config); LaunchRecord.Companion.endRecord("initWeex"); } private void initFresco() { LaunchRecord.Companion.startRecord(); Fresco.initialize(this); LaunchRecord.Companion.endRecord("initFresco"); } private void initBugly() { LaunchRecord.Companion.startRecord(); CrashReport.initCrashReport(getApplicationContext(), "注册时申请的APPID", false); LaunchRecord.Companion.endRecord("initBugly"); } } 控制台打印: =====initFresco=====278 =====initBugly=====76 =====initWeex=====83 但是这种方式导致代码不够优雅,并且侵入性强而且工作量大,不利于后期维护和扩展。 下面我给大家介绍另外一种方式就是AOP。AOP是面向切面变成,针对同一类问题的统一处理,无侵入添加代码。 我们主要使用的是AspectJ框架,在使用之前呢给大家简单介绍下相关的API: Join Points 切面的地方:函数调用、执行,获取设置变量,类初始化 PointCut:带条件的JoinPoints Advice:Hook 要插入代码的位置。 Before:PointCut之前执行 After:PointCut之后执行 Around:PointCut之前之后分别执行 具体代码如下: @Aspect public class AOPJava { @Around("call(* com.optimize.performance.MyApp.**(..))") public void applicationFun(ProceedingJoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); String name = signature.toShortString(); long time = System.currentTimeMillis(); try { joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } Log.d("AOPJava", name + " == cost ==" + (System.currentTimeMillis() - time)); } } 控制台打印结果如下: MyApp.initFresco() == cost ==288 MyApp.initBugly() == cost ==76 MyApp.initWeex() == cost ==85 但是我们没有在MyApp中做任何改动,所以采用AOP的方式来统计方法耗时更加方便并且代码无侵入性。具体AspectJ的使用学习后续文章来介绍。 异步优化 上文中我们主要是讲解了一些耗时统计的方法策略,下面我们就来具体看下如何进行启动耗时的优化。 在启动分类中我们讲过应用启动任务中有一个空白window,这是可以作为优化的一个小技巧就是Theme的切换,使用一个背景图设置给Activity,当Activity打开后再将主题设置回来,这样会让用户感觉很快。但其实从技术角度讲这种优化并没有效果,只是感官上的快。 首先现在res/drawable中新建lanucher.xml文件: 将其设置给第一个打开的Activity,如MainActivity: 最后在MainActivity中的onCreate的spuer.onCreate()中将其设置会原来的主题: override fun onCreate(savedInstanceState: Bundle?) { setTheme(R.style.AppTheme) super.onCreate(savedInstanceState) } } 这样就完成了Theme主题的切换。 下面我们说下异步优化,异步优化顾名思义就是采用异步的方式进行任务的初始化。新建子线程(线程池)分担主线称任务并发的时间,充分利用CPU。 如果使用线程池那么设置多少个线程合适呢?这里我们参考了AsyncTask源码中的设计,获取可用CPU的数量,并且根据这个数量计算一个合理的数值。 private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); @Override public void onCreate() { super.onCreate(); ExecutorService pool = Executors.newFixedThreadPool(CORE_POOL_SIZE); pool.submit(new Runnable() { @Override public void run() { initFresco(); } }); pool.submit(new Runnable() { @Override public void run() { initBugly(); } }); pool.submit(new Runnable() { @Override public void run() { initWeex(); } }); } 这样我们就将所有的任务进行异步初始化了。我们看下未异步的时间和异步的对比: 未异步时间:======210 异步的时间:======3 可以看出这个时间差还是比较明显的。这里还有另外一个问题就是,比如异步初始化Fresco,但是在MainActivity一加载就要使用而Fresco是异步加载的有可能这时候还没有加载完成,这样就会抛异常了,怎么办呢?这里教大家一个新的技巧就是使用CountDownLatch,如: private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); //1表示要被满足一次countDown private CountDownLatch mCountDownLatch = new CountDownLatch(1); @Override public void onCreate() { super.onCreate(); ExecutorService pool = Executors.newFixedThreadPool(CORE_POOL_SIZE); pool.submit(new Runnable() { @Override public void run() { initFresco(); //调用一次countDown mCountDownLatch.countDown(); } }); pool.submit(new Runnable() { @Override public void run() { initBugly(); } }); pool.submit(new Runnable() { @Override public void run() { initWeex(); } }); try { //如果await之前没有调用countDown那么就会一直阻塞在这里 mCountDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } 这样就会一直阻塞在await这里,直到Fresco初始化完成。 以上这种方式大家觉得如何呢?可以解决异步问题,但是我的Demo中只有三个需要初始化的任务,在我们真实的项目中可不止,所以在项目中我们需要书写很多的子线程代码,这样显然是不够优雅的。部分代码需要在初始化的时候就要完成,虽然可以使用countDowmLatch,但是任务较多的话,也是比较麻烦的,另外就是如果任务之间存在依赖关系,这种使用异步就很难处理了。 针对上面这些问题,我给大家介绍一种新的异步方式就是启动器。核心思想就是充分利用CPU多核,自动梳理任务顺序。核心流程: 任务代码Task化,启动逻辑抽象为Task 根据所有任务依赖关系排序生成一个有向无环图 多线程按照排序后的优先级依次执行 TaskDispatcher.init(PerformanceApp.)TaskDispatcher dispatcher = TaskDispatcher.createInstance()dispatcher.addTask(InitWeexTask()) .addTask(InitBuglyTask()) .addTask(InitFrescoTask()) .start()dispatcher.await()LaunchTimer.endRecord() 最后代码会变成这样,具体的实现有向无环图逻辑因为代码量很多,不方便贴出来,大家可以关注公众号获取。 使用有向无环图可以很好的梳理出每个任务的执行逻辑,以及它们之间的依赖关系 延迟初始化 关于延迟初始化方案这里介绍两者方式,一种是比较常规的做法,另外一个是利用IdleHandler来实现。 常规做法就是在Feed显示完第一条数据后进行异步任务的初始化。比如: override fun onCreate(savedInstanceState: Bundle?) { setTheme(R.style.AppTheme) super.onCreate(savedInstanceState) mTextView.viewTreeObserver.addOnDrawListener { // initTask() } } 这里有个问题就是更新UI是在Main线程执行的,所以做初始化任务等耗时操作时会发生UI的卡顿,这时我们可以使用Handler.postDelay(),但是delay多久呢?这个时间是不好控制的。所以这种常规的延迟初始化方案有可能会导致页面的卡顿,并且延迟加载的时机不好控制。 IdleHandler方式就是利用其特性,只有CPU空闲的时候才会执行相关任务,并且我们可以分批进行任务初始化,可以有效缓解界面的卡顿。代码如下: public class DelayInitDispatcher { private Queue mDelayTasks = new LinkedList(); private MessageQueue.IdleHandler mIdleHandler = new MessageQueue.IdleHandler() { @Override public boolean queueIdle() { if (mDelayTasks.size() >

0) {Task task = mDelayTasks.poll (); new DispatchRunnable (task). Run ();} return! mDelayTasks.isEmpty ();}}; public DelayInitDispatcher addTask (Task task) {mDelayTasks.add (task); return this } public void start () {Looper.myQueue () .addIdleHandler (mIdleHandler);}}

We call after the interface is displayed:

Override fun onCreate (savedInstanceState: Bundle?) {setTheme (R.style.AppTheme) super.onCreate (savedInstanceState) mTextView.viewTreeObserver.addOnDrawListener {val delayInitDispatcher = DelayInitDispatcher () delayInitDispatcher.addTask (DelayInitTaskA ()) .addTask (DelayInitTaskB ()) .start ()}}

In this way, you can use the idle time of the system to delay the initialization task.

Lazy loading

Lazy loading means that some Task will be used only on specific pages, so there is no need to initialize these Task in Application, we can initialize them after entering the page.

Other options

Loading SharedPreferences ahead of time, when the sp of our project is very large, the initial load is very memory-consuming and time-consuming, so we can initialize it before initializing the Multidex (if we use it) to make full use of the CPU at this stage.

The startup phase does not start the child process, the child process will share CPU resources, resulting in the tension of the main CPU resources, another point is not to start other components such as service and contentProvider in the Application life cycle.

Asynchronous class loading mode, how to determine which classes need to be loaded asynchronously in advance? Here we can customize the classload, replace the system's classload, print the log in our classload, each class will trigger the log log when loading, and then run it in the project, so we have all the classes that need to be loaded, these are the classes that need to be loaded asynchronously.

Class.forName () loads only the reference classes of the class itself and its static variables

New instances can load additional reference classes for class members

This is the end of the content of "what is the method of Android startup optimization". Thank you for your reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report