Android 启动优化:耗时的标准是怎么样?如何根本每个方法的耗时时间?StrictMode;黑白屏优化

151 阅读5分钟

前言

一、主要是优化什么?

  1. 冷启动的时间
  2. 启动阶段在哪里?

二、多少才算优化好?

  1. 标准是什么。
  2. 日志
  3. adb

三、如何分析那些地方有问题应该如何优化呢?

  1. 借助profile
  2. 严格模式:主线程是否被阻塞
  3. 黑白屏如何解决?

一、主要是优化什么?

启动优化的核心目标是 ​​缩短用户从点击应用图标到看到可交互界面的等待时间​​,提升用户体验。但具体优化方向需根据不同类型的启动场景(冷启动、温启动、热启动)针对性处理:

​启动类型​​触发条件​​优化重点​
​冷启动​应用进程未存在(完全重启)进程创建、Application初始化、首屏渲染耗时
​温启动​应用进程存在但 Activity 栈被销毁Activity 重建、数据恢复、避免重复初始化
​热启动​应用进程和 Activity 均在后台快速恢复前台界面,减少主线程卡顿

一般启动优化是针对冷启动,进程创建我们干预不了,我们一般是在Application初始化、首屏渲染(onCreateonStartonResume)耗时进行优化。


二、如何知道启动耗时是多少?

2.1 日志方式:display

图片.png

2.2 adb方式【推荐】

需要获得root

adb shell am start -S -W com.example.lifecycledemo/.MainActivity

图片.png

-s 先停止 ,-w 再启动

TotalTime: 580​【我们主要优化这个】

  • ​含义​​:​​应用自身​​从启动到完成首帧渲染(Activity.onResume() 结束)的总耗时,单位为毫秒(ms)。

WaitTime: 591

  • ​含义​​:​​系统视角​​的总耗时,包括进程创建、系统资源调度等开销,通常略大于 TotalTime
  • ​公式​​:WaitTime = TotalTime + 系统调度耗时

2.3 优化到什么程度,才算符合标准呢?

冷启动优化的目标是 ​​让用户感知不到明显等待​​,具体标准需结合设备性能和行业实践:

​设备类型​​理想冷启动耗时​​容忍上限​​优化优先级​
​高端设备​≤ 400ms≤ 800ms
​中端设备​≤ 800ms≤ 1200ms
​低端设备​≤ 1200ms≤ 2000ms
  • ​行业参考​​:

    • ​Google 官方建议​​:应用应在 ​​1秒内​​ 完成冷启动(用户可交互)。
    • ​Android Vitals​​:冷启动时间超过 ​​5秒​​ 会被标记为性能问题。
    • ​用户体验研究​​:超过 ​​2秒​​ 的冷启动会导致 ​​用户流失率显著上升​​。

但注意,这只是一个参考,并没有严格的标准,因为本身就有些应用需要启动的东西比较多。


三、如何分析那些地方有问题应该如何优化呢?

3.1 Android Studio Profiler​​:使用 ​​CPU 跟踪​​分析冷启动各阶段耗时(如类加载、布局渲染)。

图片.png 运行app,运行一段时间后,结束跟踪。

图片.png

可以跳转到源码位置

图片.png

图片.png

可以看到子线程中也可以看到。

如果我们在主线程中增加,那么可以在如下地方看到。 图片.png

图片.png

图片.png

总结:

  1. 这里面,可以找出有耗时的地方进行优化。
  2. Top Down(自顶向下)​​:从 ​​调用栈的顶层方法(入口)​​ 开始,逐层向下展开,展示每个方法的调用路径和耗时占比。
  3. Flame Chart(火焰图)​​:根据耗时百分比查看调用栈,便于发现总耗时很长的调用链【常用】
  4. Bottom Up(自底向上)​​:相对于Top Down,能够更方便查看耗时方法【常用】

3.2 StrictMode模式

StrictMode 是 Android 提供的一个 ​​开发阶段​​ 的调试工具,用于检测主线程(UI 线程)中的 ​​非法操作​​,例如:

  • ​主线程磁盘 I/O​​(如读写 SharedPreferences、数据库、文件)。
  • ​主线程网络请求​​(如未经异步处理的 HTTP 请求)。
  • ​内存泄漏​​(如未关闭的 CursorBroadcastReceiver)。
  • ​其他耗时操作​​(如密集计算、类加载)。

其核心目的是 ​​提前暴露潜在的性能问题​​,防止应用出现卡顿或 ANR(Application Not Responding)。

class MyApplication :Application(){
    private  val TAG = "MyApplication"
    override fun onCreate() {
        super.onCreate()
        if (BuildConfig.DEBUG) {
            // 配置线程策略(检测主线程问题)
            StrictMode.setThreadPolicy(
                ThreadPolicy.Builder()
                    .detectDiskReads() // 检测磁盘读
                    .detectDiskWrites() // 检测磁盘写
                    .detectNetwork() // 检测网络请求
                    .penaltyLog() // 违规时输出日志
                    .penaltyDeath() // 违规时直接崩溃
                    .build()
            )

            // 配置虚拟机策略(检测内存泄漏等)
            StrictMode.setVmPolicy(
                VmPolicy.Builder()
                    .detectLeakedSqlLiteObjects() // 检测未关闭的 SQLite 游标
                    .detectLeakedClosableObjects() // 检测未关闭的 Closeable 对象
                    .penaltyLog() // 违规时输出日志
                    .penaltyDeath() // 违规时直接崩溃
                    .build()
            )
        }
    }
}
class MainActivity : AppCompatActivity() {
    private  val TAG = "MainActivity"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
        var tvHello = findViewById<TextView>(R.id.tv_hello);
        tvHello.setOnClickListener(object :OnClickListener{
            override fun onClick(v: View?) {
                Log.d(TAG, "onClick: ")
            }
        })
        Thread.sleep(6000)
    }
}

图片.png

图片.png

3.3 黑白屏优化

在 ​​冷启动阶段​​,当用户点击应用图标后,系统会先显示一个 ​​默认窗口背景​​(通常为白色或黑色),直到首屏 Activity 的布局渲染完成。这个中间过程导致的短暂白屏或黑屏现象称为 ​​黑白屏​​。

这个是google加的,因为如果没有黑白屏,加上你app启动时间过长,那么就会想没点击一下,用户体验差,所以加了,但是黑白屏并不好看,所以我们需要换成自己的。

代码示例​​:

<!-- res/values/styles.xml -->
<style name="LaunchTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- 设置背景为启动图或颜色 -->
    <item name="android:windowBackground">@drawable/splash_background</item>
    <!-- 可选:全屏显示 -->
    <item name="android:windowFullscreen">true</item>
</style>

<!-- res/values-v31/styles.xml(适配 Android 12+ SplashScreen) -->
<style name="LaunchTheme" parent="Theme.SplashScreen">
    <item name="windowSplashScreenBackground">@color/splash_background_color</item>
    <item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
    <item name="postSplashScreenTheme">@style/AppTheme</item>
</style>
<!-- AndroidManifest.xml -->
<activity
    android:name=".MainActivity"
    android:theme="@style/LaunchTheme">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
// MainActivity.java
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // 必须在 super.onCreate 前切换主题
        setTheme(R.style.AppTheme);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

3.4 异步加载

1. 异步/延迟初始化​

​优化前代码​​(Application.java):

public class MyApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        // 主线程同步初始化
        initPushSDK();  // 耗时 400ms
        initAnalyticsSDK();  // 耗时 300ms
        initDatabase();  // 耗时 500ms
    }
}

​优化后代码​​:

public class MyApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        // 异步初始化非关键任务
        Executors.newSingleThreadExecutor().execute(() -> {
            initDatabase();  // 数据库初始化放到子线程
        });
        
        // 延迟关键路径外的 SDK 初始化
        new Handler(Looper.getMainLooper()).postDelayed(() -> {
            initPushSDK();
            initAnalyticsSDK();
        }, 2000);  // 延迟到首屏渲染后
    }
}