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

PackageInstaller代码参考:android 11。

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

一、InstallStart

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

<activity android:name=".InstallStart"
       android:theme="@android:style/Theme.Translucent.NoTitleBar"
       android:exported="true"
       android:excludeFromRecents="true">
   <intent-filter android:priority="1">
       <action android:name="android.intent.action.VIEW" />
       <action android:name="android.intent.action.INSTALL_PACKAGE" />
       <category android:name="android.intent.category.DEFAULT" />
       <data android:scheme="content" />
       <data android:mimeType="application/vnd.android.package-archive" />
   </intent-filter>
   <intent-filter android:priority="1">
       <action android:name="android.intent.action.INSTALL_PACKAGE" />
       <category android:name="android.intent.category.DEFAULT" />
       <data android:scheme="package" />
       <data android:scheme="content" />
   </intent-filter>
   <intent-filter android:priority="1">
       <action android:name="android.content.pm.action.CONFIRM_INSTALL" />
       <category android:name="android.intent.category.DEFAULT" />
   </intent-filter>
</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,非三方使用的

<intent-filter android:priority="1">
   <action android:name="android.intent.action.INSTALL_PACKAGE" />
   <category android:name="android.intent.category.DEFAULT" />
   <data android:scheme="package" />
   <data android:scheme="content" />
</intent-filter>

那么如果正常的是走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<Uri, Void, Boolean> {
    @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<Void, Void,
       PackageInstaller.Session> {
    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<PackageInstallerSession> childSessions)
        throws PackageManagerException {
    final PackageManagerService.ActiveInstallSession installingSession =
            makeSessionActiveLocked();
   if (installingSession == null) {
        return;
   }
    if (isMultiPackage()) {
        List<PackageManagerService.ActiveInstallSession> 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来执行安装。

这里补充一个分支:


session commit
    private PackageManagerService.ActiveInstallSession makeSessionActiveLocked()
            throws PackageManagerException {
        if (mRelinquished) {
            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                    "Session relinquished");
        }
        if (mDestroyed) {
            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed");
        }
        if (!mSealed) {
            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed");
        }

        final IPackageInstallObserver2 localObserver;
        if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
            localObserver = null;
        } else {
            if (!params.isMultiPackage) {
                Preconditions.checkNotNull(mPackageName);
                Preconditions.checkNotNull(mSigningDetails);
                Preconditions.checkNotNull(mResolvedBaseFile);

                if (needToAskForPermissionsLocked()) {
                    // User needs to confirm installation;
                    // give installer an intent they can use to involve
                    // user.
                    // 如果权限校验不满足,则走PackageInstallObserverAdapter回调
                    final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_INSTALL);
                    intent.setPackage(mPm.getPackageInstallerPackageName());
                    intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
                    try {
                        mRemoteObserver.onUserActionRequired(intent);
                    } catch (RemoteException ignored) {
                    }

                    // Commit was keeping session marked as active until now; release
                    // that extra refcount so session appears idle.
                    closeInternal(false);
                    return null;
                }
        ...
        return new PackageManagerService.ActiveInstallSession(mPackageName, stageDir,
                localObserver, params, mInstallerPackageName, mInstallerUid, user,
                mSigningDetails);
    }

这里needToAskForPermissionsLocked()会校验系统权限:

    private boolean needToAskForPermissionsLocked() {
        if (mPermissionsManuallyAccepted) {
            return false;
        }

        final boolean isInstallPermissionGranted =
                (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES,
                        mInstallerUid) == PackageManager.PERMISSION_GRANTED);
        final boolean isSelfUpdatePermissionGranted =
                (mPm.checkUidPermission(android.Manifest.permission.INSTALL_SELF_UPDATES,
                        mInstallerUid) == PackageManager.PERMISSION_GRANTED);
        final boolean isUpdatePermissionGranted =
                (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGE_UPDATES,
                        mInstallerUid) == PackageManager.PERMISSION_GRANTED);
        final int targetPackageUid = mPm.getPackageUid(mPackageName, 0, userId);
        final boolean isPermissionGranted = isInstallPermissionGranted
                || (isUpdatePermissionGranted && targetPackageUid != -1)
                || (isSelfUpdatePermissionGranted && targetPackageUid == mInstallerUid);
        final boolean isInstallerRoot = (mInstallerUid == Process.ROOT_UID);
        final boolean isInstallerSystem = (mInstallerUid == Process.SYSTEM_UID);
        final boolean forcePermissionPrompt =
                (params.installFlags & PackageManager.INSTALL_FORCE_PERMISSION_PROMPT) != 0;

        // Device owners and affiliated profile owners  are allowed to silently install packages, so
        // the permission check is waived if the installer is the device owner.
        return forcePermissionPrompt || !(isPermissionGranted || isInstallerRoot
                || isInstallerSystem || isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked());
    }

如果权限校验不满足,则走PackageInstallObserverAdapter回调

final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_INSTALL);
intent.setPackage(mPm.getPackageInstallerPackageName());
intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
mRemoteObserver.onUserActionRequired(intent);

这里会拉起session.commit(pendingIntent.getIntentSender());中对应的组件的回调

static class PackageInstallObserverAdapter extends PackageInstallObserver {
999          private final Context mContext;
1000          private final IntentSender mTarget;
1001          private final int mSessionId;
1002          private final boolean mShowNotification;
1003          private final int mUserId;
1004  
1005          public PackageInstallObserverAdapter(Context context, IntentSender target, int sessionId,
1006                  boolean showNotification, int userId) {
1007              mContext = context;
1008              mTarget = target;
1009              mSessionId = sessionId;
1010              mShowNotification = showNotification;
1011              mUserId = userId;
1012          }
1013  
1014          @Override
1015          public void onUserActionRequired(Intent intent) {
1016              final Intent fillIn = new Intent();
1017              fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId);
1018              fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
1019                      PackageInstaller.STATUS_PENDING_USER_ACTION);
1020              fillIn.putExtra(Intent.EXTRA_INTENT, intent);
1021              try {
1022                  mTarget.sendIntent(mContext, 0, fillIn, null, null);
1023              } catch (SendIntentException ignored) {
1024              }
1025          }

三方应用可以通过session install这种方式拉起PackageInstaller安装。

七、总结

整体流程时序:

不同阶段的任务梳理:

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中;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容