(安卓/android)第一次开机扫描过程分析与第一次启动速度优化

第一次开机扫描过程分析与第一次启动速度优化

第一次开机扫描过程分析

1.PackageMangerService的启动

不是本篇主要内容,简要了解即可
(安卓/android)第一次开机扫描过程分析与第一次启动速度优化_第1张图片

2.指定以下几个目录进行扫描

/data/data
/data/app
/data/app-lib
/data/user
/data/app-private
/vender/operater/app

PackageManagerService.java

scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM
        | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0);

scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM        | PackageParser.PARSE_IS_SYSTEM_DIR        | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);


scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM        | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);


scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM        | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);


scanDirLI(mOperatorAppInstallDir, PackageParser.PARSE_IS_OPERATOR, scanFlags, 0);


scanDirLI(mCustomAppInstallDir, PackageParser.PARSE_IS_OPERATOR        | PackageParser.PARSE_IS_SYSTEM        | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);


scanDirLI(mPluginAppInstallDir, PackageParser.PARSE_IS_OPERATOR        | PackageParser.PARSE_IS_SYSTEM        | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

....

3.遍历每一个子目录的.apk文件, 通过线程池去执行Apk的信息读取

PackageManagerService.java
  
  for (final File file : files) {
  final boolean isPackage = (isApkFile(file) || file.isDirectory())
            && !PackageInstallerService.isStageName(file.getName());   

  if (!isPackage) {
        // Ignore entries which are not packages        
     continue;    
  }

     executorService.submit(new Runnable() {
             @Override       
            public void run() {
            scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK, scanFlags, currentTime, null);       
    }
    });
 .....
}

4.通过PackageParser读取APK信息

本质上是通过XmlResourceParser读取Manifest信息, 将APK信息封装为PackageParser.Package数据结构,包括四大组件,包名,权限等等

PackageParser.java
private PackageParser.Package parseBaseApk(Resources res, XmlResourceParser parser, int flags, String[] outError){
    Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs, flags);
    pkgName = (String)packageSplit.first;
    splitName = (String)packageSplit.second;
    .....
}
public static final class Package {
    public String packageName;
    public String[] splitNames;
    public final ApplicationInfo applicationInfo = new ApplicationInfo();
    public final ArrayList<PackageParser.Permission> permissions = new ArrayList(0);
    public final ArrayList<PackageParser.PermissionGroup> permissionGroups = new ArrayList(0);
    public final ArrayList<PackageParser.Activity> activities = new ArrayList(0);
    public final ArrayList<PackageParser.Activity> receivers = new ArrayList(0);
    public final ArrayList<PackageParser.Provider> providers = new ArrayList(0);
    public final ArrayList<PackageParser.Service> services = new ArrayList(0);
    public final ArrayList<PackageParser.Instrumentation> instrumentation = new ArrayList(0);
    public final ArrayList<String> requestedPermissions = new ArrayList();
    
    .....
 }

5.读取完APK信息后, 判断是否进行dexOpt优化

优化前,首先会收集是否进行优化的先要条件,所有先要条件达成时,才会进行dexOpt优化

首先会获取apk的so库指令集,市面上大多数应用为了压缩apk体积且对老设备兼容,一般只会有32位so库,少部分应用有多种指令集so库

InstructionSets.java

public static String[] getAppDexInstructionSets(ApplicationInfo info) {
    if (info.primaryCpuAbi != null) {
        if (info.secondaryCpuAbi != null) {
            return new String[] {
                    VMRuntime.getInstructionSet(info.primaryCpuAbi),
                    VMRuntime.getInstructionSet(info.secondaryCpuAbi) };
        } 
    }

    return new String[] { getPreferredInstructionSet() };
}

由于访问安装目录需要root权限, 接下来的判断工作会通过jni交由oat_file_assistant.cc处理,在上一步我们拿到应用的指令集,假如这个指令集是arm64, 接下来会在apk根目录下判断是否有 oat/arm64/base.odex 文件 ,如果有则不进行oatOpt处理,如果没有则通过dex2oat做优化处理

oat_file_assistant.cc

bool OatFileAssistant::DexFilenameToOdexFilename(const std::string& location,
    InstructionSet isa, std::string* odex_filename, std::string* error_msg) {
  size_t pos = location.rfind('/');
  if (pos == std::string::npos) {
    *error_msg = "Dex location " + location + " has no directory.";
    return false;
  }
  std::string dir = location.substr(0, pos+1);
  dir += "oat/" + std::string(GetInstructionSetString(isa));

  std::string file;
  if (pos == std::string::npos) {
    file = location;
  } else {
    file = location.substr(pos+1);
  }

  pos = file.rfind('.');
  if (pos == std::string::npos) {
    *error_msg = "Dex location " + location + " has no extension.";
    return false;
  }
  std::string base = file.substr(0, pos);

  *odex_filename = dir + "/" + base + ".odex";
  return true;
}

启动速度优化

1.背景

对系统预编译时,不管是第三方应用还是系统应用默认会进行64位预编译,而在首次开机时,对于第三方应用会再进行一次预编译,很大程度增加了第一次开机启动的时间

2.原因分析

  • 通过阅读上述关于开机扫描的流程,可以发现开机扫描时是根据apk的so库指令集去搜索该指令集下是否有.odex文件,来判断是否要做dexopt优化

  • 在64位系统中默认对第三方应用进行64位预编译优化,而第三方应用很大概率使用的是32位指令集so,在开机扫描时会去扫描 oat/arm 目录下的odex文件而不会扫描我们预编译时在 oat/arm64 下的产物,所以开机时会重新进行32位dexopt优化,正是因为多个第三方应用再开机时进行32位dexopt优化,导致系统第一次开机时间变长

3.解决

在对第三方apk编译时通过 LOCAL_MULTILIB := 32 强制使用32位指令集进行预编译

Android.mk

include $(CLEAR_VARS)
# Module name should match apk name to be installed
LOCAL_MODULE := MXPlayer
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := PRESIGNED
LOCAL_MODULE_PATH := $(TARGET_OUT)/vendor/operator/app
LOCAL_MULTILIB := 32
# 如果APP在64位统下找不到32位so库,可以通过@指定需要加载的so库,so文件会在开机时从
# APK中提取到安装位置
LOCAL_PREBUILT_JNI_LIBS := \
        @lib/armeabi-v7a/libmxvp.so \
        @lib/armeabi-v7a/libmxutil.so  \
        @lib/armeabi-v7a/libmxsysdec.21.so \
        @lib/armeabi-v7a/libmxsysdec.18.so \
        @lib/armeabi-v7a/libmxsysdec.14.so \
        @lib/armeabi-v7a/libmxsysdec.11.so \
        @lib/armeabi-v7a/libmxass.so \
        @lib/armeabi-v7a/libloader.mx.so \
        @lib/armeabi-v7a/libft2.mx.so \
        @lib/armeabi-v7a/libffmpeg.mx.so
include $(BUILD_PREBUILT)

你可能感兴趣的:(android)