Android13-包安装器PackageInstaller-之apk安装流程

目的

  • 我们最终是为了搞明白安装的整个流程
  • 通过安卓系统自带的包安装器来了解PMS 安装流程
  • 实现需求定制:静默安装-安装界面定制-安装拦截验证。【核心目的】

安装流程和PMS了解不用多说了; 安装定制相关:

  • 手机上安装时候弹出锁屏界面需要输入密码;
  • 安装时候弹出密码框,让用户输入定制的特殊密码功能;
  • 安装页面客制化需求

安装方式

当然正常的安装分为类型我其实理解为大概3种

  • 无界面安装:PMS启动阶段 比如系统第一次启动,所有内置app自动批量安装;我们重试系统app开发时候,或者内置系统apk开发时候,删除对应的目录下的apk和apk对应的/data/分区下的apk所有安装信息后,push

    更新的apk到系统,重启。 apk 自动重新安装。

  • adb 安装: adb 命令安装,通过adb install 安装,依托守护进程来实现安装

  • 点击安装或者调用方法安装:应用市场再下载完apk后自动进入进入包管理器进行安装;sd开或者外部存储中的安装包点击安装自动进入包管理器进行安装

相关资料推荐;

承接上文,PMS安装apk之界面跳转

PackageInstaller的初始化

PackageInstaller安装APK

PMS处理APK的安装

PMS的创建过程

APK 安装流程

安装过程 界面跳转
Apk的安装过程探究

以实际应用宝安装为导向,看流程



通过界面,通过日志打印,找到对应的应用,包名和类名,然后仔细分析源码。

java 复制代码
 ACTIVITY com.android.packageinstaller/.InstallInstalling 31faba4 pid=3626
 ACTIVITY com.android.packageinstaller/.InstallSuccess 96d0412 pid=3626

其实就是要介绍和研究的 packageinstaller 包。

源码参考

PackageInstallerActivity

弹出安装弹框;根据条件弹出 未知来源 是否打开安装权限弹框

onCreate

初始化安装相关的对象

  • PackageManager mPm;
  • IPackageManager mIpm;
  • AppOpsManager mAppOpsManager;
  • UserManager mUserManager;
  • PackageInstaller mInstaller;

onResume

bindUi()

安装确认弹框

startInstall()

配置intent,跳转到 InstallInstalling 界面跳转

java 复制代码
private void startInstall() {
        // Start subactivity to actually install the application
        Intent newIntent = new Intent();
        newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
                mPkgInfo.applicationInfo);
        newIntent.setData(mPackageURI);
        newIntent.setClass(this, InstallInstalling.class);
        String installerPackageName = getIntent().getStringExtra(
                Intent.EXTRA_INSTALLER_PACKAGE_NAME);
        if (mOriginatingURI != null) {
            newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
        }
        if (mReferrerURI != null) {
            newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
        }
        if (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
            newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
        }
        if (installerPackageName != null) {
            newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
                    installerPackageName);
        }
        if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
            newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
        }
        newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
        if (mLocalLOGV) Log.i(TAG, "downloaded app uri=" + mPackageURI);
        startActivity(newIntent);
        finish();
    }
	
checkIfAllowedAndInitiateInstall
java 复制代码
private void checkIfAllowedAndInitiateInstall() {
        // Check for install apps user restriction first.
        final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource(
                UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle());
        if ((installAppsRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
            if (mLocalLOGV) Log.i(TAG, "install not allowed: " + UserManager.DISALLOW_INSTALL_APPS);
            showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER);
            return;
        } else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
            if (mLocalLOGV) {
                Log.i(TAG, "install not allowed by admin; showing "
                        + Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
            }
            startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
            finish();
            return;
        }

        if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
            if (mLocalLOGV) Log.i(TAG, "install allowed");
            initiateInstall();
        } else {
            // Check for unknown sources restrictions.
            final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource(
                    UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle());
            final int unknownSourcesGlobalRestrictionSource = mUserManager.getUserRestrictionSource(
                    UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, Process.myUserHandle());
            final int systemRestriction = UserManager.RESTRICTION_SOURCE_SYSTEM
                    & (unknownSourcesRestrictionSource | unknownSourcesGlobalRestrictionSource);
            if (systemRestriction != 0) {
                if (mLocalLOGV) Log.i(TAG, "Showing DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER");
                showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
            } else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
                startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
            } else if (unknownSourcesGlobalRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
                startAdminSupportDetailsActivity(
                        UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
            } else {
                handleUnknownSources();
            }
        }
    }

InstallInstalling

看类注释:

java 复制代码
/**
 * Send package to the package manager and handle results from package manager. Once the
 * installation succeeds, start {@link InstallSuccess} or {@link InstallFailed}.
 * <p>This has two phases: First send the data to the package manager, then wait until the package
 * manager processed the result.</p>
 */

从类注释上看,做了3个动作:

  • 将包发送到管理器进行安装,其实就是将安装包发送个系统PMS进行安装
  • 等待安装结果
  • 回调安装结果,成功就跳转到成功界面,失败就跳转到失败界面

下面我们具体看看做了哪些具体工作

注册回调监听 InstallEventReceiver.addObserver

在onCreate 方法中,看addObserver 方法

看源码注释:为安装结果注册监听,可能回调 直到结果分发回调回来

java 复制代码
 Reregister for result; might instantly call back if result was delivered while
 // Reregister for result; might instantly call back if result was delivered while
                // activity was destroyed
                try {
                    InstallEventReceiver.addObserver(this, mInstallId,
                            this::launchFinishBasedOnResult);
                } catch (EventResultPersister.OutOfIdsException e) {
                    // Does not happen
}
安装结果回调 launchFinishBasedOnResult

安装成功和失败的回调

java 复制代码
  /**
     * Launch the appropriate finish activity (success or failed) for the installation result.
     *
     * @param statusCode    The installation result.
     * @param legacyStatus  The installation as used internally in the package manager.
     * @param statusMessage The detailed installation result.
     */
    private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) {
        if (statusCode == PackageInstaller.STATUS_SUCCESS) {
            launchSuccess();
        } else {
            launchFailure(statusCode, legacyStatus, statusMessage);
        }
    }

根据结果跳转到成功或失败的界面 InstallSuccess.class or InstallFailed.class

创建安装的session createSession 拼装 creassion的params

java 复制代码
还是在onCreate 方法中看createSession 操作
mSessionId = getPackageManager().getPackageInstaller().createSession(params) 

理解:createSession

我们很多地方其实都有Session 的概念,从后台开发 服务器-浏览器的角度来说 Session 就是一次会话,在前端也可以这么理解

相机开发中,进行拍照录像 也是一个会话Session 创建,也是需要params,然后commit 提交操作。

java 复制代码
这里对createSession 的这个过程 贴上部分代码:
  PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                        PackageInstaller.SessionParams.MODE_FULL_INSTALL);
                final Uri referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
                params.setPackageSource(
                        referrerUri != null ? PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
                                : PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE);
                params.setInstallAsInstantApp(false);
                params.setReferrerUri(referrerUri);
                params.setOriginatingUri(getIntent()
                        .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI));
                params.setOriginatingUid(getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
                        UID_UNKNOWN));
                params.setInstallerPackageName(getIntent().getStringExtra(
                        Intent.EXTRA_INSTALLER_PACKAGE_NAME));
                params.setInstallReason(PackageManager.INSTALL_REASON_USER);

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

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

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

InstallingAsyncTask 异步线程操作

在onResume 方法中,通过异步线程操作。 上述描述看,已经创建了Session 操作,其实接下来想想也是session 提交相关的操作。

java 复制代码
  if (mInstallingTask == null) {
            PackageInstaller installer = getPackageManager().getPackageInstaller();
            PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);

            if (sessionInfo != null && !sessionInfo.isActive()) {
                mInstallingTask = new InstallingAsyncTask();
                mInstallingTask.execute();
            } else {
                // we will receive a broadcast when the install is finished
                mCancelButton.setEnabled(false);
                setFinishOnTouchOutside(false);
            }
        }
doInBackground 异步后台任务

下面是doInBackground 源码说明,不就是打开session,然后通过session,通过进程通讯,写入待安装的File 文件到系统嘛。

java 复制代码
 protected PackageInstaller.Session doInBackground(Void... params) {
            PackageInstaller.Session session;
            try {
                session = getPackageManager().getPackageInstaller().openSession(mSessionId);
            } catch (IOException e) {
                synchronized (this) {
                    isDone = true;
                    notifyAll();
                }
                return null;
            }
            session.setStagingProgress(0);
            try {
                File file = new File(mPackageURI.getPath());
                try (InputStream in = new FileInputStream(file)) {
                    long sizeBytes = file.length();
                    try (OutputStream out = session
                            .openWrite("PackageInstaller", 0, sizeBytes)) {
                        byte[] buffer = new byte[1024 * 1024];
                        while (true) {
                            int numRead = in.read(buffer);
                            if (numRead == -1) {
                                session.fsync(out);
                                break;
                            }
                            if (isCancelled()) {
                                session.close();
                                break;
                            }
                            out.write(buffer, 0, numRead);
                            if (sizeBytes > 0) {
                                float fraction = ((float) numRead / (float) sizeBytes);
                                session.addProgress(fraction);
                            }
                        }
                    }
                }
                return session;
            } catch (IOException | SecurityException e) {
                Log.e(LOG_TAG, "Could not write package", e);
                session.close();
                return null;
            } finally {
                synchronized (this) {
                    isDone = true;
                    notifyAll();
                }
            }
        }
onPostExecute 后台操作后,执行commit 操作

源码如下,贴出来看看具体操作:

终归还是通过 session 的 commit 操作,提交到系统去,由系统来进行安装操作。

java 复制代码
if (session != null) {
                Intent broadcastIntent = new Intent(BROADCAST_ACTION);
                broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
                broadcastIntent.setPackage(getPackageName());
                broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);

                PendingIntent pendingIntent = PendingIntent.getBroadcast(
                        InstallInstalling.this,
                        mInstallId,
                        broadcastIntent,
                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);

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

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

总结

  • PackageInstaller-之apk安装流程内容中,主要是包安装器PackageInstaller 相关内容。
    作为PMS安装apk之界面跳转 的续篇。 两篇文章规整起来就是完全分析完了。 当然,这里面还有权限相关操作只是一笔带过介绍了。
  • 主要分析了:PackageInstallerActivity InstallInstalling 两个源码的分析。 涉及到安装确认弹框-权限弹框-安装中等待弹框-注册监听安装回调-安装session创建和提交到系统

拓展

简要了解 上面介绍了Session操作,Session 操作到底操作了什么, commit 就发送到系统进行安装了呢?

我们着重看看部分代码

java 复制代码
PackageInstaller.Session session=getPackageManager().getPackageInstaller().openSession(mSessionId)
getPackageManager().getPackageInstaller().abandonSession
OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes))
session.commit

PackageInstaller

PackageInstaller

getPackageManager().getPackageInstaller() 找到PackageInstaller 类,看看类注释介绍

注释很详细了:

  • 提供了安装、更新、移除设备上app的能力
  • app的安装操作通过PackageInstaller.Session 来实现的,一旦Session创建成功就可以将一个或者多个apk通过流的方式输送到指定的位置,直到session
    销毁或者提交
  • apk 的一些基本校验,签名、包名、版本号、基本apk等。
java 复制代码
/**
  * Offers the ability to install, upgrade, and remove applications on the
  * device. This includes support for apps packaged either as a single
  * "monolithic" APK, or apps packaged as multiple "split" APKs.
  * <p>
  * An app is delivered for installation through a
  * {@link PackageInstaller.Session}, which any app can create. Once the session
  * is created, the installer can stream one or more APKs into place until it
  * decides to either commit or destroy the session. Committing may require user
   * intervention to complete the installation, unless the caller falls into one of the
   * following categories, in which case the installation will complete automatically.
   * <ul>
   * <li>the device owner
   * <li>the affiliated profile owner
   * </ul>
   * <p>
   * Sessions can install brand new apps, upgrade existing apps, or add new splits
   * into an existing app.
   * <p>
   * Apps packaged as multiple split APKs always consist of a single "base" APK
   * (with a {@code null} split name) and zero or more "split" APKs (with unique
   * split names). Any subset of these APKs can be installed together, as long as
   * the following constraints are met:
   * <ul>
   * <li>All APKs must have the exact same package name, version code, and signing
   * certificates.
   * <li>All APKs must have unique split names.
   * <li>All installations must contain a single base APK.
   * </ul>
   * <p>
   * The ApiDemos project contains examples of using this API:
   * <code>ApiDemos/src/com/example/android/apis/content/InstallApk*.java</code>.
   */

Session 到底是什么

java 复制代码
  public static class Session implements Closeable {
        /** {@hide} */
        protected final IPackageInstallerSession mSession;

        /** {@hide} */
        public Session(IPackageInstallerSession session) {
            mSession = session;
        }

        /** {@hide} */
        @Deprecated
        public void setProgress(float progress) {
            setStagingProgress(progress);
        }
   ................
   }

IPackageInstallerSession 是什么

java 复制代码
  路径: \frameworks\base\core\java\android\content\pm\IPackageInstallerSession.aidl 

它是一个aidl 文件,说明什么问题??? 说明它是一个aidl 文件,具体操作不是通过 IPackageInstallerSession 操作的,我们就需要找到 它的实现代理类。
我一般是这么来找的

java 复制代码
查找:grep -rn PackageInstallerSession.Stub
java 复制代码
   
   
fise4@ubuntu-PowerEdge-R730:~/Android/mt6769-alps-release-t0.mp1.rc$ grep -rn PackageInstallerSession.Stub
frameworks/base/config/boot-image-profile.txt:33737:Landroid/content/pm/IPackageInstallerSession$Stub$Proxy;
frameworks/base/config/boot-image-profile.txt:33738:Landroid/content/pm/IPackageInstallerSession$Stub;
frameworks/base/config/preloaded-classes:1445:android.content.pm.IPackageInstallerSession$Stub$Proxy
frameworks/base/config/preloaded-classes:1446:android.content.pm.IPackageInstallerSession$Stub
frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java:187:public class PackageInstallerSession extends IPackageInstallerSession.Stub {
  

所以在框架层,调用的其实是 PackageInstallerSession 类的方法,我们去看看

java 复制代码
fise4@ubuntu-PowerEdge-R730:~/Android/mt6769-alps-release-t0.mp1.rc$ find . -name PackageInstallerSession.java 
./frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java

根据上面我们再梳理下,通过commit 方法来梳理流程,跟踪流程->分析代码

java 复制代码
InstallInstalling.java         ->session.commit(pendingIntent.getIntentSender())
PackageInstall.java            ->mSession.commit(statusReceiver, false);
PackageInstallerSession.java   ->dispatchSessionSealed()

如下:PackageInstallerSession.java 里面的commit 方法

java 复制代码
public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
        if (hasParentSessionId()) {
            throw new IllegalStateException(
                    "Session " + sessionId + " is a child of multi-package session "
                            + getParentSessionId() +  " and may not be committed directly.");
        }

        if (!markAsSealed(statusReceiver, forTransfer)) {
            return;
        }
        if (isMultiPackage()) {
            synchronized (mLock) {
                boolean sealFailed = false;
                for (int i = mChildSessions.size() - 1; i >= 0; --i) {
                    // seal all children, regardless if any of them fail; we'll throw/return
                    // as appropriate once all children have been processed
                    if (!mChildSessions.valueAt(i).markAsSealed(null, forTransfer)) {
                        sealFailed = true;
                    }
                }
                if (sealFailed) {
                    return;
                }
            }
        }

        dispatchSessionSealed();
    }

结语

分析到了框架层了已经,到此结束。 所有的安装层应用PackageInstaller 的代码梳理了一遍。剩下的具体安装操作在框架framework层,后续讨论。

相关推荐
庆 、4 天前
Django REST framework 源码剖析-认证器详解(Authentication)
后端·python·django·framework·restful·authentication
千里马学框架8 天前
安卓15/aosp15/lineage21使用brunch编译老是报错OOM内存不足
android·车载系统·framework·系统开发·aosp·lineage
亚瑟-灰太狼22 天前
preloaded-classes裁剪
framework
Android小码家1 个月前
Android Framework startServices 流程
android·framework
画个太阳作晴天1 个月前
Android13修改多媒体默认音量
android·framework
Android小码家1 个月前
Android SystemUI开发(一)
android·framework·systemui
猿小帅011 个月前
androidnetflix手机版遥控器操作
android·framework
JabamiLight1 个月前
Lineageos 22.1(Android 15) 编译隐藏API的 android.jar
android·java·framework·jar·android 15·lineageos 22.1
庆 、2 个月前
Django REST framework 源码剖析-渲染器图解(Renderers)
django·framework·restful·drf·rest·renders