apk的安装过程

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

整体过程如下:

  1. 在 onCreate() 中初始化安装所需的各种对象,比如 PackageManager、IPackageManager、AppOpsManager、PackageInstaller 和 UserManager;
  2. 在 processPackageUri() 方法中会根据 packageUri 的 Scheme 协议分别对 package 协议和 file 协议进行处理,如果不是这两种协议,会抛出异常。这里我们主要看 file 协议的处理过程,使用 PackageUtil.getPackageInfo() 获取 apk 的 PackageInfo,其内部还是调用的 ParsingPackageUtils 的 parsePackage() 方法来进行 apk 的解析工作。使用 PackageUtil.getAppSnippet() 获取 apk 摘要:图标、名字;
  3. 使用 bindUi 初始化了弹框 UI 界面,使用 checkIfAllowedAndInitiateInstall() 检查用户是否被允许安装应用,根据检查结果展示上面图 2 所示的 "未知来源apk安装" (即非来自官方商店的 apk)的对话框,这里点击 "Settings" 按钮后会跳转到设置页,上图 3 所示即为设置页;
  4. 打开设置中的 "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 初始化过程如下:

  1. 在 InstallStart 中根据 Uri 的 Scheme 协议不同,跳转不同的界面,content 协议先跳转到 InstallStaging,package 协议直接跳转到 PackageInstallerActivity。
  2. InstallStaging 从 content://Uri 读取 apk 数据流写入临时文件目录,将 content 协议的 Uri 转换为 file 协议,然后跳转到 DeleteStagedFileOnResult。
  3. DeleteStagedFileOnResult 会启动 PackageInstallerActivity 并在 PackageInstallerActivity 返回结果后(安装完成或取消),再删除临时文件。
  4. 在 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);
            } 
            ...
        }
    }
}

主要工作如下:

  1. 显示安装界面,对应上面第 5 张截图。
  2. 在 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
  3. 在 onResume() 中通过 mSessionId 拿到 SessionInfo, SessionInfo 代表安装会话的详细信息,如果 SessionInfo 不是活跃状态,就开启 InstallingAsyncTask。
  4. 在 doInBackground() 中通过 PackageInstaller.Session 将 apk 文件内容写入到安装会话中
  5. 在 onPostExecute() 中创建 PendingIntent 作为安装完成后的回调,调用 PackageInstaller.Session 的 commit() 触发实际的安装过程,安装结果会通过广播(BROADCAST_ACTION)返回。
  6. 安装结束后,会回调 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个阶段:

  1. 准备,分析当前安装状态,解析包并初始校验:在 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 的安装过程 就梳理完毕了,整个过程如下:

  1. 把 apk 写入 Session,把安装操作提交给 PMS;
  2. PMS 把 apk 拷贝到 /data/app,然后使用 PackageParser2 解析 apk 获取 四大组件、签名、PackageSetting 等信息,并进行校验确保安装成功;
  3. 接着提交信息包、更新系统状态、更新 PMS 的内存数据;
  4. 准备用户目录/data/user、执行 dex 优化;
  5. 最后发送安装结果通知UI层;
相关推荐
源码宝2 天前
无需从零开发:医院智慧随访系统完整源码,一键快速部署
java·开源·源码·随访·随访系统源码·医院随访
千寻技术帮3 天前
10352_基于Springboot的房屋销售平台
java·spring boot·mysql·vue·源码·代码·远程
工业互联网专业3 天前
基于协同过滤算法的招聘信息推荐系统 _django+spider
python·django·毕业设计·源码·课程设计·协同过滤·spider
luoluoal4 天前
基于python的医疗问句中的实体识别算法的研究(源码+文档)
python·mysql·django·毕业设计·源码
暮色妖娆丶5 天前
Spring 源码分析 BeanFactoryPostProcessor
spring boot·spring·源码
万岳科技程序员小金5 天前
多商户商城系统源码 + APP/小程序开发:技术架构与应用解
程序员·开源·源码·多商户商城系统源码·多商户商城小程序·多商户商城app开发·多商户商城平台开发
冬奇Lab5 天前
Android 15 ServiceManager与Binder服务注册深度解析
android·源码·源码阅读
千寻技术帮5 天前
10327_基于SpringBoot的视频剪辑咨询网站
mysql·源码·springboot·代码·视频咨询