Android App Install源码解析

我们平时会经常使用手机上的各种丰富的APP,有没有想过一个应用是如何安装到Android设备上的,安装的过程中发生了哪些事情。本文从源码角度分析记录一下Android设备上应用安装过程。

一. APP常见的安装方式有哪些

1.从应用商店下载一个apk,直接点击安装,此时会跳转到一个安装提示页面。这是最常见的安装方式了。

2.通过adb 命令安装。

3.往system/app或者system/priv-app 目录下push一个apk。

、、、

二. 应用安装流程

2.1 安装申请

当我们某个应用要安装一个APP或者点击一个apk文件的时候通常是以下面的方式去申请安装一个apk。

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

/*
* 自Android N开始,是通过FileProvider共享相关文件,但是Android Q对公
* 有目录 File API进行了限制,只能通过Uri来操作
*/
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
    // filePath是通过ContentResolver得到的
    intent.setDataAndType(Uri.parse(filePath) ,"application/vnd.android.package-archive");
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    Uri contentUri = FileProvider.getUriForFile(mContext, "com.dhl.file.fileProvider", file);
    intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
} else {
    intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
}
startActivity(intent);

// 需要在AndroidManifest添加权限
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> 

可以看到其实就是将要安装的apk参数放到intent内,并且打开了一个activity。这个activity其实是系统一个内置应用PackageInstaller里的一个页面。

<activity android:name=".InstallStart"
                android:theme="@android:style/Theme.Translucent.NoTitleBar"
            android:launchMode="singleTask"
                android:exported="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>

2.2 InstallStart 页面

主要工作:

1.判断是否勾选“未知来源”选项,若未勾选跳转到设置安装未知来源界面

2.对于大于等于 Android 8.0 版本,会先检查是否申请安装权限,若没有则中断安装

3.判断 Uri 的 Scheme 协议,若是 content 则调用 InstallStaging, 若是 package 则调用 PackageInstallerActivity

[InstallStart.java]

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

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

    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);
        // 如果targetSdkVerison小于0中止安装
        if (targetSdkVersion < 0) {
            Log.w(LOG_TAG, "Cannot get target sdk version for uid " + originatingUid);
            mAbortInstall = true;

            // 如果targetSdkVersion大于等于26(8.0), 且获取不到REQUEST_INSTALL_PACKAGES权限中止安装
        } else if (targetSdkVersion >= Build.VERSION_CODES.O && !declaresAppOpPermission(
                originatingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
            Log.e(LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
                    + Manifest.permission.REQUEST_INSTALL_PACKAGES);
            mAbortInstall = true;
        }
    }
    ...
    // 如果设置了ACTION_CONFIRM_PERMISSIONS,则调用PackageInstallerActivity。
    if (isSessionInstall) {
        nextActivity.setClass(this, PackageInstallerActivity.class);
    } else {
        Uri packageUri = intent.getData();
        // 判断Uri的Scheme协议是否是content
        if (packageUri != null && packageUri.getScheme().equals(
                ContentResolver.SCHEME_CONTENT)) {
            // [IMPORTANT] This path is deprecated, but should still work.
            // 这个路径已经被起用了,但是仍然可以工作

            // 调用InstallStaging来拷贝file/content,防止被修改
            nextActivity.setClass(this, InstallStaging.class);
        } else if (packageUri != null && packageUri.getScheme().equals(
                PackageInstallerActivity.SCHEME_PACKAGE)) {
            // 如果Uri中包含package,则调用PackageInstallerActivity
            nextActivity.setClass(this, PackageInstallerActivity.class);
        } else {
            // Uri不合法
            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();
}

根据 Uri 的 Scheme 协议,若是 content 则调用 InstallStaging,如果scheme是 PackageInstallerActivity.SCHEME_PACKAGE就打开PackageInstallerActivity。先看下InstallStaging。

[InstallStaging.java]

@Override
protected void onResume() {
    super.onResume();
    if (mStagingTask == null) {
        if (mStagedFile == null) {
            // 创建临时文件 mStagedFile 用来存储数据
            try {
                mStagedFile = TemporaryFileManager.getStagedFile(this);
            } catch (IOException e) {
                showError();
                return;
            }
        }
        // 启动 StagingAsyncTask,并传入了content协议的Uri
        mStagingTask = new StagingAsyncTask();
        mStagingTask.execute(getIntent().getData());
    }
}

1.创建临时文件 mStagedFile 用来存储数据

2.启动 StagingAsyncTask,并传入了 content 协议的 Uri

[InstallStaging.java]

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)) {
            ...
            // 将packageUri(content协议的Uri)的内容写入到mStagedFile中
            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
            Intent installIntent = new Intent(getIntent());
            installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class);
            installIntent.setData(Uri.fromFile(mStagedFile));
            ...
            startActivity(installIntent);
            InstallStaging.this.finish();
        } else {
            showError();
        }
    }
}

doInBackground 方法中将 packageUri(content 协议的 Uri)的内容写入到 mStagedFile 中。如果写入成功,调用 DeleteStagedFileOnResult 的 OnCreate 方法。DeleteStagedFileOnResult 的OnCreate 又打开了PackageInstallerActivity页面。

2.3 PackageInstallerActivity

主要工作:

1.显示安装界面

2.初始化安装需要用的各种对象,比如 PackageManager、IPackageManager、AppOpsManager、UserManager、PackageInstaller 等等

3.根据传递过来的 Scheme 协议做不同的处理

4.检查是否允许、初始化安装

5.在准备安装的之前,检查应用列表判断该应用是否已安装,若已安装则提示该应用已安装,由用户决定是否替换

6.在安装界面,提取出 APK 中权限信息并展示出来

7.点击 OK 按钮确认安装后,会调用 startInstall 开始安装工作
PackageInstallerActivity 才是应用安装器 PackageInstaller 真正的入口 Activity。
[PackageInstallerActivity .java]

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

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;
        、、、
    }
    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 方法

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

检查条件都通过后会初始化安装提示页面,当点击确认按钮后会通过点击事件的回调打开InstallInstalling页面开始安装。

2.4 InstallInstalling

[InstallInstalling.java]

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

    // 判断安装的应用是否已经存在
    if ("package".equals(mPackageURI.getScheme())) {
        try {
            getPackageManager().installExistingPackage(appInfo.packageName);
            launchSuccess();
        } catch (PackageManager.NameNotFoundException e) {
            launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
        }
    } else {
        final File sourceFile = new File(mPackageURI.getPath());
        PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
      、、、
        // 注册InstallEventReceiver,并在launchFinishBasedOnResult会接收到安装事件的回调
        mInstallId = InstallEventReceiver
                 .addObserver(this, EventResultPersister.GENERATE_NEW_ID,this::launchFinishBasedOnResult);
      、、、
}
  
 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();
        } else {
            // 安装完成后会收到广播
            mCancelButton.setEnabled(false);
            setFinishOnTouchOutside(false);
        }
    }
}

InstallingAsyncTask

 /**
     * Send the package to the package installer and then register a event result observer that
     * will call {@link #launchFinishBasedOnResult(int, int, String)}
     */
    private final class InstallingAsyncTask extends AsyncTask<Void, Void,
            PackageInstaller.Session> {
        volatile boolean isDone;

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

            session.setStagingProgress(0);

            try {
                File file = new File(mPackageURI.getPath());
                //打开一个流以将 APK 文件写入会话。
                try (InputStream in = new FileInputStream(file)) {
                    long sizeBytes = file.length();
                    try (OutputStream out = session
                            .openWrite("PackageInstaller", 0, sizeBytes)) {
                        byte[] buffer = new byte[1024 * 1024];
                        while (true) {
                            int numRead = in.read(buffer);

                            if (numRead == -1) {
                                session.fsync(out);
                                break;
                            }

                            if (isCancelled()) {
                                session.close();
                                break;
                            }
                            //将 APK 文件通过 IO 流的形式写入到 PackageInstaller.Session 中
                            out.write(buffer, 0, numRead);
                            if (sizeBytes > 0) {
                                float fraction = ((float) numRead / (float) sizeBytes);
                                session.addProgress(fraction);
                            }
                        }
                    }
                }

                return session;
            } catch (IOException | SecurityException e) {
                Log.e(LOG_TAG, "Could not write package", e);

                session.close();

                return null;
            } finally {
                synchronized (this) {
                    isDone = true;
                    notifyAll();
                }
            }
        }

        @Override
        protected void onPostExecute(PackageInstaller.Session session) {
            if (session != null) {
                、、、
                PendingIntent pendingIntent = PendingIntent.getBroadcast(
                        InstallInstalling.this,
                        mInstallId,
                        broadcastIntent,
                        PendingIntent.FLAG_UPDATE_CURRENT);
                //提交会话结果
                session.commit(pendingIntent.getIntentSender());
                、、、
            } else {
                getPackageManager().getPackageInstaller().abandonSession(mSessionId);

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

2.5 session.commit

[PackageInstaller.java]

/**
         * Attempt to commit everything staged in this session. This may require
         * user intervention, and so it may not happen immediately. The final
         * result of the commit will be reported through the given callback.
         * <p>
         * Once this method is called, the session is sealed and no additional
         * mutations may be performed on the session. If the device reboots
         * before the session has been finalized, you may commit the session again.
         * <p>
         * If the installer is the device owner or the affiliated profile owner, there will be no
         * user intervention.
         *
         * @param statusReceiver Called when the state of the session changes. Intents
         *                       sent to this receiver contain {@link #EXTRA_STATUS}. Refer to the
         *                       individual status codes on how to handle them.
         *
         * @throws SecurityException if streams opened through
         *             {@link #openWrite(String, long, long)} are still open.
         *
         * @see android.app.admin.DevicePolicyManager
         */
        public void commit(@NonNull IntentSender statusReceiver) {
            try {
                mSession.commit(statusReceiver, false);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

mSession 就是 IPackageInstallerSession 。这说明要通过 IPackageInstallerSession 来进行进程间的通信,最终会调用PackageInstallerSession 的 commit 方法

[PackageInstallerSession.java]

public class PackageInstallerSession extends IPackageInstallerSession.Stub {

    private final Handler.Callback mHandlerCallback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_COMMIT:
                    handleCommit();
                    break;
                、、、
            }
            return true;
        }
    };

	@Override
    public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
         、、、
        mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
    }

    private void handleCommit() {
        、、、
        // For a multiPackage session, read the child sessions
        // outside of the lock, because reading the child
        // sessions with the lock held could lead to deadlock
        // (b/123391593).
        List<PackageInstallerSession> childSessions = getChildSessions();

        try {
            synchronized (mLock) {
                commitNonStagedLocked(childSessions);
            }
        } catch (PackageManagerException e) {
            final String completeMsg = ExceptionUtils.getCompleteMessage(e);
            Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
            destroyInternal();
            dispatchSessionFinished(e.error, completeMsg, null);
        }
    }

    @GuardedBy("mLock")
    private void commitNonStagedLocked(List<PackageInstallerSession> childSessions)
            throws PackageManagerException {
        final PackageManagerService.ActiveInstallSession committingSession =
                makeSessionActiveLocked();
        if (committingSession == null) {
            return;
        }
        if (isMultiPackage()) {
            List<PackageManagerService.ActiveInstallSession> activeChildSessions =
                    new ArrayList<>(childSessions.size());
            boolean success = true;
            、、、
            if (!success) {
                try {
                    mRemoteObserver.onPackageInstalled(
                            null, failure.error, failure.getLocalizedMessage(), null);
                } catch (RemoteException ignored) {
                }
                return;
            }
            //mPm是PackageManagerService
            mPm.installStage(activeChildSessions);
        } else {
            //mPm是PackageManagerService
            mPm.installStage(committingSession);
        }
    }

}

此时InstallInstalling就通过session将应用的安装信息发送给了PackageManagerService,前面页面也注册了一些安装过程的回调监听,当PackageManagerService安装完成后会回调InstallEventReceiver,安装成功和失败,都会启动一个新的 Activity(InstallSuccess、InstallFailed)将结果展示给用户,然后 finish 掉 InstallInstalling。

三 push&adb 安装

当我们Push一个应用到system/app下的时候,此时并没有出发Install相关的页面打开,此时应用又是怎么安装的。我们知道当系统启动的时候会启动一系类的服务,其中包括PackageManagerService。

[PackageManagerService.java]

    public static PackageManagerService main(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
        // Self-check for initial settings.
        PackageManagerServiceCompilerMapping.checkProperties();

        PackageManagerService m = new PackageManagerService(context, installer,
                factoryTest, onlyCore);
        m.enableSystemUserPackages();
        ServiceManager.addService("package", m);
        final PackageManagerNative pmn = m.new PackageManagerNative();
        ServiceManager.addService("package_native", pmn);
        return m;
    }

public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
            、、、
            // Collect privileged system packages.
            final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
            scanDirTracedLI(privilegedAppDir,
                    mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM_DIR,
                    scanFlags
                    | SCAN_AS_SYSTEM
                    | SCAN_AS_PRIVILEGED,
                    0);

            // Collect ordinary system packages.
            final File systemAppDir = new File(Environment.getRootDirectory(), "app");
            scanDirTracedLI(systemAppDir,
                    mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM_DIR,
                    scanFlags
                    | SCAN_AS_SYSTEM,
                    0);
            、、、
}


PMS 会去扫描各个系统目录并最终走到PMS的核心流程installPackagesLI。其实adb install的方式也是如此,只不过出发方式不同。

四 PMS

在2.5讲到InstallInstalling 页面通过一个session将安装信息发通过installStage方法送给了PMS。

4.1 PMS->installStage

void installStage(ActiveInstallSession activeInstallSession) {
    、、、
    final Message msg = mHandler.obtainMessage(INIT_COPY);
    final InstallParams params = new InstallParams(activeInstallSession);
    params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params));
    msg.obj = params;
    mHandler.sendMessage(msg);
}

void doHandleMessage(Message msg) {
    switch (msg.what) {
        case INIT_COPY: {
            HandlerParams params = (HandlerParams) msg.obj;
            if (params != null) {
                、、、
                params.startCopy();
            }
            break;
            、、、
        }
    、、、
}

final void startCopy() {
    if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);
    //拷贝apk
    handleStartCopy();
    handleReturnCode();
}


@Override
void handleReturnCode() {
    if (mVerificationCompleted && mEnableRollbackCompleted) {
        、、、
        processPendingInstall(mArgs, mRet);
    }
}

installStage通过handler发送了一个INIT_COPY消息,将apk拷贝到指定的临时目录方便后续处理。processPendingInstall->processInstallRequestsAsync->installPackagesTracedLI->installPackagesLI.

我们发现最终都是进入了PMS的核心流程installPackagesLI。

4.2 installPackagesLI

[PackageManagerService.java]

/**
 * Installs one or more packages atomically. This operation is broken up into four phases:
 * <ul>
 *     <li><b>Prepare</b>
 *         <br/>Analyzes any current install state, parses the package and does initial
 *         validation on it.</li>
 *     <li><b>Scan</b>
 *         <br/>Interrogates the parsed packages given the context collected in prepare.</li>
 *     <li><b>Reconcile</b>
 *         <br/>Validates scanned packages in the context of each other and the current system
 *         state to ensure that the install will be successful.
 *     <li><b>Commit</b>
 *         <br/>Commits all scanned packages and updates system state. This is the only place
 *         that system state may be modified in the install flow and all predictable errors
 *         must be determined before this phase.</li>
 * </ul>
 *
 * Failure at any phase will result in a full failure to install all packages.
 */
@GuardedBy("mInstallLock")
private void installPackagesLI(List<InstallRequest> requests) {
    ...
    // Prepare 准备:分析任何当前安装状态,分析包并对其进行初始验证.检查SDK版本、静态库等;检查签名;设置权限;
    prepareResult = preparePackageLI(request.args, request.installResult);
    ...
    //Scan 扫描:prepare中收集的上下文,询问已分析的包。
    final List<ScanResult> scanResults = scanPackageTracedLI(
                            prepareResult.packageToScan, prepareResult.parseFlags,
                            prepareResult.scanFlags, System.currentTimeMillis(),
                            request.args.user);
    ...
    // Reconcile 调和:在彼此的上下文和当前系统状态中验证扫描的包,以确保安装成功。
    ReconcileRequest reconcileRequest = new ReconcileRequest(preparedScans, installArgs,
            installResults,
            prepareResults,
            mSharedLibraries,
            Collections.unmodifiableMap(mPackages), versionInfos,
            lastStaticSharedLibSettings);
    ...
    // Commit 提交:提交所有扫描的包并更新系统状态。这是安装流中唯一可以修改系统状态的地方,必须在此阶段之前确定所有可预测的错误。
    commitPackagesLocked(commitRequest);
    ...
    // 完成APK的安装
    executePostCommitSteps(commitRequest);
}




    /**
     * On successful install, executes remaining steps after commit completes and the package lock
     * is released. These are typically more expensive or require calls to installd, which often
     * locks on {@link #mPackages}.
     */
    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 PackageParser.Package pkg = reconciledPkg.pkgSetting.pkg;
            final String packageName = pkg.packageName;
            prepareAppDataAfterInstallLIF(pkg);
            if (reconciledPkg.prepareResult.clearCodeCache) {
                clearAppDataLIF(pkg, UserHandle.USER_ALL, FLAG_STORAGE_DE | FLAG_STORAGE_CE
                        | FLAG_STORAGE_EXTERNAL | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
            }
            if (reconciledPkg.prepareResult.replace) {
                mDexManager.notifyPackageUpdated(pkg.packageName,
                        pkg.baseCodePath, pkg.splitCodePaths);
            }

            // Prepare the application profiles for the new code paths.
            // This needs to be done before invoking dexopt so that any install-time profile
            // can be used for optimizations.
            mArtManagerService.prepareAppProfiles(
                    pkg,
                    resolveUserIds(reconciledPkg.installArgs.user.getIdentifier()),
                    /* updateReferenceProfileContent= */ true);

            // Check whether we need to dexopt the app.
            //
            // NOTE: it is IMPORTANT to call dexopt:
            //   - after doRename which will sync the package data from PackageParser.Package and
            //     its corresponding ApplicationInfo.
            //   - after installNewPackageLIF or replacePackageLIF which will update result with the
            //     uid of the application (pkg.applicationInfo.uid).
            //     This update happens in place!
            //
            // We only need to dexopt if the package meets ALL of the following conditions:
            //   1) it is not an instant app or if it is then dexopt is enabled via gservices.
            //   2) it is not debuggable.
            //
            // Note that we do not dexopt instant apps by default. dexopt can take some time to
            // complete, so we skip this step during installation. Instead, we'll take extra time
            // the first time the instant app starts. It's preferred to do it this way to provide
            // continuous progress to the useur instead of mysteriously blocking somewhere in the
            // middle of running an instant app. The default behaviour can be overridden
            // via gservices.
            final boolean performDexopt =
                    (!instantApp || Global.getInt(mContext.getContentResolver(),
                    Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
                    && ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0);

            if (performDexopt) {
              、、、
                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
                // Do not run PackageDexOptimizer through the local performDexOpt
                // method because `pkg` may not be in `mPackages` yet.
                //
                // Also, don't fail application installs if the dexopt step fails.
                DexoptOptions dexoptOptions = new DexoptOptions(packageName,
                        REASON_INSTALL,
                        DexoptOptions.DEXOPT_BOOT_COMPLETE
                                | DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE);
                mPackageDexOptimizer.performDexOpt(pkg,
                        null /* instructionSets */,
                        getOrCreateCompilerPackageStats(pkg),
                        mDexManager.getPackageUseInfoOrDefault(packageName),
                        dexoptOptions);
                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
            }

            // Notify BackgroundDexOptService that the package has been changed.
            // If this is an update of a package which used to fail to compile,
            // BackgroundDexOptService will remove it from its blacklist.
            // TODO: Layering violation
            BackgroundDexOptService.notifyPackageChanged(packageName);
        }
    }

installPackagesLI()方法内部非常复杂,上面简单调了一些主流程拿出来。

1.Prepare准备:分析任何当前安装状态,分析包并对其进行初始验证。

在这一阶段首先是将apk文件解析出来,解析它的AndroidManifest.xml文件,将结果记录起来。我们平时在清单文件中声明的Activity等组件就是在这一步被记录到Framework中的,后续才能通过startActivity等方式启动来

然后是对签名信息进行验证

设置相关权限

生成安装包abi

dex优化,实际为dex2oat操作,将apk中的dex文件转换为oat文件

2.Scan扫描:考虑到prepare中收集的上下文,询问已分析的包

3.Reconcile调和:在彼此的上下文和当前系统状态中验证扫描的包,以确保安装成功

4.Commit提交:提交所有扫描的包并更新系统状态。这是安装流中唯一可以修改系统状态的地方,必须在此阶段之前确定所有可预测的错误

五 Settings

这里是安装的最后一步将安装信息更新到系统的/data/system/packages.xml文件中。

PMS的构造方法中初始化了一个Settings对象的实例,我们看一下Settings构造方法。
[Setting.java]

Settings(File dataDir, PermissionSettings permission,
    Object lock) {
    mLock = lock;
    mPermissions = permission;
    mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);
    
    mSystemDir = new File(dataDir, "system");
    mSystemDir.mkdirs();
    FileUtils.setPermissions(mSystemDir.toString(),
        FileUtils.S_IRWXU|FileUtils.S_IRWXG
        |FileUtils.S_IROTH|FileUtils.S_IXOTH,
        -1, -1);
    // data/system  目录下的packages.xml
    mSettingsFilename = new File(mSystemDir, "packages.xml");
    //packages.xml 一个备份文件,防止系统配置信息写入出错的。一旦写入失败,可以从这个文件恢复
    mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
    mPackageListFilename = new File(mSystemDir, "packages.list");
    FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);
    
    final File kernelDir = new File("/config/sdcardfs");
    mKernelMappingFilename = kernelDir.exists() ? kernelDir : null;
    
    // Deprecated: Needed for migration
    mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
    mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
}

Settings对象的构造过程很简单,就是创建一些目录和文件。首先创建/data/system/目录,然后分别创建以下几个文件:

/data/system/packages.xml

/data/system/packages-backup.xml

/data/system/packages.list

/data/system/packages-stopped.xml

/data/system/packages-stopped-backup.xml

Settings通过addSharedUserLPw函数添加向mSharedUsers预先添加SharedUserSetting对象

那应用的安装信息是如何写入到packages.xml文件的呢?在前面的流程中已经解析收集到了安装包的所有信息,并且保存到了Settings对象实例中,4.2中 PMS.commitPackagesLocked->PMS.updateSettingsLI->PMS.updateSettingsInternalLI->Settings.writeLPr写入到系统文件。

[Settings.java]


void writeLPr() {
        // Keep the old settings around until we know the new ones have
        // been successfully written.
        if (mSettingsFilename.exists()) {
            //如果备份文件不存在,就将当前系统文件重命名为packages-backup.xml。将新的信息写入到packages.xml
            //如果备份文件存在,就删除packages.xml,后面重新写入
            if (!mBackupSettingsFilename.exists()) {
                if (!mSettingsFilename.renameTo(mBackupSettingsFilename)) {
                    Slog.wtf(PackageManagerService.TAG,
                            "Unable to backup package manager settings, "
                            + " current changes will be lost at reboot");
                    return;
                }
            } else {
                mSettingsFilename.delete();
                Slog.w(PackageManagerService.TAG, "Preserving older settings backup");
            }
        }
        mPastSignatures.clear();
        try {
            FileOutputStream fstr = new FileOutputStream(mSettingsFilename);
            BufferedOutputStream str = new BufferedOutputStream(fstr);

            //XmlSerializer serializer = XmlUtils.serializerInstance();
            XmlSerializer serializer = new FastXmlSerializer();
            serializer.setOutput(str, StandardCharsets.UTF_8.name());
            serializer.startDocument(null, true);
            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);

            serializer.startTag(null, "packages");

            、、、
            serializer.startTag(null, "permission-trees");
            mPermissions.writePermissionTrees(serializer);
            serializer.endTag(null, "permission-trees");

            serializer.startTag(null, "permissions");
            mPermissions.writePermissions(serializer);
            serializer.endTag(null, "permissions");
            //写入新的值
            for (final PackageSetting pkg : mPackages.values()) {
                writePackageLPr(serializer, pkg);
            }

            for (final PackageSetting pkg : mDisabledSysPackages.values()) {
                writeDisabledSysPackageLPr(serializer, pkg);
            }
            、、、
            serializer.endTag(null, "packages");

            serializer.endDocument();
            //刷新一下
            str.flush();
            FileUtils.sync(fstr);
            str.close();

            // New settings successfully written, old ones are no longer
            // needed.
            mBackupSettingsFilename.delete();
            FileUtils.setPermissions(mSettingsFilename.toString(),
                    FileUtils.S_IRUSR|FileUtils.S_IWUSR
                    |FileUtils.S_IRGRP|FileUtils.S_IWGRP,
                    -1, -1);

            writeKernelMappingLPr();
            writePackageListLPr();
            writeAllUsersPackageRestrictionsLPr();
            writeAllRuntimePermissionsLPr();
            com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
                    "package", SystemClock.uptimeMillis() - startTime);
            return;

        } catch(java.io.IOException e) {
            Slog.wtf(PackageManagerService.TAG, "Unable to write package manager settings, "
                    + "current changes will be lost at reboot", e);
        }
        // Clean up partially written files
        if (mSettingsFilename.exists()) {
            if (!mSettingsFilename.delete()) {
                Slog.wtf(PackageManagerService.TAG, "Failed to clean up mangled file: "
                        + mSettingsFilename);
            }
        }
        //Debug.stopMethodTracing();
    }

六 总结

根据根据 Uri 的 Scheme 找到入口 InstallStart

InstallStart 根据 Uri 的 Scheme 协议不同做不同的处理

都会调用 PackageInstallerActivity, 然后分别对package协议和 file 协议的 Uri 进行处理

PackageInstallerActivity 检查未知安装源限制,如果安装源限制弹出提示 Dialog

点击 OK 按钮确认安装后,会调用 startInstall 开始安装工作

如果用户允许安装,然后跳转到 InstallInstalling,进行 APK 的安装工作

在 InstallInstalling 中,向包管理器发送包的信息,然后注册一个观察者 InstallEventReceiver,并接受安装成功和失败的回调

PMS 拷贝apk,Prepare->Scan->Reconcile->Commit,并把应用的信息写入到packages.xml

参考:https://juejin.cn/post/6844904078015725576

       https://zhuanlan.zhihu.com/p/428859943

本文由mdnice多平台发布

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值