原文地址: http://www.xuebuyuan.com/2172921.html
这一章我们开始分析APK的安装过程,当我们从网上download一个APK后,点击这个APK文件,就会启动PackageInstallerActivity这个页面来parse这个APK文件,并提示一些信息给用户,当用户点击安装以后,就会开始APK的安装。在介绍安装APK之前,我们先来分析一下installd。
installd介绍
service installd /system/bin/installd
class main
socket installd stream 600 system system
int main(const int argc, const char *argv[]) {
char buf[BUFFER_MAX];
struct sockaddr addr;
socklen_t alen;
int lsocket, s, count;
ALOGI("installd firing up\n");
if (initialize_globals() < 0) {
ALOGE("Could not initialize globals; exiting.\n");
exit(1);
}
if (initialize_directories() < 0) {
ALOGE("Could not create directories; exiting.\n");
exit(1);
}
drop_privileges();
lsocket = android_get_control_socket(SOCKET_PATH);
if (lsocket < 0) {
ALOGE("Failed to get socket from environment: %s\n", strerror(errno));
exit(1);
}
if (listen(lsocket, 5)) {
ALOGE("Listen on socket failed: %s\n", strerror(errno));
exit(1);
}
fcntl(lsocket, F_SETFD, FD_CLOEXEC);
for (;;) {
alen = sizeof(addr);
s = accept(lsocket, &addr, &alen);
main函数中首先做一些全局化的初始化设置,然后从底层获取创建的socket号,并在这个socket上面开始listen。在这里installd就是一个server,等待client的连接。那我们再来看一下client的创建过程,也就是Installer对象的实例化,实例化Installer也是在systemServer当中:
installer = new Installer();
installer.ping();
来看一下 Installer的ping方法,它是用于建立也installd之间的socket连接,并测试是否可以正常的发送命令给installd:
public boolean ping() {
if (execute("ping") < 0) {
return false;
} else {
return true;
}
}
private int execute(String cmd) {
String res = transaction(cmd);
try {
return Integer.parseInt(res);
} catch (NumberFormatException ex) {
return -1;
}
}
private synchronized String transaction(String cmd) {
if (!connect()) {
return "-1";
}
if (!writeCommand(cmd)) {
if (!connect() || !writeCommand(cmd)) {
return "-1";
}
}
if (readReply()) {
String s = new String(buf, 0, buflen);
return s;
} else {
return "-1";
}
}
connect函数比较简单,就是创建一个socket,并且与前面的installd进行连接,如果成功连接上,就返回true。那看一下writeCommand的实现:
private boolean writeCommand(String _cmd) {
byte[] cmd = _cmd.getBytes();
int len = cmd.length;
if ((len < 1) || (len > 1024))
return false;
buf[0] = (byte) (len & 0xff);
buf[1] = (byte) ((len >> 8) & 0xff);
try {
mOut.write(buf, 0, 2);
mOut.write(cmd, 0, len);
} catch (IOException ex) {
Slog.e(TAG, "write error");
disconnect();
return false;
}
return true;
}
发送到installd的数据格式:cmd长度+cmd本身。再到installd中看如何处理ping的命令:
for (;;) {
unsigned short count;
if (readx(s, &count, sizeof(count))) {
ALOGE("failed to read size\n");
break;
}
if ((count < 1) || (count >= BUFFER_MAX)) {
ALOGE("invalid size %d\n", count);
break;
}
if (readx(s, buf, count)) {
ALOGE("failed to read command\n");
break;
}
buf[count] = 0;
if (execute(s, buf)) break;
}
首先读出两字节的长度保存在count中,再从socket读出长度为count的命令保存在buf中,最后调用execute去执行命令:
static int execute(int s, char cmd[BUFFER_MAX])
{
char reply[REPLY_MAX];
char *arg[TOKEN_MAX+1];
unsigned i;
unsigned n = 0;
unsigned short count;
int ret = -1;
reply[0] = 0;
arg[0] = cmd;
while (*cmd) {
if (isspace(*cmd)) {
*cmd++ = 0;
n++;
arg[n] = cmd;
if (n == TOKEN_MAX) {
ALOGE("too many arguments\n");
goto done;
}
}
cmd++;
}
for (i = 0; i < sizeof(cmds) / sizeof(cmds[0]); i++) {
if (!strcmp(cmds[i].name,arg[0])) {
if (n != cmds[i].numargs) {
ALOGE("%s requires %d arguments (%d given)\n",
cmds[i].name, cmds[i].numargs, n);
} else {
ret = cmds[i].func(arg + 1, reply);
}
goto done;
}
}
ALOGE("unsupported command '%s'\n", arg[0]);
done:
if (reply[0]) {
n = snprintf(cmd, BUFFER_MAX, "%d %s", ret, reply);
} else {
n = snprintf(cmd, BUFFER_MAX, "%d", ret);
}
if (n > BUFFER_MAX) n = BUFFER_MAX;
count = n;
// ALOGI("reply: '%s'\n", cmd);
if (writex(s, &count, sizeof(count))) return -1;
if (writex(s, cmd, count)) return -1;
return 0;
}
execute函数也比较简单,首先从命令行中parse出命令和参数,然后从全局的cmds数组中获取到与要执行的命令匹配的函数并执行,最后将返回值和结果发送给client端。这里的cmds数组就表示了所有installd可以执行的命令:
struct cmdinfo cmds[] = {
{ "ping", 0, do_ping },
{ "install", 4, do_install },
{ "dexopt", 3, do_dexopt },
{ "movedex", 2, do_move_dex },
{ "rmdex", 1, do_rm_dex },
{ "remove", 2, do_remove },
{ "rename", 2, do_rename },
{ "fixuid", 3, do_fixuid },
{ "freecache", 1, do_free_cache },
{ "rmcache", 2, do_rm_cache },
{ "getsize", 6, do_get_size },
{ "rmuserdata", 2, do_rm_user_data },
{ "movefiles", 0, do_movefiles },
{ "linklib", 3, do_linklib },
{ "mkuserdata", 3, do_mk_user_data },
{ "rmuser", 1, do_rm_user },
};
Package Installer过程分析
pm.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags,
installerPackageName, verificationParams, null);
这个方法的第一个参数是APK所在的路径URI;第二个参数用来监控整个安装过程;第三个参数installFlags默认为0;第四个参数是APK的包名;第五个参数用于做数字签名验证。当然这里调用到PMS的真正实现的函数需要借助binder的跨进程来实现:
public void installPackageWithVerificationAndEncryption(Uri packageURI,
IPackageInstallObserver observer, int flags, String installerPackageName,
VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES,
null);
final int uid = Binder.getCallingUid();
UserHandle user;
if ((flags&PackageManager.INSTALL_ALL_USERS) != 0) {
user = UserHandle.ALL;
} else {
user = new UserHandle(UserHandle.getUserId(uid));
}
final int filteredFlags;
if (uid == Process.SHELL_UID || uid == 0) {
if (DEBUG_INSTALL) {
Slog.v(TAG, "Install from ADB");
}
filteredFlags = flags | PackageManager.INSTALL_FROM_ADB;
} else {
filteredFlags = flags & ~PackageManager.INSTALL_FROM_ADB;
}
verificationParams.setInstallerUid(uid);
final Message msg = mHandler.obtainMessage(INIT_COPY);
msg.obj = new InstallParams(packageURI, observer, filteredFlags, installerPackageName,
verificationParams, encryptionParams, user);
mHandler.sendMessage(msg);
}
这里首先做一些权限的检查,并判断当前安装APK的user是否具有相应的权限。在安装APK的时候分为程序开发人员通过ADB安装和user通过网上下载安装,当通过ADB安装时,往往不需要对程序做验证,这就是INSTALL_FROM_ADB这个flag的作用。最后构造一个INIT_COPY的cmd,并带有InstallParams的message发给PackageHandler处理。在分析PackageHandler处理INIT_COPY之前,先来看一下几种安装Params的关系:
再来看PackageHandler处理INIT_COPY这个命令:
case INIT_COPY: {
HandlerParams params = (HandlerParams) msg.obj;
int idx = mPendingInstalls.size();
if (!mBound) {
if (!connectToService()) {
Slog.e(TAG, "Failed to bind to media container service");
params.serviceError();
return;
} else {
mPendingInstalls.add(idx, params);
}
} else {
mPendingInstalls.add(idx, params);
if (idx == 0) {
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
break;
}
在安装APK的时候,PMS还得依赖另一个service,也就是ContainerService,它具体负责实现APK等相关资源文件在内部或外部存储器上的存储工作。如果之前我们没有绑定ContainerService,这里首先会绑定服务,并把我们前面创建的InstallParams加入到mPendingInstalls列表中,mPendingInstalls保存所有的安装请求。如果我们前面绑定过ContainerService,就再发送MCS_BOUND消息给自身。当然,在我们在绑定服务之后,同样也会发送MCS_BOUND消息。我们来看MCS_BOUND的处理流程:
case MCS_BOUND: {
if (msg.obj != null) {
mContainerService = (IMediaContainerService) msg.obj;
}
if (mContainerService == null) {
} else if (mPendingInstalls.size() > 0) {
HandlerParams params = mPendingInstalls.get(0);
if (params != null) {
if (params.startCopy()) {
if (mPendingInstalls.size() > 0) {
mPendingInstalls.remove(0);
}
if (mPendingInstalls.size() == 0) {
if (mBound) {
removeMessages(MCS_UNBIND);
Message ubmsg = obtainMessage(MCS_UNBIND);
sendMessageDelayed(ubmsg, 10000);
}
} else {
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
}
} else {
}
break;
}
在处理MCS_BOUND消息时,首先把前面得到的ContainerService服务赋给mContainerService。然后不断的处理mPendingInstalls所有的安装请求,这里调用InstallParams的startCopy方法:
final boolean startCopy() {
boolean res;
try {
if (++mRetries > MAX_RETRIES) {
Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
mHandler.sendEmptyMessage(MCS_GIVE_UP);
handleServiceError();
return false;
} else {
handleStartCopy();
res = true;
}
} catch (RemoteException e) {
if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT");
mHandler.sendEmptyMessage(MCS_RECONNECT);
res = false;
}
handleReturnCode();
return res;
}
这里主要调用InstallParams的handleStartCopy方法来完成资源文件的拷贝:
public void handleStartCopy() throws RemoteException {
int ret = PackageManager.INSTALL_SUCCEEDED;
final boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0;
final boolean onInt = (flags & PackageManager.INSTALL_INTERNAL) != 0;
PackageInfoLite pkgLite = null;
if (onInt && onSd) {
} else {
final long lowThreshold;
final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager
.getService(DeviceStorageMonitorService.SERVICE);
if (dsm == null) {
Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed");
lowThreshold = 0L;
} else {
lowThreshold = dsm.getMemoryLowThreshold();
}
try {
mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, mPackageURI,
Intent.FLAG_GRANT_READ_URI_PERMISSION);
final File packageFile;
if (encryptionParams != null || !"file".equals(mPackageURI.getScheme())) {
//关于drm文件的安装
} else {
packageFile = new File(mPackageURI.getPath());
}
if (packageFile != null) {
final String packageFilePath = packageFile.getAbsolutePath();
pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath, flags,
lowThreshold);
if (pkgLite.recommendedInstallLocation
== PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
final long size = mContainerService.calculateInstalledSize(
packageFilePath, isForwardLocked());
if (mInstaller.freeCache(size + lowThreshold) >= 0) {
pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath,
flags, lowThreshold);
}
if (pkgLite.recommendedInstallLocation
== PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
pkgLite.recommendedInstallLocation
= PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
}
}
}
} finally {
mContext.revokeUriPermission(mPackageURI,
Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
if (ret == PackageManager.INSTALL_SUCCEEDED) {
int loc = pkgLite.recommendedInstallLocation;
if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
} else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) {
ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
} else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
} else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
ret = PackageManager.INSTALL_FAILED_INVALID_APK;
} else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
ret = PackageManager.INSTALL_FAILED_INVALID_URI;
} else if (loc == PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE) {
ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
} else {
loc = installLocationPolicy(pkgLite, flags);
if (loc == PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE) {
ret = PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
} else if (!onSd && !onInt) {
if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
// Set the flag to install on external media.
flags |= PackageManager.INSTALL_EXTERNAL;
flags &= ~PackageManager.INSTALL_INTERNAL;
} else {
flags |= PackageManager.INSTALL_INTERNAL;
flags &= ~PackageManager.INSTALL_EXTERNAL;
}
}
}
}
handleStartCopy这个函数比较长,我们先来看前一部分,这里首先从installFlags中检查是否有指定安装位置,然后从DeviceStorageMonitorService中取得系统设置的内存空间的最低阀值。然后调用ContainerService去计算APK文件的大小,并检查当前内存空间是否足够,如果不足够,则调用Installd的freeCache方法尝试去释放一些cache文件。最终再来检查是否有足够的空间,并决定安装位置。接着来看handleStartCopy的实现:
final InstallArgs args = createInstallArgs(this);
mArgs = args;
if (ret == PackageManager.INSTALL_SUCCEEDED) {
int userIdentifier = getUser().getIdentifier();
if (userIdentifier == UserHandle.USER_ALL
&& ((flags & PackageManager.INSTALL_FROM_ADB) != 0)) {
userIdentifier = UserHandle.USER_OWNER;
}
final int requiredUid = mRequiredVerifierPackage == null ? -1
: getPackageUid(mRequiredVerifierPackage, userIdentifier);
if (requiredUid != -1 && isVerificationEnabled(flags)) {
} else {
ret = args.copyApk(mContainerService, true);
}
}
mRet = ret;
}
这里首先根据安装路径的不同,创建不同的InstallArgs:
private InstallArgs createInstallArgs(InstallParams params) {
if (installOnSd(params.flags) || params.isForwardLocked()) {
return new AsecInstallArgs(params);
} else {
return new FileInstallArgs(params);
}
}
AsecInstallArgs就是指安装在外部存储空间上;FileInstallArgs是指安装在内部存储空间。来看一下上面的两者的类图关系:
安装在内部存储空间
int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
if (temp) {
createCopyFile();
}
File codeFile = new File(codeFileName);
if (!created) {
}
ParcelFileDescriptor out = null;
try {
out = ParcelFileDescriptor.open(codeFile, ParcelFileDescriptor.MODE_READ_WRITE);
} catch (FileNotFoundException e) {
Slog.e(TAG, "Failed to create file descriptor for : " + codeFileName);
return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
}
int ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
try {
mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,
Intent.FLAG_GRANT_READ_URI_PERMISSION);
ret = imcs.copyResource(packageURI, null, out);
} finally {
IoUtils.closeQuietly(out);
mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
if (isFwdLocked()) {
}
final File nativeLibraryFile = new File(getNativeLibraryPath());
if (nativeLibraryFile.exists()) {
NativeLibraryHelper.removeNativeBinariesFromDirLI(nativeLibraryFile);
nativeLibraryFile.delete();
}
try {
int copyRet = copyNativeLibrariesForInternalApp(codeFile, nativeLibraryFile);
if (copyRet != PackageManager.INSTALL_SUCCEEDED) {
return copyRet;
}
} catch (IOException e) {
ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
}
return ret;
}
因为前面传进来的第二个参数是true,所以这里这里首先调用createCopyFile创建一个临时文件:
void createCopyFile() {
installDir = isFwdLocked() ? mDrmAppPrivateInstallDir : mAppInstallDir;
codeFileName = createTempPackageFile(installDir).getPath();
resourceFileName = getResourcePathFromCodePath();
libraryPath = getLibraryPathFromCodePath();
created = true;
}
installDir就是/data/app,createTempPackageFile在/data/app目录下面创建一个以"vmdl"开始,并以".tmp"结尾的临时文件。resourceFileName就是codeFileName所在的路径名。libraryPath在/data/app-lib路径下面以"vmdlxxxx"命名的文件。回到copyApk函数,接着调用ContainerService的copyResource方法将原始packageURI所指向的文件内容拷贝到我们创建的临时文件里面。最后将APK文件里面的lib文件全部拷贝到libraryPath所指定的文件目录下面。当数据拷贝完成,接着到InstallParams的startCopy方法中会调用handleReturnCode来处理拷贝的结果:
void handleReturnCode() {
if (mArgs != null) {
processPendingInstall(mArgs, mRet);
}
}
private void processPendingInstall(final InstallArgs args, final int currentStatus) {
mHandler.post(new Runnable() {
public void run() {
mHandler.removeCallbacks(this);
PackageInstalledInfo res = new PackageInstalledInfo();
res.returnCode = currentStatus;
res.uid = -1;
res.pkg = null;
res.removedInfo = new PackageRemovedInfo();
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
args.doPreInstall(res.returnCode);
synchronized (mInstallLock) {
installPackageLI(args, true, res);
}
args.doPostInstall(res.returnCode, res.uid);
}
在processPendingInstall里面,首先调用FileInstallArgs的doPreInstall方法,默认的它什么不会做。然后执行真正的安装过程installPackageLI:
private void installPackageLI(InstallArgs args,
boolean newInstall, PackageInstalledInfo res) {
int pFlags = args.flags;
String installerPackageName = args.installerPackageName;
File tmpPackageFile = new File(args.getCodePath());
boolean forwardLocked = ((pFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0);
boolean onSd = ((pFlags & PackageManager.INSTALL_EXTERNAL) != 0);
boolean replace = false;
int scanMode = (onSd ? 0 : SCAN_MONITOR) | SCAN_FORCE_DEX | SCAN_UPDATE_SIGNATURE
| (newInstall ? SCAN_NEW_INSTALL : 0);
res.returnCode = PackageManager.INSTALL_SUCCEEDED;
int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
| (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
| (onSd ? PackageParser.PARSE_ON_SDCARD : 0);
PackageParser pp = new PackageParser(tmpPackageFile.getPath());
pp.setSeparateProcesses(mSeparateProcesses);
final PackageParser.Package pkg = pp.parsePackage(tmpPackageFile,
null, mMetrics, parseFlags);
if (pkg == null) {
res.returnCode = pp.getParseError();
return;
}
String pkgName = res.name = pkg.packageName;
if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_TEST_ONLY) != 0) {
if ((pFlags&PackageManager.INSTALL_ALLOW_TEST) == 0) {
res.returnCode = PackageManager.INSTALL_FAILED_TEST_ONLY;
return;
}
}
if (GET_CERTIFICATES && !pp.collectCertificates(pkg, parseFlags)) {
res.returnCode = pp.getParseError();
return;
}
这里首先调用PackageParser的parsePackage方法解析AndroidManifest文件,构造一个Package对象,前面我们讲过Package对象了,大致内容如下:
pp = null;
String oldCodePath = null;
boolean systemApp = false;
synchronized (mPackages) {
if ((pFlags&PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
}
PackageSetting ps = mSettings.mPackages.get(pkgName);
if (ps != null) {
}
if (!args.doRename(res.returnCode, pkgName, oldCodePath)) {
res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
return;
}
setApplicationInfoPaths(pkg, args.getCodePath(), args.getResourcePath());
pkg.applicationInfo.nativeLibraryDir = args.getNativeLibraryPath();
if (replace) {
} else {
installNewPackageLI(pkg, parseFlags, scanMode, args.user,
installerPackageName, res);
}
synchronized (mPackages) {
final PackageSetting ps = mSettings.mPackages.get(pkgName);
if (ps != null) {
res.newUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true);
}
}
}
这里先不关注APK的更新,直接来看FileInstallArgs的doRename方法:
boolean doRename(int status, final String pkgName, String oldCodePath) {
if (status != PackageManager.INSTALL_SUCCEEDED) {
} else {
final File oldCodeFile = new File(getCodePath());
final File oldResourceFile = new File(getResourcePath());
final File oldLibraryFile = new File(getNativeLibraryPath());
final String apkName = getNextCodePath(oldCodePath, pkgName, ".apk");
final File newCodeFile = new File(installDir, apkName + ".apk");
if (!oldCodeFile.renameTo(newCodeFile)) {
return false;
}
codeFileName = newCodeFile.getPath();
final File newResFile = new File(getResourcePathFromCodePath());
if (isFwdLocked() && !oldResourceFile.renameTo(newResFile)) {
return false;
}
resourceFileName = newResFile.getPath();
final File newLibraryFile = new File(getLibraryPathFromCodePath());
if (newLibraryFile.exists()) {
NativeLibraryHelper.removeNativeBinariesFromDirLI(newLibraryFile);
newLibraryFile.delete();
}
if (!oldLibraryFile.renameTo(newLibraryFile)) {
Slog.e(TAG, "Cannot rename native library directory "
+ oldLibraryFile.getPath() + " to " + newLibraryFile.getPath());
return false;
}
libraryPath = newLibraryFile.getPath();
if (!setPermissions()) {
return false;
}
return true;
}
}
getNextCodePath返回一个类似"包名-num.apk"的文件名, 例如以新浪微博为例,apkName为"com.sina.weibo-1.apk"。然后将原来的codeFileName改为新的名字。resourceFileName和libraryPath也会做类似的改变。回到installPackageLI函数中,接着调用installNewPackageLI方法将新的package文件里面的资源加入到PMS的数据结构中,让PMS来管理这些activity、service、receiver:
private void installNewPackageLI(PackageParser.Package pkg,
int parseFlags, int scanMode, UserHandle user,
String installerPackageName, PackageInstalledInfo res) {
String pkgName = pkg.packageName;
boolean dataDirExists = getDataPathForPackage(pkg.packageName, 0).exists();
synchronized(mPackages) {
if (mSettings.mRenamedPackages.containsKey(pkgName)) {
res.returnCode = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
return;
}
if (mPackages.containsKey(pkgName) || mAppDirs.containsKey(pkg.mPath)) {
res.returnCode = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
return;
}
}
mLastScanError = PackageManager.INSTALL_SUCCEEDED;
PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode,
System.currentTimeMillis(), user);
if (newPackage == null) {
} else {
updateSettingsLI(newPackage,
installerPackageName,
null, null,
res);
}
}
在installNewPackageLI方法中首先调用scanPackageLI方法把新package的资源归入到PMS中,并创建一个PackageSettings对象,加入到Settings中的mPackages这个map中。这个函数我们在前面一章中启动PMS中大致介绍过了,这里主要来看一下与安装有关的部分:
private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
int parseFlags, int scanMode, long currentTime, UserHandle user) {
File dataPath;
if (mPlatformPackage == pkg) {
} else {
dataPath = getDataPathForPackage(pkg.packageName, 0);
boolean uidError = false;
if (dataPath.exists()) {
} else {
int ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid,
pkg.applicationInfo.seinfo);
if (dataPath.exists()) {
pkg.applicationInfo.dataDir = dataPath.getPath();
} else {
Slog.w(TAG, "Unable to create data directory: " + dataPath);
pkg.applicationInfo.dataDir = null;
}
}
String path = scanFile.getPath();
if (pkg.applicationInfo.nativeLibraryDir != null) {
try {
File nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir);
final String dataPathString = dataPath.getCanonicalPath();
if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) {
} else {
if (!isForwardLocked(pkg) && !isExternal(pkg)) {
if (nativeLibraryDir.getPath().startsWith(dataPathString)) {
setInternalAppNativeLibraryPath(pkg, pkgSetting);
nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir);
}
try {
if (copyNativeLibrariesForInternalApp(scanFile, nativeLibraryDir) != PackageManager.INSTALL_SUCCEEDED) {
Slog.e(TAG, "Unable to copy native libraries");
mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
return null;
}
} catch (IOException e) {
Slog.e(TAG, "Unable to copy native libraries", e);
mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
return null;
}
}
if (DEBUG_INSTALL) Slog.i(TAG, "Linking native library dir for " + path);
final int[] userIds = sUserManager.getUserIds();
synchronized (mInstallLock) {
for (int userId : userIds) {
if (mInstaller.linkNativeLibraryDirectory(pkg.packageName,
pkg.applicationInfo.nativeLibraryDir, userId) < 0) {
Slog.w(TAG, "Failed linking native library dir (user=" + userId
+ ")");
mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
return null;
}
}
}
}
} catch (IOException ioe) {
Slog.e(TAG, "Unable to get canonical file " + ioe.toString());
}
}
pkg.mScanPath = path;
if ((scanMode&SCAN_NO_DEX) == 0) {
if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
== DEX_OPT_FAILED) {
mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
return null;
}
}
首先取得Package的dataPath,路径为/data/data/包名,当我们第一次安装时,这个目录还没有创建,所以调用createDataDirsLI去创建data目录:
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) {
}
return res;
}
static int do_install(char **arg, char reply[REPLY_MAX])
{
return install(arg[0], atoi(arg[1]), atoi(arg[2]), arg[3]); /* pkgname, uid, gid, seinfo */
}
int install(const char *pkgname, uid_t uid, gid_t gid, const char *seinfo)
{
char pkgdir[PKG_PATH_MAX];
char libsymlink[PKG_PATH_MAX];
char applibdir[PKG_PATH_MAX];
struct stat libStat;
if ((uid < AID_SYSTEM) || (gid < AID_SYSTEM)) {
ALOGE("invalid uid/gid: %d %d\n", uid, gid);
return -1;
}
if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0)) {
ALOGE("cannot create package path\n");
return -1;
}
if (create_pkg_path(libsymlink, pkgname, PKG_LIB_POSTFIX, 0)) {
ALOGE("cannot create package lib symlink origin path\n");
return -1;
}
if (create_pkg_path_in_dir(applibdir, &android_app_lib_dir, pkgname, PKG_DIR_POSTFIX)) {
ALOGE("cannot create package lib symlink dest path\n");
return -1;
}
if (mkdir(pkgdir, 0751) < 0) {
ALOGE("cannot create dir '%s': %s\n", pkgdir, strerror(errno));
return -1;
}
if (chmod(pkgdir, 0751) < 0) {
ALOGE("cannot chmod dir '%s': %s\n", pkgdir, strerror(errno));
unlink(pkgdir);
return -1;
}
if (lstat(libsymlink, &libStat) < 0) {
if (errno != ENOENT) {
ALOGE("couldn't stat lib dir: %s\n", strerror(errno));
return -1;
}
} else {
if (S_ISDIR(libStat.st_mode)) {
if (delete_dir_contents(libsymlink, 1, 0) < 0) {
ALOGE("couldn't delete lib directory during install for: %s", libsymlink);
return -1;
}
} else if (S_ISLNK(libStat.st_mode)) {
if (unlink(libsymlink) < 0) {
ALOGE("couldn't unlink lib directory during install for: %s", libsymlink);
return -1;
}
}
}
if (symlink(applibdir, libsymlink) < 0) {
ALOGE("couldn't symlink directory '%s' -> '%s': %s\n", libsymlink, applibdir,
strerror(errno));
unlink(pkgdir);
return -1;
}
if (chown(pkgdir, uid, gid) < 0) {
ALOGE("cannot chown dir '%s': %s\n", pkgdir, strerror(errno));
unlink(libsymlink);
unlink(pkgdir);
return -1;
}
return 0;
}
这里首先构造几个目录名:pkgdir为/data/data/包名,libsymlink为/data/data/包名/lib,applibdir为/data/app-lib/包名。然后创建pkgdir的目录,并修改相应的权限。然后创建/data/data/包名/lib指向/data/app-lib/包名的符号链接。我们知道安装到data/app-lib下面的nativeLibrary的目录其实”包名-num“,所以我们需要重新建立符号链接,这也就是在scanPackageLI方法中mInstaller.linkNativeLibraryDirectory的作用:
static int do_linklib(char **arg, char reply[REPLY_MAX])
{
return linklib(arg[0], arg[1], atoi(arg[2]));
}
int linklib(const char* pkgname, const char* asecLibDir, int userId)
{
char pkgdir[PKG_PATH_MAX];
char libsymlink[PKG_PATH_MAX];
struct stat s, libStat;
int rc = 0;
if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, userId)) {
ALOGE("cannot create package path\n");
return -1;
}
if (create_pkg_path(libsymlink, pkgname, PKG_LIB_POSTFIX, userId)) {
ALOGE("cannot create package lib symlink origin path\n");
return -1;
}
if (stat(pkgdir, &s) < 0) return -1;
if (chown(pkgdir, AID_INSTALL, AID_INSTALL) < 0) {
ALOGE("failed to chown '%s': %s\n", pkgdir, strerror(errno));
return -1;
}
if (chmod(pkgdir, 0700) < 0) {
ALOGE("linklib() 1: failed to chmod '%s': %s\n", pkgdir, strerror(errno));
rc = -1;
goto out;
}
if (lstat(libsymlink, &libStat) < 0) {
if (errno != ENOENT) {
ALOGE("couldn't stat lib dir: %s\n", strerror(errno));
rc = -1;
goto out;
}
} else {
if (S_ISDIR(libStat.st_mode)) {
if (delete_dir_contents(libsymlink, 1, 0) < 0) {
rc = -1;
goto out;
}
} else if (S_ISLNK(libStat.st_mode)) {
if (unlink(libsymlink) < 0) {
ALOGE("couldn't unlink lib dir: %s\n", strerror(errno));
rc = -1;
goto out;
}
}
}
if (symlink(asecLibDir, libsymlink) < 0) {
ALOGE("couldn't symlink directory '%s' -> '%s': %s\n", libsymlink, asecLibDir,
strerror(errno));
rc = -errno;
goto out;
}
这里的第一个参数是packageName;第二个参数是nativeLibraryDir,也就是/data/app-lib/包名-num;第三个参数userID为0。在linklib中,与install一样,首先创建几个目录路径:pkgdir为/data/data/包名,libsymlink为/data/data/包名/lib。然后先把/data/data/包名/lib这个符号链接删除,然后创建一个新的符号链接/data/data/包名/lib指向/data/app-lib/包名-num。回到scanPackageLI函数中,最后调用performDexOptLI对APK安装包中的dex文件做优化,当然这里会依据当前是采用DVM还是ART的运行环境,分别用dexopt和dex2oat两种不同的命令对classes.dex文件做优化并存储在/data/dalvik-cache下面的文件里,关于dex文件优化的部分,可以参考一下Android虚拟机相关知识。接着回到installNewPackageLI函数中调用updateSettingsLI去更新Settings中的设置:
private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName,
int[] allUsers, boolean[] perUserInstalled,
PackageInstalledInfo res) {
String pkgName = newPackage.packageName;
synchronized (mPackages) {
mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_INCOMPLETE);
mSettings.writeLPr();
}
if ((res.returnCode = moveDexFilesLI(newPackage))
!= PackageManager.INSTALL_SUCCEEDED) {
// Discontinue if moving dex files failed.
return;
}
if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + newPackage.mPath);
synchronized (mPackages) {
updatePermissionsLPw(newPackage.packageName, newPackage,
UPDATE_PERMISSIONS_REPLACE_PKG | (newPackage.permissions.size() > 0
? UPDATE_PERMISSIONS_ALL : 0));
if (isSystemApp(newPackage)) {
}
res.name = pkgName;
res.uid = newPackage.applicationInfo.uid;
res.pkg = newPackage;
mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_COMPLETE);
mSettings.setInstallerPackageName(pkgName, installerPackageName);
res.returnCode = PackageManager.INSTALL_SUCCEEDED;
//to update install status
mSettings.writeLPr();
}
}
这里首先调用Settings的writeLPr方法更新packages.xml文件,将新安装的package信息写到这个xml文件。moveDexFilesLI这里用于处理系统更新后安装目录有变化的情况,需要将原来的优化后的dex文件做重命名。updatePermissionsLPw我们在前面一章也介绍过,它用于给当前安装的APK分配权限,并把相应的gid号保存在PackageSetting或者SharedUserSetting的gids数组中。到这里installPackageLI就介绍完了,回到processPendingInstall方法中:
final boolean update = res.removedInfo.removedPackage != null;
boolean doRestore = (!update
&& res.pkg != null
&& res.pkg.applicationInfo.backupAgentName != null);
int token;
if (mNextInstallToken < 0) mNextInstallToken = 1;
token = mNextInstallToken++;
PostInstallData data = new PostInstallData(args, res);
mRunningInstalls.put(token, data);
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) {
//与BackAgent(云备份)相关
}
if (!doRestore) {
Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
mHandler.sendMessage(msg);
}
}
});
}
首先构造一个PostInstallData数据结构并添加到正在安装的列表mRunningInstalls中,这里跳过与BackAgent相关的部分。最后往PackageHandler发送一个POST_INSTALL消息,来看PackageHandler如何处理POST_INSTALL消息:
case POST_INSTALL: {
if (DEBUG_INSTALL) Log.v(TAG, "Handling post-install for " + msg.arg1);
PostInstallData data = mRunningInstalls.get(msg.arg1);
mRunningInstalls.delete(msg.arg1);
boolean deleteOld = false;
if (data != null) {
InstallArgs args = data.args;
PackageInstalledInfo res = data.res;
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
res.removedInfo.sendBroadcast(false, true, false);
Bundle extras = new Bundle(1);
extras.putInt(Intent.EXTRA_UID, res.uid);
int[] firstUsers;
int[] updateUsers = new int[0];
if (res.origUsers == null || res.origUsers.length == 0) {
firstUsers = res.newUsers;
} else {
}
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
res.pkg.applicationInfo.packageName,
extras, null, null, firstUsers);
final boolean update = res.removedInfo.removedPackage != null;
if (update) {
extras.putBoolean(Intent.EXTRA_REPLACING, true);
}
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
res.pkg.applicationInfo.packageName,
extras, null, null, updateUsers);
if (update) {
}
if (res.removedInfo.args != null) {
deleteOld = true;
}
}
Runtime.getRuntime().gc();
if (deleteOld) {
synchronized (mInstallLock) {
res.removedInfo.args.doPostDeleteLI(true);
}
}
if (args.observer != null) {
try {
args.observer.packageInstalled(res.name, res.returnCode);
} catch (RemoteException e) {
Slog.i(TAG, "Observer no longer exists.");
}
}
} else {
Slog.e(TAG, "Bogus post-install token " + msg.arg1);
}
} break;
在POST_INSTALL消息中,主要就是发送一些安装成功的广播,这些广播可以被launch接收,并在桌面上面增加图标等。然后向我们注册的observer回调安装成功的callback。这样在内部存储空间上APK安装过程就介绍完了。我们来看一下大致的的流程图:
安装在外部存储空间
/data 存放的是用户的软件信息(非自带rom安装的软件);/data/app 存放用户安装的软件;/data/data 存放所有软件(包括/system/app 和 /data/app 和 /mnt/asec中装的软件)的一些lib和xml文件等数据信息;/data/dalvik-cache 缓存的dex文件,这里的文件都是可以删除的。
/sdcard 目录,这是一个软链接(相当于windows的文件夹的快捷方式),链接到/mnt/sdcard 目录,即这个目录的内容就是sdcard的内容。
在Android 2.2之后的版本允许将应用程序安装于SD卡,每一个安装在SD卡的应用程序,都可以在SD卡中的/sdcard/.android_secure 目录里找到名称中有出现它的程序名,和副文件名为asec的经过特殊加密处理后的档案。当SD卡挂载于手机时,/mnt/sdcard/.android_secure 目录会被映射到/mnt/asec 目录和 /mnt/secure 目录。其中/mnt/asec 目录中主要是程序的安装目录,包括其执行文件和lib文件等;而/mnt/secure 目录中就存放程序加密后的档案。也就是说,在/mnt路径下看到的/mnt/asec目录和/mnt/secure目录并不是真正存在在手机内存或者sd卡的分区挂载目录,它们只是/mnt/sdcard/.android_secure目录的一个影像而已。
因此,用户程序安装到到sd卡上后,其内容可能分散到:/mnt/asec , /mnt/secure , /data/data 。
int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
if (temp) {
createCopyFile();
} else {
/*
* Pre-emptively destroy the container since it's destroyed if
* copying fails due to it existing anyway.
*/
PackageHelper.destroySdDir(cid);
}
final String newCachePath;
try {
mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,
Intent.FLAG_GRANT_READ_URI_PERMISSION);
newCachePath = imcs.copyResourceToContainer(packageURI, cid, getEncryptKey(),
RES_FILE_NAME, PUBLIC_RES_FILE_NAME, isExternal(), isFwdLocked());
} finally {
mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
if (newCachePath != null) {
setCachePath(newCachePath);
return PackageManager.INSTALL_SUCCEEDED;
} else {
return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
}
}
首先调用createCopyFile顺序的在SD卡上面创建目录结构,这些目录用于保存待安装的APK文件以及它们的library等资源:
void createCopyFile() {
cid = getTempContainerId();
}
static String getTempContainerId() {
int tmpIdx = 1;
String list[] = PackageHelper.getSecureContainerList();
if (list != null) {
for (final String name : list) {
// Ignore null and non-temporary container entries
if (name == null || !name.startsWith(mTempContainerPrefix)) {
continue;
}
String subStr = name.substring(mTempContainerPrefix.length());
try {
int cid = Integer.parseInt(subStr);
if (cid >= tmpIdx) {
tmpIdx = cid + 1;
}
} catch (NumberFormatException e) {
}
}
}
return mTempContainerPrefix + tmpIdx;
}
getTempContainerId调用MountService的方法去获取/mnt/asec下面的所有子目录,这些子目录都是以"smdl2tmp"开始,结尾是一个数字来命名。getTempContainerId找到一个尚未被使用的目录名赋予给cid。在copyApk方法中调用ContainService的copyResourceToContainer方法完成真正的创建目录以及拷贝文件的操作,这部分有兴趣的读者可以看看这个方法的实现。