App 启动性能优化

关于应用启动优化,这算是个老生常谈的话题了,相信大家对于一些基本概念应该是有一定认识了,比如启动方式的区分冷启动、热启动之类的,这篇文章主要是通过一个实例来分析各个环节需要处理的问题。下面就是直接上手环节。

明确概念

命令行: adb shell am start -W [PACKAGE-NAME]/[ACTIVITY-NAME]

有没有想过,我们启动一个只有 Empty Activity 的 App 是怎么样的,没有对比就没有伤害,那我们直接试试

冷启动
热启动

场景创建

为了说明问题,我们先来模拟一个实际开发中常见的项目环境。通常我们项目中会接入一些第三方库来支撑起应用的基础服务,有些三方库会要求在应用启动时就进行初始化。又比如应用可能会有一些日志上报和事件统计的数据要在启动时发,还有像启动时读取本地配置,页面需要预加载资源内容等等。这些呢,都是现实业务场景中经常会遇到的一些内容,如果开发只考虑业务上的实现而不考虑性能问题,那么就很有可能会出现一些不太好的用户体验。比如说用户启动应用后要长时间等待,或着页面交互时卡顿等等,这些问题在一些旧设备或低端机上面会更明显。所以说找台性能差的设备多体验下还是很有必要的,这样可以更直观的暴露问题。

为什么出现白屏
冷启动白屏持续时间可能会很长,这可是个槽糕的体验,它的启动速度是由于以下引起的:
1、Application的onCreate流程,对于大型的APP来说,通常会在这里做大量的通用组件的初始化操作;
建议:很多第三方SDK都放在Application初始化,我们可以放到用到的地方才进行初始化操作。

2、Activity的onCreate流程,特别是UI的布局与渲染操作,如果布局过于复杂很可能导致严重的启动性能问题;
建议:Activity仅初始化那些立即需要的对象,xml布局减少冗余或嵌套布局。

优化APP启动速度意义重大,启动时间过长,可能会使用户直接卸载APP。

启动耗时分析

分析启动,除了上面提到的运用 ADB 命令来看,可以通过借助 Android Studio 中的工具来分析,这里推荐结合我前面一篇文章中提到的 Android 应用性能分析工具 — CPU Profiler
来使用吧,这里不作展开了。下面直接来看看实际优化的措施。

实际优化措施

1. 设置启动页面

设置一个 Activity 作为启动页是否有必要,这要根据自身实际业务考量吧,但是提升App 的启动速度肯定是有一定帮助的,因为通常应用的Home 页肯定会复杂很多。减少启动渲染耗时,从页面交互上考虑。

public class SplashActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 不设置 ContentView 可节约布局加载时间,使用主题的背景图片即可

        getWindow().getDecorView().postDelayed(new Runnable() {
            @Override
            public void run() {
                startActivity(MainActivity.class);
                       finish();
            }
        }, 1000);
    }
}

2.设置启动动画

从用户点击桌面图标到应用呈现渲染,这个过程如果只是干等着,会显得应用是在点击以后没有反应,所以及时响应用户这个点击动作就显得非常有必要了。

<activity android:name=".SplashActivity"
                android:theme="@style/AppTheme.Launcher"
                android:launchMode="singleTask"
                android:screenOrientation="portrait">
          <intent-filter>
              <action android:name="android.intent.action.MAIN"/>

              <category android:name="android.intent.category.LAUNCHER"/>
          </intent-filter>
</activity>
<activity
        android:name=".GameActivity"
        android:configChanges="keyboard|locale|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize"
        android:hardwareAccelerated="true"
        android:launchMode="singleTask"
        android:screenOrientation="portrait"
        android:theme="@style/AppTheme"
        android:windowSoftInputMode="stateHidden|adjustResize"/>
 <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="android:screenOrientation">portrait</item>
        <item name="android:windowEnableSplitTouch">false</item>
        <item name="android:splitMotionEvents">false</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowBackground">@null</item>
        <item name="android:windowAnimationStyle">@style/Animation</item>
        <item name="windowNoTitle">true</item>
    </style>

    <style name="AppTheme.Launcher"  parent="AppTheme">
        <item name="android:windowBackground">@drawable/launch_screens</item>
    </style>

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- 顶层启动图 -->
    <item>
        <bitmap
                android:src="@drawable/splash_bg" />
    </item>

</layer-list>

效果

图片发自简书App

3. 页面数据懒加载

关于懒加载的概念相信很多人都不会陌生,我们常常会在实际开发中接触到。这里的页面数据懒加载,其实就是为了减少用户在视觉上的等待时间,对于数据填充可以延后进行,减少用户页面切换时的等待。

 // 懒加载
public class MainActivity extends Activity {
    ....
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ....
        // 页面启动时懒加载
        this.getWindow().getDecorView().post(new Runnable() {
            @Override
            public void run() {
                mHandler.post(mLoadingRunnable);
            }
        });
   }
}
 private Runnable mLoadingRunnable = new Runnable() {
        @Override
        public void run() {
              // 数据填充,懒加载
        }
 };

注意:如果配置上面的 Style 后发现点击桌面图标没有理解展示启动图,很有可能是配置了android:windowIsTranslucent属性导致的:

<item name="android:windowIsTranslucent">true</item>

Application 启动异步初始化第三方库

应用中很多三方库会要求在启动时初始化,并且建议在 Application 的 onCreate 中进行初始化方法调用,这就有可能导致了启动时方法处理任务过重,影响启动速度。

一般建议把三方库进行分类,考虑优先级,把除了把部分支持库的初始化延后到实际需要时才进行,也可在 Application 中执行异步加载的方式。

下面就举例通过 IntentService 的方式来进行三方库的集中异步初始化。


/**
 * An {@link IntentService} subclass for handling asynchronous task requests in
 * a service on a separate handler thread.
 * helper methods.
 * @author baishixian
 */
public class InitializeService extends IntentService {
    /**
     *    IntentService can perform
     */
    private static final String ACTION_INIT_WHEN_APP_CREATE = "gdut.bai.service.action.INIT";

    public InitializeService() {
        super("InitializeService");
    }

    /**
     * Starts this service to perform action init with the given parameters. If
     * the service is already performing a task this action will be queued.
     *
     * @see IntentService
     */
    public static void startActionInit(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)) {
                handleActionInit();
            }
        }
    }

    /**
     * Handle action Init in the provided background thread with the provided
     * parameters.
     */
    private void handleActionInit() {
        LogUtils.d("InitializeService handleActionInit begin " + System.currentTimeMillis());

        DataCacheHelper.initDataCacheHelper(this.getApplicationContext());

        String channelName = ChannelUtil.getChannelName(this.getApplicationContext());
        // App ID: 在TalkingData Game Analytics创建应用后会得到App ID。
        // 渠道 ID: 是渠道标识符,可通过不同渠道单独追踪数据。
        TalkingDataGA.init(this.getApplicationContext(), "xxxxxxxx", channelName);
     
        // AdTrack
        TalkingDataAppCpa.init(this.getApplicationContext(),"xxxxxxxx", channelName);

        // App Analytics
        TCAgent.LOG_ON=true;
        // App ID: 在TalkingData创建应用后,进入数据报表页中,在“系统设置”-“编辑应用”页面里查看App ID。
        // 渠道 ID: 是渠道标识符,可通过不同渠道单独追踪数据。
        TCAgent.init(this.getApplicationContext(), "xxxxxxxx", channelName);
        // 如果已经在AndroidManifest.xml配置了App ID和渠道ID,调用TCAgent.init(this)即可;或与AndroidManifest.xml中的对应参数保持一致。
        TCAgent.setReportUncaughtExceptions(true);

        // 开启反作弊功能
        TCAgent.setAntiCheatingEnabled(this.getApplicationContext(), true);

        // 为了提高webview场景稳定性,及时发现并解决x5相关问题,
        // 当客户端发生crash等异常情况并上报给服务器时带上x5内核相关信息
        // 以bugly日志上报为例
        CrashReport.UserStrategy strategy = new CrashReport.UserStrategy(this.getApplicationContext());
        strategy.setCrashHandleCallback(new CrashReport.CrashHandleCallback() {
            @Override
            public Map<String, String>  onCrashHandleStart(int crashType, String errorType, String errorMessage, String errorStack) {
                LinkedHashMap<String, String> map = new LinkedHashMap<>();
                String x5CrashInfo = com.tencent.smtt.sdk.WebView.getCrashExtraMessage(InitializeService.this.getApplicationContext());
                map.put("x5crashInfo", x5CrashInfo);
                return map;
            }

            @Override
            public byte[] onCrashHandleStart2GetExtraDatas(int crashType, String errorType, String errorMessage, String errorStack) {
                try {
                    return "Extra data.".getBytes("UTF-8");
                } catch (Exception e) {
                    return null;
                }
            }
        });

        Bugly.setAppChannel(this.getApplicationContext(), channelName);
        Bugly.init(this.getApplicationContext(), "xxxxxxxx", false, strategy);

        LogUtils.d("InitializeService handleActionInit end " + System.currentTimeMillis());
    }
}

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        InitializeService.startActionInit(this);
    }
}

参考

Android应用启动优化:一种DelayLoad的实现和原理(下篇)
Android性能优化典范 - 第6季
Android Performance

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,129评论 25 707
  • 本文首发个人博客:iOS App 启动性能优化 应用启动时间,直接影响用户对一款应用的判断和使用体验。ZAKER新...
    Chars阅读 2,165评论 1 11
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,656评论 18 139
  • 转载自一触即发 App启动优化最佳实践 一触即发 App启动优化最佳实践 文中的很多图都是Google性能优化指南...
    CP9阅读 601评论 0 3
  • 一直在想,怎样写一篇不矫情的文章给你看,这好像很难。 高考之前,想了很多,也在猜测我们彼此未来的去向,有一点惆怅惘...
    庭亭阅读 243评论 0 0