Android开机时长优化

文章目录

一、背景说明二、开机流程介绍三、分析方法&工具3.1 手动秒表计时3.2 bootchart3.2.1 生成log文件3.2.2 生成bootchart.png3.2.3 分析bootchart.png

3.3 bootlog3.3.1 完善sepolicyselinux修改案例:

3.3.2 关闭kernel log

3.4 PKMS apk扫描优化3.4.1 Android8.0版本之前的PKMS优化

3.5 其他

四、总结

一、背景说明

随着Android 版本的升级,rom越来越大,功能基础的越来越多,Android 启动时间随之也越来越长,本文将围绕Android 10.0 的开机时长优化展开探讨,软件环境如下:

平台:qcom 8970 Android 10.0

DDR/Flash: 4G /64G

开机时长:21s

二、开机流程介绍

为了更好地分析开机耗时原因,有必要了解系统在开机过程中执行了哪些动作,在结合数据找出哪些过程耗时较长,再进一步分析耗时原因。 Android 开机流程大致如下:

1、bootload识别到wakeup物理按键,进行开机,初始化一些基本外设,panel、keypad、led等,load property,播放bootlogo(开机第一画)等,并将kernel加载至ram。2、kernel启动,加载硬件有关的驱动,初始化内存,缓存等,查找并启动用户空间第一个进程init进程。3、init进程,解析init.rc文件,创建并挂载分区,启动zygote进程、bootanimation进程(开机第二画)等。4、zygote进程启动systemserver服务,fork其他应用进程。5、systemserver启动核心服务,系统服务,其他服务,其中包括AMS,PMS,PKMS等。6、打开Launcher应用。

三、分析方法&工具

为了搞清楚开机时长到底耗时多久,什么任务耗时,措施优化程度如何,需要使用合适的方法和工具进行分析和验证,下面将从以下几个方面展开分析。

3.1 手动秒表计时

通过秒表手动掐表计时能粗略得到手机开机时长,由于设备开机过程存在一定偶然和不确定性,为了提高结果准确性,于是适当增加样本数量。 计时方法:设备关机后,从按下power键唤醒设备,出现画面开始秒表计时,到设备启动显示Launcher画面结束秒表计时,取这段时间为开机时长。 采集样本数据如下:

序号开机时长(秒)120.0217.5317.7418.12517.5617.4718.0817.7918.31017.9平均时长17.8

样本1由于是烧录完系统第一次开机,很多程序进行首次初始化,因此开机时长会更长一点,从数据上可以看出,后续结果基本稳定,因此这里舍弃第一次次结果,取后9次数据平均值作为参考结果。

3.2 bootchart

bootchart是一个用于Linux启动过程性能分析的开源工具软件,在系统启动过程中自动手机CPU占用率,磁盘吞吐率,进程等信息,并以图形方式显示分析结果,指导优化开机启动过程。

3.2.1 生成log文件

bootchart在android源码的路径为 system\core\init\bootchart.cpp

static Result do_bootchart_start() { //启动bootchart

std::string start;

///data/bootchart/enabled标志文件存在才能启动bootchart

if (!android::base::ReadFileToString("/data/bootchart/enabled", &start)) {

return Success();

}

启动bootchart主线程

g_bootcharting_thread = new std::thread(bootchart_thread_main);

return Success();

}

static void bootchart_thread_main() {

// Open log files.//在/data/bootchart/路径下创建proc_stat.log、proc_ps.log、proc_diskstats.log

auto stat_log = fopen_unique("/data/bootchart/proc_stat.log", "we");

if (!stat_log) return;

auto proc_log = fopen_unique("/data/bootchart/proc_ps.log", "we");

if (!proc_log) return;

auto disk_log = fopen_unique("/data/bootchart/proc_diskstats.log", "we");

if (!disk_log) return;

log_header();

while (true) {

{

std::unique_lock lock(g_bootcharting_finished_mutex);

g_bootcharting_finished_cv.wait_for(lock, 200ms);

if (g_bootcharting_finished) break;

}

log_file(&*stat_log, "/proc/stat");

log_file(&*disk_log, "/proc/diskstats");

log_processes(&*proc_log);

}

}

从上面代码可以看出需要在设备中创建/data/bootchart/enabled

xxx:/ # su

xxx:/ # touch /data/bootchart/enabled //创建/data/bootchart/enabled文件

xxx:/ # ls -al /data/bootchart/

total 11

drwxr-xr-x 2 shell shell 3488 2022-12-01 09:05 .

drwxrwx--x 52 system system 4096 2022-12-01 08:54 ..

-rw-rw-rw- 1 root root 0 2022-12-01 09:06 enabled

重启设备可以发现生成以下文件:

xxx:/ # ls -al /data/bootchart/

total 4072

drwxr-xr-x 2 shell shell 3488 2022-12-01 09:09 .

drwxrwx--x 52 system system 4096 2022-12-01 09:09 ..

-rw-rw-rw- 1 root root 0 2022-12-01 09:06 enabled

-rw-rw-rw- 1 root root 1326 2022-12-01 09:09 header

-rw-rw-rw- 1 root root 214560 2022-12-01 09:09 proc_diskstats.log

-rw-rw-rw- 1 root root 3892696 2022-12-01 09:09 proc_ps.log

-rw-rw-rw- 1 root root 39506 2022-12-01 09:09 proc_stat.log

3.2.2 生成bootchart.png

首先需要在源码的系统环境中安装bootchart,由于不同ubuntu版本差异的原因,推荐使用ubuntu16.04

sudo apt upgrade

sudo apt update

//ubuntu16.04

sudo apt install bootchart

//ubuntu18.04以上

sudo apt install bootchart pybootchartgui

方式一、 可以通过源码的脚本自动生成bootchart.png:

mart!nhu@xxx:~/repo_work/AOSP$ system/core/init/grab-bootchart.sh

parsing '/tmp/android-bootchart/bootchart.tgz'

parsing 'header'

parsing 'proc_stat.log'

parsing 'proc_ps.log'

warning: no parent for pid '2' with ppid '0'

parsing 'proc_diskstats.log'

merged 0 logger processes

pruned 142 process, 0 exploders, 19 threads, and 0 runs

False

bootchart written to 'bootchart.png'

system/core/init/grab-bootchart.sh: 21: system/core/init/grab-bootchart.sh: gnome-open: not found

Clean up /tmp/android-bootchart/ and ./bootchart.png when done

mart!nhu@xxx:~/repo_work/AOSP$

脚本源码解读 system\core\init\grab-bootchart.sh

#!/bin/sh

#

# This script is used to retrieve a bootchart log generated by init.

# All options are passed to adb, for better or for worse.

# See the readme in this directory for more on bootcharting.

#临时存放bootchart.tgz和bootchart.png路径

TMPDIR=/tmp/android-bootchart

rm -rf $TMPDIR

mkdir -p $TMPDIR

LOGROOT=/data/bootchart

TARBALL=bootchart.tgz

FILES="header proc_stat.log proc_ps.log proc_diskstats.log"

#获取设备/data/bootchart/文件,保存至临时路径

for f in $FILES; do

adb "${@}" pull $LOGROOT/$f $TMPDIR/$f 2>&1 > /dev/null

done

#打包为bootchart.tgz

(cd $TMPDIR && tar -czf $TARBALL $FILES)

#--android 9.0--#

#bootchart ${TMPDIR}/${TARBALL}

#gnome-open ${TARBALL%.tgz}.png

#--android 10.0--解析bootchart.tgz生成bootchart.png#

pybootchartgui ${TMPDIR}/${TARBALL}

#打开bootchart.png

xdg-open ${TARBALL%.tgz}.png

echo "Clean up ${TMPDIR}/ and ./${TARBALL%.tgz}.png when done"

从脚本中不难看出:

1、通过adb 将设备中/data/bootchart/下的header及3个log文件pull至本地/tmp/android-bootchart2、将tmp/android-bootchart/中的文件打包为bootchart.tgz3、使用bootchart(AN9)/pybootchartgui(AN10)处理bootchart.tgz生成bootchart.png4、使用gnome(AN9)/xdg-open(AN10)打开生成的bootchart.png

如果grab-bootchart.sh脚本运行出错,需要自行分析处理,或者按以下步骤尝试手动bootchart处理 方式二、

安装bootchart

$sudo apt install bootchart

$bootchart --version

bootchart v0.0.0

打包/data/bootchart/生成bootchart.png

$cd /data/bootchart/

$ tar -czf bootchart.tgz header *.log //将/data/bootchart/ header和3个log文件打包为bootchart.tgz

mart!nhu:/data/bootchart $ ls -al

total 4692

drw-r--r-- 2 shell shell 3488 2022-12-01 10:13 .

drwxrwx--x 52 system system 4096 2022-12-01 09:09 ..

-rw-rw-rw- 1 root root 630905 2022-12-01 10:13 bootchart.tgz

-rw-r--r-- 1 root root 0 2022-12-01 09:06 enabled

-rw-r--r-- 1 root root 1326 2022-12-01 09:09 header

-rw-r--r-- 1 root root 214560 2022-12-01 09:09 proc_diskstats.log

-rw-r--r-- 1 root root 3892696 2022-12-01 09:09 proc_ps.log

-rw-r--r-- 1 root root 39506 2022-12-01 09:09 proc_stat.log

将bootchart.tgz放至ubuntu(推荐16.04)环境下,使用bootchart生成bootchart.png

$ bootchart bootchart.tgz parsing 'bootchart.tgz'

parsing 'bootchart.tgz'

parsing 'header'

parsing 'proc_diskstats.log'

parsing 'proc_ps.log'

warning: no parent for pid '2' with ppid '0'

parsing 'proc_stat.log'

warning: path 'parsing' does not exist, ignoring.

parsing 'bootchart.tgz'

parsing 'header'

parsing 'proc_diskstats.log'

parsing 'proc_ps.log'

warning: no parent for pid '2' with ppid '0'

parsing 'proc_stat.log'

merged 0 logger processes

pruned 231 process, 0 exploders, 8 threads, and 1 runs

False

bootchart written to 'bootchart.png'

方式三、 使用bootchart源码生成bootchart.jar 源码地址 bootchart.jar java -jar bootchart.jar boochart.tgz

得到bootchart.png:

3.2.3 分析bootchart.png

bootchart.png分为四部分:

图表开头描述的设备的基本信息:linux内核版本,CPU型号,kernel配置,已经启动耗时时长。开机过程CPU,I/O利用情况。开机过程磁盘利用情况。开机过程各进程运行情况,可以看到各进程的启动时间,并检查是否存在可移除进程。

图表以时间线为轴,每一小格表示1秒。由于bootchart的测量时间段是init进程启动之后,不包含uboot和kernel的启动时间,time记录的时间为bootchart从init启动后至boot.completed=1所耗时间。 默认无优化措施软件10次bootchart time数据如下:

序号time(秒)114.94215.20315.39415.04515.26615.46715.46814.90915.221015.22平均15.21

这里的平均结果将与优化措施进行对比。

3.3 bootlog

结合3.1的数据结果,为了更好的分析开机耗时原因,可以抓取对应的log进行分析。 adb 无法抓到完整的开机log,如果有条件可以通过串口抓到完成的开机log,分为以下几个模块分析开机耗时情况:

bootload时长。kernel时长。init进程,system_server进程时长。bootanimation时长。launcher启动时长。

使用adb 获取开机日志dmesg

adb shell "dmesg" >boot.log

分析dmesg log查找一些重复且可疑的打印:

1、频繁出现avc: denied xxx2、频繁出现类似[kworke]的[xxx]打印 根据以上两点可进行优化

3.3.1 完善sepolicy

avc: denied表示SeLinux安全策略拒绝了某些进程的某些动作,例如访问系统熟悉,读写app文件数据等。频繁的avc denied占用了一定系统资源,增加了开机耗时。

selinux修改案例:

例如avc log如下:

12-29 10:08:14.769 638 638 I health@2.0-serv: type=1400 audit(0.0:79): avc: denied { read } for name="present" dev="sysfs" ino=42693 scontext=u:r:hal_health_default:s0 tcontext=u:object_r:sysfs:s0 tclass=file permissive=1

12-29 10:08:14.769 638 638 I health@2.0-serv: type=1400 audit(0.0:80): avc: denied { open } for path="/sys/devices/platform/soc/4a84000.i2c/i2c-0/0-000b/power_supply/battery/present" dev="sysfs" ino=42693 scontext=u:r:hal_health_default:s0 tcontext=u:object_r:sysfs:s0 tclass=file permissive=1

denied {xxx}: 表示缺少什么权限scontext:表示谁缺少权限tcontext:表示对那些文件缺少权限:tclass:表示什么文件类型缺少权限

avc: denied { read } for name=“present” dev=“sysfs” ino=42693 scontext=u:r:hal_health_default:s0 tcontext=u:object_r:sysfs:s0 tclass=file permissive=1 表示hal_health_default进程对sysfs的file文件类型缺少read权限

avc: denied { open } for path=“/sys/devices/platform/soc/4a84000.i2c/i2c-0/0-000b/power_supply/battery/present” dev=“sysfs” ino=42693 scontext=u:r:hal_health_default:s0 tcontext=u:object_r:sysfs:s0 tclass=file permissive=1 表示hal_health_default进程对sysfs的file文件类型缺少open权限

修改公式如下: 通常需要在${scontext}.te添加 allow scontext tcontext:tclass denied 带入内容:修改hal_health_default.te

allow hal_health_default sysfs:fie { read }

allow hal_health_default sysfs:fie { open}

可以合并为:

allow hal_health_default sysfs:fie { read open }

优化sepolicy后bootchart获取的开机时长数据如下:

序号开机时长(秒)114.97215.25315.28415.25515.51615.19714.73815.21915.041014.81平均15.12

3.3.2 关闭kernel log

根据频繁[kworke]打印,在源码中搜索可知该打印埋在kernel 代码中,可通过以下命令查看kernel log level:

$ cat /pro/sys/kernel/printk

6 6 1 7

通过修改源码 设置默认kernel log level device/qcom/common/rootdir/etc/init.qcom.sh

case "$buildvariant" in

"userdebug" | "eng")

#set default loglevel to KERN_INFO

- echo "6 6 1 7" > /proc/sys/kernel/printk

+ echo "0 6 0 7" > /proc/sys/kernel/printk

;;

*)

#set default loglevel to KERN_WARNING

echo "4 4 1 4" > /proc/sys/kernel/printk

;;

esac

关闭kernel log后,bootchart获取的开机时长数据如下:

序号开机时长(秒)115.28215.28314.77415.01515.22615.21714.96815.39915.351014.61平均15.11

对比默认15.21,关闭kernle log节省开机时长0.1秒 kernel log level详细介绍参考文档:Android 10 设置kernel log level

3.4 PKMS apk扫描优化

PackageManagerService.java(PKMS)会在开机过程中通过其构造方法把设备安装的app进行扫描,检查apk的合法性,扫描apk的AndroidManifest.xml的一些属性,以及读取apk的asset等,并记录到mSettings结构体中。 在Android 8.0之前,是单线程扫描,8.0之后通过ParallelPackageParser类多线程扫描。 frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java

private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {

//获取apk扫描路径列表

final File[] files = scanDir.listFiles();

if (ArrayUtils.isEmpty(files)) {

Log.d(TAG, "No files in app dir " + scanDir);

return;

}

//创建parallelPackageParser并行安装包解析类实例,进行多线程扫描apk

try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(

mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,

mParallelPackageParserCallback)) {

// Submit files for parsing in parallel

int fileCount = 0;

for (File file : files) {

final boolean isPackage = (isApkFile(file) || file.isDirectory())

&& !PackageInstallerService.isStageName(file.getName());

if (!isPackage) {

// Ignore entries which are not packages

continue;

}

//解析apk

parallelPackageParser.submit(file, parseFlags);

fileCount++;

}

// Process results one by one,处理apk解析结果

for (; fileCount > 0; fileCount--) {

ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();

Throwable throwable = parseResult.throwable;

int errorCode = PackageManager.INSTALL_SUCCEEDED;

if (throwable == null) {

// TODO(toddke): move lower in the scan chain

// Static shared libraries have synthetic package names

if (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) {

renameStaticSharedLibraryPackage(parseResult.pkg);

}

try {

scanPackageChildLI(parseResult.pkg, parseFlags, scanFlags,

currentTime, null);

} catch (PackageManagerException e) {

errorCode = e.error;

Slog.w(TAG, "Failed to scan " + parseResult.scanFile + ": " + e.getMessage());

}

} else if (throwable instanceof PackageParser.PackageParserException) {

PackageParser.PackageParserException e = (PackageParser.PackageParserException)

throwable;

errorCode = e.error;

Slog.w(TAG, "Failed to parse " + parseResult.scanFile + ": " + e.getMessage());

} else {

throw new IllegalStateException("Unexpected exception occurred while parsing "

+ parseResult.scanFile, throwable);

}

// Delete invalid userdata apps

if ((scanFlags & SCAN_AS_SYSTEM) == 0 &&

errorCode != PackageManager.INSTALL_SUCCEEDED) {

logCriticalInfo(Log.WARN,

"Deleting invalid package at " + parseResult.scanFile);

removeCodePathLI(parseResult.scanFile);

}

}

}

}

frameworks\base\services\core\java\com\android\server\pm\ParallelPackageParser.java

class ParallelPackageParser implements AutoCloseable {

private static final int QUEUE_CAPACITY = 10;//多线程队列容量

private static final int MAX_THREADS = 4;//最大线程数

private final String[] mSeparateProcesses;

private final boolean mOnlyCore;

private final DisplayMetrics mMetrics;

private final File mCacheDir;

private final PackageParser.Callback mPackageParserCallback;

private volatile String mInterruptedInThread;

private final BlockingQueue mQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);

private final ExecutorService mService = ConcurrentUtils.newFixedThreadPool(MAX_THREADS,

"package-parsing-thread", Process.THREAD_PRIORITY_FOREGROUND);

ParallelPackageParser(String[] separateProcesses, boolean onlyCoreApps,

DisplayMetrics metrics, File cacheDir, PackageParser.Callback callback) {

mSeparateProcesses = separateProcesses;

mOnlyCore = onlyCoreApps;

mMetrics = metrics;

mCacheDir = cacheDir;

mPackageParserCallback = callback;

}

为了进一步加快开机时apk扫描,

一方面可以裁剪系统不必要的apk,从而减少apk扫描个数,缩短开机apk扫描耗时另一方面增大ParallelPackageParser线程数,提高apk扫描速度。 frameworks\base\services\core\java\com\android\server\pm\ParallelPackageParser.java

class ParallelPackageParser implements AutoCloseable {

private static final int QUEUE_CAPACITY = 10;

- private static final int MAX_THREADS = 4;

+ private static final int MAX_THREADS = 8;

private final String[] mSeparateProcesses;

优化后结果如下:

序号开机时长(秒)114.70214.98313.86414.46513.99613.69714.04813.90914.431014.96平均14.30

3.4.1 Android8.0版本之前的PKMS优化

如上所述Android8.0之前PKMS是单线程扫描apk,优化措施如下: 新建多线程处理类MultiTaskDealer.java frameworks/base/services/core/java/com/android/server/pm/MultiTaskDealer.java

package com.android.server.pm;

import java.lang.ref.WeakReference;

import java.util.HashMap;

import java.util.concurrent.LinkedBlockingQueue;

import java.util.concurrent.ThreadFactory;

import java.util.concurrent.ThreadPoolExecutor;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.atomic.AtomicInteger;

import java.util.concurrent.locks.ReentrantLock;

import android.util.Log;

public class MultiTaskDealer {

public static final String TAG = "MultiTaskDealer";

public static final String PACKAGEMANAGER_SCANER = "packagescan";

private static final boolean DEBUG_TASK = false;

private static HashMap> map = new HashMap>();

public static MultiTaskDealer getDealer(String name) {

WeakReference ref = map.get(name);

MultiTaskDealer dealer = ref!=null?ref.get():null;

return dealer;

}

public static MultiTaskDealer startDealer(String name,int taskCount) {

MultiTaskDealer dealer = getDealer(name);

if(dealer==null) {

dealer = new MultiTaskDealer(name,taskCount);

WeakReference ref = new WeakReference(dealer);

map.put(name,ref);

}

return dealer;

}

public void startLock() {

mLock.lock();

}

public void endLock() {

mLock.unlock();

}

private ThreadPoolExecutor mExecutor;

private int mTaskCount = 0;

private boolean mNeedNotifyEnd = false;

private Object mObjWaitAll = new Object();

private ReentrantLock mLock = new ReentrantLock();

public MultiTaskDealer(String name,int taskCount) {

final String taskName = name;

ThreadFactory factory = new ThreadFactory()

{

private final AtomicInteger mCount = new AtomicInteger(1);

public Thread newThread(final Runnable r) {

if (DEBUG_TASK) Log.d(TAG, "create a new thread:" + taskName);

return new Thread(r, taskName + "-" + mCount.getAndIncrement());

}

};

mExecutor = new ThreadPoolExecutor(taskCount, taskCount, 5, TimeUnit.SECONDS,

new LinkedBlockingQueue(), factory){

protected void afterExecute(Runnable r, Throwable t) {

if(t!=null) {

t.printStackTrace();

}

MultiTaskDealer.this.TaskCompleteNotify(r);

if (DEBUG_TASK) Log.d(TAG, "end task");

super.afterExecute(r,t);

}

protected void beforeExecute(Thread t, Runnable r) {

if (DEBUG_TASK) Log.d(TAG, "start task");

super.beforeExecute(t,r);

}

};

}

public void addTask(Runnable task) {

synchronized (mObjWaitAll) {

mTaskCount+=1;

}

mExecutor.execute(task);

if (DEBUG_TASK) Log.d(TAG, "addTask");

}

private void TaskCompleteNotify(Runnable task) {

synchronized (mObjWaitAll) {

mTaskCount-=1;

if(mTaskCount<=0 && mNeedNotifyEnd) {

if (DEBUG_TASK) Log.d(TAG, "complete notify");

mObjWaitAll.notify();

}

}

}

public void waitAll() {

if (DEBUG_TASK) Log.d(TAG, "start wait all");

synchronized (mObjWaitAll) {

if(mTaskCount>0) {

mNeedNotifyEnd = true;

try {

mObjWaitAll.wait();

} catch (Exception e) {

}

mNeedNotifyEnd = false;

}

if (DEBUG_TASK) Log.d(TAG, "wait finish");

return;

}

}

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

+ import com.android.server.pm.MultiTaskDealer;

...

private void scanDirLI(File dir, final int parseFlags, int scanFlags, long currentTime) {

final File[] files = dir.listFiles();

if (ArrayUtils.isEmpty(files)) {

Log.d(TAG, "No files in app dir " + dir);

return;

}

//通过系统属性persist.pm.multitask灵活设置apk扫描多线程数

+ int iMultitaskNum = SystemProperties.getInt("persist.pm.multitask", 6);

+ final MultiTaskDealer dealer = (iMultitaskNum > 1) ? MultiTaskDealer.startDealer(

MultiTaskDealer.PACKAGEMANAGER_SCANER, iMultitaskNum) : null;

for (File file : files) {

final boolean isPackage = (isApkFile(file) || file.isDirectory())

&& !PackageInstallerService.isStageName(file.getName());

if (!isPackage) {

// Ignore entries which are not packages

continue;

}

try {

scanPackageTracedLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,

scanFlags, currentTime, null);

} catch (PackageManagerException e) {

Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage());

// Delete invalid userdata apps

if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&

e.error == PackageManager.INSTALL_FAILED_INVALID_APK) {

logCriticalInfo(Log.WARN, "Deleting invalid package at " + file);

removeCodePathLI(file);

}

}

if (dealer != null)

dealer.addTask(scanTask);

else

scanTask.run();

}

if (dealer != null)

dealer.waitAll();

Log.d(TAG, "end scanDirLI:"+dir);

}

3.5 其他

除了上述优化措施,还可以在通过拿掉或缩短开机动画时间来缩短开机时长,根据设备特点,关闭不必要的系统启动服务。

四、总结

总结本文内容如下: 1、系统开机主要流程:

bootloader: 开机引导程序,初始化panel、led、keypad等基本外设,初始化env和prop,加载kernel等。kernel:加载硬件相关驱动程序,查找并启动init进程。init进程:解析init.rc文件,创建并挂载分区,启动zygote服务和其他服务。zygote:启动systemserver,fork应用进程systemserver:启动系统核心服务,其他服务。显示开机动画,进入Launcher,显示桌面

2、bootchart开机图的生成步骤,如何简要分析 3、开机时长优化措施及数据效果

分析开机log:sepolicy优化分析开机log:关闭/降低kernel log levelPKMS多线程扫描apk优化移除系统不必要apk、服务移除/缩短开机动画时长

导入sepolicy优化,PKMS多线程扫描apk优化、关闭kernel log lever优化后的开机时长数据如下:

序号开机时长(秒)115.14215.14313.85413.66515.08613.66715.09814.06914.011014.81平均14.45