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层,后续讨论。

相关推荐
JabamiLight3 天前
Lineageos 22.1(Android 15) 编译隐藏API的 android.jar
android·java·framework·jar·android 15·lineageos 22.1
庆 、5 天前
Django REST framework 源码剖析-渲染器图解(Renderers)
django·framework·restful·drf·rest·renders
画个太阳作晴天6 天前
Android10 音频参数导出合并
android·framework·音频
a3158238069 天前
Android设置个性化按钮按键的快捷启动应用
android·开发语言·framework·源码·android13
蜘蛛侠不会飞10 天前
init的service 启动顺序
framework·安卓源码·service·init·稳定性
刘争Stanley1 个月前
Android系统开发(八):从麦克风到扬声器,音频HAL框架的奇妙之旅
android·c语言·framework·音视频·框架·c·hal
千里马学框架1 个月前
aosp系统源码aidl文件如何查看对应生成的java文件-安卓系统开发实战小技巧分享
android·java·开发语言·车载系统·framework·系统开发·aosp15
庆 、1 个月前
Django REST framework 源码剖析-视图集详解(ViewSet)
后端·python·django·framework·restful·rest·viewset
蜘蛛侠不会飞1 个月前
基于安卓14 的ANR dump信息原理
android·java·framework·安卓源码