安装 apk 示例
启动 Android Studio 的模拟器,把 apk 文件 push 到模拟器的 /sdcard 下,然后可以用代码来安装这个 apk,我使用的是 Android 11 的模拟器。代码如下:
xml
// AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.test">
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Test">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Test">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.test.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path" />
</provider>
</application>
</manifest>
java
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private static final int REQUEST_PERMISSION_CODE = 11;
private static final int REQUEST_MANAGE_EXTERNAL_STORAGE = 12;
private static final String[] mRequestPermissions = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void buttonClick(View view) {
checkPermission();
}
private void checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
if (isAllGranted()) {
install();
} else {
verifyPermission(mRequestPermissions, REQUEST_PERMISSION_CODE);
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (Environment.isExternalStorageManager()) {
install();
} else {
verifyPermission(mRequestPermissions, REQUEST_PERMISSION_CODE);
requestManageExternalStoragePermission();
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
checkPermission();
}
public boolean isAllGranted() {
for (int i = 0; i < mRequestPermissions.length; i++) {
if (ActivityCompat.checkSelfPermission(this, mRequestPermissions[i]) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
public void verifyPermission(String[] permission, int code) {
if (permission != null) {
List<String> lists = new ArrayList<>();
for (int i = 0; i < permission.length; i++) {
if (ActivityCompat.checkSelfPermission(this, permission[i]) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission[i])) {
}
lists.add(permission[i]);
}
}
if (lists.size() > 0) {
String[] ps = new String[lists.size()];
for (int i = 0; i < lists.size(); i++) {
ps[i] = lists.get(i);
}
ActivityCompat.requestPermissions(this, ps, code);
}
}
}
private void install() {
String sdCardPath = Environment.getExternalStorageDirectory().getPath();
File file = new File(sdCardPath, "kugou.apk");
Log.d(TAG, "path:" + file.getAbsolutePath());
// 通过以下代码来安装指定路径中的 apk
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Uri apkUri = FileProvider.getUriForFile(this, "com.example.test.provider", file);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// Android 7.0 及以上的版本需要添加这行代码,否则会报 FileUriExposedException
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
startActivity(intent);
}
private void requestManageExternalStoragePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.setData(Uri.parse("package:" + this.getPackageName()));
startActivityForResult(intent, REQUEST_MANAGE_EXTERNAL_STORAGE);
}
}
// private void requestPackageInstall(){
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Uri packageURI = Uri.parse("package:" + BuildConfig.APPLICATION_ID);
// Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI);
// startActivityForResult(intent, REQUEST_INSTALL_PACKAGE);
// }
// }
}
在 res 下的 xml 目录下要添加 file_path.xml 文件:
xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<!--代表的目录即为:Environment.getExternalStorageDirectory()-->
<external-path
name="external_storage_root"
path="." />
</paths>
点击 button 开始安装,整个安装过程模拟器截图如下:

使用 logcat | grep Displayed 命令, 打印如下:
bash
Displayed com.google.android.packageinstaller/com.android.packageinstaller.InstallStaging: +138ms
Displayed com.google.android.packageinstaller/com.android.packageinstaller.PackageInstallerActivity: +196ms
Displayed com.android.settings/.Settings$ManageAppExternalSourcesActivity: +214ms
Displayed com.google.android.packageinstaller/com.android.packageinstaller.InstallInstalling: +192ms
Displayed com.google.android.packageinstaller/com.android.packageinstaller.InstallSuccess: +352ms
PackageInstaller 的初始化
安装 apk 是通过系统中的 PackageInstaller 来完成的,对应的源码目录在 Android 11.0 的 frameworks/base/packages/PackageInstaller 目录下,上面安装的代码中,lntent 的 Action 属性为 ACTION_ VIEW , 数据类型为 application/vnd.android.package-archive ,在 PackageInstaller 的 AndroidManifest.xml 中可以看到能隐式匹配的 Activity 为 InstallStart。其代码如下:
java
public class InstallStart extends Activity {
...
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
Intent nextActivity = new Intent(intent);
...
if (isSessionInstall) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Uri packageUri = intent.getData();
if (packageUri != null && packageUri.getScheme().equals( // 1
ContentResolver.SCHEME_CONTENT)) {
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();
}
}
在注释 1 处判断 Uri 的 Scheme 是否是 "content"(ContentResolver.SCHEME_CONTENT 即 "content"),如果是就跳转到 InstallStaging。
在 Android 7.0 及以上的版本中,系统启用了更严格的 StrictMode 策略,如果你的应用尝试通过 Intent 向其他应用(比如相机、安装器、邮件等)传递一个 file://Uri(例如 file:///storage/emulated/0/app.apk),系统会抛出:FileUriExposedException 异常。这是为了防止应用无意中将私有文件路径暴露给其他应用,提升应用的安全性。为了解决这个问题,Google 提供了 FileProvider,FileProvider 是 ContentProvider 的子类,专门用于安全地共享应用内部文件,它会将 file://Uri 替换为 content://Uri。(例如 content://com.example.fileprovider/apk/app.apk)。其他应用只能通过 ContentResolver 和你授予的临时权限(FLAG_GRANT_READ_URI_PERMISSION)访问该文件,无法得知其真实路径。
注意,如果你在应用内部使用 file://Uri,通常不会触发异常。使用 FileProvider 需要在 AndroidManifest.xml 中声明,并指定可共享的文件路径(通过 xml 资源文件定义 <paths>),参考文章最开始的 AndroidManifest.xml。
这种情况下,不能直接安装,因为 PackageInstaller 无法直接读取 content://Uri(出于安全限制)。所以先跳转到 InstallStaging。InstallStaging 的作用是将 content://Uri 对应的 apk 复制到系统可访问的临时位置(如 /data/local/tmp/) ,然后再启动真正的安装流程。InstallStaging 代码如下:
java
public class InstallStaging extends AlertActivity {
private static final String LOG_TAG = InstallStaging.class.getSimpleName();
private static final String STAGED_FILE = "STAGED_FILE";
/**
* Currently running task that loads the file from the content URI into a file
*/
private @Nullable StagingAsyncTask mStagingTask;
/**
* The file the package is in
*/
private @Nullable File mStagedFile;
@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;
}
}
}
@Override
protected void onResume() {
super.onResume();
if (mStagingTask == null) {
if (mStagedFile == null) {
try {
// 临时文件路径(类似 /data/data/com.android.packageinstaller/files/staged/apk-12345.tmp)
mStagedFile = TemporaryFileManager.getStagedFile(this);
} catch (IOException e) {
showError();
return;
}
}
mStagingTask = new StagingAsyncTask();
mStagingTask.execute(getIntent().getData());
}
}
...
private final class StagingAsyncTask extends AsyncTask<Uri, Void, Boolean> {
@Override
protected Boolean doInBackground(Uri... params) {
...
Uri packageUri = params[0];
try (InputStream in = getContentResolver().openInputStream(packageUri)) {
...
try (OutputStream out = new FileOutputStream(mStagedFile)) {
byte[] buffer = new byte[1024 * 1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) >= 0) {
...
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)); // 转为 file://Uri
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();
}
}
}
}
- 在 onCreate() 中显示了一个弹框,对应的就是上面第 1 张截图。
- 在 onResume() 中创建了 mStagedFile,mStagedFile 是一个临时文件路径,然后启动了 StagingAsyncTask,并传入了 content 协议的 Uri。
- 在 doInBackground() 中从 content://Uri 读取 apk 数据流写入 mStagedFile。
- 在 onPostExecute() 方法中,判断如果写入成功,跳转到 DeleteStagedFileOnResult,并将 mStagedFile 对应的 Uri 传进去。
Android 的安装服务(PackageManagerService)长期以来只接受文件路径(file://Uri 或直接路径),不支持从 ContentProvider 读取数据。InstallStaging 主要起转换作用就是将 content 协议的 Uri 转换为 file 协议的。这样就可以像 Android 7.0 之前的版本一样启动安装流程了。DeleteStagedFileOnResult 很简单,代码如下:
java
public class DeleteStagedFileOnResult extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
Intent installIntent = new Intent(getIntent());
// 跳转到 PackageInstallerActivity
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) {
// 删除 mStagedFile
File sourceFile = new File(getIntent().getData().getPath());
sourceFile.delete();
setResult(resultCode, data);
finish();
}
}
里面会跳转到 PackageInstallerActivity 并在 onActivityResult() 中删除 mStagedFile 临时文件。从功能上来说,PackageInstallerActivity 才是应用安装器 PackageInstaller 真正的入口,其代码如下:
java
public class PackageInstallerActivity extends AlertActivity {
private Uri mPackageURI;
PackageManager mPm;
IPackageManager mIpm;
PackageInstaller mInstaller;
PackageInfo mPkgInfo;
@Override
protected void onCreate(Bundle icicle) {
...
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();
final Uri packageUri;
if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction())) {
...
} else {
packageUri = intent.getData();
}
...
boolean wasSetUp = processPackageUri(packageUri);
if (!wasSetUp) {
return;
}
bindUi(); // 初始化UI
checkIfAllowedAndInitiateInstall(); // 检查是否允许未知来源,调整UI
}
// 解析 Uri,开始准备安装
private boolean processPackageUri(final Uri packageUri) {
mPackageURI = packageUri;
final String scheme = packageUri.getScheme();
switch (scheme) {
case SCHEME_PACKAGE: {
...
}
break;
case ContentResolver.SCHEME_FILE: {
File sourceFile = new File(packageUri.getPath());
// 解析 apk,参数为 PackageManager.GET_PERMISSIONS
mPkgInfo = PackageUtil.getPackageInfo(this, sourceFile,
PackageManager.GET_PERMISSIONS);
// 获取 apk 的摘要:图标、名字
mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
}
break;
default: {
throw new IllegalArgumentException("Unexpected URI scheme " + packageUri);
}
}
return true;
}
// 点击"安装"按钮后执行这里
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 (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);
// 跳转到 InstallInstalling
startActivity(newIntent);
finish();
}
}
整体过程如下:
- 在 onCreate() 中初始化安装所需的各种对象,比如 PackageManager、IPackageManager、AppOpsManager、PackageInstaller 和 UserManager;
- 在 processPackageUri() 方法中会根据 packageUri 的 Scheme 协议分别对 package 协议和 file 协议进行处理,如果不是这两种协议,会抛出异常。这里我们主要看 file 协议的处理过程,使用 PackageUtil.getPackageInfo() 获取 apk 的 PackageInfo,其内部还是调用的 ParsingPackageUtils 的 parsePackage() 方法来进行 apk 的解析工作。使用 PackageUtil.getAppSnippet() 获取 apk 摘要:图标、名字;
- 使用 bindUi 初始化了弹框 UI 界面,使用 checkIfAllowedAndInitiateInstall() 检查用户是否被允许安装应用,根据检查结果展示上面图 2 所示的 "未知来源apk安装" (即非来自官方商店的 apk)的对话框,这里点击 "Settings" 按钮后会跳转到设置页,上图 3 所示即为设置页;
- 打开设置中的 "Allow from this source" 开关后,回到 PackageInstallerActivity,在 onActivityResult()中,展示 "Do you want to install this application?" 提示,如上图图 4 所示,点击 "Install" ,调用 startInstall() 方法,跳转到 InstallInstalling 页面,开始安装。
我们来看看 PackageInfo 的解析过程------PackageUtil.getPackageInfo():
java
public static PackageInfo getPackageInfo(Context context, File sourceFile, int flags) {
...
return context.getPackageManager().getPackageArchiveInfo(sourceFile.getAbsolutePath(), flags);
...
}
里面调用了 PackageManager 的 getPackageArchiveInfo():
java
public abstract class PackageManager {
public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath,
@PackageInfoFlags int flags) {
...
ParseInput input = ParseTypeImpl.forParsingWithoutPlatformCompat().reset();
ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefault(input,
new File(archiveFilePath), 0, collectCertificates);
...
return PackageInfoWithoutStateUtils.generate(result.getResult(), null, flags, 0, 0, null,
new PackageUserState(), UserHandle.getCallingUserId());
}
}
这里调用了 ParsingPackageUtils 的静态方法 parseDefault(),里面创建了 ParsingPackageUtils 实例,继续调用它的 parsePackage()。在 PackageManagerService 的构造方法中解析 apk 也是调用的 ParsingPackageUtils 的 parsePackage() 方法,这里就不再跟踪进去了。
小结一下,PackageInstaller 初始化过程如下:
- 在 InstallStart 中根据 Uri 的 Scheme 协议不同,跳转不同的界面,content 协议先跳转到 InstallStaging,package 协议直接跳转到 PackageInstallerActivity。
- InstallStaging 从 content://Uri 读取 apk 数据流写入临时文件目录,将 content 协议的 Uri 转换为 file 协议,然后跳转到 DeleteStagedFileOnResult。
- DeleteStagedFileOnResult 会启动 PackageInstallerActivity 并在 PackageInstallerActivity 返回结果后(安装完成或取消),再删除临时文件。
- 在 PackageInstallerActivity 中,判断如果是 file 协议的 Uri ,会解析 apk 文件得到 PackageInfo(包信息),检查安装权限,显示安装确认弹窗。如果是 package 协议的 Uri,会获取已安装的包信息,显示"重新安装"界面。
PackageInstaller 安装 apk 过程
写入 Session
前面点击 "install" 后跳转到了 InstallInstalling:
java
public class InstallInstalling extends AlertActivity {
private static final String BROADCAST_ACTION =
"com.android.packageinstaller.ACTION_INSTALL_COMMIT";
/**
* Task that sends the package to the package installer
*/
private InstallingAsyncTask mInstallingTask;
/**
* Id of the session to install the package
*/
private int mSessionId;
/**
* Id of the install event we wait for
*/
private int mInstallId;
/**
* URI of package to install
*/
private Uri mPackageURI;
@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);
...
setupAlert(); // 显示弹框
...
if (savedInstanceState != null) { // 1
mSessionId = savedInstanceState.getInt(SESSION_ID);
mInstallId = savedInstanceState.getInt(INSTALL_ID);
...
InstallEventReceiver.addObserver(this, mInstallId,
this::launchFinishBasedOnResult); // 2
} else {
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL); //3
...
File file = new File(mPackageURI.getPath());
PackageParser.PackageLite pkg = PackageParser.parsePackageLite(file, 0); //4
params.setAppPackageName(pkg.packageName);
params.setInstallLocation(pkg.installLocation);
params.setSize(
PackageHelper.calculateInstalledSize(pkg, false, params.abiOverride));
...
mInstallId = InstallEventReceiver.addObserver(this, EventResultPersister.GENERATE_NEW_ID,
this::launchFinishBasedOnResult); // 5
...
// IPC:PackageInstaller 内部会通过 IPackageInstaller 调用 PackageInstallerService 的 createSession() 方法来创建
mSessionId = getPackageManager().getPackageInstaller().createSession(params); // 6
...
}
}
}
@Override
protected void onResume() {
super.onResume();
if (mInstallingTask == null) {
PackageInstaller installer = getPackageManager().getPackageInstaller();
// 通过 mSessionId 拿到 SessionInfo
PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);
if (sessionInfo != null && !sessionInfo.isActive()) {
mInstallingTask = new InstallingAsyncTask();
// 开启安装任务
mInstallingTask.execute();
}
...
}
}
// 把包发送给 package installer 然后注册监听
private final class InstallingAsyncTask extends AsyncTask<Void, Void,
PackageInstaller.Session> {
@Override
protected PackageInstaller.Session doInBackground(Void... params) {
PackageInstaller.Session session;
...
// 通过 mSessionId 拿到 Session
session = getPackageManager().getPackageInstaller().openSession(mSessionId);
...
try {
File file = new File(mPackageURI.getPath());
try (InputStream in = new FileInputStream(file)) {
long sizeBytes = file.length();
// 通过 Session 获取输出流
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;
}
...
}
@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(pendingIntent.getIntentSender());
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
}
...
}
}
}
主要工作如下:
- 显示安装界面,对应上面第 5 张截图。
- 在 onCreate() 中会分别对 "package" 和其他协议的 Uri 进行处理,我们来看其他协议的 Uri 处理部分。如果注释 1 处的 savedInstanceState 不为 null,则获取此前保存的 mSessionId 和 mInstallld,其中 mSessionId 是安装包的会话 id,mInstallId 是等待的安装事件 id。注释 2 处根据 mInstallId 向 InstallEventReceiver 注册了一个观察者,其中 InstallEventReceiver 继承自 BroadcastReceiver,launchFinishBasedOnResult() 方法会接收安装事件的回调,跳转到安装成功或失败的 Activity。如果 savedInstanceState 为 null,代码的逻辑也是类似的,则在注释 3 处创建 SessionParams,它用来代表安装会话的参数。注释 4 处根据 mPackageUri 对 apk 进行轻量级的解析,并将解析的参数赋值给 SessionParams。注释 5 处和注释 2 处类似,向 InstallEventReceiver 注册一个观察者并返回一个新的 mInstallId。注释 6 处 PackageInstaller 的 createSession() 方法内部会通过 IPackageInstaller 与 PackageInstallerService 进行进程间通信,最终调用的是 PackageInstallerService 的 createSession() 方法来创建并返回 mSessionId。
- 在 onResume() 中通过 mSessionId 拿到 SessionInfo, SessionInfo 代表安装会话的详细信息,如果 SessionInfo 不是活跃状态,就开启 InstallingAsyncTask。
- 在 doInBackground() 中通过 PackageInstaller.Session 将 apk 文件内容写入到安装会话中。
- 在 onPostExecute() 中创建 PendingIntent 作为安装完成后的回调,调用 PackageInstaller.Session 的 commit() 触发实际的安装过程,安装结果会通过广播(BROADCAST_ACTION)返回。
- 安装结束后,会回调 launchFinishBasedOnResult() 方法,成功或失败都会启动一个新的 Activity(InstallSuccess、InstallFailed)将结果展示给用户,然后 finish 掉 InstallInstalling。
发送去哪了呢?我们接着查看 PackageInstaller.Session 的 commit() 方法:
java
public static class Session implements Closeable {
public void commit(@NonNull IntentSender statusReceiver) {
try {
mSession.commit(statusReceiver, false);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
mSession 的类型为 IPackageInstallerSession,这说明要通过 IPackageInstallerSession 进行进程间的通信,最终会调用 PackageInstallerSession 的 commit() 方法,这样代码逻辑就到了 Java 框架层。
这里再对几个关键类说明一下:
注释 6 处 getPackageManager().getPackageInstaller() 是通过 ApplicationPackageManager 的 getPackageInstaller() 创建的:
java
public PackageInstaller getPackageInstaller() {
synchronized (mLock) {
if (mInstaller == null) {
...
mInstaller = new PackageInstaller(mPM.getPackageInstaller(),
mContext.getPackageName(), getUserId());
...
}
return mInstaller;
}
}
在这里创建了 PackageInstaller 实例并把 mPM.getPackageInstaller() 传入了,mPM.getPackageInstaller() 返回的是 IPackageInstaller,IPackageInstaller 是一个 aidl 接口,IPackageInstaller 在系统服务端的具体实现是 PackageInstallerService。
PackageInstallerService 的实例是在 PMS 的构造方法中创建的,初始化时会读取 /data/system 目录下的 install_sessions.xml 文件,这个文件中保存了系统中未完成的安装会话。PMS 会根据文件的内容创建 PackageInstallerSession 对象并插入到 mSessions 中,具体在 PackageInstallerService 的 readSessionsLocked() 方法中。
PackageInstaller 提供了 应用安装、更新、移除的能力,具体实现是 PackageInstallerService。 Session 是与 mSessionId 绑定的安装会话,代表一个正在进行中的安装操作。Session 类是对 IPackageInstaller.openSession(sessionId) 获取的 PackageInstallerSession(系统服务端)的封装。Session 管理安装的参数,并提供将安装包临时复制到特定路径(data/app-staging)的能力。Session 的创建和打开具体实现是在 PackageInstallerService 中,主要是初始化 apk 的安装信息及环境,并创建一个 sessionId,将安装 Session 与 sessionId 进行绑定。
Java 框架层的处理
接着看 PackageInstallerSession 的 commit() 方法
java
public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
...
dispatchStreamValidateAndCommit();
}
private void dispatchStreamValidateAndCommit() {
mHandler.obtainMessage(MSG_STREAM_VALIDATE_AND_COMMIT).sendToTarget();
}
这里发送了一个 Handler 消息------MSG_STREAM_VALIDATE_AND_COMMIT:
java
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;
...
}
return true;
}
};
private void handleStreamValidateAndCommit() {
...
mHandler.obtainMessage(MSG_INSTALL).sendToTarget();
}
private void handleInstall() {
...
List<PackageInstallerSession> childSessions = getChildSessionsNotLocked();
...
installNonStagedLocked(childSessions);
...
}
private void installNonStagedLocked(List<PackageInstallerSession> childSessions)
throws PackageManagerException {
final PackageManagerService.ActiveInstallSession installingSession =
makeSessionActiveLocked();
...
// 安装走到了 PMS 中
mPm.installStage(installingSession);
}
最后安装过程走到了 PMS 的 installStage() 方法,真正的安装过程是 PMS 来执行的。
接着看 PMS 的 installStage() 方法:
java
void installStage(ActiveInstallSession activeInstallSession) {
...
final Message msg = mHandler.obtainMessage(INIT_COPY);
final InstallParams params = new InstallParams(activeInstallSession);
...
msg.obj = params;
...
mHandler.sendMessage(msg);
}
处理 INIT_COPY 消息是 HandlerParams 的 startCopy() 方法:
java
final void startCopy() {
handleStartCopy();
handleReturnCode();
}
两个方法实现在 PMS 的内部类 InstallParams 中,先看看 handleStartCopy():
java
class InstallParams extends HandlerParams {
public void handleStartCopy() {
int ret = PackageManager.INSTALL_SUCCEEDED;
...
// 确定 apk 的安装位置,onInt:安装到内部存储(即 Data分区)中;
// ephemeral:安装到临时存储中(Instant Apps 安装)
final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;
final boolean ephemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
PackageInfoLite pkgLite = null;
// 解析 apk 的基本信息(包名、版本、所需存储空间等),推荐安装位置:内部存储、外部存储、即时应用等
// 根据 installFlags 和包信息决定最终安装位置
pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,
origin.resolvedPath, installFlags, packageAbiOverride);
...
if (!origin.staged && pkgLite.recommendedInstallLocation
== PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
// 尝试释放缓存空间
mInstaller.freeCache(null, sizeBytes + lowThreshold, 0, 0);
// 重新评估安装位置
pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(...);
}
...
if (ret == PackageManager.INSTALL_SUCCEEDED) {
int loc = pkgLite.recommendedInstallLocation;
// 将 PackageHelper 的推荐结果转换为标准的 PackageManager 错误码
if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
} else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) {
ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
} else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
} else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
ret = PackageManager.INSTALL_FAILED_INVALID_APK;
} else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
ret = PackageManager.INSTALL_FAILED_INVALID_URI;
} else if (loc == PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE) {
ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
} else {
// 用于确定 apk 的安装位置
loc = installLocationPolicy(pkgLite);
...
if (!onInt) {
// 根据策略调整安装标志
if (loc == RECOMMEND_INSTALL_EXTERNAL) {
installFlags &= ~INSTALL_INTERNAL; // 安装到SD卡
} else if (loc == RECOMMEND_INSTALL_EPHEMERAL) {
installFlags |= INSTALL_INSTANT_APP; // 安装为即时应用
}
}
}
}
// 安装参数
final InstallArgs args = createInstallArgs(this);
mVerificationCompleted = true;
mIntegrityVerificationCompleted = true;
mEnableRollbackCompleted = true;
mArgs = args;
if (ret == PackageManager.INSTALL_SUCCEEDED) {
final int verificationId = mPendingVerificationToken++;
// Perform package verification (unless we are simply moving the package).
if (!origin.existing) {
PackageVerificationState verificationState =
new PackageVerificationState(this);
mPendingVerification.append(verificationId, verificationState);
// 包完整性校验
sendIntegrityVerificationRequest(verificationId, pkgLite, verificationState);
// 安装权限验证
ret = sendPackageVerificationRequest(
verificationId, pkgLite, verificationState);
// If both verifications are skipped, we should remove the state.
if (verificationState.areAllVerificationsComplete()) {
mPendingVerification.remove(verificationId);
}
}
...
}
mRet = ret;
}
}
这里解析 apk 的基本信息(包名、版本、所需存储空间等),检查存储空间,确定安装位置,启动包验证和完整性验证,初始化回滚(Rollback)机制,准备安装参数(InstallArgs)。
再看看 handleReturnCode():
java
void handleReturnCode() {
// 所有验证都通过了
if (mVerificationCompleted
&& mIntegrityVerificationCompleted && mEnableRollbackCompleted) {
if (mRet == PackageManager.INSTALL_SUCCEEDED) {
// apk 的复制
mRet = mArgs.copyApk();
}
// 后续安装流程
processPendingInstall(mArgs, mRet);
}
}
这里在所有必要的验证步骤完成后,决定是否执行 apk 的复制和后续安装流程。先看看 InstallArgs 的 copyApk():
java
static abstract class InstallArgs {
abstract int copyApk();
}
InstallArgs是一个抽象类,具体实现在其子类:MoveInstallArgs 和 FileInstallArgs,这里执行 FileInstallArgs 的 copyApk() 方法。
java
class FileInstallArgs extends InstallArgs {
int copyApk() {
...
return doCopyApk();
...
}
private int doCopyApk() {
// 表示 apk 已经位于系统认可的"已暂存"位置,
// 例如通过 PackageInstaller.createSession() + openWrite() 写入的 session 目录。
// 此时无需再次复制,直接使用原文件路径作为 codeFile 和 resourceFile。
// 常见于通过 PackageInstaller API 安装的场景(如应用商店)
if (origin.staged) {
...
codeFile = origin.file;
resourceFile = origin.file;
return PackageManager.INSTALL_SUCCEEDED;
}
try {
// 判断是否为"即时应用"(Instant App),影响存储位置或权限。
final boolean isEphemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
// 分配一个唯一的临时目录(通常在 /data/app/vmdl<id>.tmp/)
final File tempDir =
mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);
codeFile = tempDir;
resourceFile = tempDir;
} catch (IOException e) {
Slog.w(TAG, "Failed to create copy file: " + e);
return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
}
// 复制 apk 文件
int ret = PackageManagerServiceUtils.copyPackage(
origin.file.getAbsolutePath(), codeFile);
if (ret != PackageManager.INSTALL_SUCCEEDED) {
Slog.e(TAG, "Failed to copy package");
return ret;
}
final boolean isIncremental = isIncrementalPath(codeFile.getAbsolutePath());
final File libraryRoot = new File(codeFile, LIB_DIR_NAME);
NativeLibraryHelper.Handle handle = null;
try {
handle = NativeLibraryHelper.Handle.create(codeFile);
// 复制 .so 文件
ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
abiOverride, isIncremental);
} catch (IOException e) {
Slog.e(TAG, "Copying native libraries failed", e);
ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
} finally {
IoUtils.closeQuietly(handle);
}
return ret;
}
}
这里先创建了临时目录,比如 /data/app/vmd118312388.tmp,其中 118312388 是安装的 sessionId,然后使用 PackageManagerServiceUtils.copyPackage() 将 apk 复制到临时存储目录中,比如 /data/app/vmd118312388.tmp/base.apk。接着是 .so 文件的拷贝。也就是说,把发送到 Session 暂存目录 data/app-staging 的 apk 拷贝到了 /data/app 目录。
到这里,apk 的复制工作就完成了,接着就是 apk 的安装过程了。
PMS 处理 apk 的安装
接着看看执行安装的方法 processPendingInstall():
java
private void processPendingInstall(final InstallArgs args, final int currentStatus) {
...
// 创建 res 用于存放安装结果
PackageInstalledInfo res = createPackageInstalledInfo(currentStatus);
processInstallRequestsAsync(
res.returnCode == PackageManager.INSTALL_SUCCEEDED,
Collections.singletonList(new InstallRequest(args, res)));
...
}
// 将安装操作入队,因为安装需要一点时间
private void processInstallRequestsAsync(boolean success,
List<InstallRequest> installRequests) {
mHandler.post(() -> {
if (success) {
for (InstallRequest request : installRequests) {
// 为安装做准备
request.args.doPreInstall(request.installResult.returnCode);
}
synchronized (mInstallLock) {
// 核心安装
installPackagesTracedLI(installRequests);
}
for (InstallRequest request : installRequests) {
// 安装后的清理与回调
request.args.doPostInstall(
request.installResult.returnCode, request.installResult.uid);
}
}
for (InstallRequest request : installRequests) {
// 通用收尾:恢复状态并发送最终结果(无论成功/失败)
restoreAndPostInstall(request.args.user.getIdentifier(), request.installResult,
new PostInstallData(request.args, request.installResult, null));
}
});
}
这里我们重点看安装过程,installPackagesTracedLI() (LI 表示 "Locked Install" ------ 必须在 mInstallLock 保护下调用)又走到 installPackagesLI():
java
private void installPackagesLI(List<InstallRequest> requests) {
for (InstallRequest request : requests) {
// 1.准备:分析当前安装状态,解析包并初始验证
final PrepareResult prepareResult = preparePackageLI(request.args, request.installResult);
// 2.扫描:根据准备阶段解析的包信息上下文 进一步解析
final ScanResult result = scanPackageTracedLI(
prepareResult.packageToScan, prepareResult.parseFlags,
prepareResult.scanFlags, System.currentTimeMillis(),
request.args.user, request.args.abiOverride);
...
createdAppId.put(packageName, optimisticallyRegisterAppId(result));
// 保存 version 信息
versionInfos.put(result.pkgSetting.pkg.getPackageName(),
getSettingsVersionForPackage(result.pkgSetting.pkg));
...
}
ReconcileRequest reconcileRequest = new ReconcileRequest(preparedScans, installArgs,
installResults,
prepareResults,
mSharedLibraries,
Collections.unmodifiableMap(mPackages), versionInfos,
lastStaticSharedLibSettings);
CommitRequest commitRequest = null;
synchronized (mLock) {
// 3.核对:验证扫描后的包信息和系统状态,确保安装成功
Map<String, ReconciledPackage> reconciledPackages = reconcilePackagesLocked(
reconcileRequest, mSettings.mKeySetManagerService);
...
// 4.提交:提交扫描的包、更新系统状态。
// 这是唯一可以修改系统状态的地方,并且要对所有可预测的错误进行检测。
commitRequest = new CommitRequest(reconciledPackages,
mUserManager.getUserIds());
commitPackagesLocked(commitRequest);
success = true;
}
// 安装成功的后续才做:准备app数据、编译布局资源、执行dex优化
executePostCommitSteps(commitRequest);
}
...
}
里面分成了4个阶段:
- 准备,分析当前安装状态,解析包并初始校验:在 preparePackageLI() 内使用 PackageParser2.parsePackage() 解析AndroidManifest.xml,获取四大组件等信息;使用ParsingPackageUtils.getSigningDetails() 解析签名信息;重命名包最终路径等。 2. 扫描,根据准备阶段解析的包信息上下文进一步解析:确认包名真实;根据解析出的信息校验包有效性(是否有签名信息等);搜集apk信息------PackageSetting、apk的静态库/动态库信息等。 3. 核对,验证扫描后的包信息,确保安装成功:主要就是覆盖安装的签名匹配验证。 4. 提交,提交扫描的包、更新系统状态:添加 PackageSetting 到 PMS 的 mSettings、添加 AndroidPackage 到 PMS 的 mPackages 、添加 秘钥集 到系统、应用的权限添加到 mPermissionManager、四大组件信息添加到 mComponentResolver 。这是唯一可以修改系统状态的地方,并且要对所有可预测的错误进行检测。
前 3 步主要是 解析和校验,第 4 步是把 包信息 提交到 PMS 内存数据结构中。其中解析和提交在上面的 PMS 初始化中 扫描apk目录后 也是同样的过程。 这里就不再展开跟踪了。
安装完成后,调用了 executePostCommitSteps() 准备app数据、执行dex优化:
java
private void executePostCommitSteps(CommitRequest commitRequest) {
for (ReconciledPackage reconciledPkg : commitRequest.reconciledPackages.values()) {
final boolean instantApp = ((reconciledPkg.scanResult.request.scanFlags
& PackageManagerService.SCAN_AS_INSTANT_APP) != 0);
final AndroidPackage pkg = reconciledPkg.pkgSetting.pkg;
final String packageName = pkg.getPackageName();
...
// 提供目录结构/data/user/用户ID/包名/cache(/data/user/用户ID/包名/code_cache)
prepareAppDataAfterInstallLIF(pkg);
...
// 检查 app 是否要 dexopt
final boolean performDexopt =
(!instantApp || Global.getInt(mContext.getContentResolver(),
Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
&& !pkg.isDebuggable()
&& (!onIncremental);
if (performDexopt) {
...
DexoptOptions dexoptOptions = new DexoptOptions(packageName,
REASON_INSTALL,
flags);
...
// 执行 dex 优化:dexopt 是对 dex 文件 进行 verification 和 optimization
// 的操作,其对 dex 文件的优化结果变成了 odex 文件,这个文件和 dex 文件很像,
// 只是使用了一些优化操作码(譬如优化调用虚拟指令等)。
mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
null /* instructionSets */,
getOrCreateCompilerPackageStats(pkg),
mDexManager.getPackageUseInfoOrDefault(packageName),
dexoptOptions);
}
}
}
里面提供目录结构/data/user/用户ID/包名/cache,然后对 dex 进行了优化。这两个操作最终都是使用 Installer 来完成的。就是前面介绍 PMS 创建时传入了 Installer 的实例,而 Installer 继承自 SystemService 也是一个系统服务:
java
public class Installer extends SystemService {
public void onStart() {
if (mIsolated) {
mInstalld = null;
} else {
connect();
}
}
private void connect() {
IBinder binder = ServiceManager.getService("installd");
if (binder != null) {
try {
binder.linkToDeath(new DeathRecipient() {
@Override
public void binderDied() {
Slog.w(TAG, "installd died; reconnecting");
connect();
}
}, 0);
} catch (RemoteException e) {
binder = null;
}
}
if (binder != null) {
mInstalld = IInstalld.Stub.asInterface(binder);
try {
invalidateMounts();
} catch (InstallerException ignored) {
}
} else {
Slog.w(TAG, "installd not found; trying again");
BackgroundThread.getHandler().postDelayed(() -> {
connect();
}, DateUtils.SECOND_IN_MILLIS);
}
}
public long createAppData(String uuid, String packageName, int userId, int flags, int appId,
String seInfo, int targetSdkVersion) throws InstallerException {
if (!checkBeforeRemote()) return -1;
try {
return mInstalld.createAppData(uuid, packageName, userId, flags, appId, seInfo,
targetSdkVersion);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet, int dexoptNeeded, @Nullable String outputPath ...) throws InstallerException {
...
if (!checkBeforeRemote()) return;
try {
mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath,
dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo, downgrade,
targetSdkVersion, profileName, dexMetadataPath, compilationReason);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
}
- 在 Installer 的 onStart() 方法中 通过 installd 拿到了对应的 IBinder 对象,获取到了 mInstalld 实例。可见这里是IPC操作,即从 SystemServer 进程中的 Installer IPC 到 init 进程。 像目录 /data/user 的创建 必须要有root权限。
- PMS 中使用了 Installer 的很多方法,Installer 是 Java 层提供的 Java API 接口,Installd 则是在 init 进程启动的具有 root 权限的 Daemon 进程。
到这里安装完成,我们回到 PMS 的 processInstallRequestsAsync(),这里最后调用restoreAndPostInstall() 进行 备份、可能的回滚、发送安装完成相关的广播:
java
private void restoreAndPostInstall(
int userId, PackageInstalledInfo res, @Nullable PostInstallData data) {
...
Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
mHandler.sendMessage(msg);
...
}
mHandler 收到消息后调用 handlePackagePostInstall() 方法进行了处理:
java
private void handlePackagePostInstall(PackageInstalledInfo res, boolean grantPermissions,
boolean killApp, boolean virtualPreload,
String[] grantedPermissions, List<String> whitelistedRestrictedPermissions,
int autoRevokePermissionsMode,
boolean launchedForRestore, String installerPackage,
IPackageInstallObserver2 installObserver, int dataLoaderType) {
...
if (res.pkg.getStaticSharedLibName() == null) {
...
// 发送 Intent.ACTION_PACKAGE_ADDED 广播,
// 桌面 Launcher 收到广播后就会在桌上增加 App 的icon
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
extras, 0 /*flags*/,
null /*targetPackage*/, null /*finishedReceiver*/,
updateUserIds, instantUserIds, newBroadcastWhitelist);
...
// 发送 Intent.ACTION_PACKAGE_REPLACED 广播
if (update) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
packageName, extras, 0 /*flags*/,
null /*targetPackage*/, null /*finishedReceiver*/,
updateUserIds, instantUserIds, res.removedInfo.broadcastWhitelist);
}
...
}
...
// 发送安装成功的通知
notifyInstallObserver(res, installObserver);
}
- 根据安装结果发送 Intent.ACTION_PACKAGE_ADDED 等广播,桌面 Launcher 收到广播后就会在桌上增加 App 的 icon。
- 调用 PackageInstallSession 中保存的 IPackageInstallObserver2 实例的 onPackageInstalled() 方法,最后发送安装成功的通知显示在通知栏,通过 IntentSender 发送 在 InstallInstalling 中就定义好的广播,最后 InstallInstalling 页面根据结果展示 安装成功/安装失败 的提示 。
到这里,apk 的安装过程 就梳理完毕了,整个过程如下:
- 把 apk 写入 Session,把安装操作提交给 PMS;
- PMS 把 apk 拷贝到 /data/app,然后使用 PackageParser2 解析 apk 获取 四大组件、签名、PackageSetting 等信息,并进行校验确保安装成功;
- 接着提交信息包、更新系统状态、更新 PMS 的内存数据;
- 准备用户目录/data/user、执行 dex 优化;
- 最后发送安装结果通知UI层;