概述
APK安装流程
(1) 拷贝文件到指定的目录
在Android系统中,APK安装文件是会被保存起来的,默认情况下,用户安装的apk首先会被拷贝到/data/app目录下,/data/app目录是用户有权限访问的目录,在安装APK的时候会自动选择该目录存放用户安装的文件,而系统APK文件则被放到了/system分区下,包括/system/app,/system/vendor/app,以及/system/priv-app等等,该分区只有ROOT权限的用户才能访问,这也就是为什么在没有Root手机之前,我们没法删除系统APP的原因。
(2) 解压缩APK,拷贝可执行文件,创建应用的数据目录
为了加快APP的启动速度,APK在安装的时候,会首先将APP的可执行文件dex拷贝到/data/dalvik-cache目录,缓存起来。然后,在/data/data/目录下创建应用程序的数据目录(以应用的包名命名),存放在应用的相关数据,如数据库、xml文件、cache、二进制的so动态库等。
(3) 解析APK的AndroidManifest.xml文件
系统在安装这个APK的过程中,会解析APK的AndroidManifest.xml文件,提取出这个APK的重要信息写入到packages.xml文件中,这些信息包括:权限、应用包名、APK的安装位置、版本、userID等等。
(4) APK信息记录到系统
packages.xm就是Android系统中一个类似注册表的文件,用来记录当前所有安装的应用的基本信息,每次系统安装或者卸载了任何APK文件,都会更新这个文件。这个文件位于如下目录:/data/system/packages.xml。
(5) 显示快捷方式
如果这些应用程序在PackageManagerService服务注册好了,如果我们想要在Android桌面上看到这些应用程序,还需要有一个Home应用程序,负责从PackageManagerService服务中把这些安装好的应用程序取出来,并以友好的方式在桌面上展现出来,例如以快捷图标的形式。在Android系统中,负责把系统中已经安装的应用程序在桌面中展现出来的Home应用就是Launcher了。
APK安装方式
Android上应用安装可以分为以下几种方式:
- 系统预装;
- adb install 命令安装:没有安装界面;
- 应用市场安装;
- 第三方安装:有安装界面,通过packageinstaller.apk来处理安装及卸载的过程的界面。
APK安装目录
应用安装涉及到的目录:
- /system/app:系统自带的应用程序,获得adb root 权限才能删除;
- /data/app:用户程序安装的目录。安装时把apk文件复制到此目录;
- /data/data:存放应用程序的数据;
- /data/dalvik-cache:将apk中的dex文件安装到dalvik-cache目录下(dex文件是dalvik虚拟机的可执行文件,ART-Android Runtime的可执行文件格式为.oat,启动ART时,系统会执行dex文件转换至oat文件);
- /data/system:包含手机系统的相关信息。
APK安装过程
Base on: Android 13
Branch: android-13.0.0_r30
PackageInstaller介绍
PackageInstaller的包名为com.android.packageinstaller,是Android默认的用来安装普通APP的程序。
PackageInstallerActivity.onCreate()
PackageInstallerActivity是com.android.packageinstaller进程中用户可见的Activity,首先在其中执行一些对APK的操作。
protected void onCreate(Bundle icicle) {
if (mLocalLOGV) Log.i(TAG, "creating for user " + getUserId());
getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
super.onCreate(null);
if (icicle != null) {
mAllowUnknownSources = icicle.getBoolean(ALLOW_UNKNOWN_SOURCES_KEY);
}
mPm = getPackageManager();
mIpm = AppGlobals.getPackageManager();
mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
mInstaller = mPm.getPackageInstaller();
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
final Intent intent = getIntent();
mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE);
mCallingAttributionTag = intent.getStringExtra(EXTRA_CALLING_ATTRIBUTION_TAG);
mSourceInfo = intent.getParcelableExtra(EXTRA_ORIGINAL_SOURCE_INFO);
mOriginatingUid = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
PackageInstaller.SessionParams.UID_UNKNOWN);
mOriginatingPackage = (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN)
? getPackageNameForUid(mOriginatingUid) : null;
final Uri packageUri;
if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction())) {
final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
finish();
return;
}
mSessionId = sessionId;
packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath));
mOriginatingURI = null;
mReferrerURI = null;
} else {
mSessionId = -1;
packageUri = intent.getData();
mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
}
// if there's nothing to do, quietly slip into the ether
if (packageUri == null) {
Log.w(TAG, "Unspecified source");
setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
finish();
return;
}
if (DeviceUtils.isWear(this)) {
showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
return;
}
boolean wasSetUp = processPackageUri(packageUri);
if (mLocalLOGV) Log.i(TAG, "wasSetUp: " + wasSetUp);
if (!wasSetUp) {
return;
}
}
执行了以下步骤:
- 初始化相关参数:如mPm、mAppOpsManager、mUserManager、mCallingPackage等;
- 获取重要参数:mSessionId、packageUri、mOriginatingURI、mReferrerURI;
- 是否为穿戴设备;
- 调用processPackageUri()进行下一步安装。
PackageInstallerActivity.processPackageUri()
通过packageUri获取到scheme,之后都要通过getPackageInfo()获取信息。
我用的是APK本地安装的方式,走的是SCHEME_FILE流程。流程如下:
- 通过PackageUtil.getPackageInfo()调用到PackageManager.getPackageArchiveInfo()方法中,返回PackageInfo对象。
- PackageUtil.getAppSnippet()设置apk的程序名称和图标
/**
* Parse the Uri and set up the installer for this package.
*
* @param packageUri The URI to parse
*
* @return {@code true} iff the installer could be set up
*/
private boolean processPackageUri(final Uri packageUri) {
mPackageURI = packageUri;
final String scheme = packageUri.getScheme();
if (mLocalLOGV) Log.i(TAG, "processPackageUri(): uri=" + packageUri + ", scheme=" + scheme);
switch (scheme) {
case SCHEME_PACKAGE: {
try {
mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
PackageManager.GET_PERMISSIONS
| PackageManager.MATCH_UNINSTALLED_PACKAGES);
} catch (NameNotFoundException e) {
}
if (mPkgInfo == null) {
Log.w(TAG, "Requested package " + packageUri.getScheme()
+ " not available. Discontinuing installation");
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
return false;
}
CharSequence label = mPm.getApplicationLabel(mPkgInfo.applicationInfo);
if (mLocalLOGV) Log.i(TAG, "creating snippet for " + label);
mAppSnippet = new PackageUtil.AppSnippet(label,
mPm.getApplicationIcon(mPkgInfo.applicationInfo));
} break;
case ContentResolver.SCHEME_FILE: {
File sourceFile = new File(packageUri.getPath());
mPkgInfo = PackageUtil.getPackageInfo(this, sourceFile,
PackageManager.GET_PERMISSIONS);
// Check for parse errors
if (mPkgInfo == null) {
Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
return false;
}
if (mLocalLOGV) Log.i(TAG, "creating snippet for local file " + sourceFile);
mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
} break;
default: {
throw new IllegalArgumentException("Unexpected URI scheme " + packageUri);
}
}
return true;
}
PM.getPackageArchiveInfo()
PM=PackageManager
这一步执行一下内容:
- 调用PackageParser.parsePackage()解析APK,返回PackageParser.Package对象
- 通过返回的对象生成PackageInfo,这里面包含APK文件的相关信息。
/**
* See {@link #getPackageArchiveInfo(String, int)}.
*/
@Nullable
public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath,
@NonNull PackageInfoFlags flags) {
long flagsBits = flags.getValue();
final PackageParser parser = new PackageParser();
parser.setCallback(new PackageParser.CallbackImpl(this));
final File apkFile = new File(archiveFilePath);
try {
if ((flagsBits & (MATCH_DIRECT_BOOT_UNAWARE | MATCH_DIRECT_BOOT_AWARE)) != 0) {
// Caller expressed an explicit opinion about what encryption
// aware/unaware components they want to see, so fall through and
// give them what they want
} else {
// Caller expressed no opinion, so match everything
flagsBits |= MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
}
PackageParser.Package pkg = parser.parsePackage(apkFile, 0, false);
if ((flagsBits & GET_SIGNATURES) != 0 || (flagsBits & GET_SIGNING_CERTIFICATES) != 0) {
PackageParser.collectCertificates(pkg, false /* skipVerify */);
}
return PackageParser.generatePackageInfo(pkg, null, (int) flagsBits, 0, 0, null,
FrameworkPackageUserState.DEFAULT);
} catch (PackageParser.PackageParserException e) {
Log.w(TAG, "Failure to parse package archive", e);
return null;
}
}
PackageParser.parsePackage()
/**
* Parse the package at the given location. Automatically detects if the
* package is a monolithic style (single APK file) or cluster style
* (directory of APKs).
* <p>
* This performs checking on cluster style packages, such as
* requiring identical package name and version codes, a single base APK,
* and unique split names.
* <p>
* Note that this <em>does not</em> perform signature verification; that
* must be done separately in {@link #collectCertificates(Package, boolean)}.
*
* If {@code useCaches} is true, the package parser might return a cached
* result from a previous parse of the same {@code packageFile} with the same
* {@code flags}. Note that this method does not check whether {@code packageFile}
* has changed since the last parse, it's up to callers to do so.
*
* @see #parsePackageLite(File, int)
*/
@UnsupportedAppUsage
public Package parsePackage(File packageFile, int flags, boolean useCaches)
throws PackageParserException {
if (packageFile.isDirectory()) {
return parseClusterPackage(packageFile, flags);
} else {
return parseMonolithicPackage(packageFile, flags);
}
}
对单一APK,调用parseMonolithicPackage()。
PackageParser.parseMonolithicPackage()
/**
* Parse the given APK file, treating it as as a single monolithic package.
* <p>
* Note that this <em>does not</em> perform signature verification; that
* must be done separately in
* {@link #collectCertificates(Package, boolean)}.
*/
@UnsupportedAppUsage
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
if (mOnlyCoreApps) {
if (!lite.coreApp) {
throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"Not a coreApp: " + apkFile);
}
}
final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
try {
final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags);
pkg.setCodePath(apkFile.getCanonicalPath());
pkg.setUse32bitAbi(lite.use32bitAbi);
return pkg;
} catch (IOException e) {
throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to get path: " + apkFile, e);
} finally {
IoUtils.closeQuietly(assetLoader);
}
}
PackageParser.parseBaseApk(3 args)
parseBaseApk()方法解析一个APK,并将解析结果生成一个Package对象。
private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
throws PackageParserException {
final String apkPath = apkFile.getAbsolutePath();
String volumeUuid = null;
if (apkPath.startsWith(MNT_EXPAND)) {
final int end = apkPath.indexOf('/', MNT_EXPAND.length());
volumeUuid = apkPath.substring(MNT_EXPAND.length(), end);
}
mParseError = PackageManager.INSTALL_SUCCEEDED;
mArchiveSourcePath = apkFile.getAbsolutePath();
if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
XmlResourceParser parser = null;
try {
final int cookie = assets.findCookieForPath(apkPath);
if (cookie == 0) {
throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Failed adding asset path: " + apkPath);
}
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
final Resources res = new Resources(assets, mMetrics, null);
final String[] outError = new String[1];
final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);
if (pkg == null) {
throw new PackageParserException(mParseError,
apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
}
pkg.setVolumeUuid(volumeUuid);
pkg.setApplicationVolumeUuid(volumeUuid);
pkg.setBaseCodePath(apkPath);
pkg.setSigningDetails(SigningDetails.UNKNOWN);
return pkg;
} catch (PackageParserException e) {
throw e;
} catch (Exception e) {
throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to read manifest from " + apkPath, e);
} finally {
IoUtils.closeQuietly(parser);
}
}
PackageParser.parseBaseApk(5 args)
PackageParser.parseBaseApkCommon()
在这一步解析AndroidManifest.xml下面的每一个标签内容,包括application、overlay、permisstion、uses-permission、Activity等四大组件的信息。
其它
packages.xml文件
(1) 从手机中拉取packages.xml
packages.xml在Android找那个被保存为二进制的xml格式,直接打开是乱码。
可以通过修改配置将其改为普通的xml格式。连接设备,执行如下目录。
# 将二进制的packages.xml修改为普通文本格式
adb shell
setprop persist.sys.binary_xml false
reboot
# 从手机中拉取packages.xml
adb root
adb pull /data/system/packages.xml
(2) /data/system/packages.xml说明
类似于Window的注册表,这个文件是解析apk时由writeLP()创建的,里面记录了系统的permissons,以及每个apk的name、permissons、签名、codePath、flag、version、userid等信息,这些信息主要通过apk的AndroidManifest解析获取,解析完apk后将更新信息写入这个文件并保存到flash,下次开机的时候直接从里面读取相关信息并添加到内存相关列表中。当有APK升级,安装或删除时会更新这个文件
(3) 示例
以自己写的一个DemoApp为例,包名com.demoapp.activitydemo,在/data/system/packages.xml中的信息如下。主要是package、package-state、user-states这三个标签。
<package name="com.demoapp.activitydemo" codePath="/data/app/~~jLP88_QhQOc-XzokQ3TcQg==/com.demoapp.activitydemo-o8iGsFMBh4DStalTvq1fcw==" nativeLibraryPath="/data/app/~~jLP88_QhQOc-XzokQ3TcQg==/com.demoapp.activitydemo-o8iGsFMBh4DStalTvq1fcw==/lib" publicFlags="541638470" privateFlags="-1946152960" ft="18963007e20" ut="18963007f7e" version="1" userId="10108" packageSource="0" loadingProgress="1.0" domainSetId="745e4145-cf75-4878-83d7-35a03a5e5196">
<sigs count="1" schemeVersion="2">
<cert index="5" />
</sigs>
<proper-signing-keyset identifier="18" />
</package>
<package-state packageName="com.demoapp.activitydemo" id="745e4145-cf75-4878-83d7-35a03a5e5196" />
<user-states packageName="com.demoapp.activitydemo">
<user-state userId="0" state="0" />
</user-states>
packages.list文件
/data/system/packages.list:packages.list指定了应用的默认存储位置/data/data/com.xxx.xxx。
如:
com.demoapp.activitydemo 10108 1 /data/user/0/com.demoapp.activitydemo default:targetSdkVersion=33 none 0 1 1 @null