稳定性性能系列之九——启动性能优化:Boot、冷启动与热启动

启动速度,是用户对App的第一印象。慢一秒,可能就流失了一个用户。本文将带你系统化地攻克Android三种启动类型的性能优化难题。

引言

启动性能是影响用户体验的关键指标。根据Google的统计,启动时间每增加1秒,用户留存率会下降约5%。在实际项目中,启动耗时超过3秒的应用往往面临用户流失和业务压力。

典型的启动性能问题表现为:

  • 点击图标后出现白屏,持续1秒以上
  • Logo显示阶段耗时过长
  • 首页内容加载缓慢,用户需要等待3秒以上才能看到内容

面对启动性能问题,常见的优化思路包括:

  • Application初始化优化
  • Activity加载流程优化
  • 网络请求异步化
  • 首屏资源加载优化

然而,仅凭经验判断往往无法准确定位性能瓶颈。性能优化必须基于数据驱动,通过专业的性能分析工具定位问题。

本文将系统化地讲解Android三种启动类型的优化方法,帮助你也能实现从3秒到1秒的性能飞跃。

读完本文,你将学会:

  1. ✅ 区分System Boot、冷启动、热启动三种启动类型
  2. ✅ 使用Systrace/Perfetto精确测量启动性能
  3. ✅ 掌握Application、Activity、首屏渲染三阶段的优化技巧
  4. ✅ 学会System Boot的深度优化(内核、Init、Zygote、SystemServer)
  5. ✅ 建立CI/CD自动化启动性能监控体系
  6. ✅ 了解车机场景的特殊优化策略

一、启动类型详解:三种启动,三种策略

很多开发者会把"启动优化"笼统地当成一件事,但实际上Android的启动分为三种完全不同的类型,它们的触发场景、优化策略和性能基准都截然不同。

1.1 三种启动类型全面对比

启动类型 触发场景 进程状态 典型耗时 优化难度 主要优化点 关键指标
System Boot 设备开机 设备重启 系统从零启动 20-60秒 ⭐⭐⭐⭐⭐ BootLoader Kernel Init Zygote SystemServer boot_progress_*事件 ACTION_BOOT_COMPLETED
冷启动 App首次安装后启动 App被系统杀死后启动 设备重启后首次启动App 进程不存在 需要创建进程 1-5秒 ⭐⭐⭐⭐ Application Activity 首屏渲染 reportFullyDrawn() TotalTime指标
热启动 App从后台恢复 按Home后再打开 切换到其他App后返回 进程存在 Activity需重建 0.3-1秒 ⭐⭐⭐ Activity恢复 View状态恢复 数据刷新 onRestart() onResume()耗时

三者的本质区别:

  1. System Boot: 系统级启动,涉及硬件初始化、内核加载、系统服务启动,影响所有应用
  2. 冷启动: 进程级启动,从无到有创建进程、加载代码、初始化应用
  3. 热启动: Activity级启动,进程已存在,只需恢复Activity和UI状态

优化策略的差异:

  • System Boot需要从BootLoader、Kernel、Framework层面优化,需要系统级权限
  • 冷启动需要优化Application、Activity、渲染流程,应用开发者可控
  • 热启动主要优化Activity生命周期和数据恢复,相对简单

1.2 System Boot:系统启动的完整链路

System Boot是用户开机后到看到Launcher桌面的完整过程,涉及从硬件到软件的多个阶段。

启动流程图:

各阶段详解:

1. BootLoader阶段 (2-5秒)

  • 职责: 硬件自检、内存初始化、加载Kernel到内存
  • 关键文件 : aboot.img (Android BootLoader)
  • 优化空间: 有限,主要受硬件限制

2. Kernel阶段 (3-8秒)

  • 职责:

    • 初始化CPU、内存、I/O子系统
    • 加载设备驱动(显示、触摸、音频等)
    • 挂载根文件系统
    • 启动第一个用户空间进程 /init
  • 关键事件:

    bash 复制代码
    [    0.000000] Booting Linux on physical CPU 0x0
    [    1.234567] Freeing unused kernel memory: 1024K
    [    2.345678] init: init first stage started!
  • 优化空间: 中等,可精简驱动、优化挂载

3. Init阶段 (5-10秒)

  • 职责:

    • 解析并执行 init.rc 脚本
    • 创建文件系统挂载点
    • 启动属性服务 property_service
    • 启动守护进程:
      • servicemanager (Binder服务管理)
      • surfaceflinger (显示服务)
      • zygote (进程孵化器)
  • 关键代码:

    cpp 复制代码
    // system/core/init/init.cpp
    int main(int argc, char** argv) {
        // 解析init.rc
        Parser parser;
        parser.ParseConfig("/init.rc");
    
        // 启动服务
        ServiceList& sm = ServiceList::GetInstance();
        sm.StartServices();
    }
  • 优化空间: 大,可并行启动、延迟启动非关键服务

4. Zygote阶段 (2-5秒)

Zygote是所有App进程的"父进程",通过fork()机制快速创建新进程。

  • 职责:

    • 预加载常用类 (约5000个类)
    • 预加载系统资源
    • 创建JVM和ART运行时
    • 等待Socket连接(接收fork请求)
  • 关键代码:

    java 复制代码
    // frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
    public static void main(String argv[]) {
        // 预加载类
        preloadClasses();
    
        // 预加载资源
        preloadResources();
    
        // 预加载共享库
        preloadSharedLibraries();
    
        // 启动SystemServer
        forkSystemServer();
    
        // 进入Loop,等待创建App进程
        runSelectLoop();
    }
    
    private static void preloadClasses() {
        // 加载 /system/etc/preloaded-classes 中的类
        InputStream is = ClassLoader.getSystemResourceAsStream("preloaded-classes");
        // ... 加载约5000个类,耗时2-3秒
    }
  • 优化空间: 大,可精简预加载类列表

5. SystemServer阶段 (5-15秒)

SystemServer是Android系统的核心进程,运行着所有系统服务。

  • 职责:

    • 启动Bootstrap Services (核心服务):
      • ActivityManagerService (AMS)
      • PowerManagerService (PMS)
      • PackageManagerService (PMS)
    • 启动Core Services (必须服务):
      • BatteryService
      • UsageStatsService
    • 启动Other Services (其他服务):
      • WindowManagerService (WMS)
      • InputManagerService (IMS)
      • LocationManagerService
      • ...约80个服务
  • 关键代码:

    java 复制代码
    // frameworks/base/services/java/com/android/server/SystemServer.java
    private void run() {
        // 阶段1: 启动Bootstrap服务
        startBootstrapServices();
    
        // 阶段2: 启动Core服务
        startCoreServices();
    
        // 阶段3: 启动Other服务
        startOtherServices();
    
        // 进入Loop
        Looper.loop();
    }
    
    private void startBootstrapServices() {
        // 启动AMS
        mActivityManagerService = ActivityManagerService.Lifecycle.startService(
            mSystemServiceManager, atm);
    
        // 启动PMS
        mPackageManagerService = PackageManagerService.main(...);
    }
  • 优化空间: 最大,可分阶段启动、延迟启动

6. Launcher阶段 (3-5秒)

  • 职责: 显示桌面图标,响应用户点击
  • 关键指标 : ACTION_BOOT_COMPLETED 广播发送时间

Boot性能基准:

设备类型 优秀 良好 一般 较差
旗舰手机 < 20s 20-30s 30-40s > 40s
中端手机 < 25s 25-35s 35-45s > 45s
车载系统 < 10s 10-15s 15-20s > 20s

1.3 App冷启动:从点击到可见的全过程

冷启动是用户点击App图标后,系统创建进程、加载代码、初始化Application、启动Activity、渲染首屏的完整过程。

启动流程图:

复制代码
用户点击图标
   ↓
Launcher通知ActivityManagerService(AMS)
   ↓
AMS检查进程是否存在
   ↓
进程不存在,通知Zygote fork新进程
   ↓
Zygote fork进程 (100-300ms)
   ↓
新进程启动,加载Application
   ↓
Application.onCreate() 执行 (200-800ms)
   ├─ SDK初始化
   ├─ 数据库初始化
   └─ 配置加载
   ↓
AMS通知启动Activity
   ↓
Activity.onCreate() 执行 (300-1000ms)
   ├─ setContentView()
   ├─ 数据加载
   └─ UI初始化
   ↓
Activity.onStart() / onResume() 执行
   ↓
首帧渲染 (measure → layout → draw) (100-300ms)
   ↓
用户看到首屏!

耗时分布参考 (某电商App实测数据):

阶段 耗时 占比 可优化空间
fork进程 200ms 9% ❌ 系统控制
Application.onCreate() 800ms 38% ✅ 延迟/异步初始化
Activity.onCreate() 800ms 38% ✅ 异步加载/预加载
首屏渲染 300ms 14% ✅ 布局/图片优化
总计 2100ms 100% 优化到950ms

关键时间节点:

kotlin 复制代码
// 测量各阶段耗时
class MyApp : Application() {
    override fun onCreate() {
        val startTime = System.currentTimeMillis()
        super.onCreate()

        // 初始化操作
        initSDKs()

        val appTime = System.currentTimeMillis() - startTime
        Log.d("Performance", "Application耗时: ${appTime}ms")
    }
}

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val startTime = System.currentTimeMillis()
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 等待首帧渲染
        window.decorView.viewTreeObserver.addOnPreDrawListener(
            object : ViewTreeObserver.OnPreDrawListener {
                override fun onPreDraw(): Boolean {
                    window.decorView.viewTreeObserver.removeOnPreDrawListener(this)
                    val totalTime = System.currentTimeMillis() - startTime
                    Log.d("Performance", "Activity+首屏耗时: ${totalTime}ms")
                    return true
                }
            }
        )
    }
}

冷启动性能基准:

评级 耗时范围 用户体验 建议
✅ 优秀 < 1s 几乎无感知 保持,可作为标杆
⚠️ 良好 1-1.5s 轻微等待感 有优化空间
⚠️ 一般 1.5-2s 明显等待 需要优化
❌ 较差 2-3s 较长等待 必须优化
❌ 很差 > 3s 想关闭App 严重问题

1.4 App热启动:从后台恢复的快速路径

热启动是App进程已存在,只需恢复Activity和UI状态的快速启动方式。

启动流程图:

复制代码
用户从Recent或按返回键
   ↓
ActivityManagerService(AMS)检测到进程存在
   ↓
直接恢复Activity栈
   ↓
Activity.onRestart() 执行 (10-50ms)
   ↓
Activity.onStart() 执行 (10-50ms)
   ↓
Activity.onResume() 执行 (30-100ms)
   ├─ 刷新数据
   └─ 恢复UI状态
   ↓
View重建/更新 (50-150ms)
   ↓
首帧渲染 (50-100ms)
   ↓
用户看到界面!

耗时分布 (某社交App实测数据):

阶段 耗时 占比 可优化空间
onRestart() 20ms 4% ✅ 避免重复操作
onStart() 30ms 6% ✅ 缓存数据
onResume() 150ms 30% ✅ 异步刷新
View重建 200ms 40% ✅ 状态缓存
渲染更新 100ms 20% ✅ 局部刷新
总计 500ms 100% 优化到300ms

热启动性能基准:

评级 耗时范围 用户体验
✅ 优秀 < 300ms 即时响应
⚠️ 良好 300-500ms 轻微延迟
❌ 较差 > 500ms 明显卡顿

二、System Boot启动优化:系统级性能调优

System Boot优化主要针对系统开发者车载系统工程师,需要系统级权限和源码访问。普通App开发者可以跳过此节。

2.1 Boot性能分析方法

方法1: 使用Bootchart分析整体启动时间

Bootchart是Android提供的Boot性能分析工具,生成CPU、I/O、进程的时间图表。

开启Bootchart:

bash 复制代码
# 1. 开启bootchart (重启生效)
adb root
adb shell 'echo 1 > /data/bootchart/enabled'
adb reboot

# 2. 设备启动完成后,获取bootchart数据
adb pull /data/bootchart bootchart/

# 3. 生成PNG图表 (需要bootchart工具)
bootchart bootchart/

# 查看生成的 bootchart.png

Bootchart图表解读:

  • CPU使用率: 哪个进程占用CPU最多
  • I/O等待: 是否有I/O瓶颈
  • 进程启动顺序: 哪些进程启动早,哪些启动晚
  • 关键时间节点 : boot_progress_* 事件
方法2: 使用Systrace分析启动细节

Systrace可以抓取Boot过程的详细trace,分析每个服务的启动耗时。

抓取Boot Trace:

bash 复制代码
# 方法1: 开机时自动抓取 (需要修改init.rc)
# 在 init.rc 中添加:
on boot
    # 开启ftrace
    write /sys/kernel/debug/tracing/tracing_on 1
    write /sys/kernel/debug/tracing/events/sched/enable 1

# 方法2: 手动抓取重启后的trace
adb shell atrace --async_start -b 32768 -c \
    sched freq idle load disk mmc am wm gfx view dalvik

# 重启设备
adb reboot

# 启动完成后停止抓取
adb wait-for-device
adb shell atrace --async_stop -o /data/local/tmp/boot_trace.txt
adb pull /data/local/tmp/boot_trace.txt

# 使用Perfetto UI查看
# 打开 ui.perfetto.dev,上传trace文件

关键指标查看:

sql 复制代码
-- 在Perfetto UI的SQL查询中执行
-- 查看boot_progress事件的时间
SELECT
  ts / 1e9 AS time_sec,
  name,
  CAST(value AS TEXT) AS timestamp_ms
FROM counter
JOIN counter_track ON counter.track_id = counter_track.id
WHERE counter_track.name LIKE 'boot_progress%'
ORDER BY ts;
方法3: 使用logcat分析关键事件
bash 复制代码
# 过滤boot相关日志
adb logcat -d | grep -E "boot_progress|SystemServer"

# 输出示例:
# boot_progress_start: 12345
# boot_progress_preload_start: 15678
# boot_progress_preload_end: 18901
# boot_progress_system_run: 21234
# boot_progress_pms_start: 22345
# boot_progress_pms_ready: 28901
# boot_progress_ams_ready: 32345
# boot_progress_enable_screen: 35678

2.2 Kernel启动优化

优化1: 精简内核配置

移除不需要的功能:

bash 复制代码
# 编辑内核配置 kernel/.config
# 禁用不需要的功能可减少内核大小和启动时间

# 示例:禁用调试功能
CONFIG_DEBUG_KERNEL=n
CONFIG_DEBUG_INFO=n
CONFIG_KALLSYMS=n

# 禁用不需要的文件系统
CONFIG_BTRFS_FS=n
CONFIG_XFS_FS=n

# 禁用不需要的网络协议
CONFIG_NETFILTER=n
CONFIG_IPV6=n

# 重新编译内核
make -j8

实测效果: 节省1-2秒

优化2: 优化文件系统挂载

并行挂载分区:

bash 复制代码
# 修改 init.rc
# 优化前:串行挂载
on fs
    mount ext4 /dev/block/by-name/system /system
    mount ext4 /dev/block/by-name/vendor /vendor
    mount ext4 /dev/block/by-name/userdata /data

# 优化后:并行挂载
on fs
    exec - root -- /system/bin/sh -c "mount ext4 /dev/block/by-name/system /system &"
    exec - root -- /system/bin/sh -c "mount ext4 /dev/block/by-name/vendor /vendor &"
    mount ext4 /dev/block/by-name/userdata /data

使用noatime选项:

bash 复制代码
# 挂载时禁用atime(访问时间)更新,减少I/O
mount ext4 /dev/block/by-name/userdata /data noatime,nosuid,nodev

实测效果: 节省0.5-1秒

优化3: 延迟加载非关键驱动

修改驱动初始化顺序:

c 复制代码
// drivers/video/display_driver.c
// 优化前:device_initcall (启动阶段6)
device_initcall(display_driver_init);

// 优化后:late_initcall (启动阶段7,延后执行)
late_initcall(display_driver_init);

按需加载驱动:

c 复制代码
// 将驱动编译为模块,启动后再加载
// Kconfig
config DISPLAY_DRIVER
    tristate "Display Driver"  # 改为tristate而非bool

实测效果: 节省1-3秒

2.3 Init阶段优化

优化1: 并行启动服务
bash 复制代码
# init.rc
# 优化前:串行启动
on boot
    start serviceA
    start serviceB
    start serviceC

# 优化后:并行启动(使用&后台执行)
on boot
    exec - root -- /system/bin/sh -c "start serviceA &"
    exec - root -- /system/bin/sh -c "start serviceB &"
    exec - root -- /system/bin/sh -c "start serviceC &"
优化2: 延迟启动非关键服务
bash 复制代码
# init.rc
# 优化前:所有服务在boot阶段启动
on boot
    start critical_service
    start non_critical_service_1
    start non_critical_service_2

# 优化后:关键服务立即启动,非关键服务延后
on boot
    start critical_service

# Boot完成后再启动非关键服务
on property:sys.boot_completed=1
    start non_critical_service_1
    start non_critical_service_2
优化3: 精简init.rc
bash 复制代码
# 移除不需要的on触发器和exec命令
# 合并重复的属性设置
# 优化前:
on boot
    setprop prop1 value1
on boot
    setprop prop2 value2

# 优化后:
on boot
    setprop prop1 value1
    setprop prop2 value2

实测效果: Init阶段从10秒优化到6秒

2.4 Zygote启动优化

优化1: 精简预加载类列表
bash 复制代码
# 编辑预加载类列表
# frameworks/base/config/preloaded-classes

# 优化前:约5000个类
# 优化后:移除不常用的类,保留约3500个

# 分析方法:通过Systrace查看哪些类真正被使用
# 移除示例:
# android.bluetooth.*  # 如果设备无蓝牙
# android.nfc.*        # 如果设备无NFC
# com.android.internal.telephony.*  # 如果设备无电话功能

验证脚本:

bash 复制代码
#!/bin/bash
# 统计类的使用频率
adb logcat -c
adb logcat | grep "Class load" > class_usage.log
# 运行一天后分析
awk '{print $NF}' class_usage.log | sort | uniq -c | sort -nr > class_frequency.txt

实测效果: Zygote启动从5秒优化到3秒

优化2: 优化预加载资源
java 复制代码
// frameworks/base/core/java/android/app/ApplicationLoaders.java
private static void preloadResources() {
    // 优化前:预加载所有drawable
    Resources resources = Resources.getSystem();
    resources.preloadDrawables();

    // 优化后:只预加载常用drawable
    int[] commonDrawables = {
        android.R.drawable.ic_menu_add,
        android.R.drawable.ic_menu_delete,
        // ... 只列出最常用的100个
    };
    for (int id : commonDrawables) {
        resources.getDrawable(id, null);
    }
}

实测效果: 节省0.5-1秒

2.5 SystemServer启动优化

SystemServer是优化空间最大的环节,包含约80个系统服务。

优化1: 分阶段、分优先级启动服务
java 复制代码
// frameworks/base/services/java/com/android/server/SystemServer.java
private void run() {
    // 阶段1: Bootstrap Services (必须立即启动)
    startBootstrapServices();
    // AMS, PMS, PowerManager...

    // 阶段2: Core Services (必须服务,但可稍后)
    startCoreServices();
    // BatteryService, UsageStatsService...

    // 阶段3: Other Services (可延后启动)
    startOtherServices();
    // LocationManager, NetworkPolicy, MediaRouter...

    // 阶段4: 延迟服务 (Boot完成后启动)
    mHandler.postDelayed(() -> {
        startDelayedServices();
    }, 5000);
}

private void startDelayedServices() {
    // 非关键服务延后5秒启动
    // 例如:TelephonyRegistry, NetworkTime, Wallpaper...
}
优化2: 并行启动服务
java 复制代码
// SystemServer.java
private void startOtherServices() {
    // 优化前:串行启动
    startService(LocationManagerService.class);
    startService(NetworkPolicyManagerService.class);
    startService(MediaRouterService.class);

    // 优化后:并行启动(使用线程池)
    ExecutorService executor = Executors.newFixedThreadPool(4);

    executor.submit(() -> startService(LocationManagerService.class));
    executor.submit(() -> startService(NetworkPolicyManagerService.class));
    executor.submit(() -> startService(MediaRouterService.class));

    // 等待关键服务启动完成
    executor.shutdown();
    executor.awaitTermination(10, TimeUnit.SECONDS);
}
优化3: 延迟初始化服务内部逻辑
java 复制代码
// 示例: LocationManagerService
public class LocationManagerService extends ILocationManager.Stub {
    @Override
    public void systemReady() {
        // 优化前:systemReady时就加载所有Provider
        loadProviders();

        // 优化后:延迟加载,只加载必须的GPS Provider
        loadGpsProvider();

        // 其他Provider在首次使用时再加载
        mHandler.postDelayed(() -> {
            loadNetworkProvider();
            loadPassiveProvider();
        }, 10000);
    }
}

Boot优化效果总结:

优化项 优化前 优化后 节省时间
Kernel启动 8s 5s 3s ⬇️
Init阶段 10s 6s 4s ⬇️
Zygote启动 5s 3s 2s ⬇️
SystemServer启动 15s 9s 6s ⬇️
总Boot时间 48s 33s 15s ⬇️ (31%)

三、App冷启动优化:从3秒到1秒的实战

App冷启动是用户最常遇到的场景,也是优化效果最明显的领域。本节将提供完整的、可落地的优化方案。

3.1 冷启动性能测量

方法1: 使用adb命令快速测量
bash 复制代码
# 完全冷启动 (清除数据+force-stop)
adb shell am force-stop com.example.myapp
adb shell pm clear com.example.myapp

# 启动并测量时间
adb shell am start-activity -W com.example.myapp/.MainActivity

# 输出:
# Starting: Intent { act=android.intent.action.MAIN ... }
# Status: ok
# Activity: com.example.myapp/.MainActivity
# ThisTime: 1245        ← Activity启动耗时
# TotalTime: 1245       ← 总启动耗时 (这就是冷启动时间)
# WaitTime: 1267        ← 包括系统处理时间
# Complete
方法2: 使用Systrace精确分析
bash 复制代码
# 1. 清除App数据
adb shell am force-stop com.example.myapp
adb shell pm clear com.example.myapp

# 2. 后台启动systrace
python systrace.py -t 10 -o launch.html \
    sched freq gfx view wm am dalvik res \
    -a com.example.myapp &

# 3. 等待2秒让systrace准备好
sleep 2

# 4. 启动App
adb shell am start-activity -W com.example.myapp/.MainActivity

# 5. 打开trace分析
google-chrome launch.html

Trace中查找关键时间点:

  • bindApplication: Application开始创建
  • activityStart: Activity开始启动
  • Choreographer#doFrame: 首帧开始渲染
  • reportFullyDrawn: 应用自己上报的启动完成时间
方法3: 代码中精确计时
kotlin 复制代码
// MyApplication.kt
class MyApp : Application() {
    companion object {
        var appStartTime = 0L
        var appCreateTime = 0L
    }

    override fun attachBaseContext(base: Context) {
        super.attachBaseContext(base)
        appStartTime = System.currentTimeMillis()
    }

    override fun onCreate() {
        super.onCreate()

        // Application初始化操作
        initSDKs()

        appCreateTime = System.currentTimeMillis() - appStartTime
        Log.d("LaunchTime", "Application.onCreate耗时: ${appCreateTime}ms")
    }
}

// MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val activityStartTime = System.currentTimeMillis()
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val activityCreateTime = System.currentTimeMillis() - activityStartTime
        Log.d("LaunchTime", "Activity.onCreate耗时: ${activityCreateTime}ms")

        // 等待首帧渲染完成
        window.decorView.viewTreeObserver.addOnPreDrawListener(
            object : ViewTreeObserver.OnPreDrawListener {
                override fun onPreDraw(): Boolean {
                    window.decorView.viewTreeObserver.removeOnPreDrawListener(this)

                    val totalTime = System.currentTimeMillis() - MyApp.appStartTime
                    val renderTime = System.currentTimeMillis() - activityStartTime

                    Log.d("LaunchTime", """
                        ==== 启动性能统计 ====
                        Application耗时: ${MyApp.appCreateTime}ms
                        Activity耗时: ${activityCreateTime}ms
                        首帧渲染耗时: ${renderTime - activityCreateTime}ms
                        总启动时间: ${totalTime}ms
                        ======================
                    """.trimIndent())

                    // 上报到服务器
                    reportLaunchTime(totalTime)

                    return true
                }
            }
        )
    }

    private fun reportLaunchTime(time: Long) {
        // 上报启动时间到Firebase/自建服务器
        FirebasePerformance.getInstance()
            .newTrace("cold_start")
            .apply {
                putMetric("duration_ms", time)
                start()
                stop()
            }
    }
}

3.2 Application.onCreate()优化

Application.onCreate()是冷启动的第一个瓶颈,很多开发者习惯在这里初始化各种SDK,导致启动缓慢。

反面案例:典型的慢Application
kotlin 复制代码
// ❌ 糟糕的Application实现
class BadApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        // 问题1: 同步初始化多个第三方SDK (耗时500ms+)
        BuglySDK.init(this, "app_id", false)  // 180ms
        UmengSDK.init(this, "umeng_key")      // 150ms
        WeChatSDK.registerApp("wx_app_id")    // 200ms

        // 问题2: 同步数据库操作 (耗时150ms)
        DatabaseHelper.getInstance(this).init()

        // 问题3: 同步文件I/O (耗时100ms)
        ConfigManager.loadFromDisk(this)

        // 问题4: 复杂的初始化逻辑 (耗时200ms)
        initGlobalConfig()
        preloadAssets()

        // 总耗时: 1000ms+
        // 用户体验: 白屏1秒,很糟糕
    }
}
优化策略1: 延迟初始化 (Lazy Initialization)
kotlin 复制代码
// ✅ 优化后: 只初始化必须的
class GoodApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        // 只初始化崩溃上报 (必须立即初始化,否则捕获不到早期崩溃)
        BuglySDK.init(this, "app_id", false)  // 180ms

        // 其他SDK在主线程空闲时初始化
        Looper.myQueue().addIdleHandler {
            Thread {
                delayedInit()
            }.start()
            false  // 只执行一次
        }

        // Application.onCreate耗时: 180ms (减少82%)
    }

    private fun delayedInit() {
        // 延迟5秒后初始化非关键SDK
        Thread.sleep(5000)
        UmengSDK.init(this, "umeng_key")
        WeChatSDK.registerApp("wx_app_id")
        DatabaseHelper.getInstance(this).init()
        ConfigManager.loadFromDisk(this)
    }
}

IdleHandler原理:

kotlin 复制代码
// Looper.myQueue().addIdleHandler 会在主线程空闲时执行
// 即: MessageQueue中没有消息需要处理时

// 实现原理(简化版):
class MessageQueue {
    fun next(): Message? {
        // ... 获取下一个消息

        // 如果没有消息,执行IdleHandler
        if (message == null && idleHandlers.isNotEmpty()) {
            for (handler in idleHandlers) {
                handler.queueIdle()
            }
        }

        return message
    }
}
优化策略2: 异步初始化 (Async Initialization)
kotlin 复制代码
// ✅ 更好的方案: 并行异步初始化
class BetterApplication : Application() {
    private val initExecutor = Executors.newFixedThreadPool(3)

    override fun onCreate() {
        super.onCreate()

        // 崩溃上报必须同步初始化
        BuglySDK.init(this, "app_id", false)  // 180ms

        // 其他SDK异步并行初始化
        initExecutor.submit { UmengSDK.init(this, "umeng_key") }      // 150ms
        initExecutor.submit { WeChatSDK.registerApp("wx_app_id") }    // 200ms
        initExecutor.submit { DatabaseHelper.getInstance(this).init() }  // 150ms

        // Application.onCreate耗时: 180ms
        // 其他SDK在后台并行初始化,互不阻塞
    }
}
优化策略3: 懒加载 (Lazy Loading)
kotlin 复制代码
// ✅ 最佳方案: 按需加载
class BestApplication : Application() {
    // 使用Kotlin的lazy委托,首次使用时才初始化
    val database by lazy {
        DatabaseHelper.getInstance(this).apply { init() }
    }

    val config by lazy {
        ConfigManager.loadFromDisk(this)
    }

    override fun onCreate() {
        super.onCreate()

        // 只初始化崩溃上报
        BuglySDK.init(this, "app_id", false)  // 180ms

        // Application.onCreate耗时: 180ms (减少82%)
        // database和config只在首次使用时才初始化
    }
}

// 使用示例
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 首次访问时才初始化database
        val app = application as BestApplication
        val user = app.database.getUserInfo()  // 此时才执行数据库初始化
    }
}
优化策略4: 启动器模式 (Startup Library)

使用Google的App Startup库管理初始化流程:

kotlin 复制代码
// build.gradle
dependencies {
    implementation "androidx.startup:startup-runtime:1.1.1"
}
kotlin 复制代码
// 定义初始化器
class UmengInitializer : Initializer<Unit> {
    override fun create(context: Context) {
        UmengSDK.init(context, "umeng_key")
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        // 声明依赖关系
        return emptyList()
    }
}

class DatabaseInitializer : Initializer<Unit> {
    override fun create(context: Context) {
        DatabaseHelper.getInstance(context).init()
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        return emptyList()
    }
}
xml 复制代码
<!-- AndroidManifest.xml -->
<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false">

    <meta-data
        android:name="com.example.UmengInitializer"
        android:value="androidx.startup" />

    <meta-data
        android:name="com.example.DatabaseInitializer"
        android:value="androidx.startup" />
</provider>
kotlin 复制代码
// Application中不需要手动初始化,Startup库会自动按依赖顺序初始化
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        // 只需要初始化崩溃上报
        BuglySDK.init(this, "app_id", false)
    }
}

Application优化效果对比:

方案 Application耗时 优化幅度 用户体验 实现难度
原始方案 1000ms - 白屏1秒 简单
延迟初始化 180ms ⬇️ 82% 几乎无感知 简单
异步初始化 180ms ⬇️ 82% 几乎无感知 中等
懒加载 180ms ⬇️ 82% 几乎无感知 简单
Startup库 180ms ⬇️ 82% 几乎无感知 中等

3.3 Activity.onCreate()优化

Activity.onCreate()是冷启动的第二个瓶颈,主要耗时在数据加载和UI渲染。

反面案例:典型的慢Activity
kotlin 复制代码
// ❌ 糟糕的Activity实现
class BadActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 问题1: 主线程同步网络请求 (耗时400ms)
        val userInfo = NetworkApi.getUserInfoSync()  // 阻塞主线程!

        // 问题2: 主线程同步数据库查询 (耗时200ms)
        val products = DatabaseHelper.queryProducts()

        // 问题3: 复杂的RecyclerView初始化 (耗时300ms)
        recyclerView.adapter = ComplexAdapter(products)
        recyclerView.layoutManager = GridLayoutManager(this, 2)

        // 问题4: 加载30张大图片 (耗时500ms)
        loadAllImages(products)

        // Activity.onCreate耗时: 1400ms
        // 加上Application 180ms = 总启动时间 1580ms
        // 用户体验: 白屏1.6秒,非常糟糕
    }
}
优化策略1: 异步加载数据
kotlin 复制代码
// ✅ 优化后: 异步加载,先显示占位
class GoodActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 先显示骨架屏/占位UI
        showSkeletonScreen()

        // 异步加载数据
        lifecycleScope.launch {
            try {
                // 在IO线程加载数据
                val userInfo = withContext(Dispatchers.IO) {
                    NetworkApi.getUserInfoSync()
                }

                val products = withContext(Dispatchers.IO) {
                    DatabaseHelper.queryProducts()
                }

                // 切回主线程更新UI
                withContext(Dispatchers.Main) {
                    hideSkeletonScreen()
                    updateUI(userInfo, products)
                }
            } catch (e: Exception) {
                showError(e)
            }
        }

        // Activity.onCreate耗时: 80ms (只渲染骨架屏)
        // 数据在后台加载,不阻塞启动
    }

    private fun showSkeletonScreen() {
        // 显示骨架屏动画
        skeletonView.visibility = View.VISIBLE
        contentView.visibility = View.GONE
    }

    private fun hideSkeletonScreen() {
        skeletonView.visibility = View.GONE
        contentView.visibility = View.VISIBLE
    }
}
优化策略2: 数据预加载
kotlin 复制代码
// 在SplashActivity预加载数据
class SplashActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_splash)

        // 在显示Splash的同时,预加载数据
        lifecycleScope.launch {
            // 并行预加载多个数据源
            val userInfoDeferred = async(Dispatchers.IO) {
                NetworkApi.getUserInfoSync()
            }

            val configDeferred = async(Dispatchers.IO) {
                NetworkApi.getConfig()
            }

            val productsDeferred = async(Dispatchers.IO) {
                DatabaseHelper.queryProducts()
            }

            // 等待所有数据加载完成
            val userInfo = userInfoDeferred.await()
            val config = configDeferred.await()
            val products = productsDeferred.await()

            // 缓存到全局单例
            DataCache.userInfo = userInfo
            DataCache.config = config
            DataCache.products = products

            // 延迟1秒(展示Splash),然后跳转主页
            delay(1000)
            startActivity(Intent(this@SplashActivity, MainActivity::class.java))
            finish()
        }
    }
}

// MainActivity直接使用缓存数据
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 直接使用预加载的数据,无需等待
        val userInfo = DataCache.userInfo
        val products = DataCache.products

        if (userInfo != null && products != null) {
            updateUI(userInfo, products)
        } else {
            // 降级方案:数据未准备好,异步加载
            loadDataAsync()
        }

        // Activity.onCreate耗时: 80ms
        // 总启动时间: 180ms + 80ms = 260ms (优化前1580ms,减少83%)
    }
}
优化策略3: 布局优化

使用ViewStub延迟加载:

xml 复制代码
<!-- activity_main.xml -->
<LinearLayout ...>
    <!-- 首屏可见的内容 -->
    <TextView ... />
    <RecyclerView ... />

    <!-- 非首屏内容使用ViewStub延迟加载 -->
    <ViewStub
        android:id="@+id/stub_detail_panel"
        android:layout="@layout/layout_detail_panel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <ViewStub
        android:id="@+id/stub_comment_section"
        android:layout="@layout/layout_comment_section"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>
kotlin 复制代码
// 需要时才加载
findViewById<ViewStub>(R.id.stub_detail_panel).inflate()

使用ConstraintLayout减少层级:

xml 复制代码
<!-- 优化前: 嵌套5层 (measure/layout耗时150ms) -->
<LinearLayout>
    <RelativeLayout>
        <LinearLayout>
            <FrameLayout>
                <TextView />
            </FrameLayout>
        </LinearLayout>
    </RelativeLayout>
</LinearLayout>

<!-- 优化后: 只有2层 (measure/layout耗时50ms) -->
<ConstraintLayout>
    <TextView
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</ConstraintLayout>

异步Inflate布局:

kotlin 复制代码
// 对于复杂布局,使用AsyncLayoutInflater异步加载
AsyncLayoutInflater(this).inflate(R.layout.complex_layout, container) { view, resid, parent ->
    // 在主线程回调,添加到容器
    parent?.addView(view)
}
优化策略4: 图片优化

懒加载图片:

kotlin 复制代码
// 使用Coil/Glide的占位符和懒加载
imageView.load("https://example.com/image.jpg") {
    placeholder(R.drawable.placeholder)  // 先显示占位图
    crossfade(true)  // 淡入淡出
    memoryCacheKey("user_avatar_${userId}")  // 内存缓存
}

只加载可见区域的图片:

kotlin 复制代码
// RecyclerView只加载可见Item的图片
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        when (newState) {
            RecyclerView.SCROLL_STATE_IDLE -> {
                // 停止滚动时才加载图片
                Glide.with(context).resumeRequests()
            }
            else -> {
                // 滚动中暂停加载
                Glide.with(context).pauseRequests()
            }
        }
    }
})

使用WebP格式:

bash 复制代码
# 将PNG/JPG转换为WebP (减小30-50%文件大小)
cwebp input.png -q 80 -o output.webp

3.4 首屏渲染优化

首屏渲染是用户感知启动完成的关键时刻。

测量首屏渲染时间
kotlin 复制代码
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val startTime = System.currentTimeMillis()
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 监听首帧绘制完成
        window.decorView.post {
            val renderTime = System.currentTimeMillis() - startTime
            Log.d("Performance", "首屏渲染耗时: ${renderTime}ms")

            // 上报给分析平台
            Analytics.track("first_screen_render", mapOf("duration" to renderTime))
        }
    }
}
优化RecyclerView首屏加载
kotlin 复制代码
// 优化RecyclerView性能
recyclerView.apply {
    // 1. 设置固定大小 (跳过measure)
    setHasFixedSize(true)

    // 2. 增加View缓存
    setItemViewCacheSize(20)  // 默认2,增加到20
    recycledViewPool.setMaxRecycledViews(0, 20)

    // 3. 预取 (prefetch)
    (layoutManager as? LinearLayoutManager)?.apply {
        isItemPrefetchEnabled = true
        initialPrefetchItemCount = 4
    }

    // 4. 使用DiffUtil增量更新
    adapter = MyAdapter().apply {
        submitList(dataList)
    }
}
使用启动窗口 (SplashScreen API)
xml 复制代码
<!-- themes.xml -->
<!-- 定义启动主题,显示自定义背景 -->
<style name="AppTheme.Launcher" parent="Theme.SplashScreen">
    <item name="windowSplashScreenBackground">@color/splash_background</item>
    <item name="windowSplashScreenAnimatedIcon">@drawable/ic_launcher</item>
    <item name="postSplashScreenTheme">@style/AppTheme</item>
</style>
xml 复制代码
<!-- AndroidManifest.xml -->
<activity
    android:name=".MainActivity"
    android:theme="@style/AppTheme.Launcher"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
kotlin 复制代码
// MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // 安装SplashScreen
        val splashScreen = installSplashScreen()

        // 保持SplashScreen直到数据准备好
        var isReady = false
        splashScreen.setKeepOnScreenCondition { !isReady }

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 异步加载数据
        lifecycleScope.launch {
            loadData()
            isReady = true  // 数据准备好,移除SplashScreen
        }
    }
}

Activity优化效果对比:

方案 Activity+渲染耗时 总启动时间 优化幅度
原始方案 1400ms 1580ms -
异步加载 80ms 260ms ⬇️ 84%
数据预加载 80ms 260ms ⬇️ 84%
布局优化 50ms 230ms ⬇️ 85%
图片优化 80ms 260ms ⬇️ 84%
综合优化 50ms 230ms ⬇️ 85%

四、App热启动优化:毫秒级的用户体验

热启动是App从后台恢复的场景,虽然比冷启动快,但优化不当仍会让用户感到卡顿。

4.1 热启动性能测量

bash 复制代码
# 测量热启动时间
# 1. 启动App
adb shell am start-activity com.example.myapp/.MainActivity

# 2. 按Home键(App进入后台)
adb shell input keyevent KEYCODE_HOME

# 3. 等待2秒

# 4. 重新启动App并测量
adb shell am start-activity -W com.example.myapp/.MainActivity

# 输出:
# TotalTime: 450ms  ← 这就是热启动时间

4.2 热启动优化策略

策略1: 避免onResume()中的重复操作
kotlin 复制代码
// ❌ 不好的做法
class MainActivity : AppCompatActivity() {
    override fun onResume() {
        super.onResume()

        // 每次onResume都执行,包括热启动
        loadUserInfo()       // 400ms
        loadProductList()    // 300ms
        updateUI()          // 100ms

        // 热启动耗时: 800ms (太慢!)
    }
}
kotlin 复制代码
// ✅ 好的做法: 区分首次和后续Resume
class MainActivity : AppCompatActivity() {
    private var isFirstResume = true

    override fun onResume() {
        super.onResume()

        if (isFirstResume) {
            // 首次Resume: 加载所有数据
            loadUserInfo()
            loadProductList()
            updateUI()
            isFirstResume = false
        } else {
            // 热启动: 只刷新必要数据
            refreshData()  // 只刷新时间敏感的数据,如消息数、通知等
        }
    }

    private fun refreshData() {
        // 只刷新增量数据,耗时50ms
        lifecycleScope.launch {
            val newMessages = withContext(Dispatchers.IO) {
                MessageApi.getNewMessages()
            }
            updateMessageBadge(newMessages)
        }
    }
}
策略2: 缓存View状态
kotlin 复制代码
// 保存和恢复滚动位置
class MainActivity : AppCompatActivity() {
    private var scrollPosition = 0

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        // 保存滚动位置
        scrollPosition = recyclerView.computeVerticalScrollOffset()
        outState.putInt("scroll_position", scrollPosition)
    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle) {
        super.onRestoreInstanceState(savedInstanceState)
        // 恢复滚动位置
        scrollPosition = savedInstanceState.getInt("scroll_position", 0)
        recyclerView.post {
            recyclerView.scrollBy(0, scrollPosition)
        }
    }
}
策略3: 使用Fragment缓存
kotlin 复制代码
// ViewPager2缓存Fragment,避免重复创建
viewPager.offscreenPageLimit = 2  // 缓存左右各2页

// FragmentManager复用Fragment
supportFragmentManager.beginTransaction()
    .replace(R.id.container, fragment, "tag")
    .commit()

// 下次使用时先查找缓存
val cachedFragment = supportFragmentManager.findFragmentByTag("tag")
if (cachedFragment != null) {
    // 复用缓存的Fragment
    supportFragmentManager.beginTransaction()
        .show(cachedFragment)
        .commit()
} else {
    // 创建新Fragment
}

热启动优化效果:

方案 热启动耗时 优化幅度 用户体验
原始方案 800ms - 明显卡顿
避免重复操作 300ms ⬇️ 62.5% 轻微延迟
缓存View状态 250ms ⬇️ 68.75% 几乎无感知
Fragment缓存 200ms ⬇️ 75% 即时响应

五、车机场景特殊优化

车载系统对启动性能要求更高,同时有特殊的约束条件。

5.1 车机Boot优化特点

挑战:

  • 启动时间要求更严格 (< 10秒到Launcher,< 5秒到倒车影像)
  • 硬件资源相对受限 (CPU、RAM比旗舰手机低)
  • 安全关键功能必须快速可用 (倒车影像、胎压监测)

优化策略:

bash 复制代码
# init.rc: 关键服务优先启动
on boot
    # 优先级1: 倒车影像相关 (必须3秒内可用)
    start camera_hal
    start rvc_service  # Rear View Camera

    # 优先级2: 仪表显示
    start instrument_cluster

    # 优先级3: 其他功能 (延后启动)
    exec - root -- /system/bin/sh -c "start media_service &"
    exec - root -- /system/bin/sh -c "start navi_service &"

5.2 车机App启动优化

Launcher预加载常用App:

kotlin 复制代码
class CarLauncher : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_launcher)

        // 在Launcher显示后,后台预加载导航和音乐App
        window.decorView.post {
            preloadApp("com.example.navi")
            preloadApp("com.example.music")
        }
    }

    private fun preloadApp(packageName: String) {
        Thread {
            try {
                // 启动App但不显示
                val intent = packageManager.getLaunchIntentForPackage(packageName)
                intent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                startActivity(intent)

                // 延迟1秒后移到后台 (进程已创建,下次启动更快)
                Thread.sleep(1000)

                val am = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
                am.moveTaskToBack(true)
            } catch (e: Exception) {
                Log.e("Preload", "预加载失败: $packageName", e)
            }
        }.start()
    }
}

六、总结与最佳实践

6.1 启动优化方法论

1. 测量优先,数据驱动

  • 不要凭感觉优化,先用Systrace/Perfetto精确测量
  • 找到真正的瓶颈,而非想象中的瓶颈
  • 优化前后都要测量,验证效果

2. 分阶段优化

  • Application阶段: 延迟初始化、异步初始化、懒加载
  • Activity阶段: 异步加载数据、预加载、骨架屏
  • 首屏渲染: 布局优化、图片懒加载、RecyclerView优化

3. 异步为王

  • 能异步的坚决不同步
  • 主线程只做UI相关操作
  • 使用Coroutines/RxJava管理异步任务

4. 延迟加载

  • 非必须的功能延后初始化
  • 非首屏的内容延后加载
  • 使用IdleHandler、postDelayed、lazy等机制

5. 持续监控

  • 建立CI/CD自动化测试
  • 线上监控启动性能
  • 设置性能阈值,防止倒退

6.2 避坑指南

不要在Application中做太多事情

  • Application.onCreate()只初始化崩溃上报等必须的服务
  • 其他SDK异步或延迟初始化

不要在主线程做网络请求和数据库操作

不要过早初始化不需要的SDK

  • 很多SDK支持延迟初始化
  • 懒加载:用到时再初始化

不要在首屏加载所有数据

  • 先显示骨架屏,给用户即时反馈
  • 数据异步加载,分批渲染

不要忽略启动窗口的体验

  • 使用SplashScreen API提供平滑过渡
  • 避免白屏

6.3 性能基准参考

冷启动性能基准:

应用类型 优秀 良好 需优化
社交类 < 0.8s 0.8-1.5s > 1.5s
电商类 < 1s 1-1.8s > 1.8s
工具类 < 0.5s 0.5-1s > 1s
游戏类 < 2s 2-3s > 3s
车机应用 < 0.8s 0.8-1.2s > 1.2s

热启动性能基准:

应用类型 优秀 良好 需优化
所有类型 < 300ms 300-500ms > 500ms

6.4 参考资源

官方文档:

源码参考:

工具链:


作者简介: 多年Android系统开发经验,专注于系统稳定性与性能优化领域。欢迎关注本系列,一起深入Android系统的精彩世界!


🎉 感谢关注,让我们一起深入Android系统的精彩世界!

找到我 : 个人主页

相关推荐
·云扬·12 分钟前
MySQL四大系统库详解:作用、核心表与实用SQL查询
android·sql·mysql
普马萨特12 分钟前
移动网络信号指标与单位整理(2G/3G/4G/5G Android vs IoT)
android·网络·物联网
de之梦-御风21 分钟前
【电视投屏】针对“局域网投屏开源项目(Android 手机 ↔ Android TV)
android·智能手机·开源
threelab36 分钟前
Merge3D 三维引擎中 GeoJSON 数据加载的整体设计
android·3d
麦兜*1 小时前
【Spring Boot】 接口性能优化“十板斧”:从数据库连接到 JVM 调优的全链路提升
java·大数据·数据库·spring boot·后端·spring cloud·性能优化
优选资源分享1 小时前
Escrcpy 便携版 v2.0.0:安卓手机电脑同屏软件
android·智能手机·电脑
2501_915918412 小时前
介绍如何在电脑上查看 iPhone 和 iPad 的完整设备信息
android·ios·小程序·uni-app·电脑·iphone·ipad
TheNextByte12 小时前
如何通过蓝牙将照片从 iPhone 分享到Android ?
android·gitee·iphone
2501_916008892 小时前
没有 Mac 如何在 Windows 上创建 iOS 应用描述文件
android·macos·ios·小程序·uni-app·iphone·webview
Android系统攻城狮4 小时前
Android ALSA进阶之处理PCM的ioctl命令snd_pcm_lib_ioctl:用法实例(一百)
android·pcm·alsa·音频进阶