Android性能优化之启动速度优化

Posted 失落夏天

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android性能优化之启动速度优化相关的知识,希望对你有一定的参考价值。

前言:

文本主要会介绍三大块:

1.简略介绍APP启动的完整流程,对整个流程有所了解,才知道在哪里可以进行优化。

2.一些常用的APP启动优化的方案,主要分为三大块优化方向。

3.一些不常见的APP启动优化的方案,甚至包含一些FW层的代码改动,有的可能是对应用开发者无效的,但是对于车载开发是有用的。

一安卓APP启动完整流程分析(冷启动)

图1:

主要分为三个阶段:

1.1 桌面点击APP图标,通知到AMS去完成应用进程的创建的流程

1.安卓系统启动后,拉起的第一个应用是桌面应用。它由SystemServer负责创建,并持有AMS的binder引用。

2.点击桌面图标后,Launcher会通过binder通知AMS启动该APP。

3.AMS会根据传递过来的信息查询APP应用进程在后台是否存在。如果在后台,则属于热启动,如果不在后台,则属于冷启动。优化的重点一般都是冷启动。

4.如果APP进程不存在。AMS主要会做两件事:

一:AMS首先会读取对应APP的Manifest信息(此配置信息是存在于AMS中,手机启动或者应用安装时读取到内存中的),然后根据MainActivity的主题设置,读取其背景图并展示到屏幕上。

二:通过socket的方式通知Zygote去fork产生APP进程。

如图2所示:

1.2 应用进程创建后的流程

5.APP进程创建后,会通过执行ActivityThread中的main方法。此方法主要做了两件事,

第一会进行Looper的初始化;

第二会通过attach方法通知AMS,进行进程的绑定,此时也会把APP创建的binder传递给AMS。

6.AMS中完成注册绑定后,会通过binder通知APP进行application的绑定。APP端binder的接收者是ApplicationThread(下同)。ApplicationThread会被调用bindApplication方法,然后通过handler通知ActivityThread去调用handleBindApplication方法。

7.handleBindApplication方法负责应用初始化的所有流程。主要流程图如下图所示:

图3:

首先,在方法中,通过ContextImpl.createAppContext(this, data.info)去加载DEX文件以及资源。

具体加载DEX的方法在LoadedApk的createOrUpdateClassLoaderLocked方法中:

private void createOrUpdateClassLoaderLocked(List<String> addedPaths) 
        ...

        //生成Classloader
            mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoaderWithSharedLibraries(
                    zip, mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
                    libraryPermittedPath, mBaseClassLoader,
                    mApplicationInfo.classLoaderName, sharedLibraries, nativeSharedLibraries);
            mAppComponentFactory = createAppFactory(mApplicationInfo, mDefaultClassLoader);

        ...
        //把上面生成的mDefaultClassLoader赋值给mClassLoader
        if (mClassLoader == null) 
            mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader,
                    new ApplicationInfo(mApplicationInfo));
        
    

第二,去执行Application的的attachBaseContext方法。

第三,执行installProvider方法,加载App中的各个ContentProvider

第四,执行Application的onCreate()方法

1.3 Activity的首屏展示流程

8.AMS通知APP创建Application后,还会通知APP进程去启动activity。这时候ApplicationThread接收到之后,会通知ActivityThread完成Activity的启动流程。这个根据android版本不同有区别,android12之后传递的是ClientTransaction。该对象包含一系列事务,对应的会通知构建activity的各个流程。

9.ActivityThread中会分别执行handleLaunchActivity,handleStartActivity,handleResumeActivity等方法,对应的会执行Activity的onCreate,onStart,onResume方法。

10.在handleResumeActivity方法中,执行完resume方法后,判断如何未关联到window上,则会把DecorView加到到ViewManager上。

public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
            boolean isForward, String reason) 
        ...
        if (!performResumeActivity(r, finalStateRequest, reason)) 
            return;
        
        ...
        if (r.window == null && !a.mFinished && willBeVisible) 
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) 
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                // Normally the ViewRoot sets up callbacks with the Activity
                // in addView->ViewRootImpl#setView. If we are instead reusing
                // the decor view we have to notify the view root that the
                // callbacks may have changed.
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) 
                    impl.notifyChildRebuilt();
                
            
            if (a.mVisibleFromClient) 
                if (!a.mWindowAdded) 
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                 else 
                    // The activity will get a callback for this @link LayoutParams change
                    // earlier. However, at that time the decor will not be set (this is set
                    // in this method), so no action will be taken. This call ensures the
                    // callback occurs with the decor set.
                    a.onWindowAttributesChanged(l);
                
            

            // If the window has already been added, but during resume
            // we started another activity, then don't yet make the
            // window visible.
         else if (!willBeVisible) 
            if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
            r.hideForNow = true;
        

        ...
    

11.ViewManager的最终实现类是WindowManagerGlobal,在addView(DecorView)时,会创建ViewRootImpl负责后续绘制的完整流程。

12.添加完成后。ViewRootImpl会生成渲染任务TraversalRunnable,在收到垂直信号量之后执行。任务执行完成后,则首屏就显示在屏幕上了。

二.如何排查启动卡顿问题

2.1 使用SystemTrace的方式进行分析

2.1.1 如何得到冷启动总耗时的准确数据

首先的确保杀死应用,adb shell pkill beanlab (包名匹配到beanlab就杀掉)

然后使用 adb shell am start -W [包名]/[包名.Activity] 。

启动APP,查询App的启动时间

查询结果中,对应的时间参数详细解析如下:

ThisTime:对应activity启动耗时;

TotalTime:应用自身启动耗时 = ThisTime + 应用application等资源启动时间

WaitTime:系统启动应用耗时 = TotalTime + 系统资源启动时间

2.1.2 如何观察冷启动耗时的各个时间段的准确数据

通过抓取systrace可以从图上观察到 bindApplication ->activityStart->activityResume->Choreographer#doFrame

也可以添加自己的方法抓取,注意开始和结束必须成对出现

Trace.beginSection("你的方法");

Trace.endSection();

2.1.3 如何得到冷启动结束的回调

首先是display time:从Android KitKat版本开始,Logcat中会输出从程序

启动到某个Activity显示到画面上所花费的时间。这个方法比较适合测量程序的启动

时间。

筛选AcctivityManager: Displayed

ActivityManager: Displayed com.beantechs.beanlab/.ui.HomeActivity: +842ms 这个时间 和 adb启动分析的 TotalTime 一致

2.2 使用matrix框架

三.如何进行启动优化

第一章时,我们了解了一个APP启动的完整流程。对这个流程分析一下,我们可以住要分成以下三块:

1.从用户点击图标,到通知系统去创建APP进程。

2.APP进程创建后,通知AMS并且进行绑定并走Application的所有流程。

3.启动Activity的流程。

所以如何进行启动优化,也主要按照这三块分类去讲解。

3.1 优化APP进程创建之前的卡顿问题

这一块由于主要运行在系统层面,所以我们可优化的点不多。虽然我们不能彻底解决,但是还是可以一定程度上优化用户的体验。

3.1.1 通过预制图进行体验感觉上的优化。

第一章的时候我们讲过,AMS会根据传递过来的信息,会在启动APP进程之前,加载一张APP的背景图。所以我们可以通过提前加载预制图,让用户感官上知道我们APP已经启动。目前市场上大的APP都有设置预制图,比如支付宝的预制图就是以下这张:

配置方法如下:

在Manifest中,给Main的Activity设置theme即可。


<activity
    android:name=".SplashActivity"
    android:theme="@style/LoadingAppTheme"
    tools:replace="android:label">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

<style name="LoadingAppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <item name="windowNoTitle">true</item>
    <item name="windowActionBar">false</item>
    <item name="android:windowContentOverlay">@null</item>
    <item name="android:listDivider">@drawable/divider_line</item>
    <item name="android:windowFullscreen">true</item>
    <item name="android:windowBackground">@drawable/launcher_previewwindow</item>
</style>

android12及以上的版本,支持黑白屏动画形式的展示,具体的使用方式这里就不展开了,原理是一样的。

将现有的启动画面实现迁移到 Android 12 及更高版本 

3.2 优化应用初始化耗时问题

应用初始化的优化,主要其实就是就是对bindApplication这个方法的优化,通过打点日志分析,我们发现其实主要有以下几个耗时点,也是对应的我们的优化点。

3.2.1 优化APK加载

第一章时我们知道,加载DEX是在ContextImpl.createAppContext()的时候,自然也是启动主流程上。加载DEX的流程是从APK包中解压,然后ODEX优化,最后加载到内存当中。自然的,如果DEX越少,那么解压的就越少,ODEX优化的越少,速度也就越快。

所以我们优化的主要方法是拆分APP,也是通过组件化,插件化的方式去进行加载。

系统需要在启动的时候读取哪些内容呢?主要有两个部分,dex文件和资源文件。所以我们可以把一个很大的APK,按照业务拆分成多个小的APK。其中主APK中的Dex文件和资源弄的很小,其余业务APK放到asset文件夹中。等到APP启动后,再去解压加载业务APK。因为主APK很小,所以启动速度自然就会快得多。而等到应用启动后,再去通过懒加载的方式,逐渐加载其它模块的业务APK,因为在后台加载,所以也不会影响用户正常的操作,这就是我们组件化启动优化的方案。

结合实际项目,大多数APP其实都不大,所以并不太需要通过这种模块化的方案进行优化,所以就不详细讲如何优化了。有兴趣的可以看一下ShadowRePlugin等框架。

3.2.2 ContentProvider优化

通过第一章的图3我们可以知道,在Application的启动流程中,会依次执行attachBaseContext,ContentProvider的onCreate,Application的onCreate方法。

所以ContentProvider的onCreate方法中,一定不能有耗时操作,否则会拖慢运行速度。

举例:

public class XXXProvider
    ...
    @Override
    public boolean onCreate() 
        //context init
        ...
        //db init
        dbUtil = DatabaseUtil.getInstance();
        dbUtil.open();
        return true;
    

    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable                     String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) 
        return dbUtil.fetchAll();
    

    ...
我们可以知道,加载数据库是耗时操作,则我们应该挪到其他步骤当中。

解决问题:

ContentProvider的onCreate()是启动流程当中的,所以我们可以把加载数据库的耗时操作放到query中,或者等到启动完成后延时加载。

public class XXXProvider
    ...
    @Override
    public boolean onCreate() 
        return true;
    

    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable                     String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) 
        if(dbUtil==null)
            dbUtil = DatabaseUtil.getInstance();
            dbUtil.open();
        
        return dbUtil.fetchAll();
    

    ...

3.2.3 Application中onCreate优化

进行onCreate优化,核心是要梳理onCreate中我们到底做了什么。通过梳理可以发现,onCreate方法中,我们经常要初始化各种框架,比如Bugly.init(),LeakCanary.init()等等,这些框架虽然每个耗时并不多,但是因为是串行执行,累加起来,总耗时反而不少。

举例:

这里以某款APP为例,我们先查看其systrace文件(该文件在5.2当中)

其中bindApplication方法中有一部分如下图所示:

图中显示有加载多个不同SDK类的操作,而且是串行的结构,所以我们可以推断,应用在bindApplication的时候做了太多的初始化操作并且并行执行,导致耗时较多,而且bindApplication对应的是我们项目中Application的onCreate方法。

解决问题:

所以我们可以进行以下几点的优化:

1.部分任务不要主线程执行的,可以挪到子线程执行。

2.有些任务在子线程执行,但是依赖主线程某个任务执行完才可以。这种我们可以等到对应主线任务执行完再把任务加入到子线程池中。

当然,如果相互依赖的逻辑复杂,上面的方式就不太合适了,我们可以使用一个已经封装好的任务拓扑依赖框架来解决这问题:android-startup

使用简介:

首先build.gradle中添加依赖:

implementation 'io.github.idisfkj:android-startup:1.1.0'

创建待执行的启动任务类:

class SampleFirstStartup : AndroidStartup<String>() 

     //是否主线程执行
    override fun callCreateOnMainThread(): Boolean = true

    //是否依赖主线程任务
    override fun waitOnMainThread(): Boolean = false

    //执行的初始化操作
    override fun create(context: Context): String? 
        // todo something
        return this.javaClass.simpleName
    

    //依赖哪些其他任务
    override fun dependenciesByName(): List<String>? 
        return null
    

然后我们在启动的时候,把这些StartUp组装一下就可以了。

class SampleApplication : Application() 

    override fun onCreate() 
        super.onCreate()
        StartupManager.Builder()
            .addStartup(SampleFirstStartup())
            .addStartup(SampleSecondStartup())
            .addStartup(SampleThirdStartup())
            .addStartup(SampleFourthStartup())
            .build(this)
            .start()
            .await()
    

或者在manifest中注册也可以

<provider
    android:name="com.rousetime.android_startup.provider.StartupProvider"
    android:authorities="$applicationId.android_startup"
    android:exported="false">

    <meta-data
        android:name="com.rousetime.sample.startup.SampleFourthStartup"
        android:value="android.startup" />

</provider>

3.2.4 其它初始化时的耗时操作

1.SharedPreferences涉及到IO操作,所以如果SP存储数据较大的话,阻塞时间会较长

 SharedPreferences sp = getSharedPreferences("1", 0);
 sp.getString("1", "1");

3.3 解决Activity加载问题

3.3.1:主线程不执行耗时操作

通过第一章的流程讲解,我们可以知道在第一帧绘制之前,主线程会执行onCreate,onStart,onResume三个方法,所以这三个方法中,一定不能有耗时的方法。

如果一定需要主线程执行的,可以使用IdelHandler的方式解决(IdelHandler会在主线程不忙时执行)。

比如我们在加载数据的时候,就可以通过IdelHandler的方式来代替:

实例如下:

原代码:

@Override
protected void onResume() 
    super.onResume();
    ...
    loadData();
    ...


private void loadData() 
    ...
    组装请求..
    发送请求...
    解析数据...

改成:

@Override
protected void onResume() 
    super.onResume();
    ...
    MessageQueue.IdleHandler idleHandler = () -> 
        loadData();
        return false;
    ;
    getMainLooper().getQueue().addIdleHandler(idleHandler);
   
    ...


private void loadData() 
    ...
    组装请求...
    发送请求...
    解析数据...

3.3.2:预加载页面

我们通过第二章的工具可以发现,Activity的几个生命周期中,onCreate方法一般是最为耗时的,而onCreate方法中,最为耗时的一般是setContentView(int)方法。

通过深入阅读源码可以可以发现,这其中最为耗时的部分就是把复杂的xml转化为ViewGroup对象。

这种问题,我们可以通过下面的方案来解决:

1. 使用AsyncLayoutInflater。

2.我可以做一个这样的操作,在Application的onCreate方法的任务中,添加一个这样的任务,子线程中把xml转化为ViewGroup。这样执行到Activity的onCreate方法中时,我们可以直接使用ViewGroup对象,从而节省了XML解析的时间。(如果布局文件中含有fragment不能采用此方案)

3.转Compose。Compose的话没有解析XML的时间,也不受到过多布局层级的影响。

3.3.3:局部加载优先显示框架或者占位图

还是通过第二章的工具,我们发现在项目中,measure/layout/draw也是很耗时的(其中一半measure是最耗时的)。这是因为我们布局太复杂了,导致界面渲染的时候需要反复计算,从而耗费时间。

所以针对这种情况,我们可以主要有4个方法来解决:

1.非主框架的部分使用ViewStub加载,等到主框架加载并显示出来后,再去加载内容的部分。我们经过测算,在第四帧之后,框架是可以完全显示出来的,所以在第四帧之后进行ViewStub内容的加载,是最为合适的。

2.降低布局层次嵌套和复杂度。复杂布局使用约束布局,尽量少使用weight属性等等。

3.解决过度绘制问题

4.优先加载占位图,等到内容显示好之后,在把占位图隐藏掉。

3.3.4:预加载数据

页面加载好了之后,自然就是请求网络加载数据了。

通过网络加载数据,大多数都是通过OKHttp进行请求。如果想更快的展示出来并且不在乎有效性,那么可以开启使用缓存,并且设置缓存有效时间。

但是这样也有个问题,如果设置了有效期,那么有效期内该请求全部都使用缓存。此时就无法获取最新的数据了,哪怕非首次请求也不行,因为服务并不会为你单独开辟一个新的接口。

我们可以实现这样的一个小需求:“首次请求使用缓存,非首次请求不使用缓存,并且还能把收到的响应更新到首次请求的缓存中”,这样就能比较好的解决上面所说的问题了。

具体例子可以参考我的另外一篇文章中的6.2。OKHttp原理讲解之责任链模式及扩展_失落夏天的博客-CSDN博客_android okhttp责任链

3.4 其它优化

除了针对我们自身代码的优化,还有什么别的优化空间吗?当然有

3.4.1 Baseline Profile

这是google2022年开发者大会新提出的方案。

其核心原理是安卓7.0以后Android支持JIT,AOT并存的混合编译模式。

两者各有优势,JIT即时编译,虽然运行速度慢,但不需要编译时间。而AOT需要编译,后续运行速度快。

一般情况下,首次启动的时候会使用JIT编译,因为AOT需要转换,会导致首次启动耗时。后续使用的时候,安卓系统会根据使用频率计算出那些高频使用的代码,转换为AOT的方式进行编译加载,保证后续这块代码的运行速度。

而Baseline Profile就是需要我们自己把这些高频使用代码,提前打包到APK中,这样首次启动的时候,安卓会通过AOT转换为ODEX代码,以后使用这些可执行文件,速度上就会更快。

3.4.2 Hardcoder

这是腾讯开源的一个框架,核心原理是在需要手机性能时,主动通知系统去提升CPU频率,从而提升手机性能。而不需要性能时,则通知系统降频,避免手机电量的浪费。

https://github.com/Tencent/Hardcoder

3.4.3 Embryo方案

这是一加手机的一个方案,简单解释下就是在后台预创建一个进程,提前加载好资源。这样等到这个APP真的启动的时候,就可以直接使用,而不是重新创建了。

这里其实我有一个更简单有效的方案,我们知道,APP冷启动的时候,是System_server通知Zygote去fork应用进程的,应用进程创建后,再回掉通知System_server进程。至少在这段时间内,是没有绑定任何应用层信息的,也就是下图红框中的部分。

 所以,就像APP中的预加载一样,我们为什么不能在系统层预加载一个APP进程呢?等到真的有APP应用创建需求的时候,直接去使用这个APP进程,而不是走创建流程创建一个。因为走的是socket通信,以及fork进程需要时间,所以整个流程有可能可以节省多达100ms的流程。这个方案还在调研探索中,目前理论上是可行的。

3.4.4 Redex方案

这个是facebook提出的一个方案,其实核心本质和proguard有一些类似,混淆字节码,让加载的DEX文件变的更小,则加载变得就更快。redex还有一个突出的亮点就是除了混淆之外,还能做一定的字节码层面的优化,比如A调用B,B调用C的场景,直接改成A调用C,这样减少了一层方法栈,执行速度肯定是更快的。

当然,这个的目标就不单纯的只是解决启动速度了,而是让APP运行的更快乐。

但是这个方法已经已经推出了很久了,到目前为止并不是很流行,其原因也是这样的操作容易引起各种各样的问题。

四.声明和备注

4.1 声明

文章中的原理和资料来自于网上搜索的资料,以及针对安卓源码的调试所得。基于的安卓版本是12。如有描述不准确的地方,或者好的建议,欢迎指出来。

Android性能优化系列之App启动优化

Android性能优化系列之布局优化

Android性能优化系列之内存优化

Android性能优化系列之apk瘦身

应用的启动速度缓慢是我们在开发过程中常常会遇到的问题,比方启动缓慢导致的黑屏。白屏问题,本篇博客就将介绍App启动优化的相关知识。

应用的启动方式

通常来说,启动方式分为两种:冷启动和热启动。

1、冷启动:当启动应用时。后台没有该应用的进程。这时系统会又一次创建一个新的进程分配给该应用。这个启动方式就是冷启动。

冷启动由于系统会又一次创建一个新的进程分配给它。所以会先创建和初始化Application类,再创建和初始化MainActivity类(包含一系列的測量、布局、绘制),最后显示在界面上。

2、热启动:当启动应用时,后台已有该应用的进程(例:按back键、home键,应用尽管会退出,可是该应用的进程是依旧会保留在后台,可进入任务列表查看)。所以在已有进程的情况下,这样的启动会从已有的进程中来启动应用,这个方式叫热启动。热启动由于会从已有的进程中来启动,所以热启动就不会走Application这步了。而是直接走MainActivity(包含一系列的測量、布局、绘制),所以热启动的过程仅仅须要创建和初始化一个MainActivity即可了,而不必创建和初始化Application,由于一个应用从新进程的创建到进程的销毁,Application仅仅会初始化一次。

App的启动过程

本文所指的优化针对冷启动。简单解释一下App的启动过程:

1.点击Launcher。启动程序,通知ActivityManagerService

2.ActivityManagerService通知zygote进程孵化出应用进程,分配内存空间等

3.运行该应用ActivityThread的main()方法

4.应用程序通知ActivityManagerService它已经启动,ActivityManagerService保存一个该应用的代理对象,ActivityManagerService通过它能够控制应用进程

5.ActivityManagerService通知应用进程创建入口的Activity实例,运行它的生命周期

启动过程中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()

技术分享

什么才是应用的启动时间

从点击应用的启动图标開始创建出一个新的进程直到我们看到了界面的第一帧。这段时间就是应用的启动时间。

我们要測量的也就是这段时间,測量这段时间能够通过adb shell命令的方式进行測量,这样的方法測量的最为精确,命令为:

adb shell am start -W [PackageName]/[PackageName.MainActivity]

1、ThisTime:一般和TotalTime时间一样。除非在应用启动时开了一个透明的Activity预先处理一些事再显示出主Activity,这样将比TotalTime小。
2、TotalTime:应用的启动时间,包含创建进程+Application初始化+Activity初始化到界面显示。


3、WaitTime:一般比TotalTime大点,包含系统影响的耗时。

利用TraceView分析启动时间

在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
Cpu Time / Call反映调用次数不多,但每次调用却须要花费非常长时间的函数
Calls + Recur Calls / Total反映自身占用时间不长,但调用却非常频繁的函数

怎样降低应用启动时的耗时

针对冷启动时候的一些耗时。能够採取下面策略:

1、在Application的构造器方法、attachBaseContext()、onCreate()方法中不要进行耗时操作的初始化,一些数据预取放在异步线程中,能够採取Callable实现。
2、对于sp的初始化,由于sp的特性在初始化时候会对数据所有读出来存在内存中,所以这个初始化放在主线程中不合适,反而会延迟应用的启动速度。对于这个还是须要放在异步线程中处理。


3、对于MainActivity,由于在获取到第一帧前。须要对contentView进行測量布局绘制操作,尽量降低布局的层次。考虑StubView的延迟载入策略,当然在onCreate、onStart、onResume方法中避免做耗时操作。

遵循上面三种策略可明显提高app启动速度。

优化应用启动时的体验

对于应用的启动时间,仅仅能是尽量的避免一些耗时的、非必要的操作在主线程中。这样相对能够缩减一部分启动的耗时。另外一方面在等待第一帧显示的时间里,能够增加一些配置以增加体验,比方增加Activity的background,这个背景会在显示第一帧前提前显示在界面上。 对于应用的启动时间,仅仅能是尽量的避免一些耗时的、非必要的操作在主线程中,这样相对能够缩减一部分启动的耗时,另外一方面在等待第一帧显示的时间里,能够增加一些配置以增加体验。比方增加Activity的background,这个背景会在显示第一帧前提前显示在界面上。

方案1:

1、先为主界面单独写一个主题style,设置一张待显示的图片,这里我设置了一个颜色,然后在manifest中设置给MainActivity:

<style name="AppTheme.Launcher">
 <item name="android:windowBackground">@drawable/bule</item>
</style>
//...
  <activity
   android:name=".MainActivity"
   android:label="@string/app_name"
   android:theme="@style/AppTheme.Launcher">
   <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
   </intent-filter>
  </activity>

2、然后在MainActivity中载入布局前把AppTheme又一次设置给MainActivity:

@Override
 protected void onCreate(Bundle savedInstanceState) {

  setTheme(R.style.AppTheme);
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
} 

这样在启动时会先显示background,然后待界面绘制完毕再显示主界面:
技术分享

方案2:通过设置Style
(1)设置背景图Theme
通过设置一张背景图。

当程序启动时。首先显示这张背景图。避免出现黑屏

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="android:screenOrientation">portrait</item>
        <item name="android:windowBackground">>@mipmap/splash</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowNoTitle">true</item>
</style>

(2)设置透明Theme
通过把样式设置为透明,程序启动后不会黑屏而是整个透明了,等到界面初始化完才一次性显示出来


    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:screenOrientation">portrait</item>
    </style>

两者对照:
Theme1 程序启动快。界面先显示背景图,然后再刷新其它界面控件。给人刷新不同步感觉。


Theme2 给人程序启动慢感觉。界面一次性刷出来。刷新同步。


(3)改动AndroidManifest.xml

 <application
        android:name=".App"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true">
        <activity android:name=".MainActivity"
         android:theme="@style/AppTheme">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    //......

</application>

參考文献:
http://www.jianshu.com/p/a0e242d57360

















以上是关于Android性能优化之启动速度优化的主要内容,如果未能解决你的问题,请参考以下文章

Android性能优化之启动速度优化

Android性能优化系列之App启动优化

Android性能优化之加快应用启动速度

Android性能优化之加快应用启动速度

Android性能优化之启动加速35%

Android性能优化之Android 10+ dex2oat实践