当前位置:   article > 正文

Android开机时长优化_android 开机时间优化

android 开机时间优化


一、背景说明

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

	平台:qcom 8970 Android 10.0
	DDR/Flash: 4G /64G
	开机时长:21s
  • 1
  • 2
  • 3

二、开机流程介绍

为了更好地分析开机耗时原因,有必要了解系统在开机过程中执行了哪些动作,在结合数据找出哪些过程耗时较长,再进一步分析耗时原因。
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.0
217.5
317.7
418.12
517.5
617.4
718.0
817.7
918.3
1017.9
平均时长17.8

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

3.2 bootchart

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

3.2.1 生成log文件

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

static Result<Success> 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<std::mutex> 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);
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

从上面代码可以看出需要在设备中创建/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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

可以通过源码的脚本自动生成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$ 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

脚本源码解读
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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

从脚本中不难看出:

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

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

  • 安装bootchart
$sudo apt install bootchart
$bootchart --version
bootchart v0.0.0
  • 1
  • 2
  • 3
  • 打包/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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

将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'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

得到bootchart.png:
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.94
215.20
315.39
415.04
515.26
615.46
715.46
814.90
915.22
1015.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
  • 1

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

  • 1、频繁出现avc: denied xxx
  • 2、频繁出现类似[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
  • 1
  • 2
  • 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}
  • 1
  • 2

可以合并为:

allow hal_health_default sysfs:fie { read open }
  • 1

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

序号开机时长(秒)
114.97
215.25
315.28
415.25
515.51
615.19
714.73
815.21
915.04
1014.81
平均15.12

3.3.2 关闭kernel log

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

$ cat /pro/sys/kernel/printk
6 6 1 7
  • 1
  • 2

通过修改源码 设置默认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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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

序号开机时长(秒)
115.28
215.28
314.77
415.01
515.22
615.21
714.96
815.39
915.35
1014.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);
                }
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64

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<ParseResult> 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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

为了进一步加快开机时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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

优化后结果如下:

序号开机时长(秒)
114.70
214.98
313.86
414.46
513.99
613.69
714.04
813.90
914.43
1014.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<String, WeakReference<MultiTaskDealer>> map = new HashMap<String, WeakReference<MultiTaskDealer>>();

public static MultiTaskDealer getDealer(String name) {
    WeakReference<MultiTaskDealer> 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<MultiTaskDealer> ref = new WeakReference<MultiTaskDealer>(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<Runnable>(), 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;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112

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);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

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 level
  • PKMS多线程扫描apk优化
  • 移除系统不必要apk、服务
  • 移除/缩短开机动画时长

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

序号开机时长(秒)
115.14
215.14
313.85
413.66
515.08
613.66
715.09
814.06
914.01
1014.81
平均14.45
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/227964
推荐阅读
相关标签
  

闽ICP备14008679号