×

Android App优化之提升你的App启动速度之实例挑战

96
anly_jun
2016.08.29 08:51* 字数 1185

系列文:

  1. 背景:Android App优化, 要怎么做?
  2. Android App优化之性能分析工具
  3. Android App优化之提升你的App启动速度之理论基础
  4. Android App优化之提升你的App启动速度之实例挑战
  5. Android App优化之Layout怎么摆
  6. Android App优化之ANR详解
  7. Android App优化之消除卡顿
  8. Android App优化之内存优化
  9. Android App优化之持久电量
  10. Android App优化之如何高效网络请求

1, 代码分析

之前写的Github App为例.

因为这个App集成了Bugly, Push, Feedback等服务, 所以Application的onCreate有很多第三方平台的初始化工作...

public class GithubApplication extends MultiDexApplication {

    @Override
    public void onCreate() {
        super.onCreate();

        // init logger.
        AppLog.init();

        // init crash helper
        CrashHelper.init(this);

        // init Push
        PushPlatform.init(this);

        // init Feedback
        FeedbackPlatform.init(this);

        // init Share
        SharePlatform.init(this);

        // init Drawer image loader
        DrawerImageLoader.init(new AbstractDrawerImageLoader() {
            @Override
            public void set(ImageView imageView, Uri uri, Drawable placeholder) {
                ImageLoader.loadWithCircle(GithubApplication.this, uri, imageView);
            }
        });
    }
}

当前冷启动效果:


code_start_before_optimize

可以看到启动时白屏了很长时间.

2, Traceview上场

接下来我们结合我们上文的理论知识, 和介绍的Traceview工具, 来分析下Application的onCreate耗时.

在onCreate开始和结尾打上trace.

Debug.startMethodTracing("GithubApp");
...
Debug.stopMethodTracing();

运行程序, 会在sdcard上生成一个"GithubApp.trace"的文件.

注意: 需要给程序加上写存储的权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

通过adb pull将其导出到本地

adb pull /sdcard/GithubApp.trace ~/temp

广告: adb的众多用法, 可以参考我的另一篇文

打开DDMS分析trace文件

ddms_open_trace

分析trace文件

traceview_ui
  1. 在下方的方法区点击"Real Time/Call", 按照方法每次调用耗时降序排.
  2. 耗时超过500ms都是值得注意的.
  3. 看左边的方法名, 可以看到耗时大户就是我们用的几大平台的初始化方法, 特别是Bugly, 还加载native的lib, 用ZipFile操作等.
  4. 点击每个方法, 可以看到其父方法(调用它的)和它的所有子方法(它调用的).
  5. 点击方法时, 上方的该方法执行时间轴会闪动, 可以看该方法的执行线程及相对时长.

3, 调整Application onCreate再试

既然已经知道了哪些地方耗时长, 我们不妨调整下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 mingjun on 16/8/25.
 */
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);
    }
}

看看现在的效果:

improved-1.gif

可以看到提升了很多, 然后还有一点瑕疵, 就是起来的时候会有一个白屏, 如果手机较慢的话, 这个白屏就会持续一段时间, 不太友好.

那么还有没有什么办法优化呢?

4, 给我们的应用窗口弄一个PlaceHolder

Android最新的Material Design有这么个建议的. 建议我们使用一个placeholder UI来展示给用户直至App加载完毕.

怎么做呢?

给Window加上背景

如第3节所言, 当App没有完全起来时, 屏幕会一直显示一块空白的窗口(一般来说是黑屏或者白屏, 根据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>

5, 最终的效果

让我们来看下最终的效果:

improved-2.gif

相比之前, 呈现给用户的不再是一个白屏了, 带上了logo, 当然这个背景要显示什么, 我们可以根据实际情况来自定义.

这种优化, 对于有些Application内的初始化工作不能移到子线程做的情况, 是非常友好的. 可以避免我们的App长时间的呈现给用户一个空白的窗口.

6, 结语

照例, 总结下.
这次关于App启动时间的优化, 写了两篇. 写这么多, 还是想传达下个人做技术的思想, 也算是个人的经验回顾, 抛砖引玉.

实际场景可能远比这个复杂,在此更多的提供一种分析思路~欢迎扩展

矫情了, 还是总结下本文相关的吧:

  1. Application的onCreate中不要做太多事情.
  2. 首屏Activity尽量简化.
  3. 善用工具分析.
  4. 多阅读官方文档, 很多地方貌似无关, 实际有关联, 例如这次就用了Material Design文档中的解决方案.

本文完整源码, 请移步Github

Android经验谈
Web note ad 1