應(yīng)用安裝(二) - PackageInstaller中轉(zhuǎn)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>

這里啟動(dòng)InstallStart主要分三種:

  • android.intent.action.VIEW 常規(guī)的三方應(yīng)用安裝設(shè)置的action
  • android.intent.action.INSTALL_PACKAGE 系統(tǒng)應(yīng)用才擁有的權(quán)限
  • 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方式啟動(dòng)才能獲取到callingpakcage猜谚,否則返回null
   String callingPackage = getCallingPackage();
 ...
   //通過(guò)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();
}

跳轉(zhuǎn)頁(yè)面規(guī)則:
action 是 sessioninstall:直接跳轉(zhuǎn)PackageInstallerActivity麻惶;
action 不是 sessioninstall:

  • uri為content scheme跳轉(zhuǎn) InstallStaging
  • uri為pacakge scheme跳轉(zhuǎn) PackageInstallerActivity
  • uri為其他scheme 這里應(yīng)該是指file溶推,則結(jié)束安裝

莫非7.0之后除了通過(guò)FileUriExposedException來(lái)強(qiáng)制使用content方式之外碍脏,Installer也有邏輯限制么?掃下7.0-11.0的Installer:發(fā)現(xiàn)從8.1開(kāi)始卖擅,原生Installer對(duì)file uri做了如上限制鸣奔,直接結(jié)束安裝。當(dāng)然Installer各廠商都會(huì)做定制惩阶,而且廠商直接的差異也是非常大的挎狸。

另外,從前面InstallStart的intent-filter了解到断楷,PackageInstallerActivity.SCHEME_PACKAGE 對(duì)應(yīng)的是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,那么會(huì)進(jìn)入到InstallStaging頁(yè)面冬筒。

二恐锣、InstallStaging

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

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

跳轉(zhuǎn)InstallStaging 核心功能在這個(gè)異步任務(wù)。接下來(lái)看看這個(gè)任務(wù)是做了什么:

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一份到當(dāng)前installer data/data對(duì)應(yīng)的目錄下
            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來(lái)進(jìn)行后續(xù)的安裝操作
           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通過(guò)三方app提供的content路徑土榴,將待安裝的apk copy一份到當(dāng)前installer data/data對(duì)應(yīng)的目錄下,然后將uri從content轉(zhuǎn)為file响牛,跳轉(zhuǎn)到DeleteStagedFileOnResult頁(yè)面玷禽。

三、DeleteStagedFileOnResult

public class DeleteStagedFileOnResult extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
       //跳轉(zhuǎn)到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做的事情很簡(jiǎn)單呀打,就是跳轉(zhuǎn)PackageInstallerActivity矢赁,然后startActivity回調(diào)之后刪除copy的apk文件。

四贬丛、PackageInstallerActivity

在前面InstallStart跳轉(zhuǎn)邏輯坯台,如果是sessionInstall 或者匹配uri scheme為package,則直接跳轉(zhuǎn)PackageInstallerActivity瘫寝,如果是uri scheme為content,則要通過(guò)InstallStaging->DeleteStagedFileOnResult然后再跳到PackageInstallerActivity稠炬。繞了遠(yuǎn)路焕阿,但也只是做了apk的copy,最終殊途同歸首启,下面來(lái)詳細(xì)看一下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;
    //確認(rèn)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();
    //因?yàn)閏opy apk后,content會(huì)轉(zhuǎn)為file毅桃,因此到PackageInstallerActivity中就沒(méi)有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;
           }
            //通過(guò)application info獲取應(yīng)用的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.
    //系統(tǒng)對(duì)多用戶管理會(huì)設(shè)置不同的權(quán)限准夷,這里是做用戶權(quán)限校驗(yàn)
   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;
   }
    //用戶手動(dòng)設(shè)置同意未知來(lái)源安裝權(quán)限
    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();
       }
    }
}

這里主要是安裝一系列權(quán)限限制檢查,在允許未知來(lái)源安裝的條件下莺掠,接著看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.
   //檢查設(shè)備上是否已經(jīng)有此名稱的軟件包, 但是它已經(jīng)被重命名為其他東西衫嵌。
   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”.
      //檢查當(dāng)前包是否已經(jīng)被安裝
       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;
    //已經(jīng)安裝的app升級(jí)
   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);
}

這里設(shè)置mOk,對(duì)應(yīng)的安裝按鈕彻秆,設(shè)置可點(diǎn)擊安裝楔绞。按鈕初始化是在前面bindUi的時(shí)候:

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();
}

跳轉(zhuǎn)到InstallInstalling來(lái)處理

五、InstallInstalling

protected void onCreate(@Nullable Bundle savedInstanceState) {
…
    //通過(guò)PackageInstallerService來(lái)創(chuàng)建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;
}

創(chuàng)建了一個(gè)PackageInstallerSession唇兑,這里通過(guò)PackageInstallerSession做進(jìn)程間通信酒朵,最終將安裝apk任務(wù)交給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);
       }
    }
}

啟動(dòng)InstallingAsyncTask異步任務(wù)

/**
* 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創(chuàng)建的PackageInstaller.Session,并將PackageInstallerSession賦給當(dāng)前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主要干三件事情:

  • 創(chuàng)建與PackageManagerService進(jìn)程間通信的PackageInstallerSession扎附;
  • 將apk通過(guò)io流寫入到PackageInstallerSession中蔫耽;
  • 調(diào)用PackageInstallerSession的commit方法,將apk交給PackageManagerService來(lái)執(zhí)行安裝留夜。

六匙铡、PackageInstallerSession

session.commit最終是PackageInstallerSession執(zhí)行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,通過(guò)installStage來(lái)執(zhí)行安裝香伴。

這里補(bǔ)充一個(gè)分支:


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.
                    // 如果權(quán)限校驗(yàn)不滿足慰枕,則走PackageInstallObserverAdapter回調(diào)
                    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()會(huì)校驗(yàn)系統(tǒng)權(quán)限:

    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());
    }

如果權(quán)限校驗(yàn)不滿足,則走PackageInstallObserverAdapter回調(diào)

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

這里會(huì)拉起session.commit(pendingIntent.getIntentSender());中對(duì)應(yīng)的組件的回調(diào)

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          }

三方應(yīng)用可以通過(guò)session install這種方式拉起PackageInstaller安裝即纲。

七具帮、總結(jié)

整體流程時(shí)序:

不同階段的任務(wù)梳理:

1)InstallStart
主要是根據(jù)不同的uri scheme執(zhí)行不同頁(yè)面的跳轉(zhuǎn):
跳轉(zhuǎn)頁(yè)面規(guī)則:
action 是 sessioninstall:直接跳轉(zhuǎn)PackageInstallerActivity;
action 不是 sessioninstall:

  • uri為content scheme跳轉(zhuǎn) InstallStaging
  • uri為pacakge scheme跳轉(zhuǎn) PackageInstallerActivity
  • uri為其他scheme 這里應(yīng)該是指file低斋,則結(jié)束安裝

2)InstallStaging
通過(guò)三方app提供的content路徑蜂厅,將待安裝的apk copy一份到當(dāng)前installer data/data對(duì)應(yīng)的目錄下,然后將uri scheme從content轉(zhuǎn)為file膊畴。

3)DeleteStagedFileOnResult
跳轉(zhuǎn)PackageInstallerActivity掘猿,然后startActivity回調(diào)之后刪除copy的apk文件。

4)PackageInstallerActivity
Uri解析和安裝前權(quán)限檢查(用戶權(quán)限檢查唇跨、安裝來(lái)源權(quán)限檢查)稠通。

5)InstallInstalling
建立與PackageManagerService進(jìn)程間通信的通道,將apk傳遞過(guò)去

  • 創(chuàng)建與PackageManagerService進(jìn)程間通信的PackageInstallerSession买猖;
  • 將apk通過(guò)io流寫入到PackageInstallerSession中改橘;
  • 調(diào)用PackageInstallerSession的commit方法,將apk交給PackageManagerService來(lái)執(zhí)行安裝玉控。

6)PackageInstallerSession
觸發(fā)PackageManagerService對(duì)apk進(jìn)行安裝飞主。

本篇文章簡(jiǎn)單梳理了原生的PackageInstaller的中轉(zhuǎn)安裝流程,目前國(guó)內(nèi)廠商基于原生PackageInstaller都進(jìn)行了深度定制,且策略各不相同碌识,與原生差異還是相當(dāng)大的碾篡。

八、廠商定制研究

  • huawei: 基于原生PackageInstaller改的筏餐,整體頁(yè)面轉(zhuǎn)換與原生高度一致开泽,對(duì)不同頁(yè)面的邏輯有自己的定制;
  • vivo:在9.0及其以下的低版本中胖烛,新加了一個(gè)VivoPackageInstallerActivity眼姐,定制邏輯在這個(gè)activity中,主流程也基本與原生保持一致;
  • xiaomi:不是基于原生PackageInstaller改的佩番,邏輯集中在PackageInstallerAcitivty中众旗;
  • oppo:不是基于原生PackageInstaller改的,邏輯集中在OppoPackageInstallerActivity中趟畏;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載贡歧,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。
  • 序言:七十年代末赋秀,一起剝皮案震驚了整個(gè)濱河市利朵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌猎莲,老刑警劉巖绍弟,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異著洼,居然都是意外死亡樟遣,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門身笤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)豹悬,“玉大人,你說(shuō)我怎么就攤上這事液荸≌胺穑” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵娇钱,是天一觀的道長(zhǎng)伤柄。 經(jīng)常有香客問(wèn)我,道長(zhǎng)文搂,這世上最難降的妖魔是什么响迂? 我笑而不...
    開(kāi)封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮细疚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己疯兼,他們只是感情好然遏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著吧彪,像睡著了一般待侵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上姨裸,一...
    開(kāi)封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天秧倾,我揣著相機(jī)與錄音,去河邊找鬼傀缩。 笑死那先,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赡艰。 我是一名探鬼主播售淡,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼慷垮!你這毒婦竟也來(lái)了揖闸?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤料身,失蹤者是張志新(化名)和其女友劉穎汤纸,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體芹血,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贮泞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祟牲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片隙畜。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖说贝,靈堂內(nèi)的尸體忽然破棺而出议惰,到底是詐尸還是另有隱情,我是刑警寧澤乡恕,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布言询,位于F島的核電站,受9級(jí)特大地震影響傲宜,放射性物質(zhì)發(fā)生泄漏运杭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一函卒、第九天 我趴在偏房一處隱蔽的房頂上張望辆憔。 院中可真熱鬧,春花似錦、人聲如沸虱咧。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)腕巡。三九已至玄坦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绘沉,已是汗流浹背煎楣。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留车伞,地道東北人择懂。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像帖世,于是被迫代替她去往敵國(guó)和親休蟹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容