美文网首页
Apk安装的源码分析(二)

Apk安装的源码分析(二)

作者: 就叫汉堡吧 | 来源:发表于2023-09-26 01:09 被阅读0次
    • 书接上回

      上文我们分析到,安装过程好像是把一个apk文件写入到了某个地方,通过PackageInstaller.Session的openWrite方法获取到一个OutputStream,然后写入apk文件,这里再贴一下代码:

      try{
            File file = new File(mPackageURI.getPath());
            try (InputStream in = new FileInputStream(file)) {
                long sizeBytes = file.length();
                //获取目标文件写入流
                try (OutputStream out = session
                       .openWrite("PackageInstaller", 0, sizeBytes)) {
                    byte[] buffer = new byte[1024 * 1024];
                    while (true) {
                          int numRead = in.read(buffer);
                        //文件已写入(写入到缓冲区)完毕
                        if (numRead == -1) {
                            //这里调用其实是IoBridge.write方法,就是真正的写入到磁盘,可以理解成flush操作
                            session.fsync(out);
                            break;
                        }
                        if (isCancelled()) {
                            session.close();
                            break;
                        }
                        //写入到缓冲区
                        out.write(buffer, 0, numRead);
                        if (sizeBytes > 0) {
                            float fraction = ((float) numRead / (float) sizeBytes);
                            session.addProgress(fraction);
                        }
                     }
                 }
            }
            return session;
      } catch (IOException | SecurityException e) {
          session.close();
          return null;
      } 
      
    • 构造session和写入

      想要知道session是什么,首先要知道它的真实身份,它是由getPackageManager().getPackageInstaller().createSession方法获取的。

      getPackageManager().getPackageInstaller()获取的是PackageInstaller,它的createSession方法内部调用的是IPackageInstaller的createSession方法,IPackageInstaller的实现类是PackageInstallerService,它的createSession方法调用了doWriteInternal方法:

      private int createSessionInternal(SessionParams params, String installerPackageName,
              String installerAttributionTag, int userId)
              throws IOException {
            ...
          //sessionId其实就是一个唯一的随机数
          int sessionId = allocateSessionIdLocked();
          session = new PackageInstallerSession(...);
            synchronized (mSessions) {
                //保存起来,openSession方法就是从mSessions中按照sessionId获取的session
              mSessions.put(sessionId, session);
          }
            ...
          return sessionId;
      }
      

      我们看到,session的真实身份是PackageInstallerSession,但这个方法并没有返回session,而是返回了一个id,我们需要通过PackageInstaller的openSession(int sessionId)来获取,这个方法中会把PackageInstallerSession包装成PackageInstaller.Session返回,创建的PackageInstallerSession在PackageInstaller.Session中被保存为mSession字段。

      所以session的openWrite调用的是PackageInstaller.Session内的这个方法:

      public @NonNull OutputStream openWrite(@NonNull String name, long offsetBytes,
              long lengthBytes) throws IOException {
          try {
              if (ENABLE_REVOCABLE_FD) {
                    //自动关闭输出流(即需要调用close方法刷新已写入的数据),我们这里没用到,因为用的是fsync方法直接刷新缓冲区,所以不需要close刷新
                  return new ParcelFileDescriptor.AutoCloseOutputStream(
                          mSession.openWrite(name, offsetBytes, lengthBytes));
              } else {
                    //此次流程会走这里
                  final ParcelFileDescriptor clientSocket = mSession.openWrite(name,
                          offsetBytes, lengthBytes);
                  return new FileBridge.FileBridgeOutputStream(clientSocket);
              }
          } ...
      }
      

      可以看到,这里返回了一个FileBridge.FileBridgeOutputStream写入流,它持有了一个ParcelFileDescriptor,通过mSession的openWrite方法获取,前面我们知道,mSession就是PackageInstallerSession,它的openWrite调用了doWriteInternal方法:

      private ParcelFileDescriptor doWriteInternal(String name, long offsetBytes, long lengthBytes,
              ParcelFileDescriptor incomingFd) throws IOException {
          ...
          final FileBridge bridge = new FileBridge();
          ...
          try {
                ...
                final File target = new File(stageDir, name);
                final FileDescriptor fd = Os.open(target.getAbsolutePath(), O_CREAT | O_WRONLY, 0644);
                ParcelFileDescriptor targetPfd = new ParcelFileDescriptor(fd);
                ...
                if(...){
                  ...
                }else{
                    //会走这
                    bridge.setTargetFile(targetPfd);
                    bridge.start();
                }
                return bridge.getClientSocket();
                ...
          } catch (ErrnoException e) {
              throw e.rethrowAsIOException();
          }
      }
      

      stageDir是在PackageInstallerService内指定的,有兴趣可以去查找一下,有内部存储和外部存储两条路径,内部存储的stageDir路径获取如下:

      private File buildSessionDir(int sessionId, SessionParams params) {
            ...
            final File result = buildTmpSessionDir(sessionId, params.volumeUuid);
            ...
            return result;
      }
      
      private File buildTmpSessionDir(int sessionId, String volumeUuid) {
            final File sessionStagingDir = getTmpSessionDir(volumeUuid);
            return new File(sessionStagingDir, "vmdl" + sessionId + ".tmp");
      }
      
      private File getTmpSessionDir(String volumeUuid) {
            return Environment.getDataAppDirectory(volumeUuid);
      }
      
      //Environment:
      public static File getDataAppDirectory(String volumeUuid) {
            return new File(getDataDirectory(volumeUuid), "app");
        }
      
      public static String getDataDirectoryPath(String volumeUuid) {
            if (TextUtils.isEmpty(volumeUuid)) {
                return DIR_ANDROID_DATA_PATH;
            } else {
                return getExpandDirectory().getAbsolutePath() + File.separator + volumeUuid;
            }
      }
      
      private static final String DIR_ANDROID_DATA_PATH = getDirectoryPath(ENV_ANDROID_DATA, "/data");
      private static final String ENV_ANDROID_DATA = "ANDROID_DATA";
      
      static String getDirectoryPath(@NonNull String variableName, @NonNull String defaultPath) {
            //经测试,这里获取的是“/data”
            String path = System.getenv(variableName);
            return path == null ? defaultPath : path;
      }
      

      因为之前在InstallInstalling中构造的SessionParams并没有设置volumeUuid,因此此属性值为空,所以最终获取的stageDir是/data/app/vmdl[sessionId].tmp/([]括号表示是变量)。
      name是在Install中通过openWrite方法传入的,值为“PackageInstaller”,因此target文件就是/data/app/vmdl[sessionId].tmp/PackageInstaller

      FileBridge是一个Thread子类,它的run方法如下:

      @Override
      public void run() {
          final ByteBuffer tempBuffer = ByteBuffer.allocateDirect(8192);
          final byte[] temp = tempBuffer.hasArray() ? tempBuffer.array() : new byte[8192];
          try {
                //从server端读取写入数据
              while (IoBridge.read(mServer.getFileDescriptor(), temp,
                                   0, MSG_LENGTH) == MSG_LENGTH) {
                  final int cmd = Memory.peekInt(temp, 0, ByteOrder.BIG_ENDIAN);
                  if (cmd == CMD_WRITE) {
                      // Shuttle data into local file
                      int len = Memory.peekInt(temp, 4, ByteOrder.BIG_ENDIAN);
                      while (len > 0) {
                          int n = IoBridge.read(mServer.getFileDescriptor(), temp, 0,
                                                Math.min(temp.length, len));
                          if (n == -1) {
                              throw new IOException(
                                      "Unexpected EOF; still expected " + len + " bytes");
                          }
                            //写入mTarget指向的文件
                          IoBridge.write(mTarget.getFileDescriptor(), temp, 0, n);
                          len -= n;
                      }
      
                  } else if (cmd == CMD_FSYNC) {
                      // Sync and echo back to confirm
                      Os.fsync(mTarget.getFileDescriptor());
                      IoBridge.write(mServer.getFileDescriptor(), temp, 0, MSG_LENGTH);
      
                  } else if (cmd == CMD_CLOSE) {
                      // Close and echo back to confirm
                      Os.fsync(mTarget.getFileDescriptor());
                      mTarget.close();
                      mClosed = true;
                      IoBridge.write(mServer.getFileDescriptor(), temp, 0, MSG_LENGTH);
                      break;
                  }
              }
      
          } catch (ErrnoException | IOException e) {
              Log.wtf(TAG, "Failed during bridge", e);
          } finally {
              forceClose();
          }
      }
      

      可见这里会将mServer中读取的数据写入到mTarget指向的文件,也就是/data/app/vmdl[sessionId].tmp/PackageInstaller,那么mServer中的数据从哪来的,mServer又是什么?

      在FileBridge的构造方法中:

      public FileBridge() {
          try {
              ParcelFileDescriptor[] fds = ParcelFileDescriptor.createSocketPair(SOCK_STREAM);
              mServer = fds[0];
              mClient = fds[1];
          } catch (IOException e) {
              throw new RuntimeException("Failed to create bridge");
          }
      }
      

      原来是建立了一对socket通道,那么mServer读取的数据一定来自于mClient,还记得上面doWriteInternal方法返回的是bridge.getClientSocket()嘛,就是返回的mClient,因此在FileBridgeOutputStream构造方法中:

      public FileBridgeOutputStream(ParcelFileDescriptor clientPfd) {
          mClientPfd = clientPfd;
          mClient = clientPfd.getFileDescriptor();
      }
      

      clientPfd就是前面传入的FileBridge的mClient,这里InstallInstalling的mClient是FileDescriptor,也就是FileBridge的mClient指向的socket文件,当我们在InstallInstalling的AsyncTask的任务中调用FileBridgeOutputStream的write写入时:

      @Override
      public void write(byte[] buffer, int byteOffset, int byteCount) throws IOException {
          ArrayUtils.throwsIfOutOfBounds(buffer.length, byteOffset, byteCount);
          Memory.pokeInt(mTemp, 0, CMD_WRITE, ByteOrder.BIG_ENDIAN);
          Memory.pokeInt(mTemp, 4, byteCount, ByteOrder.BIG_ENDIAN);
          IoBridge.write(mClient, mTemp, 0, MSG_LENGTH);
          IoBridge.write(mClient, buffer, byteOffset, byteCount);
      }
      

      实际上就是写入到FileBridge的mClient指向的socket文件。
      所以整个过程就是在InstallInstalling的AsyncTask的后台任务中不断地把apk源文件写入到socket文件的mClient端,同时在FileBridge中其mServer端不断地循环读取写入的数据,并写入到mTarget中,也就是/data/app/vmdl[sessionId].tmp/PackageInstaller这个文件。
      看来接下来,就该对这个生成的文件做处理了。

    • 提交

      通过前文分析我们知道,在onPostExecute中调用了session.commit方法,内部也就是调用了PackageInstallerSession的commit方法:

      @Override
      public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
          ...
          dispatchSessionSealed();
      }
      private void dispatchSessionSealed() {
          mHandler.obtainMessage(MSG_ON_SESSION_SEALED).sendToTarget();
      }
      

      可见,这里会通过Handler从AsyncTask回到主线程:

      private final Handler.Callback mHandlerCallback = new Handler.Callback() {
          @Override
          public boolean handleMessage(Message msg) {
              switch (msg.what) {
                  case MSG_ON_SESSION_SEALED:
                      handleSessionSealed();
                      break;
                  case MSG_STREAM_VALIDATE_AND_COMMIT:
                      handleStreamValidateAndCommit();
                      break;
                  case MSG_INSTALL:
                      handleInstall();
                      break;
                  case MSG_ON_PACKAGE_INSTALLED:
                      ...
                      sendOnPackageInstalled(mContext, statusReceiver, sessionId,
                              isInstallerDeviceOwnerOrAffiliatedProfileOwner(), userId,
                              packageName, returnCode, message, extras);
                      break;
                  case MSG_SESSION_VALIDATION_FAILURE:
                      ...
                      break;
              }
      
              return true;
          }
      };
      

      如果正常的话,handleSessionSealed中会通过Handler发送通知来相继触发handleStreamValidateAndCommit和handleInstall方法,handleInstall方法中会调用verify方法验证,验证没问题会回调onVerificationComplete方法,其内部会调用install方法:

       private CompletableFuture<Void> install() {
            //installNonStaged方法是安装方法
            List<CompletableFuture<InstallResult>> futures = installNonStaged();
            CompletableFuture<InstallResult>[] arr = new CompletableFuture[futures.size()];
            return CompletableFuture.allOf(futures.toArray(arr)).whenComplete((r, t) -> {
                //t是Throwable
                if (t == null) {
                    setSessionApplied();
                    for (CompletableFuture<InstallResult> f : futures) {
                        InstallResult result = f.join();
                        //安装成功回调
                        result.session.dispatchSessionFinished(
                                INSTALL_SUCCEEDED, "Session installed", result.extras);
                    }
                } else {
                    //出错处理
                    PackageManagerException e = (PackageManagerException) t.getCause();
                    setSessionFailed(e.error,
                            PackageManager.installStatusToString(e.error, e.getMessage()));
                    dispatchSessionFinished(e.error, e.getMessage(), null);
                    maybeFinishChildSessions(e.error, e.getMessage());
                }
            });
        }
      

      我们先来看installNonStaged方法:

      private List<CompletableFuture<InstallResult>> installNonStaged() {
            try {
                List<CompletableFuture<InstallResult>> futures = new ArrayList<>();
                CompletableFuture<InstallResult> future = new CompletableFuture<>();
                futures.add(future);
                //构造InstallParams并且设置监听回调
                final InstallParams installingSession = makeInstallParams(future);
                if (isMultiPackage()) {
                    ...
                } else if (installingSession != null) {
                    //安装
                    installingSession.installStage();
                }
                return futures;
            } catch (PackageManagerException e) {
                List<CompletableFuture<InstallResult>> futures = new ArrayList<>();
                futures.add(CompletableFuture.failedFuture(e));
                return futures;
            }
        }
      

      看一下makeInstallParams方法:

      private InstallParams makeInstallParams(CompletableFuture<InstallResult> future)
                throws PackageManagerException {
            ...
            final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
                @Override
                public void onUserActionRequired(Intent intent) {
                    throw new IllegalStateException();
                }
      
                @Override
                public void onPackageInstalled(String basePackageName, int returnCode, String msg,
                        Bundle extras) {
                    if (returnCode == INSTALL_SUCCEEDED) {
                        future.complete(new InstallResult(PackageInstallerSession.this, extras));
                    } else {
                        future.completeExceptionally(new PackageManagerException(returnCode, msg));
                    }
                }
            };
            ...
            synchronized (mLock) {
                return new InstallParams(stageDir, localObserver, params, mInstallSource, user,
                        mSigningDetails, mInstallerUid, mPackageLite, mPm);
            }
        }
      

      可以看到,这里设置了一个IPackageInstallObserver2回调监听器,在InstallParams中被赋值给mObserver。
      再来看InstallParams的installStage方法:

      public void installStage() {
            final Message msg = mPm.mHandler.obtainMessage(INIT_COPY);
            setTraceMethod("installStage").setTraceCookie(System.identityHashCode(this));
            msg.obj = this;
            ...
            mPm.mHandler.sendMessage(msg);
        }
      

      mPm也就是PackageManagerService,这里使用它的mHandler发送了一个消息,mHandler在构造方法中通过injector.getHandler()获取,injector是在PackageManagerService的main方法中构造的,是一个PackageManagerServiceInjector对象,最终找到它的getHandler方法返回的是:

      (i, pm) -> {
                        HandlerThread thread = new ServiceThread(TAG,
                                Process.THREAD_PRIORITY_DEFAULT, true /*allowIo*/);
                        thread.start();
                        return new PackageHandler(thread.getLooper(), pm);
                    },
      

      在PackageHandler的handleMessage方法中调用了doHandleMessage方法:

       void doHandleMessage(Message msg) {
            switch (msg.what) {
                case INIT_COPY: {
                    HandlerParams params = (HandlerParams) msg.obj;
                    if (params != null) {
                        ...
                        params.startCopy();
                        ...
                    }
                    break;
                }
                ...
            }
      }
      

      对应installStage方法前面发送的what码是INIT_COPY,这里的msg.obj获取的也就是InstallParams,可见调用了InstallParams的startCopy方法,它是父类HandlerParams中的方法:

       final void startCopy() {
            handleStartCopy();
            handleReturnCode();
        }
      

      可见,InstallParams实现了这俩方法,先看handleStartCopy方法:

      public void handleStartCopy() {
            ...
            PackageInfoLite pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mPm.mContext,
                    mPackageLite, mOriginInfo.mResolvedPath, mInstallFlags, mPackageAbiOverride);
            ...
            mRet = overrideInstallLocation(pkgLite.packageName, pkgLite.recommendedInstallLocation,
                    pkgLite.installLocation);
        }
      

      overrideInstallLocation方法中会进行安装位置策略的决策,比如是放在内部还是外部存储,最后没问题的话会返回PackageManager.INSTALL_SUCCEEDED码。
      我们再来看handleReturnCode方法,它调用了processPendingInstall方法:

      private void processPendingInstall() {
            InstallArgs args = createInstallArgs(this);
            if (mRet == PackageManager.INSTALL_SUCCEEDED) {
                mRet = args.copyApk();
            }
            if (mRet == PackageManager.INSTALL_SUCCEEDED) {
                F2fsUtils.releaseCompressedBlocks(
                        mPm.mContext.getContentResolver(), new File(args.getCodePath()));
            }
            if (mParentInstallParams != null) {
                mParentInstallParams.tryProcessInstallRequest(args, mRet);
            } else {
                PackageInstalledInfo res = new PackageInstalledInfo(mRet);
                processInstallRequestsAsync(
                        res.mReturnCode == PackageManager.INSTALL_SUCCEEDED,
                        Collections.singletonList(new InstallRequest(args, res)));
            }
        }
      

      copyApk方法中:

      private int doCopyApk() {
            //这里会直接return,因为之前复制过了
            if (mOriginInfo.mStaged) {
                if (DEBUG_INSTALL) Slog.d(TAG, mOriginInfo.mFile + " already staged; skipping copy");
                mCodeFile = mOriginInfo.mFile;
                return PackageManager.INSTALL_SUCCEEDED;
            }
            ...//复制apk
        }
      

      之前在构造InstallParams的时候调用的是InstallParams(File stagedDir, ...)构造方法,它内部的mOriginInfo是通过OriginInfo.fromStagedFile(stagedDir)生成的,返回的是OriginInfo(file, true, false):

      private OriginInfo(File file, boolean staged, boolean existing) {
            mFile = file;
            mStaged = staged;
            mExisting = existing;
            ...
      }
      

      因此doCopyApk方法中mOriginInfo.mStaged为true,所以会直接跳过复制操作(其实doCopyApk中的复制操作主要是用于系统应用的安装方式,我们这里分析的是常规第三方安装方式,在之前doWriteInternal中通过socket方式已经复制过了),stagedDir在PackageInstallerService的buildSessionDir方法中创建的,实际上它的值通过buildTmpSessionDir方法获取,值是/data/app/vmdl[sessionId].tmp/([]括起来的表示变量)。
      接下来F2fsUtils.releaseCompressedBlocks方法中会解压stagedDir中的所有压缩文件(包括内嵌的子目录下的)。
      最后调用processInstallRequestsAsync方法(这里只看常规方式,忽略mParentInstallParams方式):

       private void processInstallRequestsAsync(boolean success,
                List<InstallRequest> installRequests) {
            mPm.mHandler.post(() -> {
                mInstallPackageHelper.processInstallRequests(success, installRequests);
            });
        }
      

      mInstallPackageHelper是InstallPackageHelper:

      public void processInstallRequests(boolean success, List<InstallRequest> installRequests) {
            ...
            if (success) {
                for (InstallRequest request : apkInstallRequests) {
                    request.mArgs.doPreInstall(request.mInstallResult.mReturnCode);
                }
                synchronized (mPm.mInstallLock) {
                    //安装
                    installPackagesTracedLI(apkInstallRequests);
                }
                for (InstallRequest request : apkInstallRequests) {
                    request.mArgs.doPostInstall(
                            request.mInstallResult.mReturnCode, request.mInstallResult.mUid);
                }
            }
            for (InstallRequest request : apkInstallRequests) {
                restoreAndPostInstall(request.mArgs.mUser.getIdentifier(),
                        request.mInstallResult,
                        new PostInstallData(request.mArgs,
                                request.mInstallResult, null));
            }
        }
      

      installPackagesTracedLI方法中调用的是installPackagesLI方法,经过Prepare、Scan、Reconcile、Commit(内部有通过PackageDexOptimizer的performDexOpt进行opt优化)四步进行安装,最底层都是是通过IInstalld这个aidl生成的类来操作的。

      • prepare阶段

        prepare阶段通过preparePackageLI方法来完成,里面有一段代码:
        try (PackageParser2 pp = mPm.mInjector.getPreparingPackageParser()) {
                parsedPackage = pp.parsePackage(tmpPackageFile, parseFlags, false);
                AndroidPackageUtils.validatePackageDexMetadata(parsedPackage);
        }...
        
        PackageParser2的parsePackage方法中又会调用ParsingPackageUtils的parsePackage方法,它又会调用ApkLiteParseUtils的parseClusterPackageLite方法来解析apk:
        public static ParseResult<PackageLite> parseClusterPackageLite(ParseInput input,
                File packageDirOrApk, List<File> frameworkSplits, int flags) {
            final File[] files;
            ...
            if (...) {...} 
            else {
                files = packageDirOrApk.listFiles();
                ...
            }
            ...
            ApkLite baseApk = null;
        
            final ArrayMap<String, ApkLite> apks = new ArrayMap<>();
            try {
                for (File file : files) {
                    if (isApkFile(file)) {
                        final ParseResult<ApkLite> result = parseApkLite(input, file, flags);
                        ...
                        final ApkLite lite = result.getResult();
                        ...
                        //这里会把apk文件添加,getSplitName()是null作为key,这里会限制只能有一个apk文件
                        if (apks.put(lite.getSplitName(), lite) != null) {
                            return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
                                    "Split name " + lite.getSplitName()
                                            + " defined more than once; most recent was " + file);
                        }
                    }
                }
                ...
                //这里通过“null”key获取到apk文件
                if (!parsingFrameworkSplits) {
                    baseApk = apks.remove(null);
                }
            } ...
            return composePackageLiteFromApks(input, packageDirOrApk, baseApk, apks);
        }
        
        isApkFile方法会判断文件是否是.apk后缀,看到这里我陷入了疑惑,因为我们知道之前写入的apk文件名字是“PackageInstaller”,那这里就不会通过判断,因此baseApk是null,在composePackageLiteFromApks方法中又会判断baseApk如果是null的话则返回结果中会设置error,回到PackageParser2的parsePackage方法中后会判断结果是否含有error,如果有则抛出异常:
        ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags,
                    frameworkSplits);
        if (result.isError()) {
                throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(),
                        result.getException());
        }
        
        最终回到installPackagesLI方法后在catch块中会直接return,之后的scan、commit等阶段都不会执行了,很明显我们漏掉了什么,“PackageInstaller”这个apk文件一定在某个地方被添加了.apk的后缀。
        还记得在handleStreamValidateAndCommit方法中我们发送了通知开启了上面分析的commit流程嘛:
         private void handleStreamValidateAndCommit() {
            try {
                ...
                if (allSessionsReady && streamValidateAndCommit()) {
                    mHandler.obtainMessage(MSG_INSTALL).sendToTarget();
                }
            } ...
        }
        
        我们忽略了一个方法:streamValidateAndCommit,这个方法里会调用一个validateApkInstallLocked方法(不考虑Apex的情况),这个方法中会遍历stagedDir的所有文件,重命名操作就是在这里发生的(实际上这里会调用ApkLiteParseUtils.parseApkLite方法先进行过解析并验证了):
        final String targetName = ApkLiteParseUtils.splitNameToFileName(apk);
        final File targetFile = new File(stageDir, targetName);
                resolveAndStageFileLocked(addedFile, targetFile, apk.getSplitName());
        
        //ApkLiteParseUtils:
        public static final String APK_FILE_EXTENSION = ".apk";
        
        public static String splitNameToFileName(@NonNull ApkLite apk) {
            Objects.requireNonNull(apk);
            final String fileName = apk.getSplitName() == null ? "base" : "split_" + apk.getSplitName();
            return fileName + APK_FILE_EXTENSION;
        }
        
        可见targetName是base.apk,resolveAndStageFileLocked方法中会调用stageFileLocked->maybeRenameFile方法:
        private static void maybeRenameFile(File from, File to) throws PackageManagerException {
            if (!from.equals(to)) {
                if (!from.renameTo(to)) {
                    throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                            "Could not rename file " + from + " to " + to);
                }
            }
        }
        
        可见就是在这里会把“PackageInstaller”重命名成“base.apk”。
        还有一个关键的操作就是:
        if (!args.doRename(res.mReturnCode, parsedPackage)) {
                throw new PrepareFailure(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");
        }
        
        这个不起眼的doRename方法很关键:
        @Override
        boolean doRename(int status, ParsedPackage parsedPackage) {
            ...
            //就是/data/app
            final File targetDir = resolveTargetDir();
            //stagedDir(/data/app/vmdl[sessionId].tmp/)
            final File beforeCodeFile = mCodeFile;
            //路径是/data/app/~~[base64随机码]/[packageName]-[base64随机码]/,生成逻辑看下面的getNextCodePath方法解析
            final File afterCodeFile = PackageManagerServiceUtils.getNextCodePath(targetDir,
                    parsedPackage.getPackageName());
            ...
            try {
                //创建afterCodeFile文件
                makeDirRecursive(afterCodeFile.getParentFile(), 0775);
                if (onIncremental) {
                    ...
                } else {
                    //将base.apk的路径改成afterCodeFile的路径
                    Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath());
                }
            } ...
            mCodeFile = afterCodeFile;
            try {
                parsedPackage.setPath(afterCodeFile.getCanonicalPath());
            } ...
            parsedPackage.setBaseApkPath(FileUtils.rewriteAfterRename(beforeCodeFile,
                    afterCodeFile, parsedPackage.getBaseApkPath()));
            ...
            return true;
        }
        
        PackageManagerServiceUtils.getNextCodePath方法如下:
        //PackageManagerService:
        static final String RANDOM_DIR_PREFIX = "~~";
        static final char RANDOM_CODEPATH_PREFIX = '-';
        
        //PackageManagerServiceUtils:
        public static File getNextCodePath(File targetDir, String packageName) {
            SecureRandom random = new SecureRandom();
            byte[] bytes = new byte[16];
            File firstLevelDir;
            do {
                random.nextBytes(bytes);
                //值为"~~[base64随机码]"
                String firstLevelDirName = RANDOM_DIR_PREFIX
                        + Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP);
                firstLevelDir = new File(targetDir, firstLevelDirName);
            } while (firstLevelDir.exists());
        
            random.nextBytes(bytes);
            //值为"[packageName]-[base64随机码]"
            String dirName = packageName + RANDOM_CODEPATH_PREFIX + Base64.encodeToString(bytes,
                    Base64.URL_SAFE | Base64.NO_WRAP);
            //结果是/data/app/~~[base64随机码]/[packageName]-[base64随机码]/
            final File result = new File(firstLevelDir, dirName);
            ...
            return result;
        }
        
        FileUtils.rewriteAfterRename方法如下:
        public static String rewriteAfterRename(File beforeDir, File afterDir, String path) {
            if (path == null) return null;
            final File result = rewriteAfterRename(beforeDir, afterDir, new File(path));
            return (result != null) ? result.getAbsolutePath() : null;
        }
        /**
         * Given a path under the "before" directory, rewrite it to live under the
         * "after" directory. For example, {@code /before/foo/bar.txt} would become
         * {@code /after/foo/bar.txt}.
         */
        public static File rewriteAfterRename(File beforeDir, File afterDir, File file) {
            if (file == null || beforeDir == null || afterDir == null) return null;
            if (contains(beforeDir, file)) {
                final String splice = file.getAbsolutePath().substring(
                        beforeDir.getAbsolutePath().length());
                return new File(afterDir, splice);
            }
            return null;
        }
        
        总结一下,这里其实就是重新设置parsedPackage的baseApkPath为/data/app/~~[base64随机码]/[packageName]-[base64随机码]/base.apk,比如,这里贴一个虚拟机中打印的路径:/data/app/~~_2wI_UpCAckInB8-R9eQWg==/com.mph.bpp-gwds_0yTePnhkIxwACYhmQ==/base.apk,还记得吗,之前这个值是/data/app/vmdl[sessionId].tmp/base.apk。parsedPackage是ParsedPackage类型,它的实现类是PackageImpl,这里就是设置它的mBaseApkPath属性(在其父类ParsingPackageImpl中定义)。
      • scan阶段

        scan阶段通过scanPackageNewLI方法开启,然后调用ScanPackageUtils.scanPackageOnlyLI方法返回一个PackageSetting对象。这个阶段主要是读取系统配置,和当前新安装的包进行信息整合,并针对prepare阶段的scanFlags进行各项验证。
      • reconcile(核对)阶段

        使用上一步返回的ScanResult<PackageSetting>构建ReconcileRequest,然后调用ReconcilePackageUtils.reconcilePackages开启reconcile阶段,主要是整合签名信息,最后返回ReconciledPackage。
      • commit阶段

        使用上一步的ReconciledPackage构造出CommitRequest,调用commitPackagesLocked->commitReconciledScanResultLocked->commitPackageSettings中会通过mPm.mPackages.put(pkg.getPackageName(), pkg)把AndroidPackage(即实现类PackageImpl)保存到PackageManagerService的mPackages中。并且把构造的PackageSetting保存在PackageManagerService的mSettings(Settings)的mPackages中:mPackages.put(p.getPackageName(), packageSetting)。
        其外还有通过updateSettingsLI->updateSettingsInternalLI->mPm.mPermissionManager.onPackageInstalled(mPermissionManager是PermissionManagerService.PermissionManagerServiceInternalImpl)->PermissionManagerServiceImpl的onPackageInstalled方法修改权限信息的逻辑。
        然后在executePostCommitSteps方法中执行commit操作:
        private void executePostCommitSteps(CommitRequest commitRequest) {
            ...
            for (ReconciledPackage reconciledPkg : commitRequest.mReconciledPackages.values()) {
                ...
                //从上一个阶段(reconcile)获取的结果
                final AndroidPackage pkg = reconciledPkg.mPkgSetting.getPkg();
                final String packageName = pkg.getPackageName();
                final String codePath = pkg.getPath();
                ...
                //内部通过binder创建一个/data/user/[userId]/[package_name]的目录
                mAppDataHelper.prepareAppDataPostCommitLIF(pkg, 0);
                ...
                //准备好配置信息(下一步的dexopt会基于此步骤)
                mArtManagerService.prepareAppProfiles(
                        pkg,
                        mPm.resolveUserIds(reconciledPkg.mInstallArgs.mUser.getIdentifier()),
                        /* updateReferenceProfileContent= */ true);
                ...
                //检查是否支持dex优化(为了加快启动)
                final boolean performDexopt =
                        (!instantApp || android.provider.Settings.Global.getInt(
                                mContext.getContentResolver(),
                                android.provider.Settings.Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
                                && !pkg.isDebuggable()
                                && (!onIncremental)
                                && dexoptOptions.isCompilationEnabled();
                if (performDexopt) {
                    ...
                    mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
                            null /* instructionSets */,
                            mPm.getOrCreateCompilerPackageStats(pkg),
                            mDexManager.getPackageUseInfoOrDefault(packageName),
                            dexoptOptions);
                }
                ...
            }
            ...
        }
        
        调用mAppDataHelper.prepareAppDataPostCommitLIF方法,内部会通过prepareAppData->prepareAppDataLeaf调用到Installer的createAppData方法:
        public @NonNull CreateAppDataResult createAppData(@NonNull CreateAppDataArgs args)
                throws InstallerException {
            ...
            try {
                return mInstalld.createAppData(args);
            } catch (Exception e) {
                throw InstallerException.from(e);
            }
        }
        
        mInstalld是IInstalld类型,IInstalld是一个aidl类,它的实现类是InstalldNativeService.cpp:
        binder::Status InstalldNativeService::createAppData(const std::unique_ptr<std::string>& uuid,
            const std::string& packageName, int32_t userId, int32_t flags, int32_t appId,
            const std::string& seInfo, int32_t targetSdkVersion, int64_t* _aidl_return) {
            …
            //这里分FLAG_STORAGE_DE和FLAG_STORAGE_CE两种,我们只看其中之一
            auto path = create_data_user_ce_package_path(uuid_, userId, pkgname);
            if (prepare_app_dir(path, targetMode, uid) ||
                    prepare_app_cache_dir(path, "cache", 02771, uid, cacheGid) ||
                    prepare_app_cache_dir(path, "code_cache", 02771, uid, cacheGid)) {
                return error("Failed to prepare " + path);
            }
            …
            if (!prepare_app_profile_dir(packageName, appId, userId)) {
                return error("Failed to prepare profiles for " + packageName);
            }
            return ok();
        }
        
        create_data_user_ce_package_path方法最终返回一个/data/user/[userId]/[package_name]([]表示变量)路径,然后prepare_app_dir函数中会调用fs_prepare_dir_strict函数,其中会调用mkdir创建此目录,应用运行中产生的数据都放在这里。
        然后mArtManagerService.prepareAppProfiles方法会准备关于dex的配置,内部会通过InstalldNativeService.cpp(IIstalld的实现类)创建配置文件/data/misc/profiles/cur/[userid]/[package_name]/primary.prof,然后使用GetProcAddress(一个计算机函数,功能是检索指定的动态链接库(DLL)中的输出库函数地址)拿到底层操作函数(位于/system/bin/profman程序)来执行的。
        之后在PackageDexOptimizer的performDexOpt方法中进行art的dex优化加载操作(如果虚拟机支持的话),调用链最终也是调用InstalldNativeService.cpp的dexopt函数,底层的操作函数位于/system/bin/dex2oat程序,输出路径是通过getPackageOatDirIfSupported方法获取的:/data/app/~~[base64随机码]/[packageName]-[base64随机码]/oat/

      回到processInstallRequests方法主流程,调用完installPackagesTracedLI方法后,最后会调用restoreAndPostInstall方法,其内部会调用:

      Message msg = mPm.mHandler.obtainMessage(POST_INSTALL, token, 0);
      mPm.mHandler.sendMessage(msg);
      

      PackageHandler中:

       case POST_INSTALL: {
                    ...
                    mInstallPackageHelper.handlePackagePostInstall(data.res, data.args, didRestore);
                    ...
                } 
                break;
      

      InstallPackageHelper的handlePackagePostInstall方法如下:

        void handlePackagePostInstall(PackageInstalledInfo res, InstallArgs installArgs,
                boolean launchedForRestore) {
            ...
            final IPackageInstallObserver2 installObserver = installArgs.mObserver;
            ...
            mPm.notifyInstallObserver(res, installObserver);
            ...
      } 
      

      PackageManagerService的notifyInstallObserver方法中:

      void notifyInstallObserver(PackageInstalledInfo info,
                IPackageInstallObserver2 installObserver) {
            if (installObserver != null) {
                try {
                    Bundle extras = extrasForInstallResult(info);
                    installObserver.onPackageInstalled(info.mName, info.mReturnCode,
                            info.mReturnMsg, extras);
                } catch (RemoteException e) {
                    Slog.i(TAG, "Observer no longer exists.");
                }
            }
        }
      

      可见,这里触发了之前设置的IPackageInstallObserver2的onPackageInstalled方法,之后就会调用PackageInstallerSession的dispatchSessionFinished方法,最终会调用PackageManagerService的sendSessionCommitBroadcast方法发送安装成功通知。

      public void sendSessionCommitBroadcast(PackageInstaller.SessionInfo sessionInfo, int userId,
                int launcherUid, @Nullable ComponentName launcherComponent,
                @Nullable String appPredictionServicePackage) {
            if (launcherComponent != null) {
                Intent launcherIntent = new Intent(PackageInstaller.ACTION_SESSION_COMMITTED)
                        .putExtra(PackageInstaller.EXTRA_SESSION, sessionInfo)
                        .putExtra(Intent.EXTRA_USER, UserHandle.of(userId))
                        .setPackage(launcherComponent.getPackageName());
                mContext.sendBroadcastAsUser(launcherIntent, UserHandle.of(launcherUid));
            }
            ...
        }
      

      launcherComponent就是表示桌面Launcher组件的,猜测Launcher会收到这个通知。

    • Launcher接收通知

      我们在源码的/packages/apps/Launcher3/AndroidManifest-common.xml中找到了:

      <receiver android:name="com.android.launcher3.SessionCommitReceiver" >
          <intent-filter>
              <action android:name="android.content.pm.action.SESSION_COMMITTED" />
          </intent-filter>
      </receiver>
      

      这里的action的name属性指定的字符串正是launcherIntent构造时指定的action的值,因此这个receiver就是接收安装成功通知的地方。onReceive方法就不看了,就是创建图标、建立关联等Launcher的工作了。

    相关文章

      网友评论

          本文标题:Apk安装的源码分析(二)

          本文链接:https://www.haomeiwen.com/subject/myncbdtx.html