PMS安装apk之界面跳转


需求

实现静默安装和静默卸载功能

基于这个需求,首先需要了解的是PMS相关逻辑,本文从安装apk 为切入点,先了解界面跳转逻辑

源码位置

PackageInstaller 源码在线查看
MTK 平台位置,源码位置:

frameworks\base\packages\PackageInstaller\src\com\android\packageinstaller

相关资料博客推荐

PackageInstaller的初始化

PackageInstaller安装APK

PMS处理APK的安装

PMS的创建过程

APK 安装流程

安装过程 界面跳转

PackageInstaller介绍

PackageManager简介

与ActivityManager和AMS的关系类似,PMS也有一个对应的管理类PackageManager,用于向应用程序进程提供一些功能。PackageManager是一个抽象类,它的具体实现类为ApplicationPackageManager,ApplicationPackageManager中的方法会通过IPackageManager与AMS进行进程间通信,因此PackageManager所提供的功能最终是由PMS来实现的,这么设计的主要用意是为了避免系统服务PMS直接被访问。PackageManager提供了一些功能,主要有以下几点:

  • 获取一个应用程序的所有信息(ApplicationInfo)。
  • 获取四大组件的信息。
  • 查询permission相关信息。
  • 获取包的信息。
  • 安装、卸载APK.

Apk安装方式

APK的安装场景主要有以下几种:

  • 通过adb命令安装:adb 命令包括adb push/install
  • 用户下载的Apk,通过系统安装器packageinstaller安装该Apk。packageinstaller是系统内置的应用程序,用于安装和卸载应用程序。
  • 系统开机时安装系统应用。
  • 电脑或者手机上的应用商店自动安装。

这4种方式最终都是由PMS来进行处理,在此之前的调用链是不同的,本篇文章会介绍第二种方式,对于用户来说,这是比较常用的安装方式;对于开发者来说,这是调用链比较长的安装方式,能学到的更多内容。

PackageInstaller入口 源码分析

PackageInstaller 进入之前的安装代码

本地有apk 点击或者执行代码进行安装:

   private String authority;
        private static final String intentType = "application/vnd.android.package-archive";
         String applicationID = mContext.getPackageName();
        authority = applicationID + ".fileProvider";
        Intent intent = new Intent(Intent.ACTION_VIEW);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            Uri contentUri = FileProvider.getUriForFile(mContext, authority, apkFile);
            intent.setDataAndType(contentUri, intentType);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        } else {
            intent.setDataAndType(Uri.parse("file://" + apkFile.toString()), intentType);
        }
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mContext.startActivity(intent);
		
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(xxxxx, "application/vnd.android.package-archive");

//FileProvider,FileProvider继承自ContentProvider ,使用它可以将file://Uri替换为content://Uri

PackageInstaller AndroidManifest.xml

packages/apps/PackageInstaller/AndroidManifest.xml

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

核心点:

<data android:scheme="content" />
 <data android:mimeType="application/vnd.android.package-archive" />

这里得出结论:

InstallStartPackageInstaller中的入口Activity,其中PackageInstaller是系统内置的应用程序,用于安装和卸载应用。当我们调用PackageInstaller来安装应用时会跳转到InstallStart,并调用它的onCreate方法:
packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStart.java

PackageInstaller InstallStart

我们看onCreate 方法,添加了部分注释

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
         Intent intent = getIntent();
        /*
		intent的action是否等于PackageInstaller.ACTION_CONFIRM_INSTALL来判断是否是
		PackageInstaller session安装,如果是,会去intent中取出SESSION_ID,并且得到SessionInfo信息,并从中得到callingPackage、callingAttributionTag。当前Activity是运行在安装应用进程中,PackageInstaller session是在系统进程中。在系统进程中,如果检测到权限不够,需要用户来确认,这时,会启动InstallStart以便进入用户确认界面。
		*/
        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);
           ...
         //若是是PackageInstaller session安装
        if (isSessionInstall) {
            nextActivity.setClass(this, PackageInstallerActivity.class);
        } else {
            Uri packageUri = intent.getData();
            //如果得到的URI的getScheme()是ContentResolver.SCHEME_CONTENT,则会跳转到InstallStagin
            if (packageUri != null && packageUri.getScheme().equals(
                    ContentResolver.SCHEME_CONTENT)) {
                nextActivity.setClass(this, InstallStaging.class);
            } else if (packageUri != null && packageUri.getScheme().equals(
                    PackageInstallerActivity.SCHEME_PACKAGE)) {
			   //如果得到的URI的getScheme()是PackageInstallerActivity.SCHEME_PACKAGE,则会跳转到PackageInstallerActivity中		
                nextActivity.setClass(this, PackageInstallerActivity.class);
            } else {
			//如果不是以上情况,则返回PackageManager.INSTALL_FAILED_INVALID_URI给调用者Activity。最后就是跳转,并且关闭InstallStart
                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();
    }

跳转逻辑:
判断是否勾选“未知来源”选项,若未勾选跳转到设置安装未知来源界面
对于大于等于 Android 8.0 版本,会先检查是否申请安装权限,若没有则中断安装
判断 Uri 的 Scheme 协议,若是 content 则调用 InstallStaging, 若是 package 则调用 PackageInstallerActivity

其中第二点就关联到了

  Uri contentUri = FileProvider.getUriForFile(mContext, authority, apkFile);

FileProvider 本身就是Content 的子类,为什么这么说,分析如下:
FileProvider继承自ContentProvider ,使用它可以将file://Uri替换为content://Uri,具体怎么使用FileProvider并不是本文的重点,但是简要接着分析。

在跳转安装的时候,如上代码:

Uri contentUri = FileProvider.getUriForFile(mContext, authority, apkFile);


@Override
        public Uri getUriForFile(File file) {
            String path;
            try {
                path = file.getCanonicalPath();
            } catch (IOException e) {
                throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
            }

            // Find the most-specific root path
            Map.Entry<String, File> mostSpecific = null;
            for (Map.Entry<String, File> root : mRoots.entrySet()) {
                final String rootPath = root.getValue().getPath();
                if (path.startsWith(rootPath) && (mostSpecific == null
                        || rootPath.length() > mostSpecific.getValue().getPath().length())) {
                    mostSpecific = root;
                }
            }

            if (mostSpecific == null) {
                throw new IllegalArgumentException(
                        "Failed to find configured root that contains " + path);
            }

            // Start at first char of path under root
            final String rootPath = mostSpecific.getValue().getPath();
            if (rootPath.endsWith("/")) {
                path = path.substring(rootPath.length());
            } else {
                path = path.substring(rootPath.length() + 1);
            }

            // Encode the tag and path separately
            path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/");
            return new Uri.Builder().scheme("content")
                    .authority(mAuthority).encodedPath(path).build();
        }


返回的Uri 里面,构造了scheme:“content”

PackageInstaller InstallStaging

packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java

copy uri 传递过来的apk 数据

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

        // This is the first onResume in a single life of the activity
        if (mStagingTask == null) {
            // File does not exist, or became invalid
            if (mStagedFile == null) {
                // Create file delayed to be able to show error
                try {
				    // 创建临时文件 mStagedFile 用来存储数据
                    mStagedFile = TemporaryFileManager.getStagedFile(this);
                } catch (IOException e) {
                    showError();
                    return;
                }
            }

            mStagingTask = new StagingAsyncTask();
			// 启动 StagingAsyncTask,并传入了content协议的Uri
            mStagingTask.execute(getIntent().getData());
        }
    }

StagingAsyncTask 开启了一个线程:

创建临时文件 mStagedFile 用来存储数据
启动 StagingAsyncTask,并传入了 content 协议的 Uri

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) {
			/*
			进入DeleteStagedFileOnResult  这个Activity是为了跳入PackageInstallerActivity,然后等待返回结果,收到返回结果之后,然后删除前面生成的安装文件。
			
			
			这里可以看到在doInBackground(Uri… params)中实现了,文件的复制。
 在onPostExecute(Boolean success)中,根据复制结果来做下一步。如果复制成功,会跳入DeleteStagedFileOnResult这个Activity中。在这里会设置跳转Intent的mData成员变量

进入DeleteStagedFileOnResult 这个Activity是为了跳入PackageInstallerActivity,然后等待返回结果,收到返回结果之后,然后删除前面生成的安装文件。
 
			*/
                // 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 方法中将 packageUri(content 协议的 Uri)的内容写入到 mStagedFile 中

PackageInstaller DeleteStagedFileOnResult

跳转PackageInstallerActivity 等待结果后删除生成的Path 对应的file


/**
 * Trampoline activity. Calls PackageInstallerActivity and deletes staged install file onResult.
 */
public class DeleteStagedFileOnResult extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        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) {
        setResult(resultCode, data);
        finish();
    }

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

        if (isFinishing()) {
            File sourceFile = new File(getIntent().getData().getPath());
            new Thread(sourceFile::delete).start();
        }
    }
}

PackageInstaller PackageInstallerActivity

从功能上来说,PackageInstallerActivity才是应用安装器PackageInstaller真正的入口Activity,PackageInstallerActivity的onCreate方法如下所示。

packages/apps/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java

主要任务
  • 显示安装界面
  • 初始化安装需要用的各种对象,比如
    PackageManager、IPackageManager、AppOpsManager、UserManager、PackageInstaller
    等等
  • 根据传递过来的 Scheme 协议做不同的处理
  • 检查是否允许、初始化安装
  • 在准备安装的之前,检查应用列表判断该应用是否已安装,若已安装则提示该应用已安装,由用户决定是否替换
  • 在安装界面,提取出 APK 中权限信息并展示出来
  • 点击 OK 按钮确认安装后,会调用 startInstall 开始安装工作
onCreate
protected void onCreate(Bundle icicle) {
    getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
    super.onCreate(null);
    // 初始化安装需要用到的对象
    mPm = getPackageManager();
    mIpm = AppGlobals.getPackageManager();
    mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
    mInstaller = mPm.getPackageInstaller();
    mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);

    // 根据Uri的Scheme做不同的处理
    boolean wasSetUp = processPackageUri(packageUri);
    if (!wasSetUp) {
        return;
    }
    // 显示安装界面
    bindUi();
    // 检查是否允许安装包,如果允许则启动安装。如果不允许显示适当的对话框
    checkIfAllowedAndInitiateInstall();
}

主要做了对象的初始化,解析 Uri 的 Scheme,初始化界面,安装包检查等等工作,接着查看一下 processPackageUri 方法

processPackageUri

private boolean processPackageUri(final Uri packageUri) {
    mPackageURI = packageUri;
    final String scheme = packageUri.getScheme();
    // 根据这个Scheme协议分别对package协议和file协议进行处理
    switch (scheme) {
        case SCHEME_PACKAGE: {
            try {
                // 通过PackageManager对象获取指定包名的包信息
                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: {
            // 根据packageUri创建一个新的File
            File sourceFile = new File(packageUri.getPath());
            // 解析APK得到APK的信息,PackageParser.Package存储了APK的所有信息
            PackageParser.Package parsed = PackageUtil.getPackageInfo(this, sourceFile);

            if (parsed == null) {
                Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
                showDialogInner(DLG_PACKAGE_ERROR);
                setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
                return false;
            }
            // 根据PackageParser.Package得到的APK信息,生成PackageInfo
            mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
                    PackageManager.GET_PERMISSIONS, 0, 0, null,
                    new PackageUserState());
            mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
        } break;

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

    return true;
}

主要对 Scheme 协议分别对 package 协议和 file 协议进行处理

SCHEME_PACKAGE:

在 package 协议中调用了 PackageManager.getPackageInfo 方法生成 PackageInfo,PackageInfo 是跨进程传递的包数据(activities、receivers、services、providers、permissions等等)包含 APK 的所有信息
SCHEME_FILE:

在 file 协议的处理中调用了 PackageUtil.getPackageInfo 方法,方法内部调用了 PackageParser.parsePackage() 把 APK 文件的 manifest 和签名信息都解析完成并保存在了 Package,Package 包含了该 APK 的所有信息
调用 PackageParser.generatePackageInfo 生成 PackageInfo
接着往下走,都解析完成之后,回到 onCreate 方法,继续调用 checkIfAllowedAndInitiateInstall 方法

checkIfAllowedAndInitiateInstall

private void checkIfAllowedAndInitiateInstall() {
    // 首先检查安装应用程序的用户限制,如果有限制并弹出弹出提示Dialog或者跳转到设置界面
    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;
    }

    // 判断如果允许安装未知来源或者根据Intent判断得出该APK不是未知来源
    if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
        initiateInstall();
    } else {
        // 检查未知安装源限制,如果有限制弹出Dialog,显示相应的信息
        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 {
            // 处理未知来源的APK
            handleUnknownSources();
        }
    }
}

主要检查安装应用程序的用户限制,当 APK 文件不对或者安装有限制则调用 showDialogInner 方法,弹出 dialog 提示用户,显示相应的错误信息,来看一下都有那些错误信息

如果用户允许安装未知来源,会调用 initiateInstall 方法

initiateInstall

private void initiateInstall() {
    String pkgName = mPkgInfo.packageName;
    // 检查设备上是否存在相同包名的APK
    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;
    }
    // 检查package是否已安装, 如果已经安装则显示对话框提示用户是否替换。
    try {
        mAppInfo = mPm.getApplicationInfo(pkgName,
                PackageManager.MATCH_UNINSTALLED_PACKAGES);
        if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
            mAppInfo = null;
        }
    } catch (NameNotFoundException e) {
        mAppInfo = null;
    }
    // 初始化确认安装界面
    startInstallConfirm();
}

根据包名获取应用程序的信息,调用 startInstallConfirm 方法初始化安装确认界面后,当用户点击确认按钮之后发生了什么,接着查看确认按钮点击事件

bindUi

private void bindUi() {
   ...
    // 点击确认按钮,安装APK
    mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install),
            (ignored, ignored2) -> {
                if (mOk.isEnabled()) {
                    if (mSessionId != -1) {
                        mInstaller.setPermissionsResult(mSessionId, true);
                        finish();
                    } else {
                        // 启动Activity来完成应用的安装
                        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);
}

当用户点击确认按钮调用了 startInstall 方法,启动子 Activity 完成 APK 的安装

startInstall
private void startInstall() {
    // 启动子Activity来完成应用的安
    Intent newIntent = new Intent();
    newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
            mPkgInfo.applicationInfo);
    newIntent.setData(mPackageURI);
    newIntent.setClass(this, InstallInstalling.class);
    ...
    if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
    startActivity(newIntent);
    finish();
}

总结

PackageInstaller初始化的过程

  • 根据Uri的Scheme协议不同,跳转到不同的界面,content协议跳转到InstallStart,其他的跳转到PackageInstallerActivity。本文应用场景中,会跳转到InstallStart。
  • InstallStart将content协议的Uri转换为File协议,然后跳转到PackageInstallerActivity。
  • PackageInstallerActivity会分别对package协议和file协议的Uri进行处理,如果是file协议会解析APK文件得到包信息PackageInfo。
  • PackageInstallerActivity中会对未知来源进行处理,如果允许安装未知来源或者根据Intent判断得出该APK不是未知来源,就会初始化安装确认界面,如果管理员限制来自未知源的安装,就弹出提示Dialog或者跳转到设置界面。

UI角度 下载安装流程

  • 根据根据 Uri 的 Scheme 找到入口 InstallStart
  • InstallStart 根据 Uri 的 Scheme 协议不同做不同的处理
  • 都会调用 PackageInstallerActivity, 然后分别对package协议和 file 协议的 Uri 进行处理
  • PackageInstallerActivity 检查未知安装源限制,如果安装源限制弹出提示 Dialog
    点击 OK 按钮确认安装后,会调用 startInstall 开始安装工作

扩展分析

这里我们阅读了系统真正安装的前面部分的逻辑和UI跳转业务,后面我们关注安装的业务,如何安装成功apk的。

PMS(Project Management System,项目管理系统)中安装APK时,通常会检查设备可用的存储空间来确定能否继续安装。以下是一个常见的步骤,你可以通过Android API来实现这一功能: 1. **获取可用存储空间信息**:首先,你需要访问`Context`对象的`getExternalFilesDir()`或`getExternalCacheDir()`方法获取外部存储空间的路径。然后,你可以读取该目录的`File`实例的`length()`属性来获取可用空间的总量。 ```java public long getAvailableSpace() { File externalStorageDir = Environment.getExternalStorageDirectory(); if (externalStorageDir != null && externalStorageDir.canWrite()) { return externalStorageDir.getFreeSpace(); } else { // 如果外部存储不可写,检查内部存储 File appCacheDir = context.getCacheDir(); return appCacheDir.getFreeSpace(); } } ``` 2. **比较APK大小与可用空间**:计算要安装APK文件的大小,通常是通过读取APK文件本身的信息,例如从`Uri`获取。如果APK大小加上现有的应用数量(因为其他应用也需要空间),大于可用空间,则认为无法安装。 ```java long apkSizeInBytes = getApkFileSizeFromPath(apkFilePath); // 获取APK的实际大小 long totalRequiredSpace = apkSizeInBytes + getEstimatedAppSumSize(); // 总需空间(APK + 已有应用占用) if (totalRequiredSpace > getAvailableSpace()) { // 显示存储空间不足提示 Toast.makeText(context, "Insufficient storage for installation", Toast.LENGTH_SHORT).show(); return false; // 返回false表示无法安装 } else { // 其他安装逻辑... return true; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ItJavawfc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值