Android安装应用分两种,一种是直接调用PKMS的接口安装,另一种是扫描目录安装,比如系统开机的时候会扫描data/app目录进行安装。
一、调用installPackageAsUser函数安装
我们先来看调用接口应用安装应用,都会通过如下函数,这个函数先会通过传进来的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);//发送消息
- }
1.1 adb install
普通的安装我们就调用这个接口,我们来看下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函数。
1.2 MCS_BOUND
上面分析到发送一个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;
- }
1.3 startCopy
我们来看下面这个函数,如果超过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下面的临时目录中。
1.4 processPendingInstall函数
然后在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;
- }
1.5 installPackageLI函数
我们先来看下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
1.6 POST_INSTALL消息的处理
我们再来看看之前在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;
1.7 installNewPackageLI函数
我们来看下这个函数,这个函数当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();
- }
- }
- }
- }
- }
2.1 scanPackageLI函数
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);
- }
- }
- }
2.2 scanPackageDirtyLI函数
这个函数之前博客分析过了,这里我们说下几点:
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@app@IflytekInput.apk@classes.dex
- data@app@NotePadPlus.apk@classes.dex
- system@app@Bluetooth@Bluetooth.apk@classes.dex
- system@app@Galaxy4@Galaxy4.apk@classes.dex
- system@app@Gallery2@Gallery2.apk@classes.dex
- system@app@HoloSpiralWallpaper@HoloSpiralWallpaper.apk@classes.dex
- system@app@NoiseField@NoiseField.apk@classes.dex
- system@app@PhaseBeam@PhaseBeam.apk@classes.dex
- system@app@webview@webview.apk@classes.dex
- system@framework@am.jar@classes.dex
- system@framework@android.test.runner.jar@classes.dex
- system@framework@appops.jar@classes.dex
- system@framework@appwidget.jar@classes.dex
- system@framework@bmgr.jar@classes.dex
- system@framework@boot.art
- system@framework@boot.oat
- system@framework@bu.jar@classes.dex
- system@framework@com.android.future.usb.accessory.jar@classes.dex
- system@framework@com.android.location.provider.jar@classes.dex
- system@framework@com.android.media.remotedisplay.jar@classes.dex
- system@framework@com.android.mediadrm.signer.jar@classes.dex
- system@framework@com.android.nfc_extras.jar@classes.dex
- system@framework@com.broadcom.bt.jar@classes.dex
- system@framework@com.broadcom.nfc.jar@classes.dex
- system@framework@com.vzw.nfc.jar@classes.dex
- system@framework@content.jar@classes.dex
普通的应用就在其oat/arm目录下
- ******:/data/app/com.moji.mjweather-1/oat/arm # ls
- base.odex
原文地址
http://blog.csdn.net/kc58236582/article/details/53022791
小结:
序列图
无论是开机扫描各个目录的apk文件,还是安装新的apk最后都会调用scanPackageDirtyLI方法。
我们来简单回顾下,开机扫描各个目录的时候是调用scanDirLI函数,然后调用scanPackageLI函数这个函数第一个参数是File,这个函数会新建一个PackageParser对象,来解析文件。最后也会调用另一个scanPackageLI方法这个参数是Packageparser.Package类。而在这个scanPackageLI方法中最后调用了scanPackageDirtyLI方法。
在安装一个新应用的时候先copy apk文件到指定目录,后面会调用installPackageLI,这个函数也会创建一个PackageParser对象来解析apk文件,后面会调用installNewPackageLI继续处理,最后会发送广播通知其他应用,比如Launcher增加图标等。在installNewPackageLI函数中也会调用scanPackageLI函数,是参数是Packageparser.Package类。而最后就会调用scanPackageDirtyLI方法。
所以其实无论开机扫描还是安装应用其实流程差不多。