[Android基础知识] 第八章 性能优化 之 App启动优化(二)

235 阅读6分钟

第八章 性能优化 之 App启动优化(二)

(一)启动页白屏/黑屏解决

1、现象

打开app,往往会先白屏停顿一下后再进入启动页面(Splash)

2、原因

在启动Acitivty的onCreate()方法里面,系统先绘制窗体,再执行setContentView(R.layout.activity_splash),窗体绘制后布局资源还没加载,于是就使用默认背景色。 如果主题使用Theme.AppCompat.Light(亮色系)则显示白色闪屏,若使用ThemeOverlay.AppCompat.Dark(暗色系)则显示黑色闪屏

3、解决

步骤1:把启动图bg_splash设置为窗体背景,避免刚刚启动App的时候出现,黑/白屏 步骤2 :设置为背景bg_splash显示的时候,后台负责加载资源,同时去下载广告图,广告图下载成功或者超时的时候显示SplashActivity的真实样子 步骤3:随后进入MainAcitivity

       <style name="ThemeSplash" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:background">@mipmap/bg_splash</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowFullscreen">true</item>
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
        </style>

(二)启动速度优化

1、Android Application启动流程分析

(1)App基础理论

每个Android App都在一个独立空间里, 意味着其运行在一个单独的进程中, 拥有自己的VM, 被系统分配一个唯一的user ID. Android App由很多不同组件组成, 这些组件还可以启动其他App的组件. 因此, Android App并没有一个类似程序入口的main()方法. Android进程与Linux进程一样. 默认情况下, 每个apk运行在自己的Linux进程中. 另外, 默认一个进程里面只有一个线程---主线程. 这个主线程中有一个Looper实例, 通过调用Looper.loop()从Message队列里面取出Message来做相应的处理. 进程在其需要的时候被启动. 任意时候, 当用户或者其他组件调取你的apk中的任意组件时, 如果你的apk没有运行, 系统会为其创建一个新的进程并启动. 通常, 这个进程会持续运行直到被系统杀死.

(2)App启动流程

在这里插入图片描述 用户点击Home上的一个App图标, 启动一个应用时: Click事件会调用startActivity(Intent), Launcer会通过Binder IPC机制, 最终通知ActivityManagerService(AMS是Android系统的一个进程,用于管理系统四大组件运行状态)去启动Activity。 该Service会执行如下操作: 第一步:通过PackageManager的resolveIntent()收集这个intent对象的指向信息.指向信息被存储在一个intent对象中 第二步:通过grantUriPermissionLocked()方法来验证用户是否有足够的权限去调用该intent对象指向的Activity. 如果有权限, ActivityManagerService会检查并在新的task中启动目标activity 第三步:检查这个进程的ProcessRecord是否存在了.若存在,直接启动activity,如果ProcessRecord是null, ActivityManagerService会创建新的进程来实例化目标activity 第四步:ActivityManagerService调用startProcessLocked()方法来创建新的进程, 该方法会通过前面讲到的socket通道传递参数给Zygote进程. Zygote孵化自身, 并调用ZygoteInit.main()方法来实例化ActivityThread对象并最终返回新进程的pid ActivityThread随后依次调用Looper.prepareLoop()和Looper.loop()来开启消息循环 第五步:将进程和指定的Application绑定起来 第六步:在该存在的进程中调用realStartActivity()来启动Activity

2、App启动方式

(1)冷启动

App没有启动过或App进程被killed, 系统中不存在该App进程, 此时启动App即为冷启动. 冷启动的流程即为第2节所描述的App启动流程的全过程, 需要创建App进程, 加载相关资源, 启动Main Thread, 初始化首屏Activity等. 在这个过程中, 屏幕会显示一个空白的窗口(颜色基于主题), 直至首屏Activity完全启动. 冷启动时间线: 在这里插入图片描述

(2)热启动

热启动意味着你的App进程只是处于后台, 系统只是将其从后台带到前台, 展示给用户. 类同与冷启动, 在这个过程中, 屏幕会显示一个空白的窗口(颜色基于主题), 直至activity渲染完毕.

(3)温启动

介于冷启动和热启动之间, 一般来说在以下两种情况下发生: a.用户back退出了App, 然后又启动. App进程可能还在运行, 但是activity需要重建. b.用户退出App后, 系统可能由于内存原因将App杀死, 进程和activity都需要重启, 但是可以在onCreate中将被动杀死锁保存的状态(saved instance state)恢复.

通过三种启动状态的相关描述, 可以看出我们要做的启动优化其实就是针对冷启动. 热启动和温启动都相对较快.

3、导致App启动慢原因

根据冷启动的时间图, 可以看出, 对于App来说, 我们可以控制的启动时间线的点无外乎: (3.1)Application的onCreate (3.2)首屏Activity的渲染 而我们现在的App动不动集成了很多第三方服务, 启动时需要检查广告, 注册状态等等一系列接口都是在Application的onCreate或是首屏的onCreate中做的.

4、实例分析

(1)代码分析

因为这个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);
            }
        });
    }
}

(2)利用Traceview分析application的onCreate耗时

接下来我们结合我们上文的理论知识, 和介绍的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

打开DDMS分析trace文件 ddms_open_trace 分析trace文件 在这里插入图片描述 在下方的方法区点击"Real Time/Call", 按照方法每次调用耗时降序排. 耗时超过500ms都是值得注意的. 看左边的方法名, 可以看到耗时大户就是我们用的几大平台的初始化方法, 特别是Bugly, 还加载native的lib, 用ZipFile操作等. 点击每个方法, 可以看到其父方法(调用它的)和它的所有子方法(它调用的). 点击方法时, 上方的该方法执行时间轴会闪动, 可以看该方法的执行线程及相对时长.

(3)对Application的onCreate优化

将第三方SDK初始化放在一个单独的线程中处理。这里用了一个InitializeService的IntentService来做初始化工作.(IntentService不同于Service, 它是工作在后台线程的.) InitializeService.java代码如下:

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);
    }
}

(4)启动界面优化

步骤1:制作一个主题,带上背景

<style name="SplashTheme" parent="AppTheme">
    <item name="android:windowBackground">@drawable/logo_splash</item>
</style>

步骤2:将一个不渲染布局的Activity作为启动屏,并加上主题

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();
    }
}
<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、总结

(1)Application的onCreate中不要做太多事情. (2)首屏Activity尽量简化. (3)善用性能分析工具分析.