本文主要介绍Android系统如何实现APK安装、卸载、更新等操作。主要内容包括以下内容:
注:本文是在本人阅读
常见的APK安装方式有以下几种:
安装方式 | 触发时机 | 面向的用户类型 |
---|---|---|
系统自带和厂商预装 | 系统首次启动 | System Designer |
adb命令安装 | 使用adb push命令 | Programmer |
adb命令安装 | 使用adb (shell pm) install命令 | Programmer |
网络下载应用安装 | 下载完成之后安装 | User |
第三方应用安装(PackageInstaller.apk等) | 通过SdCard中的APK安装 | User |
Android系统中,APK安装、卸载的原理,与Windows系统软件安装(具体原理参考软件安装原理、MSI文件简介)的基本原理类似。
adb root
adb push flyflow-release.apk /system/app
adb reboot
使用该方式安装APK,具有以下特点:
adb install flyflow-release.apk
adb shell pm install flyflow-release.apk
这两种命令最终都会调用Android端的/bin/pm的install命令完成安装。网络应用下载安装APK之后,会调用PackageManager的installPackage(*)方法完成安装工作;该方法最终会调用PackageManagerService中的installPackage来完成APK安装。可通过以下形式实现验证: a) 将APK文件实现放入sdcard中; b) 设计一个Android InstallDemo,包含一个按钮,点击按钮便会调用installPackage方法进行安装。其中installPackage安装的文件为步骤a中的APK; 注意事项有:
使用PackageInstaller.apk进行安装时,其启动PackageInstallerActivity来完成安装包解析;在解析完成并得到用户的安装确认之后,启动 InstallAppProgress并调用安装接口pm.installPackage进行安装。(该方式未进行深入调研,道听途说)
本节详细介绍APK的几种安装方式,主要包括开机扫描安装、adb install命令安装等。
Android设备在启动的过程中,会扫描本地安装的所有的App。整个扫描过程可以通过以下流程图来说明: 扫描安装过程分析如下:
mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),mSdkVersion, mOnlyCore);
// scanDirLI(**)
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 {
scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
scanFlags, currentTime, null);
} catch (PackageManagerException e) {
// exception process
.....
}
Android adb的实现原理见adb原理与adb常用命令。在Console中输入adb的命令,最终会使用android /platform/system/core/adb/commandline.c 进行命令解析。
adb命令的intall命令在commandline.c中对应的实现函数为install_app(....),该函数的代码有如下调用:
if (!(err = do_sync_push(filename, to, 1 /* verify APK */))) {
/* file in place; tell the Package Manager to install it */
argv[argc - 1] = to;
/* destination name, not source location */
pm_command(transport, serial, argc, argv);
delete_file(transport, serial, to);
}
pm_command(...)函数实现如下:
static int pm_command(transport_type transport, char* serial,
int argc, char** argv)
{
char buf[4096];
snprintf(buf, sizeof(buf), "shell:pm");
while(argc-- > 0) {
char *quoted;
quoted = dupAndQuote (*argv++);
strncat(buf, " ", sizeof(buf)-1);
strncat(buf, quoted, sizeof(buf)-1);
free(quoted);
}
send_shellcommand(transport, serial, buf);
return 0;
}
pm_command通过最终通过send_shellcommand(...)将数据发送到手机端的adbd守护进程中;adbd在收到PC的Console发来的数据之后,会启动一个Shell,然后执行pm。
Android设备端pm命令位于/system/bin目录下,其是一个脚本,具体内容如下:
#!/system/bin/sh
base=/system
export CLASSPATH=$base/framework/pm.jar
exec app_process $base/bin com.android.commands.pm.Pm "$@"
1. 从上面脚本可以看出,pm命令通过app_process执行pm.jar包的main函数;
// Populate verificationURI, optionally present
final String verificationFilePath = nextArg();
if (verificationFilePath != null) {
System.err.println("\tver: " + verificationFilePath);
verificationURI = Uri.fromFile(new File(verificationFilePath));
} else {
verificationURI = null;
}
LocalPackageInstallObserver obs = new LocalPackageInstallObserver();
try {
VerificationParams verificationParams = new VerificationParams(verificationURI,originatingURI,
referrerURI, VerificationParams.NO_UID, null);
// 调用PMS完成安装
mPm.installPackageAsUser(apkFilePath, obs.getBinder(), installFlags,
installerPackageName, verificationParams, abi, userId);
// 安装结果后续处理
....
} catch (RemoteException e) {
System.err.println(e.toString());
System.err.println(PM_NOT_RUNNING_ERR);
return 1;
}
adb安装命令最终调用PMS中的installPackageAsUser函数进行APK安装。整个过程的时序图如下图所示: 安装APK的过程中具有以下特征:
从上述过程可以看出,不论是开机扫描安装APK,还是通过adb命令安装APK,最终都会调用scanPackageDirtyLI函数进行APK安装。该函数的流程图如下图所示: 该函数完成的主要工作有:
上个小节已经说明,scanPackageDirLI函数会调用createDataDirsLI的函数来完成安装。该函数主要做了两件事情:
private int createDataDirsLI(String packageName, int uid, String seinfo) {
int[] users = sUserManager.getUserIds();
int res = mInstaller.install(packageName, uid, uid, seinfo);
if (res < 0) {
return res;
}
for (int user : users) {
if (user != 0) {
res = mInstaller.createUserData(packageName,
UserHandle.getUid(user, uid), user, seinfo);
if (res < 0) {
return res;
}
}
}
return res;
}
通过Installer类中install函数代码如下:
public int install(String name, int uid, int gid, String seinfo) {
StringBuilder builder = new StringBuilder("install");
builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(uid);
builder.append(' ');
builder.append(gid);
builder.append(' ');
builder.append(seinfo != null ? seinfo : "!");
return mInstaller.execute(builder.toString());
}
分析得出以下结论: 1. Installer.install()函数和createUserData()进行完成了命令组装工作,在组装完命令之后,将命令传递给InstallerConnection处理。 通过分析InstallerConnection.java得到以下结论: 1. InstallerConnection连接一个名为Installd的服务 2. Install具体的命令有Installd完成。 其中InstallerConnnection.connect()函数代码如下:
private boolean connect() {
if (mSocket != null) {
return true;
}
Slog.i(TAG, "connecting...");
try {
mSocket = new LocalSocket();
LocalSocketAddress address = new LocalSocketAddress("installd",
LocalSocketAddress.Namespace.RESERVED);
mSocket.connect(address);
mIn = mSocket.getInputStream();
mOut = mSocket.getOutputStream();
} catch (IOException ex) {
disconnect();
return false;
}
return true;
}
Installd是一个native进程,该进程启动一个socket,然后处理来之Installer的命令。Installd的实现原理可以参考博文Android安装服务installd源码分析。 installd源码位于frameworks/base/cmds/installd目录下,其中install操作对应的源代码在frameworks/base/cmds/installd/commands.c中,具体代码如下:
int install(const char *pkgname, uid_t uid, gid_t gid)
{
char pkgdir[PKG_PATH_MAX];//程序目录路径最长为256
char libdir[PKG_PATH_MAX];//程序lib路径最长为256
//权限判断
if ((uid < AID_SYSTEM) || (gid < AID_SYSTEM)) {
ALOGE("invalid uid/gid: %d %d\n", uid, gid);
return -1;
}
//组合应用程序安装目录pkgdir=/data/data/应用程序包名
if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0)) {
ALOGE("cannot create package path\n");
return -1;
}
//组合应用程序库目录libdir=/data/data/应用程序包名/lib
if (create_pkg_path(libdir, pkgname, PKG_LIB_POSTFIX, 0)) {
ALOGE("cannot create package lib path\n");
return -1;
}
//创建目录pkgdir=/data/data/应用程序包名
if (mkdir(pkgdir, 0751) < 0) {
ALOGE("cannot create dir '%s': %s\n", pkgdir, strerror(errno));
return -errno;
}
//修改/data/data/应用程序包名目录的权限
if (chmod(pkgdir, 0751) < 0) {
ALOGE("cannot chmod dir '%s': %s\n", pkgdir, strerror(errno));
unlink(pkgdir);
return -errno;
}
//创建目录libdir=/data/data/应用程序包名/lib
if (mkdir(libdir, 0755) < 0) {
ALOGE("cannot create dir '%s': %s\n", libdir, strerror(errno));
unlink(pkgdir);
return -errno;
}
//修改/data/data/应用程序包名/lib目录的权限
if (chmod(libdir, 0755) < 0) {
ALOGE("cannot chmod dir '%s': %s\n", libdir, strerror(errno));
unlink(libdir);
unlink(pkgdir);
return -errno;
}
//修改/data/data/应用程序包名目录的所有权限
if (chown(libdir, AID_SYSTEM, AID_SYSTEM) < 0) {
ALOGE("cannot chown dir '%s': %s\n", libdir, strerror(errno));
unlink(libdir);
unlink(pkgdir);
return -errno;
}
//修改/data/data/应用程序包名/lib目录的所有权限
if (chown(pkgdir, uid, gid) < 0) {
ALOGE("cannot chown dir '%s': %s\n", pkgdir, strerror(errno));
unlink(libdir);
unlink(pkgdir);
return -errno;
}
return 0;
}
从Installd的实现中,我们可以看到unInstall操作其实就是删除相应的数据文件和资源文件。unInstall的具体实现如下:
int uninstall(const char *pkgname, uid_t persona)
{
char pkgdir[PKG_PATH_MAX];
if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, persona))
return -1;
/* delete contents AND directory, no exceptions */
return delete_dir_contents(pkgdir, 1, NULL);
}