应用安装(二) - PackageInstaller中转apk安装

PackageInstaller代码参考:android 11。

aosp的PackageInstaller所在位置:frameworks/base/packages/PackageInstaller

一、InstallStart

从AndroidManifest.xml了解到InstallStart为入口Activity


   
       
       
       
       
       
   
   
       
       
       
       
   
   
       
       
   

这里启动InstallStart主要分三种:

  • android.intent.action.VIEW 常规的三方应用安装设置的action
  • android.intent.action.INSTALL_PACKAGE 系统应用才拥有的权限
  • android.content.pm.action.CONFIRM_INSTALL session install

frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java

protected void onCreate(@Nullable Bundle savedInstanceState) {
...
  //Activity中的getCallingPackage需要呼起源按startActivityForResult方式启动才能获取到callingpakcage,否则返回null
   String callingPackage = getCallingPackage();
 ...
   //通过callingpackage获取ApplicationInfo
   final ApplicationInfo sourceInfo = getSourceInfo(callingPackage);
   final int originatingUid = getOriginatingUid(sourceInfo);
...
   if (isSessionInstall) {
        nextActivity.setClass(this, PackageInstallerActivity.class);
   } else {
        Uri packageUri = intent.getData();
       if (packageUri != null && packageUri.getScheme().equals(
                ContentResolver.SCHEME_CONTENT)) {
            // [IMPORTANT] This path is deprecated, but should still work. Only necessary
           // features should be added.
           // Copy file to prevent it from being changed underneath this process
           nextActivity.setClass(this, InstallStaging.class);
       } else if (packageUri != null && packageUri.getScheme().equals(
                PackageInstallerActivity.SCHEME_PACKAGE)) {
            nextActivity.setClass(this, PackageInstallerActivity.class);
       } else {
            Intent result = new Intent();
           result.putExtra(Intent.EXTRA_INSTALL_RESULT,
                   PackageManager.INSTALL_FAILED_INVALID_URI);
           setResult(RESULT_FIRST_USER, result);
           nextActivity = null;
       }
    }
    if (nextActivity != null) {
        startActivity(nextActivity);
   }
    finish();
}

跳转页面规则:
action 是 sessioninstall:直接跳转PackageInstallerActivity;
action 不是 sessioninstall:

  • uri为content scheme跳转 InstallStaging
  • uri为pacakge scheme跳转 PackageInstallerActivity
  • uri为其他scheme 这里应该是指file,则结束安装

莫非7.0之后除了通过FileUriExposedException来强制使用content方式之外,Installer也有逻辑限制么?扫下7.0-11.0的Installer:发现从8.1开始,原生Installer对file uri做了如上限制,直接结束安装。当然Installer各厂商都会做定制,而且厂商直接的差异也是非常大的。

另外,从前面InstallStart的intent-filter了解到,PackageInstallerActivity.SCHEME_PACKAGE 对应的是android.intent.action.INSTALL_PACKAGE action,非三方使用的


   
   
   
   

那么如果正常的是走action:android.intent.action.VIEW + uri conent,那么会进入到InstallStaging页面。

二、InstallStaging

frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java

protected void onResume() {
    super.onResume();
...
        mStagingTask = new StagingAsyncTask();
       mStagingTask.execute(getIntent().getData());
}

跳转InstallStaging 核心功能在这个异步任务。接下来看看这个任务是做了什么:

private final class StagingAsyncTask extends AsyncTask {
    @Override
    protected Boolean doInBackground(Uri... params) {
        if (params == null || params.length <= 0) {
            return false;
       }

        Uri packageUri = params[0];
       try (InputStream in = getContentResolver().openInputStream(packageUri)) {
            // Despite the comments in ContentResolver#openInputStream the returned stream can
           // be null.
           if (in == null) {
                return false;
           }

            //将待安装的apk copy一份到当前installer data/data对应的目录下
            try (OutputStream out = new FileOutputStream(mStagedFile)) {
                byte[] buffer = new byte[1024 * 1024];
               int bytesRead;
               while ((bytesRead = in.read(buffer)) >= 0) {
                    // Be nice and respond to a cancellation
                   if (isCancelled()) {
                        return false;
                   }
                    out.write(buffer, 0, bytesRead);
               }
            }
        } catch (IOException | SecurityException | IllegalStateException e) {
            Log.w(LOG_TAG, "Error staging apk from content URI", e);
           return false;
       }
        return true;
   }

    @Override
    protected void onPostExecute(Boolean success) {
        if (success) {
            // Now start the installation again from a file
           Intent installIntent = new Intent(getIntent());
           //基于copy后的apk路径,将uri从content切为file来进行后续的安装操作
           installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class);
           installIntent.setData(Uri.fromFile(mStagedFile));
           if (installIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
                installIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
           }
            installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
           startActivity(installIntent);
           InstallStaging.this.finish();
       } else {
            showError();
       }
    }
}

InstallStaging通过三方app提供的content路径,将待安装的apk copy一份到当前installer data/data对应的目录下,然后将uri从content转为file,跳转到DeleteStagedFileOnResult页面。

三、DeleteStagedFileOnResult

public class DeleteStagedFileOnResult extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
       //跳转到PackageInstallerActivity
       if (savedInstanceState == null) {
            Intent installIntent = new Intent(getIntent());
           installIntent.setClass(this, PackageInstallerActivity.class);
           installIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
           startActivityForResult(installIntent, 0);
       }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
       //删除copy的apk文件
        File sourceFile = new File(getIntent().getData().getPath());
       sourceFile.delete();
       setResult(resultCode, data);
       finish();
   }
}

DeleteStagedFileOnResult做的事情很简单,就是跳转PackageInstallerActivity,然后startActivity回调之后删除copy的apk文件。

四、PackageInstallerActivity

在前面InstallStart跳转逻辑,如果是sessionInstall 或者匹配uri scheme为package,则直接跳转PackageInstallerActivity,如果是uri scheme为content,则要通过InstallStaging->DeleteStagedFileOnResult然后再跳到PackageInstallerActivity。绕了远路,但也只是做了apk的copy,最终殊途同归,下面来详细看一下PackageInstallerActivity:

frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java

protected void onCreate(Bundle icicle) {
    getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
   super.onCreate(null);
   if (icicle != null) {
        mAllowUnknownSources = icicle.getBoolean(ALLOW_UNKNOWN_SOURCES_KEY);
   }

    mPm = getPackageManager();
   mIpm = AppGlobals.getPackageManager();
   mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
   mInstaller = mPm.getPackageInstaller();
   mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
   final Intent intent = getIntent();
   mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE);
   mSourceInfo = intent.getParcelableExtra(EXTRA_ORIGINAL_SOURCE_INFO);
   mOriginatingUid = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
           PackageInstaller.SessionParams.UID_UNKNOWN);
   mOriginatingPackage = (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN)
            ? getPackageNameForUid(mOriginatingUid) : null;
   final Uri packageUri;
    //确认sessionInstall方式的sessionInfo
   if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction())) {
        final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
       final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
       if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
            Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
           finish();
           return;
       }
        mSessionId = sessionId;
       packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath));
       mOriginatingURI = null;
       mReferrerURI = null;
   } else {
        mSessionId = -1;
       packageUri = intent.getData();
       mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
       mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
   }
    // if there's nothing to do, quietly slip into the ether
   if (packageUri == null) {
        Log.w(TAG, "Unspecified source");
       setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
       finish();
       return;
   }
    if (DeviceUtils.isWear(this)) {
        showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
       return;
   }
    //1 解析uri
    boolean wasSetUp = processPackageUri(packageUri);
   if (!wasSetUp) {
        return;
   }
    // load dummy layout with OK button disabled until we override this layout in
   // startInstallConfirm
   bindUi();
    //2 安装前检查
   checkIfAllowedAndInitiateInstall();
}

先看1,解析uri

private boolean processPackageUri(final Uri packageUri) {
    mPackageURI = packageUri;
   final String scheme = packageUri.getScheme();
    //因为copy apk后,content会转为file,因此到PackageInstallerActivity中就没有content的scheme类型了
   switch (scheme) {
        case SCHEME_PACKAGE: {
            try {
                mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
                       PackageManager.GET_PERMISSIONS
                                | PackageManager.MATCH_UNINSTALLED_PACKAGES);
           } catch (NameNotFoundException e) {
            }
            if (mPkgInfo == null) {
                Log.w(TAG, "Requested package " + packageUri.getScheme()
                        + " not available. Discontinuing installation");
               showDialogInner(DLG_PACKAGE_ERROR);
               setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
               return false;
           }
            mAppSnippet = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
                   mPm.getApplicationIcon(mPkgInfo.applicationInfo));
       } break;

       case ContentResolver.SCHEME_FILE: {
            File sourceFile = new File(packageUri.getPath());
           mPkgInfo = PackageUtil.getPackageInfo(this, sourceFile,
                   PackageManager.GET_PERMISSIONS);
           // Check for parse errors
           if (mPkgInfo == null) {
                Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
               showDialogInner(DLG_PACKAGE_ERROR);
               setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
               return false;
           }
            //通过application info获取应用的icon和label,并包装为PackageUtil.AppSnippet
            mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
       } break;

       default: {
            throw new IllegalArgumentException("Unexpected URI scheme " + packageUri);
       }
    }
    return true;
}

再看2安装前检查

/**
* Check if it is allowed to install the package and initiate install if allowed. If not allowed
* show the appropriate dialog.
*/
private void checkIfAllowedAndInitiateInstall() {
    // Check for install apps user restriction first.
    //系统对多用户管理会设置不同的权限,这里是做用户权限校验
   final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource(
            UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle());
   if ((installAppsRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
        showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER);
       return;
   } else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
        startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
       finish();
       return;
   }
    //用户手动设置同意未知来源安装权限
    if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
        initiateInstall();
   } else {
        // Check for unknown sources restrictions.
       final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource(
                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle());
       final int unknownSourcesGlobalRestrictionSource = mUserManager.getUserRestrictionSource(
                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, Process.myUserHandle());
       final int systemRestriction = UserManager.RESTRICTION_SOURCE_SYSTEM
                & (unknownSourcesRestrictionSource | unknownSourcesGlobalRestrictionSource);
       if (systemRestriction != 0) {
            showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
       } else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
            startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
       } else if (unknownSourcesGlobalRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
            startAdminSupportDetailsActivity(
                    UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
       } else {
            handleUnknownSources();
       }
    }
}

这里主要是安装一系列权限限制检查,在允许未知来源安装的条件下,接着看initiateInstall

private void initiateInstall() {
    String pkgName = mPkgInfo.packageName;
   // Check if there is already a package on the device with this name
   // but it has been renamed to something else.
   //检查设备上是否已经有此名称的软件包, 但是它已经被重命名为其他东西。
   String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName });
   if (oldName != null && oldName.length > 0 && oldName[0] != null) {
        pkgName = oldName[0];
       mPkgInfo.packageName = pkgName;
       mPkgInfo.applicationInfo.packageName = pkgName;
   }
    // Check if package is already installed. display confirmation dialog if replacing pkg
   try {
        // This is a little convoluted because we want to get all uninstalled
       // apps, but this may include apps with just data, and if it is just
       // data we still want to count it as "installed”.
      //检查当前包是否已经被安装
       mAppInfo = mPm.getApplicationInfo(pkgName,
               PackageManager.MATCH_UNINSTALLED_PACKAGES);
       if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
            mAppInfo = null;
       }
    } catch (NameNotFoundException e) {
        mAppInfo = null;
   }
    startInstallConfirm();
}
private void startInstallConfirm() {
    View viewToEnable;
    //已经安装的app升级
   if (mAppInfo != null) {
        viewToEnable = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
               ? requireViewById(R.id.install_confirm_question_update_system)
                : requireViewById(R.id.install_confirm_question_update);
   } else {
    //新安装app 
        // This is a new application with no permissions.
       viewToEnable = requireViewById(R.id.install_confirm_question);
   }
    viewToEnable.setVisibility(View.VISIBLE);
   mEnableOk = true;
   mOk.setEnabled(true);
   mOk.setFilterTouchesWhenObscured(true);
}

这里设置mOk,对应的安装按钮,设置可点击安装。按钮初始化是在前面bindUi的时候:

private void bindUi() {
    mAlert.setIcon(mAppSnippet.icon);
   mAlert.setTitle(mAppSnippet.label);
   mAlert.setView(R.layout.install_content_view);
   mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install),
           (ignored, ignored2) -> {
                if (mOk.isEnabled()) {
                    if (mSessionId != -1) {
                        mInstaller.setPermissionsResult(mSessionId, true);
                       finish();
                   } else {
                        startInstall();
                   }
                }
            }, null);
   mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
           (ignored, ignored2) -> {
                // Cancel and finish
               setResult(RESULT_CANCELED);
               if (mSessionId != -1) {
                    mInstaller.setPermissionsResult(mSessionId, false);
               }
                finish();
           }, null);
   setupAlert();
   mOk = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
   mOk.setEnabled(false);
   if (!mOk.isInTouchMode()) {
        mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).requestFocus();
   }
}

如果是正常用户安装这里直接就走startInstall()

private void startInstall() {
    // Start subactivity to actually install the application
   Intent newIntent = new Intent();
   newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
           mPkgInfo.applicationInfo);
   newIntent.setData(mPackageURI);
   newIntent.setClass(this, InstallInstalling.class);
   String installerPackageName = getIntent().getStringExtra(
            Intent.EXTRA_INSTALLER_PACKAGE_NAME);
   if (mOriginatingURI != null) {
        newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
   }
    if (mReferrerURI != null) {
        newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
   }
    if (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
        newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
   }
    if (installerPackageName != null) {
        newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
               installerPackageName);
   }
    if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
        newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
   }
    newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
   if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
   startActivity(newIntent);
   finish();
}

跳转到InstallInstalling来处理

五、InstallInstalling

protected void onCreate(@Nullable Bundle savedInstanceState) {
…
    //通过PackageInstallerService来创建session
    mSessionId = getPackageManager().getPackageInstaller().createSession(params);
...
}

frameworks/base/services/core/java/com/android/server/pm/PackageInstallerService.java

private int createSessionInternal(SessionParams params, String installerPackageName, int userId)
        throws IOException {
...
   session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
           mInstallThread.getLooper(), mStagingManager, sessionId, userId, callingUid,
           installSource, params, createdMillis,
           stageDir, stageCid, null, false, false, false, false, null, SessionInfo.INVALID_ID,
           false, false, false, SessionInfo.STAGED_SESSION_NO_ERROR, "");
   synchronized (mSessions) {
        mSessions.put(sessionId, session);
   }
 ...
   return sessionId;
}

创建了一个PackageInstallerSession,这里通过PackageInstallerSession做进程间通信,最终将安装apk任务交给PackageManagerService

再接着往下看InstallInstalling的onResume

protected void onResume() {
    super.onResume();
   // This is the first onResume in a single life of the activity
   if (mInstallingTask == null) {
        PackageInstaller installer = getPackageManager().getPackageInstaller();
       PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);
       if (sessionInfo != null && !sessionInfo.isActive()) {
            mInstallingTask = new InstallingAsyncTask();
           mInstallingTask.execute();
       } else {
            // we will receive a broadcast when the install is finished
           mCancelButton.setEnabled(false);
           setFinishOnTouchOutside(false);
       }
    }
}

启动InstallingAsyncTask异步任务

/**
* Send the package to the package installer and then register a event result observer that
* will call {@link #launchFinishBasedOnResult(int, int, String)}
*/
private final class InstallingAsyncTask extends AsyncTask {
    volatile boolean isDone;
   @Override
    protected PackageInstaller.Session doInBackground(Void... params) {
        PackageInstaller.Session session;
       try {
            //openSession就是获取onCreate创建的PackageInstaller.Session,并将PackageInstallerSession赋给当前session
            session = getPackageManager().getPackageInstaller().openSession(mSessionId);
       } catch (IOException e) {
            return null;
       }
        session.setStagingProgress(0);
        //将apk文件写到session中
       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) {
                            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) {
            Log.e(LOG_TAG, "Could not write package", e);
           session.close();
           return null;
       } finally {
            synchronized (this) {
                isDone = true;
               notifyAll();
           }
        }
    }
    @Override
    protected void onPostExecute(PackageInstaller.Session session) {
        if (session != null) {
            Intent broadcastIntent = new Intent(BROADCAST_ACTION);
           broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
           broadcastIntent.setPackage(getPackageName());
           broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
           PendingIntent pendingIntent = PendingIntent.getBroadcast(
                    InstallInstalling.this,
                   mInstallId,
                   broadcastIntent,
                   PendingIntent.FLAG_UPDATE_CURRENT);
           //session commit
           session.commit(pendingIntent.getIntentSender());
           mCancelButton.setEnabled(false);
           setFinishOnTouchOutside(false);
       } else {
            getPackageManager().getPackageInstaller().abandonSession(mSessionId);
           if (!isCancelled()) {
                launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null);
           }
        }
    }
}

InstallInstalling主要干三件事情:

  • 创建与PackageManagerService进程间通信的PackageInstallerSession;
  • 将apk通过io流写入到PackageInstallerSession中;
  • 调用PackageInstallerSession的commit方法,将apk交给PackageManagerService来执行安装。

六、PackageInstallerSession

session.commit最终是PackageInstallerSession执行commit

frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java

public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
...
   dispatchStreamValidateAndCommit();
}
private void dispatchStreamValidateAndCommit() {
   mHandler.obtainMessage(MSG_STREAM_VALIDATE_AND_COMMIT).sendToTarget();
}
private final Handler.Callback mHandlerCallback = new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_STREAM_VALIDATE_AND_COMMIT:
                handleStreamValidateAndCommit();
               break;
           case MSG_INSTALL:
                handleInstall();
               break;
           case MSG_ON_PACKAGE_INSTALLED:
                final SomeArgs args = (SomeArgs) msg.obj;
               final String packageName = (String) args.arg1;
               final String message = (String) args.arg2;
               final Bundle extras = (Bundle) args.arg3;
               final IntentSender statusReceiver = (IntentSender) args.arg4;
               final int returnCode = args.argi1;
               args.recycle();
               sendOnPackageInstalled(mContext, statusReceiver, sessionId,
                       isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked(), userId,
                       packageName, returnCode, message, extras);
               break;
           case MSG_SESSION_VERIFICATION_FAILURE:
                final int error = msg.arg1;
               final String detailMessage = (String) msg.obj;
               onSessionVerificationFailure(error, detailMessage);
               break;
       }
        return true;
   }
};
private void handleStreamValidateAndCommit() {
  ...
    mHandler.obtainMessage(MSG_INSTALL).sendToTarget();
}
private void handleInstall() {
...
        synchronized (mLock) {
            installNonStagedLocked(childSessions);
       }
  ...
}
private void installNonStagedLocked(List childSessions)
        throws PackageManagerException {
    final PackageManagerService.ActiveInstallSession installingSession =
            makeSessionActiveLocked();
   if (installingSession == null) {
        return;
   }
    if (isMultiPackage()) {
        List installingChildSessions =
                new ArrayList<>(childSessions.size());
       boolean success = true;
       PackageManagerException failure = null;
       for (int i = 0; i < childSessions.size(); ++i) {
            final PackageInstallerSession session = childSessions.get(i);
           try {
                final PackageManagerService.ActiveInstallSession installingChildSession =
                        session.makeSessionActiveLocked();
               if (installingChildSession != null) {
                    installingChildSessions.add(installingChildSession);
               }
            } catch (PackageManagerException e) {
                failure = e;
               success = false;
           }
        }
        if (!success) {
            sendOnPackageInstalled(mContext, mRemoteStatusReceiver, sessionId,
                   isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked(), userId, null,
                   failure.error, failure.getLocalizedMessage(), null);
           return;
       }
        mPm.installStage(installingChildSessions);
   } else {
        mPm.installStage(installingSession);
   }
}

这里mPm是PackageManagerService,通过installStage来执行安装。

七、总结

整体流程时序:

不同阶段的任务梳理:

1)InstallStart
主要是根据不同的uri scheme执行不同页面的跳转:
跳转页面规则:
action 是 sessioninstall:直接跳转PackageInstallerActivity;
action 不是 sessioninstall:

  • uri为content scheme跳转 InstallStaging
  • uri为pacakge scheme跳转 PackageInstallerActivity
  • uri为其他scheme 这里应该是指file,则结束安装

2)InstallStaging
通过三方app提供的content路径,将待安装的apk copy一份到当前installer data/data对应的目录下,然后将uri scheme从content转为file。

3)DeleteStagedFileOnResult
跳转PackageInstallerActivity,然后startActivity回调之后删除copy的apk文件。

4)PackageInstallerActivity
Uri解析和安装前权限检查(用户权限检查、安装来源权限检查)。

5)InstallInstalling
建立与PackageManagerService进程间通信的通道,将apk传递过去

  • 创建与PackageManagerService进程间通信的PackageInstallerSession;
  • 将apk通过io流写入到PackageInstallerSession中;
  • 调用PackageInstallerSession的commit方法,将apk交给PackageManagerService来执行安装。

6)PackageInstallerSession
触发PackageManagerService对apk进行安装。

本篇文章简单梳理了原生的PackageInstaller的中转安装流程,目前国内厂商基于原生PackageInstaller都进行了深度定制,且策略各不相同,与原生差异还是相当大的。

八、厂商定制研究

  • huawei: 基于原生PackageInstaller改的,整体页面转换与原生高度一致,对不同页面的逻辑有自己的定制;
  • vivo:在9.0及其以下的低版本中,新加了一个VivoPackageInstallerActivity,定制逻辑在这个activity中,主流程也基本与原生保持一致;
  • xiaomi:不是基于原生PackageInstaller改的,逻辑集中在PackageInstallerAcitivty中;
  • oppo:不是基于原生PackageInstaller改的,逻辑集中在OppoPackageInstallerActivity中;

你可能感兴趣的:(应用安装(二) - PackageInstaller中转apk安装)