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

Apk安装的源码分析(二)

作者: Horps | 来源:发表于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