android安装应用分两种,一种是直接调用PKMS的接口安装,另一种是扫描目录安装,比如系统开机的时候会扫描data/app目录进行安装。
我们先来看调用接口应用安装应用,都会通过如下函数,这个函数先会通过传进来的uid来判断是否是adb安装的,然后创建了一个InstallParams对象这个对象是HandlerParams的子类,然后发送消息。
@Override public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer, int installFlags, String installerPackageName, VerificationParams verificationParams, String packageAbiOverride, int userId) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null); final int callingUid = Binder.getCallingUid(); enforceCrossUserPermission(callingUid, userId, true, true, "installPackageAsUser"); if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) { try { if (observer != null) { observer.onPackageInstalled("", INSTALL_FAILED_USER_RESTRICTED, null, null); } } catch (RemoteException re) { } return; } if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) { installFlags |= PackageManager.INSTALL_FROM_ADB;//根据uid来判断是否是adb安装的 } else { // Caller holds INSTALL_PACKAGES permission, so we're less strict // about installerPackageName. installFlags &= ~PackageManager.INSTALL_FROM_ADB; installFlags &= ~PackageManager.INSTALL_ALL_USERS; } UserHandle user; if ((installFlags & PackageManager.INSTALL_ALL_USERS) != 0) { user = UserHandle.ALL; } else { user = new UserHandle(userId); } // Only system components can circumvent runtime permissions when installing. if ((installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0 && mContext.checkCallingOrSelfPermission(Manifest.permission .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) { throw new SecurityException("You need the " + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission " + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag"); } verificationParams.setInstallerUid(callingUid); final File originFile = new File(originPath); final OriginInfo origin = OriginInfo.fromUntrustedFile(originFile); final Message msg = mHandler.obtainMessage(INIT_COPY); InstallParams installParams = new InstallParams(origin, null, observer, installFlags, installerPackageName, null, verificationParams, user, packageAbiOverride, null); installParams.setHandlerType(HandlerParams.INSTALL_TYPE); msg.obj = installParams; mHandler.sendMessage(msg);//发送消息 }
普通的安装我们就调用这个接口,我们来看下adb install又是如何实现的。
我们来看下adb中Commandline.cpp中的如下代码,就是执行adb install命令的。
static int install_app(transport_type transport, const char* serial, int argc, const char** argv) { static const char *const DATA_DEST = "/data/local/tmp/%s"; static const char *const SD_DEST = "/sdcard/tmp/%s"; const char* where = DATA_DEST; int i; struct stat sb; for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "-s")) { where = SD_DEST; } } // Find last APK argument. // All other arguments passed through verbatim. int last_apk = -1; for (i = argc - 1; i >= 0; i--) { const char* file = argv[i]; char* dot = strrchr(file, '.'); if (dot && !strcasecmp(dot, ".apk")) { if (stat(file, &sb) == -1 || !S_ISREG(sb.st_mode)) { fprintf(stderr, "Invalid APK file: %s\n", file); return -1; } last_apk = i; break; } } if (last_apk == -1) { fprintf(stderr, "Missing APK file\n"); return -1; } const char* apk_file = argv[last_apk]; char apk_dest[PATH_MAX]; snprintf(apk_dest, sizeof apk_dest, where, get_basename(apk_file)); int err = do_sync_push(apk_file, apk_dest, 0 /* no show progress */);//先push apk文件 if (err) { goto cleanup_apk; } else { argv[last_apk] = apk_dest; /* destination name, not source location */ } err = pm_command(transport, serial, argc, argv);//调用pm命令 cleanup_apk: delete_file(transport, serial, apk_dest);//删除文件 return err; }
上面函数先是把apk文件push到data/local/tmp下面,然后调用pm命令,最后完成之后再删除文件。
下面我们分别看下几个函数。
static int pm_command(transport_type transport, const char* serial, int argc, const char** argv) { std::string cmd = "shell:pm"; while (argc-- > 0) { cmd += " " + escape_arg(*argv++); } return send_shell_command(transport, serial, cmd); }
删除就是调用了adb shell rm命令。
static int delete_file(transport_type transport, const char* serial, char* filename) { std::string cmd = "shell:rm -f " + escape_arg(filename); return send_shell_command(transport, serial, cmd); }
我们再看下PM命令,是在Pm.java文件中,最后也是调用了installPackageAsUser函数,这个我们就不看了。下面继续分析installPackageAsUser函数。
上面分析到发送一个INIT_COPY消息,我们再来看下消息处理。
case INIT_COPY: { HandlerParams params = (HandlerParams) msg.obj; int idx = mPendingInstalls.size(); if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params); // If a bind was already initiated we dont really // need to do anything. The pending install // will be processed later on. if (!mBound) { // If this is the only one pending we might // have to bind to the service again. if (!connectToService()) { Slog.e(TAG, "Failed to bind to media container service"); params.serviceError(); return; } else { // Once we bind to the service, the first // pending request will be processed. mPendingInstalls.add(idx, params); } } else { mPendingInstalls.add(idx, params); // Already bound to the service. Just make // sure we trigger off processing the first request. if (idx == 0) { mHandler.sendEmptyMessage(MCS_BOUND); } } break; }
消息处理先要看mBound是否为true,为true代表DefaultContainerService连接上了(这个service后续需要copy apk等文件的),这个时候会发送一个MCS_BOUND消息。我们先等等看这个消息的处理。这里如果mBound是false,就要调用connectToService函数。
我们来看这个函数,就是调用BindService来启动一个Service
private boolean connectToService() { if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to bind to" + " DefaultContainerService"); Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT); Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); if (mContext.bindServiceAsUser(service, mDefContainerConn, Context.BIND_AUTO_CREATE, UserHandle.OWNER)) { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); mBound = true; return true; } Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); return false; }
static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName( DEFAULT_CONTAINER_PACKAGE, "com.android.defcontainer.DefaultContainerService");
我们再来看Connection,连接上之后会把这个Service的Binder对象IMediaContainerService ,发送一个MCS_BOUND消息。
final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection(); class DefaultContainerConnection implements ServiceConnection { public void onServiceConnected(ComponentName name, IBinder service) { if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceConnected"); IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service); mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs)); } public void onServiceDisconnected(ComponentName name) { if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceDisconnected"); } }
现在我们再来看这个消息处理。其实这个函数主要就是调用了调用HandlerParams的startCopy函数,其他就是判断Service是否断开,是否mPendingInstalls还有其他没有安装的应用等处理。
case MCS_BOUND: { if (DEBUG_INSTALL) Slog.i(TAG, "mcs_bound"); if (msg.obj != null) { mContainerService = (IMediaContainerService) msg.obj; } if (mContainerService == null) { if (!mBound) { // Something seriously wrong since we are not bound and we are not // waiting for connection. Bail out. Slog.e(TAG, "Cannot bind to media container service"); for (HandlerParams params : mPendingInstalls) { // Indicate service bind error params.serviceError(); } mPendingInstalls.clear(); } else { Slog.w(TAG, "Waiting to connect to media container service"); } } else if (mPendingInstalls.size() > 0) { HandlerParams params = mPendingInstalls.get(0); if (params != null) { if (params.startCopy()) {//调用HandlerParams的startCopy函数 // We are done... look for more work or to // go idle. if (DEBUG_SD_INSTALL) Log.i(TAG, "Checking for more work or unbind..."); // Delete pending install if (mPendingInstalls.size() > 0) { mPendingInstalls.remove(0); } if (mPendingInstalls.size() == 0) { if (mBound) { if (DEBUG_SD_INSTALL) Log.i(TAG, "Posting delayed MCS_UNBIND"); removeMessages(MCS_UNBIND); Message ubmsg = obtainMessage(MCS_UNBIND); // Unbind after a little delay, to avoid // continual thrashing. sendMessageDelayed(ubmsg, 10000); } } else { // There are more pending requests in queue. // Just post MCS_BOUND message to trigger processing // of next pending install. if (DEBUG_SD_INSTALL) Log.i(TAG, "Posting MCS_BOUND for next work"); mHandler.sendEmptyMessage(MCS_BOUND); } } } } else { // Should never happen ideally. Slog.w(TAG, "Empty queue"); } break; }
我们来看下面这个函数,如果超过4次出错,直接发送一个MCS_GIVE_UP消息,这个消息里面会调用mPendingInstalls.remove(0);把mPendingInstalls中第一项去除(就是去除第一个要安装的应用),当我们成功安装完在MCS_BOUND也会将第一个去除的。
当然正常的是调用handleStartCopy函数,只有出现RemoteException的是否才会返回false,代表这个copy不成功,也就不会remove这项应用信息。下次还会再调调用startCopy函数继续处理。
final boolean startCopy() { boolean res; try { if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this); if (++mRetries > MAX_RETRIES) {//如果安装没有成功,超过4次。错误处理 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; }
我们先看下handleServiceError函数,也像handleStartCopy会调用createInstallArgs,创建一个InstallArgs,然后mRet为失败的。这些后面在处理processPendingInstall函数的时候需要
@Override void handleServiceError() { mArgs = createInstallArgs(this); mRet = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; }
我们再来看看handleStartCopy函数,先会调用createInstallArgs来创建一个InstallArgs,这里只是一个基类而已,然后会调用InstallArgs的copyApk函数,来copy apk文件到data/app下面.
public void handleStartCopy() throws RemoteException { int ret = PackageManager.INSTALL_SUCCEEDED; ...... final InstallArgs args = createInstallArgs(this); mArgs = args; ...... else { /* * No package verification is enabled, so immediately start * the remote call to initiate copy using temporary file. */ ret = args.copyApk(mContainerService, true); } } mRet = ret; }
我们先来看下createInstallArgs函数,根据InstallParams来创建不同的InstallArgs,这里我们只关心FileInstallArgs。
private InstallArgs createInstallArgs(InstallParams params) { if (params.move != null) { return new MoveInstallArgs(params); } else if (installOnExternalAsec(params.installFlags) || params.isForwardLocked()) { return new AsecInstallArgs(params); } else { return new FileInstallArgs(params); } }
我们来看下FileInstallArgs 的copyApk函数,先是调用PackageInstallerService的allocateStageDirLegacy函数来创建一个目录,然后调用ImediaContainerService binder调用copyPackage函数,这个就是之前的DefaultContainerService。
int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException { if (origin.staged) { if (DEBUG_INSTALL) Slog.d(TAG, origin.file + " already staged; skipping copy"); codeFile = origin.file; resourceFile = origin.file; return PackageManager.INSTALL_SUCCEEDED; } try { final File tempDir = mInstallerService.allocateStageDirLegacy(volumeUuid); codeFile = tempDir; resourceFile = tempDir; } catch (IOException e) { Slog.w(TAG, "Failed to create copy file: " + e); return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; } final IParcelFileDescriptorFactory target = new IParcelFileDescriptorFactory.Stub() { @Override public ParcelFileDescriptor open(String name, int mode) throws RemoteException { if (!FileUtils.isValidExtFilename(name)) { throw new IllegalArgumentException("Invalid filename: " + name); } try { final File file = new File(codeFile, name); final FileDescriptor fd = Os.open(file.getAbsolutePath(), O_RDWR | O_CREAT, 0644); Os.chmod(file.getAbsolutePath(), 0644); return new ParcelFileDescriptor(fd); } catch (ErrnoException e) { throw new RemoteException("Failed to open: " + e.getMessage()); } } }; int ret = PackageManager.INSTALL_SUCCEEDED; ret = imcs.copyPackage(origin.file.getAbsolutePath(), target); if (ret != PackageManager.INSTALL_SUCCEEDED) { Slog.e(TAG, "Failed to copy package"); return ret; } final File libraryRoot = new File(codeFile, LIB_DIR_NAME); NativeLibraryHelper.Handle handle = null; try { handle = NativeLibraryHelper.Handle.create(codeFile); ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot, abiOverride); } catch (IOException e) { Slog.e(TAG, "Copying native libraries failed", e); ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; } finally { IoUtils.closeQuietly(handle); } return ret; }
这里的data/app下的临时文件我们来看下是怎么样的
# ls IflytekInput.apk MOffice.apk NotePadPlus.apk vmdl202298311.tmp
这里vmdl202298311.tmp只是一个目录,下面还有base.apk 和lib,lib也是一个目录里面还有各个so文件
base.apk lib
这里就完成了将apk copy到data/app下面的临时目录中。
然后在startCopy函数中又会调用handleReturnCode函数,这里就是调用了processPendingInstall函数,注意参数mRett就是之前调用copyApk的返回值。
@Override void handleReturnCode() { // If mArgs is null, then MCS couldn't be reached. When it // reconnects, it will try again to install. At that point, this // will succeed. if (mArgs != null) { processPendingInstall(mArgs, mRet); } }
processPendingInstall函数如下,这里的currentStatus参数就是之前调用copyApk的返回值代表是否copy文件成功,然后调用installPackageLI装载应用到PKMS中,新建一个PostInstallData对象放入mRunningInstalls中代表正在安装的应用,最后发送一个POST_INSTALL消息
private void processPendingInstall(final InstallArgs args, final int currentStatus) { mHandler.post(new Runnable() { public void run() { mHandler.removeCallbacks(this); // Result object to be returned PackageInstalledInfo res = new PackageInstalledInfo(); res.returnCode = currentStatus; res.uid = -1; res.pkg = null; res.removedInfo = new PackageRemovedInfo(); if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {//copy文件成功 args.doPreInstall(res.returnCode); synchronized (mInstallLock) { installPackageLI(args, res); } args.doPostInstall(res.returnCode, res.uid); } final boolean update = res.removedInfo.removedPackage != null; final int flags = (res.pkg == null) ? 0 : res.pkg.applicationInfo.flags; boolean doRestore = !update && ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0); 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) { IBackupManager bm = IBackupManager.Stub.asInterface( ServiceManager.getService(Context.BACKUP_SERVICE)); if (bm != null) { try { if (bm.isBackupServiceActive(UserHandle.USER_OWNER)) { bm.restoreAtInstall(res.pkg.applicationInfo.packageName, token); } else { doRestore = false; } } catch (RemoteException e) { // can't happen; the backup manager is local } catch (Exception e) { Slog.e(TAG, "Exception trying to enqueue restore", e); doRestore = false; } } else { Slog.e(TAG, "Backup Manager not found!"); doRestore = false; } } if (!doRestore) { Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0); mHandler.sendMessage(msg); } } }); }
上面我们还要注意在调用installPackageLI函数前后都有调用args.doPostInstall函数,下面我么来看这个函数,没有uid参数的那个函数也是一样的。这个函数当没有安装成功就删除之前在data/app下面创建的apk临时目录。特别注意在调用完installPackageLI之后也要调用下这个函数,因为在installPackageLI函数的处理中可以会有比如应用已经安装过这样的情况等,都会导致安装失败,这个时候都要把临时文件清除。
int doPostInstall(int status, int uid) { if (status != PackageManager.INSTALL_SUCCEEDED) { cleanUp(); } return status; }
我们先来看下installPackageLI函数如下代码,我们先会新建一个PackageParser来解析apk文件,这个我们之前的博客中分析过,这里就不看了。后面成功之后会调用args的rename函数,把临时文件重新命名。然后如果是一个新应用会调用installNewPackageLI函数。
private void installPackageLI(InstallArgs args, PackageInstalledInfo res) { final int installFlags = args.installFlags; final String installerPackageName = args.installerPackageName; final String volumeUuid = args.volumeUuid; final File tmpPackageFile = new File(args.getCodePath()); final boolean forwardLocked = ((installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0); final boolean onExternal = (((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) || (args.volumeUuid != null)); ...... final PackageParser.Package pkg; try { pkg = pp.parsePackage(tmpPackageFile, parseFlags); } catch (PackageParserException e) { res.setError("Failed parse during installPackageLI", e); return; } ...... if (!args.doRename(res.returnCode, pkg, oldCodePath)) {//重新命名 res.setError(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename"); return; } startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg); if (replace) {//升级 replacePackageLI(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user, installerPackageName, volumeUuid, res); } else { installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES, args.user, installerPackageName, volumeUuid, res); } ...... }
FileInstallArgs的doRename函数调用了getNextCodePath函数来获取新的apk的目录的name,然后调用os的rename函数重新命名。然后Package的一些变量改下。
boolean doRename(int status, PackageParser.Package pkg, String oldCodePath) { if (status != PackageManager.INSTALL_SUCCEEDED) { cleanUp(); return false; } final File targetDir = codeFile.getParentFile(); final File beforeCodeFile = codeFile; final File afterCodeFile = getNextCodePath(targetDir, pkg.packageName);//获取新名字 if (DEBUG_INSTALL) Slog.d(TAG, "Renaming " + beforeCodeFile + " to " + afterCodeFile); try { Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath());//rename } catch (ErrnoException e) { Slog.w(TAG, "Failed to rename", e); return false; } if (!SELinux.restoreconRecursive(afterCodeFile)) { Slog.w(TAG, "Failed to restorecon"); return false; } // Reflect the rename internally codeFile = afterCodeFile; resourceFile = afterCodeFile; // Reflect the rename in scanned details pkg.codePath = afterCodeFile.getAbsolutePath(); pkg.baseCodePath = FileUtils.rewriteAfterRename(beforeCodeFile, afterCodeFile, pkg.baseCodePath); pkg.splitCodePaths = FileUtils.rewriteAfterRename(beforeCodeFile, afterCodeFile, pkg.splitCodePaths); // Reflect the rename in app info pkg.applicationInfo.volumeUuid = pkg.volumeUuid; pkg.applicationInfo.setCodePath(pkg.codePath); pkg.applicationInfo.setBaseCodePath(pkg.baseCodePath); pkg.applicationInfo.setSplitCodePaths(pkg.splitCodePaths); pkg.applicationInfo.setResourcePath(pkg.codePath); pkg.applicationInfo.setBaseResourcePath(pkg.baseCodePath); pkg.applicationInfo.setSplitResourcePaths(pkg.splitCodePaths); return true; }
我们看下getNextCodePath获取新的apk目录名字,就是apk的那么加一个后缀。
private File getNextCodePath(File targetDir, String packageName) { int suffix = 1; File result; do { result = new File(targetDir, packageName + "-" + suffix); suffix++; } while (result.exists()); return result; }
类似如下:
com.moji.mjweather-1
我们再来看看之前在processPendingInstall函数中发送的POST_INSTALL消息的处理:
这里主要的逻辑是当应用安装成功后,发送广播(比如Launcher应用接受广播后显示桌面图标)。
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) { final String packageName = res.pkg.applicationInfo.packageName; res.removedInfo.sendBroadcast(false, true, false); Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, res.uid); // Now that we successfully installed the package, grant runtime // permissions if requested before broadcasting the install. if ((args.installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0) { grantRequestedRuntimePermissions(res.pkg, args.user.getIdentifier(), args.installGrantPermissions); } // Determine the set of users who are adding this // package for the first time vs. those who are seeing // an update. int[] firstUsers; int[] updateUsers = new int[0]; if (res.origUsers == null || res.origUsers.length == 0) { firstUsers = res.newUsers; } else { firstUsers = new int[0]; for (int i=0; i<res.newUsers.length; i++) { int user = res.newUsers[i]; boolean isNew = true; for (int j=0; j<res.origUsers.length; j++) { if (res.origUsers[j] == user) { isNew = false; break; } } if (isNew) { int[] newFirst = new int[firstUsers.length+1]; System.arraycopy(firstUsers, 0, newFirst, 0, firstUsers.length); newFirst[firstUsers.length] = user; firstUsers = newFirst; } else { int[] newUpdate = new int[updateUsers.length+1]; System.arraycopy(updateUsers, 0, newUpdate, 0, updateUsers.length); newUpdate[updateUsers.length] = user; updateUsers = newUpdate; } } } sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, 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, packageName, extras, null, null, updateUsers); if (update) { sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, extras, null, null, updateUsers); sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, packageName, null, updateUsers); // treat asec-hosted packages like removable media on upgrade if (res.pkg.isForwardLocked() || isExternal(res.pkg)) { if (DEBUG_INSTALL) { Slog.i(TAG, "upgrading pkg " + res.pkg + " is ASEC-hosted -> AVAILABLE"); } int[] uidArray = new int[] { res.pkg.applicationInfo.uid }; ArrayList<String> pkgList = new ArrayList<String>(1); pkgList.add(packageName); sendResourcesChangedBroadcast(true, true, pkgList,uidArray, null); } } if (res.removedInfo.args != null) { // Remove the replaced package's older resources safely now deleteOld = true; } // If this app is a browser and it's newly-installed for some // users, clear any default-browser state in those users if (firstUsers.length > 0) { // the app's nature doesn't depend on the user, so we can just // check its browser nature in any user and generalize. if (packageIsBrowser(packageName, firstUsers[0])) { synchronized (mPackages) { for (int userId : firstUsers) { mSettings.setDefaultBrowserPackageNameLPw(null, userId); } } } } // Log current value of "unknown sources" setting EventLog.writeEvent(EventLogTags.UNKNOWN_SOURCES_ENABLED, getUnknownSourcesSettings()); } // Force a gc to clear up things Runtime.getRuntime().gc(); // We delete after a gc for applications on sdcard. if (deleteOld) { synchronized (mInstallLock) { res.removedInfo.args.doPostDeleteLI(true); } } if (args.observer != null) { try { Bundle extras = extrasForInstallResult(res); args.observer.onPackageInstalled(res.name, res.returnCode, res.returnMsg, extras); } catch (RemoteException e) { Slog.i(TAG, "Observer no longer exists."); } } } else { Slog.e(TAG, "Bogus post-install token " + msg.arg1); } } break;
我们来看下这个函数,这个函数当mSettings和mPackages已经有这个pkg了,那就代表这个应用已经安装过了,直接调用PackageInstalledInfo的setError,这个函数会把returnCode变为INSTALL_FAILED_ALREADY_EXISTS,这样在processPendingInstall函数在调用完installPackageLI之后再调用args.doPostInstall的时候会把apk的临时文件删除。然后继续这里调用了scanPackageLI(注意这个参数是Package的那个函数,后面在扫描目录的时候再介绍),最后还是没有安装成功会在deletePackageLI删除目录,以及PKMS各个成员变量的信息。
private void installNewPackageLI(PackageParser.Package pkg, int parseFlags, int scanFlags, UserHandle user, String installerPackageName, String volumeUuid, PackageInstalledInfo res) { // Remember this for later, in case we need to rollback this install String pkgName = pkg.packageName; final boolean dataDirExists = Environment .getDataUserPackageDirectory(volumeUuid, UserHandle.USER_OWNER, pkgName).exists(); synchronized(mPackages) { if (mSettings.mRenamedPackages.containsKey(pkgName)) { res.setError(INSTALL_FAILED_ALREADY_EXISTS, "Attempt to re-install " + pkgName + " without first uninstalling package running as " + mSettings.mRenamedPackages.get(pkgName)); return; } if (mPackages.containsKey(pkgName)) { res.setError(INSTALL_FAILED_ALREADY_EXISTS, "Attempt to re-install " + pkgName + " without first uninstalling."); return; } } try { PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanFlags, System.currentTimeMillis(), user); updateSettingsLI(newPackage, installerPackageName, volumeUuid, null, null, res, user); if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) { deletePackageLI(pkgName, UserHandle.ALL, false, null, null, dataDirExists ? PackageManager.DELETE_KEEP_DATA : 0, res.removedInfo, true); } } catch (PackageManagerException e) { res.setError("Package couldn't be installed in " + pkg.codePath, e); } }
我们知道在PKMS启动的时候会扫描system/app data/app等目录,如果其中有apk文件会完成apk安装
我们先来看扫描目录的函数scanDirLI,大致就是遍历各个文件调用scanPackageLI函数,但是不会扫描子目录。
private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) { final File[] files = dir.listFiles(); if (ArrayUtils.isEmpty(files)) { Log.d(TAG, "No files in app dir " + dir); return; } if (DEBUG_PACKAGE_SCANNING) { Log.d(TAG, "Scanning app dir " + dir + " scanFlags=" + scanFlags + " flags=0x" + Integer.toHexString(parseFlags)); } 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) { Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage()); // Delete invalid userdata apps if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 && e.error == PackageManager.INSTALL_FAILED_INVALID_APK) { logCriticalInfo(Log.WARN, "Deleting invalid package at " + file); if (file.isDirectory()) { mInstaller.rmPackageDir(file.getAbsolutePath()); } else { file.delete(); } } } } }
scanPackageLI函数,先是解析apk文件,然后处理升级包,扫描文件签名、应用包冲突、应用代码路径资源路径,设置应用相关路劲等,最后调用了scanPackageLI(注意这个参数是Package)继续。
private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags, long currentTime, UserHandle user) throws PackageManagerException { if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile); parseFlags |= mDefParseFlags; PackageParser pp = new PackageParser(); pp.setSeparateProcesses(mSeparateProcesses); pp.setOnlyCoreApps(mOnlyCore); pp.setDisplayMetrics(mMetrics); if ((scanFlags & SCAN_TRUSTED_OVERLAY) != 0) { parseFlags |= PackageParser.PARSE_TRUSTED_OVERLAY; } final PackageParser.Package pkg; try { pkg = pp.parsePackage(scanFile, parseFlags);//解析 } catch (PackageParserException e) { throw PackageManagerException.from(e); } ...... // Set application objects path explicitly. pkg.applicationInfo.volumeUuid = pkg.volumeUuid; pkg.applicationInfo.setCodePath(pkg.codePath);//设置各种路径 pkg.applicationInfo.setBaseCodePath(pkg.baseCodePath); pkg.applicationInfo.setSplitCodePaths(pkg.splitCodePaths); pkg.applicationInfo.setResourcePath(resourcePath); pkg.applicationInfo.setBaseResourcePath(baseResourcePath); pkg.applicationInfo.setSplitResourcePaths(pkg.splitCodePaths); PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags | SCAN_UPDATE_SIGNATURE, currentTime, user); ...... return scannedPkg; }
scanPackageLI就是调用scanPackageDirtyLI函数,出错了再删除相关目录。
private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags, int scanFlags, long currentTime, UserHandle user) throws PackageManagerException { boolean success = false; try { final PackageParser.Package res = scanPackageDirtyLI(pkg, parseFlags, scanFlags, currentTime, user); success = true; return res; } finally { if (!success && (scanFlags & SCAN_DELETE_DATA_ON_FAILURES) != 0) { removeDataDirsLI(pkg.volumeUuid, pkg.packageName); } } }
这个函数之前博客分析过了,这里我们说下几点:
1. 安装应用中的动态库,如果应用自带了本地动态库,安装在data/data/<pacage-name>/lib下。如果是系统应用,展开的动态库放在/system/lib下。
我们来看下墨迹天气的lib目录:
********:/data/data/com.moji.mjweather/lib # ls libedittextutil.so libgetuiext.so libskinEncrypt.so libuninstall.so libusedes.so
2. 重新优化dex,会调用performDexOptLI函数
3. 最后会把应用的Activity、Service、Provider、Receiver、Permission、PermissionGroup信息都提取出来放在如下变量中。
final ActivityIntentResolver mActivities = new ActivityIntentResolver(); final ActivityIntentResolver mReceivers = new ActivityIntentResolver(); final ServiceIntentResolver mServices = new ServiceIntentResolver(); final ProviderIntentResolver mProviders = new ProviderIntentResolver();
PKMS的performDexOpt函数,最终会调用PackageDexOptimizer的performDexOpt函数,代码如下,看是否需要持锁。然后调用performDexOptLI函数。
int performDexOpt(PackageParser.Package pkg, String[] instructionSets, boolean forceDex, boolean defer, boolean inclDependencies, boolean bootComplete) { ArraySet<String> done; if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) { done = new ArraySet<String>(); done.add(pkg.packageName); } else { done = null; } synchronized (mPackageManagerService.mInstallLock) { final boolean useLock = mSystemReady; if (useLock) { mDexoptWakeLock.setWorkSource(new WorkSource(pkg.applicationInfo.uid)); mDexoptWakeLock.acquire(); } try { return performDexOptLI(pkg, instructionSets, forceDex, defer, bootComplete, done); } finally { if (useLock) { mDexoptWakeLock.release(); } } } }
performDexOptLI函数,先看有没有lib需要dex。最后调用了PKMS的mInstaller.dexopt函数来优化。最后就到Installd中执行。
private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets, boolean forceDex, boolean defer, boolean bootComplete, ArraySet<String> done) { final String[] instructionSets = targetInstructionSets != null ? targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo); if (done != null) { done.add(pkg.packageName); if (pkg.usesLibraries != null) { performDexOptLibsLI(pkg.usesLibraries, instructionSets, forceDex, defer, bootComplete, done); } if (pkg.usesOptionalLibraries != null) { performDexOptLibsLI(pkg.usesOptionalLibraries, instructionSets, forceDex, defer, bootComplete, done); } } if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) == 0) { return DEX_OPT_SKIPPED; } final boolean vmSafeMode = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0; final boolean debuggable = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly(); boolean performedDexOpt = false; final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); for (String dexCodeInstructionSet : dexCodeInstructionSets) { if (!forceDex && pkg.mDexOptPerformed.contains(dexCodeInstructionSet)) { continue; } for (String path : paths) { final int dexoptNeeded; if (forceDex) { dexoptNeeded = DexFile.DEX2OAT_NEEDED; } else { try { dexoptNeeded = DexFile.getDexOptNeeded(path, pkg.packageName, dexCodeInstructionSet, defer); } catch (IOException ioe) { Slog.w(TAG, "IOException reading apk: " + path, ioe); return DEX_OPT_FAILED; } } if (!forceDex && defer && dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) { // We're deciding to defer a needed dexopt. Don't bother dexopting for other // paths and instruction sets. We'll deal with them all together when we process // our list of deferred dexopts. addPackageForDeferredDexopt(pkg); return DEX_OPT_DEFERRED; } if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) { final String dexoptType; String oatDir = null; if (dexoptNeeded == DexFile.DEX2OAT_NEEDED) { dexoptType = "dex2oat"; try { oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet); } catch (IOException ioe) { Slog.w(TAG, "Unable to create oatDir for package: " + pkg.packageName); return DEX_OPT_FAILED; } } else if (dexoptNeeded == DexFile.PATCHOAT_NEEDED) { dexoptType = "patchoat"; } else if (dexoptNeeded == DexFile.SELF_PATCHOAT_NEEDED) { dexoptType = "self patchoat"; } else { throw new IllegalStateException("Invalid dexopt needed: " + dexoptNeeded); } final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); final int ret = mPackageManagerService.mInstaller.dexopt(path, sharedGid, !pkg.isForwardLocked(), pkg.packageName, dexCodeInstructionSet, dexoptNeeded, vmSafeMode, debuggable, oatDir, bootComplete); if (ret == 0) { performedDexOpt = true; } } } ......
这里我们需要注意下oatDir这个参数,是通过下面函数获取的。
oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet);
我们来看createOatDirIfSupported,如果codePath是一个目录就返回一个oatDir,否则就是空。
private String createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet) throws IOException { if (!pkg.canHaveOatDir()) { return null; } File codePath = new File(pkg.codePath); if (codePath.isDirectory()) { File oatDir = getOatDir(codePath); mPackageManagerService.mInstaller.createOatDir(oatDir.getAbsolutePath(), dexInstructionSet); return oatDir.getAbsolutePath(); } return null; }
传到Installd中,是空的话,默认会在data/dalvik-cache下面,我们来看下,这个是arm目录,如果是64为是在arm64目录下:
*******:/data/dalvik-cache/arm # ls data@[email protected]@classes.dex data@[email protected]@classes.dex system@app@[email protected]@classes.dex system@app@[email protected]@classes.dex system@app@[email protected]@classes.dex system@app@[email protected]@classes.dex system@app@[email protected]@classes.dex system@app@[email protected]@classes.dex system@app@[email protected]@classes.dex system@[email protected]@classes.dex system@[email protected]@classes.dex system@[email protected]@classes.dex system@[email protected]@classes.dex system@[email protected]@classes.dex system@[email protected] system@[email protected] system@[email protected]@classes.dex system@[email protected]@classes.dex system@[email protected]@classes.dex system@[email protected]@classes.dex system@[email protected]@classes.dex system@[email protected][email protected] system@[email protected]@classes.dex system@[email protected]@classes.dex system@[email protected]@classes.dex system@[email protected]@classes.dex
普通的应用就在其oat/arm目录下
******:/data/app/com.moji.mjweather-1/oat/arm # ls base.odex