/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);
....
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);
}
});
.....
}
本质上是通过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();
.....
}
优化前,首先会收集是否进行优化的先要条件,所有先要条件达成时,才会进行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;
}
对系统预编译时,不管是第三方应用还是系统应用默认会进行64位预编译,而在首次开机时,对于第三方应用会再进行一次预编译,很大程度增加了第一次开机启动的时间
通过阅读上述关于开机扫描的流程,可以发现开机扫描时是根据apk的so库指令集去搜索该指令集下是否有.odex文件,来判断是否要做dexopt优化
在64位系统中默认对第三方应用进行64位预编译优化,而第三方应用很大概率使用的是32位指令集so,在开机扫描时会去扫描 oat/arm 目录下的odex文件而不会扫描我们预编译时在 oat/arm64 下的产物,所以开机时会重新进行32位dexopt优化,正是因为多个第三方应用再开机时进行32位dexopt优化,导致系统第一次开机时间变长
在对第三方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)