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

第八章 性能优化 之 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)善用性能分析工具分析.

发布了74 篇原创文章 · 获赞 15 · 访问量 6255

猜你喜欢

转载自blog.csdn.net/qq_29966203/article/details/90473664