文章目录

  • 概述
    • 相关类说明
  • PMS 服务启动
  • 应用程序(APK)安装
    • 有界面安装
    • 无界面安装
    • APK 安装原理

概述

PackageManagerService(以下简称 PMS)是一个常用的系统服务,主要负责系统中的 Package 的管理,应用程序的安装、卸载、查询等相关功能。其相关类图如下

相关类说明

  • IPackageManager
    定义了服务端和客户端通信的业务函数,还定义了内部类 Stub ,该类从 Binder 派生并实现了 IPackageManager 接口
  • PackageManagerService
    继承自 IPackageManager.Stub 类,由于 Stub 类从 Binder 派生,因此 PackageManagerService 将作为服务端参与 Binder 通信
  • Stub
    定义了一个内部类 Proxy ,该类有一个 IBinder 类型(实际类型为 BinderProxy )的成员变量 mRemote ,根据 Binder 详解 中介绍的 Binder 系统的知识,mRemote 用于和服务端 PackageManagerService 通信
  • ApplicationPackageManager
    承自 PackageManager 类。它并没有直接参与 Binder 通信,而是通过 mPM 成员变量指向一个 IPackageManager.Stub.Proxy 类型的对象。

注:IPackageManager 在 Android Studio 中可能找不到,因为他是一个 AIDL 处理后的文件,这里附上相关的 IPackageManager.aidl 文件

PMS 服务启动

分析 PMS 之前我们先来看下这玩意是在哪里启动的。

我们都知道系统启动的时候会调用 SystemServer 的 main 函数。

注释已经说明了,这个 main 函数是通过 zygote 调用的,这块的调用链涉及到了进程启动相关,此处先不多说。看到 main 创建了一个自己然后调用 run 方法。

可以看到在 run 方法中系统将服务类型按照级别分为了三大类,引导服务内核服务其他服务,而 PMS 的启动就在引导服务中启动。

/*** The main entry point from zygote.*/
public static void main(String[] args) {new SystemServer().run();
}
private void run() {// Start services.try {//系统引导服务startBootstrapServices();//内核服务startCoreServices();//其他服务startOtherServices();} catch (Throwable ex) {Slog.e("System", "******************************************");Slog.e("System", "************ Failure starting system services", ex);throw ex;} finally {traceEnd();}
}
private void startBootstrapServices() {mPackageManagerService = PackageManagerService.main(mSystemContext, installer,mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);mFirstBoot = mPackageManagerService.isFirstBoot();//标注①mPackageManager = mSystemContext.getPackageManager();
}

我们进入 PackageManagerService 的 main 方法继续追踪。

可以看到在 main 方法中也是 new 了一把自己。然后将服务添加的 ServiceManager 中进行管理。到这,PMS 服务就启动完成了。

public class PackageManagerService extends IPackageManager.Stubimplements PackageSender {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;}
}

这里在额外看一下 PackageManager 的创建。上面 标注① 出获取了 PackageManager ,我们就以此为入口看下。mSystemContext 为 Content 的一个实现类,可以直接搜 ContextImpl

可以看到和上面的类图完美的对应上了,这里拿到系统提供的 PM,然后创建一个 ApplicationPackageManager 持有这个 pm 去和系统服务进行交互。

class ContextImpl extends Context {@Overridepublic PackageManager getPackageManager() {if (mPackageManager != null) {return mPackageManager;}IPackageManager pm = ActivityThread.getPackageManager();if (pm != null) {// Doesn't matter if we make more than one instance.return (mPackageManager = new ApplicationPackageManager(this, pm));}return null;}
}

注:Android 系统启动慢的原因就是在启动 PMS 的时候,需要执行扫描文件夹、处理权限、安装系统应用(文件的解压与copy)等比较耗时的操作。

应用程序(APK)安装

有界面安装

我们下载的安装包都是一个 .apk 文件,当我们点击该文件的时候,会启动系统的一个安装 apk 的应用,该应用是系统应用,在系统启动时就已经安装成功了。这里附上 PackageInstaller.apk 源码

首先这个 Intent 会启动 InstallStart ,在这里做一些校验后就跳转到 PackageInstallerActivity 进行确认安装后,进入 InstallInstalling 界面进行安装。

可以看到在 AndroidManifest.xml 中对外暴露了 InstallStart。

<activity android:name=".InstallStart"android:exported="true"android:excludeFromRecents="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="file" /><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="file" /><data android:scheme="package" /><data android:scheme="content" /></intent-filter><intent-filter android:priority="1"><action android:name="android.content.pm.action.CONFIRM_PERMISSIONS" /><category android:name="android.intent.category.DEFAULT" /></intent-filter>
</activity>

安装界面调用流程

public class InstallStart extends Activity {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);......Intent nextActivity = new Intent(intent);......nextActivity.setClass(this, PackageInstallerActivity.class);......if (nextActivity != null) {startActivity(nextActivity);}finish();}
}public class PackageInstallerActivity extends OverlayTouchActivity implements OnClickListener {public void onClick(View v) {if (v == mOk) {if (mOk.isEnabled()) {if (mOkCanInstall || mScrollView == null) {if (mSessionId != -1) {mInstaller.setPermissionsResult(mSessionId, true);finish();} else {//确认安装startInstall();}} else {mScrollView.pageScroll(View.FOCUS_DOWN);}}} else if (v == mCancel) {// Cancel and finishsetResult(RESULT_CANCELED);if (mSessionId != -1) {mInstaller.setPermissionsResult(mSessionId, false);}finish();}}private void startInstall() {// Start subactivity to actually install the applicationIntent newIntent = new Intent();......newIntent.setClass(this, InstallInstalling.class);......startActivity(newIntent);finish();}
}
public class InstallInstalling extends Activity {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);params.installFlags = PackageManager.INSTALL_FULL_APP;......try {//标注②mSessionId = getPackageManager().getPackageInstaller().createSession(params);} catch (IOException e) {launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);}}
}

上面标注②中获取了 PackageInstaller 执行真正的安装

public class ApplicationPackageManager extends PackageManager {@Overridepublic PackageInstaller getPackageInstaller() {synchronized (mLock) {if (mInstaller == null) {try {mInstaller = new PackageInstaller(mPM.getPackageInstaller(),mContext.getPackageName(), mContext.getUserId());} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}return mInstaller;}}
}
public class PackageInstaller {//标注③private final IPackageInstaller mInstaller;public int createSession(@NonNull SessionParams params) throws IOException {try {final String installerPackage;if (params.installerPackageName == null) {installerPackage = mInstallerPackageName;} else {installerPackage = params.installerPackageName;}return mInstaller.createSession(params, installerPackage, mUserId);} catch (RuntimeException e) {ExceptionUtils.maybeUnwrapIOException(e);throw e;} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}
}

上面标注③ 中的 IPackageInstaller 为 PackageInstallerService 的 Binder 对象,看到这里是不发现,系统服务将 Binder 通信用的是淋漓尽致啊。

这里安装完成后通过 Binder 和 Handler 进行回调。

public class PackageInstallerService extends IPackageInstaller.Stub {@Overridepublic int createSession(SessionParams params, String installerPackageName, int userId) {try {return createSessionInternal(params, installerPackageName, userId);} catch (IOException e) {throw ExceptionUtils.wrap(e);}}private int createSessionInternal(SessionParams params, String installerPackageName, int userId)throws IOException {final int callingUid = Binder.getCallingUid();mPermissionManager.enforceCrossUserPermission(callingUid, userId, true, true, "createSession");if (mPm.isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {throw new SecurityException("User restriction prevents installing");}if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {params.installFlags |= PackageManager.INSTALL_FROM_ADB;} else {// Only apps with INSTALL_PACKAGES are allowed to set an installer that is not the// caller.if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES) !=PackageManager.PERMISSION_GRANTED) {mAppOps.checkPackage(callingUid, installerPackageName);}params.installFlags &= ~PackageManager.INSTALL_FROM_ADB;params.installFlags &= ~PackageManager.INSTALL_ALL_USERS;params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;if ((params.installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0&& !mPm.isCallerVerifier(callingUid)) {params.installFlags &= ~PackageManager.INSTALL_VIRTUAL_PRELOAD;}}// Only system components can circumvent runtime permissions when installing.if ((params.installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0&& mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) {throw new SecurityException("You need the "+ "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission "+ "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag");}if ((params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0|| (params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {throw new IllegalArgumentException("New installs into ASEC containers no longer supported");}// Defensively resize giant app iconsif (params.appIcon != null) {final ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);final int iconSize = am.getLauncherLargeIconSize();if ((params.appIcon.getWidth() > iconSize * 2)|| (params.appIcon.getHeight() > iconSize * 2)) {params.appIcon = Bitmap.createScaledBitmap(params.appIcon, iconSize, iconSize,true);}}switch (params.mode) {case SessionParams.MODE_FULL_INSTALL:case SessionParams.MODE_INHERIT_EXISTING:break;default:throw new IllegalArgumentException("Invalid install mode: " + params.mode);}// If caller requested explicit location, sanity check it, otherwise// resolve the best internal or adopted location.if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {if (!PackageHelper.fitsOnInternal(mContext, params)) {throw new IOException("No suitable internal storage available");}} else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {if (!PackageHelper.fitsOnExternal(mContext, params)) {throw new IOException("No suitable external storage available");}} else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) {// For now, installs to adopted media are treated as internal from// an install flag point-of-view.params.setInstallFlagsInternal();} else {// For now, installs to adopted media are treated as internal from// an install flag point-of-view.params.setInstallFlagsInternal();// Resolve best location for install, based on combination of// requested install flags, delta size, and manifest settings.final long ident = Binder.clearCallingIdentity();try {params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params);} finally {Binder.restoreCallingIdentity(ident);}}final int sessionId;final PackageInstallerSession session;synchronized (mSessions) {// Sanity check that installer isn't going crazyfinal int activeCount = getSessionCount(mSessions, callingUid);if (activeCount >= MAX_ACTIVE_SESSIONS) {throw new IllegalStateException("Too many active sessions for UID " + callingUid);}final int historicalCount = mHistoricalSessionsByInstaller.get(callingUid);if (historicalCount >= MAX_HISTORICAL_SESSIONS) {throw new IllegalStateException("Too many historical sessions for UID " + callingUid);}sessionId = allocateSessionIdLocked();}final long createdMillis = System.currentTimeMillis();// We're staging to exactly one locationFile stageDir = null;String stageCid = null;if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {final boolean isInstant =(params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;stageDir = buildStageDir(params.volumeUuid, sessionId, isInstant);} else {stageCid = buildExternalStageCid(sessionId);}session = new PackageInstallerSession(mInternalCallback, mContext, mPm,mInstallThread.getLooper(), sessionId, userId, installerPackageName, callingUid,params, createdMillis, stageDir, stageCid, false, false);synchronized (mSessions) {mSessions.put(sessionId, session);}mCallbacks.notifySessionCreated(session.sessionId, session.userId);writeSessionsAsync();return sessionId;}
}

无界面安装

上面说的有界面安装是正常的安装方式,可以让用户感知的。然而还有一种比较流氓的方式,就是静默安装。用户无感知,这个应用就被安装成功了。

这种方式就是通过 adb 命令进行安装 adb install

这个命令我们可以在 commandline.cpp 中找到。可以看到通过命令匹配 执行 install_app 函数。与之相应的还有 adb uninstall 命令

int adb_commandline(int argc, const char** argv) {......else if (!strcmp(argv[0], "install")) {if (argc < 2) return syntax_error("install requires an argument");if (_use_legacy_install()) {return install_app_legacy(argc, argv);}return install_app(argc, argv);}......else if (!strcmp(argv[0], "uninstall")) {if (argc < 2) return syntax_error("uninstall requires an argument");if (_use_legacy_install()) {return uninstall_app_legacy(argc, argv);}return uninstall_app(argc, argv);}......
}
static int install_app_legacy(TransportType transport, const char* serial, int argc, const char** argv) {......result = pm_command(transport, serial, argc, argv);......return result;
}
static int pm_command(TransportType transport, const char* serial, int argc, const char** argv) {std::string cmd = "pm";while (argc-- > 0) {cmd += " " + escape_arg(*argv++);}return send_shell_command(transport, serial, cmd, false);
}

顺着调用栈走下去发现最终执行了 pm 命令。而这个命令会执行一个 pm.jar 文件,该 jar 包是通过 Pm.java 打包而成。

命令执行后会调用 Pm 的 main 函数。顺着调用往下走,最终发现还是调用了 PackageInstallerService 的 createSession 函数。殊途同归,和有界面安装走入相同的流程了。

public final class Pm {public static void main(String[] args) {int exitCode = 1;try {exitCode = new Pm().run(args);} catch (Exception e) {Log.e(TAG, "Error", e);System.err.println("Error: " + e);if (e instanceof RemoteException) {System.err.println(PM_NOT_RUNNING_ERR);}}System.exit(exitCode);}public int run(String[] args) throws RemoteException {boolean validCommand = false;if (args.length < 1) {return showUsage();}mAm = IAccountManager.Stub.asInterface(ServiceManager.getService(Context.ACCOUNT_SERVICE));mUm = IUserManager.Stub.asInterface(ServiceManager.getService(Context.USER_SERVICE));mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));if (mPm == null) {System.err.println(PM_NOT_RUNNING_ERR);return 1;}mInstaller = mPm.getPackageInstaller();......if ("install".equals(op)) {return runInstall();}......if ("uninstall".equals(op)) {return runUninstall();}......}private int runInstall() throws RemoteException {......final int sessionId = doCreateSession(params.sessionParams,params.installerPackageName, params.userId);......}private int doCreateSession(SessionParams params, String installerPackageName, int userId)throws RemoteException {userId = translateUserId(userId, "runInstallCreate");if (userId == UserHandle.USER_ALL) {userId = UserHandle.USER_SYSTEM;params.installFlags |= PackageManager.INSTALL_ALL_USERS;}final int sessionId = mInstaller.createSession(params, installerPackageName, userId);return sessionId;}
}

APK 安装原理

至此对于 PMS 的启动,和 APK 文件的两种安装方式我们都分析完了,下面对结合两种安装方式总结一下

对于 Android 系统安装 APK 应用,就是将 APK 文件解压,把相应的文件 copy 到相应的目录中即可。

  1. data/app/package_name
    安装时将 apk 文件复制到此目录,可以将文件取出并安装
  2. data/data/package_name
    开辟存放应用程序的文件数据的文件夹,包括我们的 so 库、缓存文件等
  3. data/dalvik-cache
    将 apk 解压出的 dex 文件复制到此目录

PMS启动 APK 安装流程详解相关推荐

  1. 《MySQL安装流程详解》及《MySQL安装一直失败,重新安装显示已安装》

    <MySQL安装流程详解>及<MySQL安装一直失败,重新安装显示已安装> 本文由博主经过查阅网上资料整理总结后编写,如存在错误或不恰当之处请留言以便更正,内容仅供大家参考学习 ...

  2. Android APK文件结构 完整打包编译的流程 APK安装过程 详解

    Android apk文件结构 打包编译的流程 Android官网 配置构建 流程 Configure your build The build process APK文件结构 assets res ...

  3. 工业以太网交换机的安装流程详解

    工业以太网交换机是应用于工业控制领域的以太网交换机设备,所以设备的安装调试是很重要的一环,那么,我们在安装工业交换机的过程中需要注意什么呢?工业以太网交换机的安装流程是什么呢?接下来我们就跟随飞畅科技 ...

  4. 【SealDEV 倾情奉献】Android APK 安装过程详解

    开发 Android 应用后,一般都会安装到手机上去运行,了解应用是如何安装到手机上的,可以更好的理解应用的本质,理解应用是如何运行的,在自己开发的应用遇到一些问题时,会从更深层次去分析,快速定位找到 ...

  5. IDEA社区版下载安装流程详解

    本人一直使用的是Eclipse作为开发工具的,不过现在IDEA非常的受推崇,所以决定上手试一试.网上有很多旗舰版的文章,我没有仔细看,我这次是决定使用社区版的IDEA,虽然功能会少一些,作为练手用完全 ...

  6. Android App启动流程详解

    前言:在之前的文章中已经写了apk的打包流程.安装流程,今天就是梳理一下apk系列的最后的流程--app启动流程.经过今天的梳理以后咱们就可以对apk包是怎么编译生成的.apk是怎么被安装到安卓手机的 ...

  7. win10安装SQLserver2017详解

    详解一波关于win10系统下SQL server 2017的安装完整流程以及安装过程中遇到的一小部分问题的解决方法 iso镜像文件:https://pan.baidu.com/s/1hMFiPpI0j ...

  8. [PXE] Linux(centos6)中PXE 服务器搭建,PXE安装、启动及PXE理论详解

    本篇blog主要讲述了[PXE] linux(centos)PXE无盘服务器搭建,安装,启动及pxe协议详解 , Kickstart (PXE+DHCP+TFTP+HTTP). PXE环境概述 作为中 ...

  9. S5PV210 Uboot开发与移植03:Uboot启动流程详解

    目录 1. start.S解析 1.1 uboot入口分析 1.2 头文件包含 1.2.1 config.h 1.2.2 version.h 1.2.3 asm/proc/domain.h 1.2.4 ...

最新文章

  1. 数据结构与算法(5)字符串(BF算法、KMP算法及KMP算法优化)
  2. hdu 4109 Instrction Arrangement 拓扑排序 关键路径
  3. 深度学习入门之PyTorch学习笔记:卷积神经网络
  4. boost::intrusive::member_value_traits用法的测试程序
  5. c语言gets与fgetc,区分C语言中getch、getche、fgetc、getc、getchar、fgets、gets 转
  6. LLE(局部线性嵌入)matlab代码实现
  7. android 元素点击位置,appium自动化操作之元素定位点击事件全家桶(find_element_by、find_elements_by)...
  8. [已破案] 镜像出问题了
  9. 以汉字开头,以某个词结尾的一段文字的正则
  10. 修改 Ubuntu SSH 登录后的欢迎信息
  11. python方向是干什么的_Python有哪些应用方向 在数据分析上有什么优势
  12. HTML+CSS+JAVASCRIPT 高仿低配网页版网易云音乐播放器
  13. 不用U盘从linux重装win系统,不用U盘和光盘安装win7旗舰版系统
  14. C语言strtok()函数详解
  15. 笔记本软件兼容性测试,Windows 10 技术预览版 国产杀毒软件兼容性测试:大多可以使用...
  16. 用python暴力破解压缩包密码
  17. BZOJ 4199 [Noi2015]品酒大会(后缀自动机 + parent树上统计)
  18. 【Web前端】落地成盒?达咩之——CSS盒子模型及属性
  19. ctex安装及使用技巧
  20. 【数据结构】哈希表——线性探测法、链地址法、查找成功、查找不成功的平均长度

热门文章

  1. python爬虫2.0.5ProxyIpPool---proxies使用代理IP
  2. 742_从CTAN上获取latex的支持包并安装
  3. 用计算机图形画一个杯子,计算机图形学期末考试试卷(D卷)
  4. python中eval方法的妙用
  5. android游戏盒子,安卓游戏盒子
  6. Call 相关的知识
  7. 2011中国互联网企业分析(100强名单)
  8. 黑五、网一等节日大促临近!独立站选品分析,请收好!
  9. 【云原生 | 从零开始学Docker】七丶实战提交自己的镜像以及docker网络
  10. 02 - pyrebox shell