Android 安装过程一 界面跳转

安装Apk的代码

  应用安装Apk的代码如下:

			val intent = Intent(Intent.ACTION_VIEW)
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            val apk = File(filesDir,"ff/ss.apk")
            val uri = FileProvider.getUriForFile(this, "$packageName.fileProvider", apk)
            intent.setDataAndType(uri, "application/vnd.android.package-archive")
            startActivity(intent)

  AndroidManifest中provider配置路径的xml文件如下:

<?xml version="1.0" encoding="UTF-8" ?>
<paths>
    <external-path
        name="path1"
        path="/data/dir1"/>
    <files-path
        name="path2"
        path="/ff" />
</paths>

  这里uri的String为content://com.example.testapplication.fileProvider/path2/ss.apk。

进入包安装进程

进入InstallStart

  下面它会跳入另外一个进程"com.android.packageinstaller"(这里叫它包安装进程)中,首先会进入InstallStart。它是包安装进程中的一个Activity,它在AndroidManifest.xml指那个的配置 action为"android.intent.action.VIEW",所以能通过该action找到它。首先进入InstallStart的onCreate()生命周期方法:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPackageManager = getPackageManager();
        mUserManager = getSystemService(UserManager.class);
        Intent intent = getIntent();
        String callingPackage = getCallingPackage();
        String callingAttributionTag = null;

        final boolean isSessionInstall =
                PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());

        // If the activity was started via a PackageInstaller session, we retrieve the calling
        // package from that session
        final int sessionId = (isSessionInstall
                ? intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1)
                : -1);
        if (callingPackage == null && sessionId != -1) {
            PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
            PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
            callingPackage = (sessionInfo != null) ? sessionInfo.getInstallerPackageName() : null;
            callingAttributionTag =
                    (sessionInfo != null) ? sessionInfo.getInstallerAttributionTag() : null;
        }

        final ApplicationInfo sourceInfo = getSourceInfo(callingPackage);
        final int originatingUid = getOriginatingUid(sourceInfo);
        boolean isTrustedSource = false;
        if (sourceInfo != null
                && (sourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
            isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false);
        }

        if (!isTrustedSource && originatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
            final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid);
            if (targetSdkVersion < 0) {
                Log.w(LOG_TAG, "Cannot get target sdk version for uid " + originatingUid);
                // Invalid originating uid supplied. Abort install.
                mAbortInstall = true;
            } else if (targetSdkVersion >= Build.VERSION_CODES.O && !isUidRequestingPermission(
                    originatingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
                Log.e(LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
                        + Manifest.permission.REQUEST_INSTALL_PACKAGES);
                mAbortInstall = true;
            }
        }
        if (mAbortInstall) {
            setResult(RESULT_CANCELED);
            finish();
            return;
        }

        Intent nextActivity = new Intent(intent);
        nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
                | Intent.FLAG_GRANT_READ_URI_PERMISSION);

        // The the installation source as the nextActivity thinks this activity is the source, hence
        // set the originating UID and sourceInfo explicitly
        nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);
        nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_ATTRIBUTION_TAG,
                callingAttributionTag);
        nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);
        nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);

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

  首先通过getCallingPackage()得到调用方的包名,放到变量callingPackage中。
  接着通过intent的action是否等于PackageInstaller.ACTION_CONFIRM_INSTALL来判断是否是PackageInstaller session安装,如果是,会去intent中取出SESSION_ID,并且得到SessionInfo信息,并从中得到callingPackage、callingAttributionTag。
  接着再通过getSourceInfo(callingPackage)得到调用方的ApplicationInfo,getOriginatingUid(sourceInfo)得到调用方的uid。
  接着判断是否是信任的安装源,在sourceInfo.privateFlags存在ApplicationInfo.PRIVATE_FLAG_PRIVILEGED时,如果传过来的intent中有Intent.EXTRA_NOT_UNKNOWN_SOURCE的值为true时,就是信任的。
  如果是信任的,就不用去检查Manifest.permission.REQUEST_INSTALL_PACKAGES权限,如果是不信任的,需要检查应用是否有Manifest.permission.REQUEST_INSTALL_PACKAGES权限,这个权限是要在安装APP的应用中声明的。
  Manifest.permission.REQUEST_INSTALL_PACKAGES权限是在Build.VERSION_CODES.O版本及之后才有的,所以需要先找到安装应用的目标Sdk,如果没有也会报错;如果目标Sdk版本比Build.VERSION_CODES.O版本之后,就需要检查Manifest.permission.REQUEST_INSTALL_PACKAGES权限。
  如果没得到目标sdk版本及没声明Manifest.permission.REQUEST_INSTALL_PACKAGES权限都会报错返回。
  接着会复制intent,得到nextActivity,然后给nextActivity设置Intent.FLAG_ACTIVITY_FORWARD_RESULT|Intent.FLAG_GRANT_READ_URI_PERMISSION标签。
  接着向新生成的Intent对象里面添加数据,包括调用者包名callingPackage、调用者AttributionTag、调用者的应用信息、调用者的uid。
  若是是PackageInstaller session安装,直接就跳转到PackageInstallerActivity中去;如果得到的URI的getScheme()是ContentResolver.SCHEME_CONTENT,则会跳转到InstallStaging中,我们的例子就是属于这种;如果得到的URI的getScheme()是PackageInstallerActivity.SCHEME_PACKAGE,则会跳转到PackageInstallerActivity中;如果不是以上情况,则返回PackageManager.INSTALL_FAILED_INVALID_URI给调用者Activity。
  最后就是跳转,并且关闭InstallStart。

进入InstallStaging

  InstallStaging 在InstallStart 中根据Uri的scheme是Content,跳转到该Activity中。 该Activity是用来缓存APK文件的。
  它是继承AlertActivity,界面是以对框的形式展示的。看下它的onCreate()方法:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mAlert.setIcon(R.drawable.ic_file_download);
        mAlert.setTitle(getString(R.string.app_name_unknown));
        mAlert.setView(R.layout.install_content_view);
        mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
                (ignored, ignored2) -> {
                    if (mStagingTask != null) {
                        mStagingTask.cancel(true);
                    }
                    setResult(RESULT_CANCELED);
                    finish();
                }, null);
        setupAlert();
        requireViewById(R.id.staging).setVisibility(View.VISIBLE);

        if (savedInstanceState != null) {
            mStagedFile = new File(savedInstanceState.getString(STAGED_FILE));

            if (!mStagedFile.exists()) {
                mStagedFile = null;
            }
        }
    }

  对话框的布局是install_content_view。这里是显示的id为staging的控件,它是LinearLayout控件,里面有个文本,文本下面是水平进度条。文本内容显示为“Staging app…”。在复制APK文件完成之前,跳转到DeleteStagedFileOnResult之前,一直显示这个界面。

创建临时文件

  它有一个成员变量StagingAsyncTask,它继承自AsyncTask,用来执行异步任务。在异步任务过程中,它会将安装的APK文件重新保存一份,保存的文件位于成员变量File 类型mStagedFile中。
  看一下文件的生成代码,它是由TemporaryFileManager.getStagedFile(this)生成的文件:

    @NonNull
    public static File getStagedFile(@NonNull Context context) throws IOException {
        return File.createTempFile("package", ".apk", context.getNoBackupFilesDir());
    }

  这时创建临时文件以package作为开头,".apk"作为结尾的文件,目录为context.getNoBackupFilesDir()。
  这里context为Activity,它是通过代理模式最终会调用到ContextImpl类的getNoBackupFilesDir(),如下代码:

    @Override
    public File getNoBackupFilesDir() {
        synchronized (mSync) {
            if (mNoBackupFilesDir == null) {
                mNoBackupFilesDir = new File(getDataDir(), "no_backup");
            }
            return ensurePrivateDirExists(mNoBackupFilesDir);
        }
    }

  这里如果mNoBackupFilesDir不存在,会给他声明。并且ensurePrivateDirExists(mNoBackupFilesDir)确保该目录存在。
  再看一下getDataDir()的实现:

    @Override
    public File getDataDir() {
        if (mPackageInfo != null) {
            File res = null;
            if (isCredentialProtectedStorage()) {
                res = mPackageInfo.getCredentialProtectedDataDirFile();
            } else if (isDeviceProtectedStorage()) {
                res = mPackageInfo.getDeviceProtectedDataDirFile();
            } else {
                res = mPackageInfo.getDataDirFile();
            }

            if (res != null) {
                if (!res.exists() && android.os.Process.myUid() == android.os.Process.SYSTEM_UID) {
                    Log.wtf(TAG, "Data directory doesn't exist for package " + getPackageName(),
                            new Throwable());
                }
                return res;
            } else {
                throw new RuntimeException(
                        "No data directory found for package " + getPackageName());
            }
        } else {
            throw new RuntimeException(
                    "No package details found for package " + getPackageName());
        }
    }

  可以看到分了好几种情况,并且它的值是来自成员变量mPackageInfo。
  isCredentialProtectedStorage()、isDeviceProtectedStorage()是和当前ContextImpl类的flags(Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE、Context.CONTEXT_DEVICE_PROTECTED_STORAGE)有关,而这两个flag又和mPackageInfo的值相关。
  mPackageInfo是LoadedApk类,它用来描述目前加载apk的本地状态。它的生成是在ActivityThread的performLaunchActivity()中。
  在哪里生成的ApplicationInfo,在PackageInfoUtils的generateApplicationInfo()中。  PackageInfoWithoutStateUtils.generateApplicationInfoUnchecked()里会调用assignUserFields(),assignUserFields(ParsingPackageRead pkg, ApplicationInfo info, int userId)有为ApplicationInfo中的dataDir赋值。

    private static void assignUserFields(ParsingPackageRead pkg, ApplicationInfo info, int userId) {
        // This behavior is undefined for no-state ApplicationInfos when called by a public API,
        // since the uid is never assigned by the system. It will always effectively be appId 0.
        info.uid = UserHandle.getUid(userId, UserHandle.getAppId(info.uid));

        String pkgName = pkg.getPackageName();
        if ("android".equals(pkgName)) {
            info.dataDir = SYSTEM_DATA_PATH;
            return;
        }

        // For performance reasons, all these paths are built as strings
        String baseDataDirPrefix =
                Environment.getDataDirectoryPath(pkg.getVolumeUuid()) + File.separator;
        String userIdPkgSuffix = File.separator + userId + File.separator + pkgName;
        info.credentialProtectedDataDir = baseDataDirPrefix + Environment.DIR_USER_CE
                + userIdPkgSuffix;
        info.deviceProtectedDataDir = baseDataDirPrefix + Environment.DIR_USER_DE + userIdPkgSuffix;

        if (pkg.isDefaultToDeviceProtectedStorage()
                && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
            info.dataDir = info.deviceProtectedDataDir;
        } else {
            info.dataDir = info.credentialProtectedDataDir;
        }
    }

  这里baseDataDirPrefix 的值为"/data"或者"/mnt/expand/" + volumeUuid,如果是系统用户,则userId 为0。并且如果是内部存储,则info.credentialProtectedDataDir=“/data/user/0/” + 包名,info.deviceProtectedDataDir=“/data/user_de/0/” + 包名。
  接着为info.dataDir赋值,pkg.isDefaultToDeviceProtectedStorage()是由Manifest文件中的标签defaultToDeviceProtectedStorage来控制的。如果没配置则默认为false。
  TemporaryFileManager.getStagedFile(this)得到的文件名字还会有随机数,因为当前安装应用是配置了android:defaultToDeviceProtectedStorage=“true”,所以TemporaryFileManager.getStagedFile(this)得到的文件的生成名字为"/data/user_de/0/com.android.packageinstaller/no_backup/package" + 随机数 + “.apk”。package7515820555458262274.apk就是一个例子。

异步任务复制APK文件

  这里实现的异步任务为AsyncTask,代码如下:

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

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

  这里可以看到在doInBackground(Uri… params)中实现了,文件的复制。
  在onPostExecute(Boolean success)中,根据复制结果来做下一步。如果复制成功,会跳入DeleteStagedFileOnResult这个Activity中。在这里会设置跳转Intent的mData成员变量

进入DeleteStagedFileOnResult

  这个Activity是为了跳入PackageInstallerActivity,然后等待返回结果,收到返回结果之后,然后删除前面生成的安装文件。

进入PackageInstallerActivity提醒用户是否允许安装

  首先进入它的onCreate()里面看:

…………
        final Intent intent = getIntent();

        mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE);
        mCallingAttributionTag = intent.getStringExtra(EXTRA_CALLING_ATTRIBUTION_TAG);
        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;
        if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction())) {
        …………
        } else {
            mSessionId = -1;
            packageUri = intent.getData();
            mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
            mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
        }     
        …………
        boolean wasSetUp = processPackageUri(packageUri);
        ……           

  取到Intent里面传递过来的相对应的数据,这里的数据对应InstallStart这个Activity里面赋的对应的值。mOriginatingUid 、mOriginatingPackage都是调用安装APP应用的uid和包名。
  接着就取得intent里面的mData的值,然后进入processPackageUri(packageUri)里处理。
  看下processPackageUri(packageUri)方法:

    private boolean processPackageUri(final Uri packageUri) {
        mPackageURI = packageUri;

        final String scheme = packageUri.getScheme();
        if (mLocalLOGV) Log.i(TAG, "processPackageUri(): uri=" + packageUri + ", scheme=" + scheme);

        switch (scheme) {
            case SCHEME_PACKAGE: {
            …………
            } 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;
                }
                if (mLocalLOGV) Log.i(TAG, "creating snippet for local file " + sourceFile);
                mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
            } break;
			…………
        }

        return true;
    }            

  在前面InstallStaging中,我们知道,intent的mData是File的。所以这里代码会走ContentResolver.SCHEME_FILE分支。
  这里得到的文件sourceFile 就是我们在InstallStaging中生成的APK文件。
  接着会调用PackageUtil.getPackageInfo()方法得到成员变量mPkgInfo。再通过它的applicationInfo,调用PackageUtil.getAppSnippet()得到mAppSnippet 。
  mPkgInfo是PackageInfo类型,它是关于一个安装包的所有的信息,对应着从AndroidManifest.xml文件里收集到的所有信息。mAppSnippet 是AppSnippet类型,它包括应用的标签名和icon标识。
  得到安装包的所有的信息,可以参考下Android 得到安装包的信息。
  接着进入PackageInstallerActivity的onResume()方法里面看看,

    @Override
    protected void onResume() {
        super.onResume();

        if (mLocalLOGV) Log.i(TAG, "onResume(): mAppSnippet=" + mAppSnippet);

        if (mAppSnippet != null) {
            // load dummy layout with OK button disabled until we override this layout in
            // startInstallConfirm
            bindUi();
            checkIfAllowedAndInitiateInstall();
        }

        if (mOk != null) {
            mOk.setEnabled(mEnableOk);
        }
    }

  mAppSnippet 不为null,代表前面解析的APK文件信息存在,接着调用bindUi()和checkIfAllowedAndInitiateInstall()来处理提示对话框。mOk则是提示对话框中的确定按钮,mEnableOk为true代表可以安装,则按钮可以点击。
  在用户自己安装APK的时候,系统不知道安装源的安全、受信任情况。所以需要用户自己确认安装源之后,才能安装。在用户确认安装源之后,mEnableOk即设置为true。之后安装按钮就能点击了。
  先看一下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();
        }
    }

  这里是主要设置对话框界面,将mAppSnippet.icon和mAppSnippet.label设置给对话框的icon和标题。然后将内容设置成布局文件install_content_view,然后设置安装按钮和取消按钮,现在安装按钮是不能点击,所以将它的enabled设置为false。
  接着看看checkIfAllowedAndInitiateInstall():

    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) {
            if (mLocalLOGV) Log.i(TAG, "install not allowed: " + UserManager.DISALLOW_INSTALL_APPS);
            showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER);
            return;
        } else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
            if (mLocalLOGV) {
                Log.i(TAG, "install not allowed by admin; showing "
                        + Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
            }
            startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
            finish();
            return;
        }

        if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
            if (mLocalLOGV) Log.i(TAG, "install allowed");
            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) {
                if (mLocalLOGV) Log.i(TAG, "Showing DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER");
                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();
            }
        }
    }

  Process.myUserHandle()是得到用户标识,当前用户是一般是UserHandle.USER_SYSTEM,关于用户的相关,可以参考一下Android UserManagerService初始化,首先调用mUserManager.getUserRestrictionSource()检查用户安装APP的限制标识。如果得到的结果有UserManager.RESTRICTION_SOURCE_SYSTEM标识,说明不允许该用户安装APP,接着会调用showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER),显示“This user is not allowed to install apps”对话框。
  如果结果不为UserManager.RESTRICTION_NOT_SET,则会跳到设置界面,并且关闭当前界面。
  USER_SYSTEM用户是不属于这两种情况,所以它会继续向下执行,下面是处理未知来源的APP的情况。mAllowUnknownSources为true是允许安装未知来源的APP,刚进入该界面时是为false。isInstallRequestFromUnknownSource(getIntent())是通过传递过来的intent,来判断是否是未知安装源。看一下它的代码:

    private boolean isInstallRequestFromUnknownSource(Intent intent) {
        if (mCallingPackage != null && intent.getBooleanExtra(
                Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)) {
            if (mSourceInfo != null) {
                if ((mSourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
                        != 0) {
                    // Privileged apps can bypass unknown sources check if they want.
                    return false;
                }
            }
        }
        return true;
    }

  可以看到,不仅要设置Intent.EXTRA_NOT_UNKNOWN_SOURCE的值为true,并且安装源得是特权APP。
  所以checkIfAllowedAndInitiateInstall()会接着检查对于用户UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES和UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY的限制。
  对于UserHandle.USER_SYSTEM,这些限制都不存在,所以会走最后的else分支,进行handleUnknownSources()处理。看一下它的代码:

    private void handleUnknownSources() {
        if (mOriginatingPackage == null) {
            Log.i(TAG, "No source found for package " + mPkgInfo.packageName);
            showDialogInner(DLG_ANONYMOUS_SOURCE);
            return;
        }
        // Shouldn't use static constant directly, see b/65534401.
        final int appOpCode =
                AppOpsManager.permissionToOpCode(Manifest.permission.REQUEST_INSTALL_PACKAGES);
        final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpCode, mOriginatingUid,
                mOriginatingPackage, mCallingAttributionTag,
                "Started package installation activity");
        if (mLocalLOGV) Log.i(TAG, "handleUnknownSources(): appMode=" + appOpMode);
        switch (appOpMode) {
            case AppOpsManager.MODE_DEFAULT:
                mAppOpsManager.setMode(appOpCode, mOriginatingUid,
                        mOriginatingPackage, AppOpsManager.MODE_ERRORED);
                // fall through
            case AppOpsManager.MODE_ERRORED:
                showDialogInner(DLG_EXTERNAL_SOURCE_BLOCKED);
                break;
            case AppOpsManager.MODE_ALLOWED:
                initiateInstall();
                break;
            default:
                Log.e(TAG, "Invalid app op mode " + appOpMode
                        + " for OP_REQUEST_INSTALL_PACKAGES found for uid " + mOriginatingUid);
                finish();
                break;
        }
    }

  mOriginatingPackage是调用安装APP的应用包名,所以不为null。
  这里接下来会走到switch的AppOpsManager.MODE_ALLOWED case。接着调用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();
    }

  在这里由于pkgName包还未安装,所以mAppInfo 为null。

    private void startInstallConfirm() {
        View viewToEnable;

        if (mAppInfo != null) {
            viewToEnable = requireViewById(R.id.install_confirm_question_update);
            mOk.setText(R.string.update);
        } else {
            // 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);
    }

  在这里会走else分支,viewToEnable 显示的内容“Do you want to install this app?”,来询问用户是否安装。这个时候,安装按钮就可以点击了。用户如果同意,就会走安装按钮点击的逻辑,看一下。
  它的点击事件逻辑在bindUi()方法中:

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

  这里会走到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 (mLocalLOGV) Log.i(TAG, "downloaded app uri=" + mPackageURI);
        startActivity(newIntent);
        finish();
    }

  这是为了要跳到InstallInstalling中做的一些设置。在目前的安装例子中,installerPackageName、mOriginatingURI、mReferrerURI都为null。intent中Intent.EXTRA_RETURN_RESULT的值也为false,这里会加上Intent.FLAG_ACTIVITY_FORWARD_RESULT标识。接下来就是跳入InstallInstalling中,关闭当前界面。

进入InstallInstalling执行安装

  先看看它的onCreate()方法:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ApplicationInfo appInfo = getIntent()
                .getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
        mPackageURI = getIntent().getData();

        if ("package".equals(mPackageURI.getScheme())) {
            …………
        } else {
            final File sourceFile = new File(mPackageURI.getPath());
            PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile);

            mAlert.setIcon(as.icon);
            mAlert.setTitle(as.label);
            mAlert.setView(R.layout.install_content_view);
            mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
                    (ignored, ignored2) -> {
                        if (mInstallingTask != null) {
                            mInstallingTask.cancel(true);
                        }

                        if (mSessionId > 0) {
                            getPackageManager().getPackageInstaller().abandonSession(mSessionId);
                            mSessionId = 0;
                        }

                        setResult(RESULT_CANCELED);
                        finish();
                    }, null);
            setupAlert();
            requireViewById(R.id.installing).setVisibility(View.VISIBLE);

            if (savedInstanceState != null) {
               …………
            } else {
                PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                        PackageInstaller.SessionParams.MODE_FULL_INSTALL);
                params.setInstallAsInstantApp(false);
                params.setReferrerUri(getIntent().getParcelableExtra(Intent.EXTRA_REFERRER));
                params.setOriginatingUri(getIntent()
                        .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI));
                params.setOriginatingUid(getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
                        UID_UNKNOWN));
                params.setInstallerPackageName(getIntent().getStringExtra(
                        Intent.EXTRA_INSTALLER_PACKAGE_NAME));
                params.setInstallReason(PackageManager.INSTALL_REASON_USER);

                File file = new File(mPackageURI.getPath());
                try {
                    final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
                    final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
                            input.reset(), file, /* flags */ 0);
                    if (result.isError()) {
                        Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.");
                        Log.e(LOG_TAG,
                                "Cannot calculate installed size " + file + ". Try only apk size.");
                        params.setSize(file.length());
                    } else {
                        final PackageLite pkg = result.getResult();
                        params.setAppPackageName(pkg.getPackageName());
                        params.setInstallLocation(pkg.getInstallLocation());
                        params.setSize(
                                PackageHelper.calculateInstalledSize(pkg, params.abiOverride));
                    }
                } catch (IOException e) {
                    Log.e(LOG_TAG,
                            "Cannot calculate installed size " + file + ". Try only apk size.");
                    params.setSize(file.length());
                }

                try {
                    mInstallId = InstallEventReceiver
                            .addObserver(this, EventResultPersister.GENERATE_NEW_ID,
                                    this::launchFinishBasedOnResult);
                } catch (EventResultPersister.OutOfIdsException e) {
                    launchFailure(PackageInstaller.STATUS_FAILURE,
                            PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
                }

                try {
                    mSessionId = getPackageManager().getPackageInstaller().createSession(params);
                } catch (IOException e) {
                    launchFailure(PackageInstaller.STATUS_FAILURE,
                            PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
                }
            }

            mCancelButton = mAlert.getButton(DialogInterface.BUTTON_NEGATIVE);
        }
    }

  这里先得到安装文件的ApplicationInfo对象appInfo,mPackageURI。接着就得到安装文件sourceFile、还有AppSnippet对象。
  再往下就是设置对话框的显示内容。这个对话框采用的布局和PackageInstallerActivity界面采用的布局文件是同一个install_content_view。不过显示内容是不同,这里直接让id为installing的控件显示出来。它是一个LinearLayout控件,看一下它的布局结构:

    <LinearLayout
        android:id="@+id/installing"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:visibility="invisible">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@android:style/TextAppearance.Material.Subhead"
            android:text="@string/installing" />

        <ProgressBar
            android:id="@+id/progress"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingTop="8dp"
            style="?android:attr/progressBarStyleHorizontal"
            android:indeterminate="true" />

    </LinearLayout>

  一个文本提示安装中,一个水平进度条提示进度。
  接着会生成一个SessionParams对象。将intent中的值取出来,设置给SessionParams对象。然后调用ApkLiteParseUtils.parsePackageLite( input.reset(), file, /* flags */ 0)得到安装包的轻量级对象PackageLite对象。接着用PackageLite对象的信息设置到SessionParams对象的AppPackageName、InstallLocation、Size。
  再接着就是生成一个安装id,一个SessionId。
  生成安装id是通过InstallEventReceiver的静态方法addObserver()方法实现,其中第二个参数EventResultPersister.GENERATE_NEW_ID代表生成一个新的、而this::launchFinishBasedOnResult则是一个回调。安装结果出来之后,就是通过这个回调通知的。
  在这里getPackageManager()得到的是ApplicationPackageManager对象,它的类成员mContext是ContextImpl实例,mPM是 PackageManagerService 在应用进程中的代理对象。ApplicationPackageManager的getPackageInstaller()代码为:

    @Override
    public PackageInstaller getPackageInstaller() {
        synchronized (mLock) {
            if (mInstaller == null) {
                try {
                    mInstaller = new PackageInstaller(mPM.getPackageInstaller(),
                            mContext.getPackageName(), mContext.getAttributionTag(), getUserId());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return mInstaller;
        }
    }

  mPM.getPackageInstaller()得到的是系统进程中的PackageInstallerService对象(它是Binder对象)的Binder代理对象,现在将它封装到PackageInstaller对象中。
  接着会调用PackageInstaller对象的createSession(params)方法,它会在系统进程中创建一个PackageInstallerSession对象。在创建过程中会生成PackageInstallerSession唯一id返回到应用进程,这里mSessionId就是。在系统进程创建PackageInstallerSession对象时,还会生成一个目录,如果是APK安装在内部,则为“/data/app/” + “/vmdl” + sessionId + “.tmp”或者指定volumeUuid时,则为"/mnt/expand/" + volumeUuid + “/app” + “/vmdl” + sessionId + “.tmp”。
  接着看一下onResume()方法:

    @Override
    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);
            }
        }
    }

  这里在异步任务未创建的情况下,调用PackageInstaller对象的getSessionInfo(mSessionId),这个主要是为了生成SessionInfo。然后开始调用执行异步任务mInstallingTask 。
  看一下异步任务mInstallingTask 的doInBackground()方法:

        @Override
        protected PackageInstaller.Session doInBackground(Void... params) {
            PackageInstaller.Session session;
            try {
                session = getPackageManager().getPackageInstaller().openSession(mSessionId);
            } catch (IOException e) {
                synchronized (this) {
                    isDone = true;
                    notifyAll();
                }
                return null;
            }

            session.setStagingProgress(0);

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

  这里主要调用PackageInstaller对象的openSession(mSessionId)来打开系统进程中的PackageInstallerSession,这个步骤主要是为了创建上面说的的目录。因为下面马上要复制APK文件了。
  下面就是接着就调用session.openWrite(“PackageInstaller”, 0, sizeBytes)打开之前创建目录下的"PackageInstaller"文件,然后将APK文件拷贝到这个文件中。还是以上面的目录为例子,未指定volumeUuid时,最后生成的文件则为“/data/app/” + “/vmdl” + sessionId + “.tmp/PackageInstaller”。
  拷贝完毕之后,会走异步任务mInstallingTask 的onPostExecute()方法,看一下它的代码:

        @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 | PendingIntent.FLAG_MUTABLE);

                session.commit(pendingIntent.getIntentSender());
                mCancelButton.setEnabled(false);
                setFinishOnTouchOutside(false);
            } else {
                getPackageManager().getPackageInstaller().abandonSession(mSessionId);

                if (!isCancelled()) {
                    launchFailure(PackageInstaller.STATUS_FAILURE,
                            PackageManager.INSTALL_FAILED_INVALID_APK, null);
                }
            }
        }

  这里将接受安装结果的通知Intent和mInstallId封装到PendingIntent 中,接着调用session.commit(pendingIntent.getIntentSender())去系统进程中封装PackageInstallerSession对象,然后系统进程开始执行安装操作。
  之后界面会一直在进度条走动那儿等待安装结果。
  在安装成功之后,会回调InstallEventReceiver.addObserver(this, EventResultPersister.GENERATE_NEW_ID, this::launchFinishBasedOnResult)注册的launchFinishBasedOnResult()方法,看一下它:

    private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) {
        if (statusCode == PackageInstaller.STATUS_SUCCESS) {
            launchSuccess();
        } else {
            launchFailure(statusCode, legacyStatus, statusMessage);
        }
    }
    private void launchSuccess() {
        Intent successIntent = new Intent(getIntent());
        successIntent.setClass(this, InstallSuccess.class);
        successIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);

        startActivity(successIntent);
        finish();
    }    

  成功之后,会走launchSuccess(),它会继续跳转到InstallSuccess这个Activity中,并且添加FLAG_ACTIVITY_FORWARD_RESULT标识,跳转之后,会将自己关闭。

InstallSuccess提示成功结果

  InstallSuccess主要用来提示用户安装成功了,后续是否运行安装的应用。看一下它的onCreate()方法:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
            // Return result if requested
            Intent result = new Intent();
            result.putExtra(Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_SUCCEEDED);
            setResult(Activity.RESULT_OK, result);
            finish();
        } else {
            Intent intent = getIntent();
            ApplicationInfo appInfo =
                    intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
            mAppPackageName = appInfo.packageName;
            Uri packageURI = intent.getData();

            // Set header icon and title
            PackageManager pm = getPackageManager();

            if ("package".equals(packageURI.getScheme())) {
                mAppSnippet = new PackageUtil.AppSnippet(pm.getApplicationLabel(appInfo),
                        pm.getApplicationIcon(appInfo));
            } else {
                File sourceFile = new File(packageURI.getPath());
                mAppSnippet = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
            }

            mLaunchIntent = getPackageManager().getLaunchIntentForPackage(mAppPackageName);

            bindUi();
        }
    }

  如果intent中设置了Intent.EXTRA_RETURN_RESULT,会直接返回了,不会提示安装成功界面。
  如果没有设置该值,会得到ApplicationInfo对象,包名、还有安装文件,接着查询安装应用中的主Activity 所需要的mLaunchIntent。
  接着调用bindUi(),看一下它的界面提示:

    private void bindUi() {
        if (mAppSnippet == null) {
            return;
        }

        mAlert.setIcon(mAppSnippet.icon);
        mAlert.setTitle(mAppSnippet.label);
        mAlert.setView(R.layout.install_content_view);
        mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.launch), null,
                null);
        mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.done),
                (ignored, ignored2) -> {
                    if (mAppPackageName != null) {
                        Log.i(LOG_TAG, "Finished installing " + mAppPackageName);
                    }
                    finish();
                }, null);
        setupAlert();
        requireViewById(R.id.install_success).setVisibility(View.VISIBLE);
        // Enable or disable "launch" button
        boolean enabled = false;
        if (mLaunchIntent != null) {
            List<ResolveInfo> list = getPackageManager().queryIntentActivities(mLaunchIntent,
                    0);
            if (list != null && list.size() > 0) {
                enabled = true;
            }
        }

        Button launchButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
        if (enabled) {
            launchButton.setOnClickListener(view -> {
                try {
                    startActivity(mLaunchIntent);
                } catch (ActivityNotFoundException | SecurityException e) {
                    Log.e(LOG_TAG, "Could not start activity", e);
                }
                finish();
            });
        } else {
            launchButton.setEnabled(false);
        }
    }

  这个对话框两个按钮一个是“Open”,一个是“Done”。“Done”按钮点击之后,直接关闭该界面,主要起到提示用户安装完成的作用。“Open”按钮则是跳转到安装的应用中,关闭该界面。
  install_content_view布局中显示的控件为id为install_success的,它是一个文本控件,内容为“App installed.”。

总结

  该篇文章说了安装应用时,相关界面的显示,以及涉及到的Activity的跳转和其作用。
  现在进行了两次APK的复制,第一次将它复制到包安装进程的私有存储空间中,第二次拷贝到的则是在系统进程中创建的安装APK文件。包安装进程中的APK文件会在DeleteStagedFileOnResult收到应答之后,执行删除,系统进程中的会在之后进行安装的时候,继续处理。
  现在我们知道了大概在APK安装过程中的界面显示,实际的安装工作都是在系统进程中进行,后续我们继续分析系统进程进行的安装行为。

你可能感兴趣的:(android,java)