Android应用的安装过程
来源:互联网 发布:网络受限制怎么办 编辑:程序博客网 时间:2024/05/24 05:41
前言
我们知道,在android手机上安装一个apk很简单,只要打开apk文件,默认就会弹出安装界面,然后点击确定,经过若干秒后,apk就安装成功了,可是你知道apk的安装过程是什么吗?你知道android系统在安装一个apk的时候都干了什么吗?在本文中,将一一解答这个问题。简单来说,apk的安装过程分两步:第一步,将apk文件复制到程序目录下(/data/app/);第二步,为应用创建数据目录(/data/data/package name/)、提取dex文件到指定目录(/data/dalvik-cache/)、修改系统包管理信息。注意,本文的分析基于Android 4.3源码。
apk的复制过程
apk的安装从PackageManager的installApk方法开始,由于PackageManager所对应的binder服务为PackageManagerService(PMS),所以,真正的安装过程都在PackageManagerService中完成。PackageManagerService的installApk方法最终调用了installPackageWithVerificationAndEncryption方法,该方法的核心就是在最后发送了一个INIT_COPY的消息,这个消息的含义是完成apk的拷贝过程。
public void installPackageWithVerificationAndEncryption(Uri packageURI, IPackageInstallObserver observer, int flags, String installerPackageName, VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null); final int uid = Binder.getCallingUid(); if (isUserRestricted(UserHandle.getUserId(uid), UserManager.DISALLOW_INSTALL_APPS)) { try { observer.packageInstalled("", PackageManager.INSTALL_FAILED_USER_RESTRICTED); } catch (RemoteException re) { } return; } UserHandle user; if ((flags&PackageManager.INSTALL_ALL_USERS) != 0) { user = UserHandle.ALL; } else { user = new UserHandle(UserHandle.getUserId(uid)); } final int filteredFlags; if (uid == Process.SHELL_UID || uid == 0) { if (DEBUG_INSTALL) { Slog.v(TAG, "Install from ADB"); } filteredFlags = flags | PackageManager.INSTALL_FROM_ADB; } else { filteredFlags = flags & ~PackageManager.INSTALL_FROM_ADB; } verificationParams.setInstallerUid(uid); final Message msg = mHandler.obtainMessage(INIT_COPY); msg.obj = new InstallParams(packageURI, observer, filteredFlags, installerPackageName, verificationParams, encryptionParams, user); mHandler.sendMessage(msg); }
通过分析代码可以发现,真正实现apk拷贝的方法是InstallParams的handleStartCopy方法,InstallParams中有重试机制,拷贝如果失败的话会重试,最多重试4次。在拷贝之前,还必须做一件事情,那就是绑定media container service,安装过程中一些状态的检查会用到这个服务,代码如下所示:
class PackageHandler extends Handler {private boolean mBound = false;final ArrayList<HandlerParams> mPendingInstalls =new ArrayList<HandlerParams>();private boolean connectToService() {if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to bind to" +" DefaultContainerService");Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);if (mContext.bindServiceAsUser(service, mDefContainerConn,Context.BIND_AUTO_CREATE, UserHandle.OWNER)) {Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);mBound = true;return true;}Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);return false;}...}
现在分析一下InstallParams的handleStartCopy方法,这个方法很长,代码就不帖出来了,大家可以自己去看看,这里主要分析下它的工作流程:
1. 检查安装位置标记位是否有冲突,如果有冲突,则安装失败,这里的有冲突是指“一个apk同时要求被安装到内部存储和sd卡”
2. 调用MCS服务的getMinimalPackageInfo方法来得到apk的推荐安装位置,并检查是否能够进行正常的安装。在这一步,有可能抛出一些无法安装的状态位:存储空间不足、程序已经安装、无效的apk文件等,这个时候安装过程终止
3. 到这一步,表示程序可以正常安装,同时MCS服务服务可能会根据需要调整安装位置,在InstallParams的installLocationPolicy中完成
4. 文件的复制过程,PMS针对内部存储和sd卡分别提供了一个类:FileInstallArgs和AsecInstallArgs,并分别调用二者的copyApk方法来完成apk的复制过程
经过了上面4步,待安装apk已经被复制到了/data/app/目录了。
apk的解析过程
上面,apk已经被复制到了/data/app/目录,安装的第一步已经完成,那么系统是什么时候对apk进行dex提取和解析的呢,这还要从PMS说起,在PMS内部有一个AppDirObserver类,顾名思义,它的作用是应用目录观察者,它时刻观察着应用目录/data/app/,当目录内部结构改变的时候(创建文件和删除文件)它会做出相应行为,下面看下它的代码:
private final class AppDirObserver extends FileObserver {public AppDirObserver(String path, int mask, boolean isrom) {super(path, mask);mRootDir = path;mIsRom = isrom;}//在/data/app/目录下添加或删除apk的时候,此方法会被调用public void onEvent(int event, String path) {String removedPackage = null;int removedAppId = -1;int[] removedUsers = null;String addedPackage = null;int addedAppId = -1;int[] addedUsers = null;// TODO post a message to the handler to obtain serial orderingsynchronized (mInstallLock) {String fullPathStr = null;File fullPath = null;if (path != null) {fullPath = new File(mRootDir, path);fullPathStr = fullPath.getPath();}if (DEBUG_APP_DIR_OBSERVER)Log.v(TAG, "File " + fullPathStr + " changed: " + Integer.toHexString(event));if (!isPackageFilename(path)) {if (DEBUG_APP_DIR_OBSERVER)Log.v(TAG, "Ignoring change of non-package file: " + fullPathStr);return;}// Ignore packages that are being installed or// have just been installed.if (ignoreCodePath(fullPathStr)) {return;}PackageParser.Package p = null;PackageSetting ps = null;// readersynchronized (mPackages) {p = mAppDirs.get(fullPathStr);if (p != null) {ps = mSettings.mPackages.get(p.applicationInfo.packageName);if (ps != null) {removedUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true);} else {removedUsers = sUserManager.getUserIds();}}addedUsers = sUserManager.getUserIds();}//当apk被删除的时候,往往意味着这个apk被卸载if ((event&REMOVE_EVENTS) != 0) {if (ps != null) {if (DEBUG_REMOVE) Slog.d(TAG, "Package disappeared: " + ps);//removePackageLI方法完成卸载apk的主要功能removePackageLI(ps, true);removedPackage = ps.name;removedAppId = ps.appId;}}//新添加了一个apk,往往意味着一个新的apk被安装if ((event&ADD_EVENTS) != 0) {if (p == null) {if (DEBUG_INSTALL) Slog.d(TAG, "New file appeared: " + fullPath);//scanPackageLI方法完成了apk安装的第二个步骤p = scanPackageLI(fullPath,(mIsRom ? PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR: 0) |PackageParser.PARSE_CHATTY |PackageParser.PARSE_MUST_BE_APK,SCAN_MONITOR | SCAN_NO_PATHS | SCAN_UPDATE_TIME,System.currentTimeMillis(), UserHandle.ALL);if (p != null) {/* * TODO this seems dangerous as the package may have * changed since we last acquired the mPackages * lock. */// writersynchronized (mPackages) {updatePermissionsLPw(p.packageName, p,p.permissions.size() > 0 ? UPDATE_PERMISSIONS_ALL : 0);}addedPackage = p.applicationInfo.packageName;addedAppId = UserHandle.getAppId(p.applicationInfo.uid);}}}// readersynchronized (mPackages) {mSettings.writeLPr();}}//下面两个if语句块大家应用不陌生吧,在我们的应用中想监听应用的安装和卸载,//就是通过收听ACTION_PACKAGE_ADDED和ACTION_PACKAGE_REMOVED这两个广播来实现的if (removedPackage != null) {Bundle extras = new Bundle(1);extras.putInt(Intent.EXTRA_UID, removedAppId);extras.putBoolean(Intent.EXTRA_DATA_REMOVED, false);sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage,extras, null, null, removedUsers);}if (addedPackage != null) {Bundle extras = new Bundle(1);extras.putInt(Intent.EXTRA_UID, addedAppId);sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, addedPackage,extras, null, null, addedUsers);}}private final String mRootDir;private final boolean mIsRom;}
下面,我们主要分析一下scanPackageLI方法,还是仅仅分析,不帖代码,因为代码太长了,帖出来没法看了,这个方法不仅仅是完成apk包的扫描,还解析AndroidManifest.xml文件并提取出所有的intent-filter和permission信息,apk安装的主要功能都由它来完成的,当apk包扫描完成后,系统会调用updatePermissionsLPw方法更新系统所具有的权限。
scanPackageLI方法有两个,其第一个参数分别接受File和PackageParser.Package类型,第一个方法会从File中提取出package信息然后再调用第二个方法,下面分析第二个scanPackageLI方法,其完成的事情如下:
1. 如果包名是android,则会做一些特殊处理,这个包名为android的应用是系统内部应用的,其他应用的包名如果叫android则安装会有问题,大家可以试一下
2. 解析常见的use-feature、shared-userId、use-library标签并保存到成员变量中
3. 进行签名验证,对应的方法是verifySignaturesLP,验证失败则应用无法安装
4. 创建应用程序目录/data/data/包名,同时将apk中提取出dex文件并保存到/data/dalvik-cache,把apk当做zip解压就能得到dex文件
5. 解析AndroidManifest.xml文件,提取出所需信息,包括具有intent-filter的四大组件信息(Activity、Service、BroadcastReceiver、ContentProvider)和声明的系统权限等
到此为止,scanPackageLI方法结束了。而updatePermissionsLPw的作用是对系统中所有的权限进行更新,大家可以查看下/system/etc/permissons目录,下面定义了android系统中所有的权限,开发中最常用的权限定义在目录下的platform.xml里面,大家可以打开看看,可以看到常见的访问网络、读写外部存储等权限等都是在这里定义的。权限更新完毕以后,系统就会发送ACTION_PACKAGE_ADDED广播,告知所有应用有新应用安装了。另外,大家可以查看下data/system/目录,里面有两个文件packages.list和packages.xml,在packages.list里面放的是手机上安装的所有应用列表,而packages.xml中存放的是所有应用的设置应用,比如一个应用声明了哪些系统权限就定义在这里面。关于应用的卸载,我们可以想到是应用安装过程的逆过程,大致要做的是:停止应用、删除各种文件,更新系统设置、权限等,大家感兴趣自己看一下,完全是安装过程的逆过程,这里不介绍了。
转载处:http://blog.csdn.net/singwhatiwanna/article/details/19578947
- Android应用的安装过程
- Android 应用安装过程
- Android应用安装过程
- Android内核解读-应用的安装过程
- Android内核解读-应用的安装过程
- Android内核解读-应用的安装过程
- Android内核解读-应用的安装过程
- Android内核解读-应用的安装过程
- Android内核解读-应用的安装过程
- Android 应用安装过程分析
- Android 应用的安装与启动过程(学习笔记)
- Findbug的安装应用过程
- android的安装过程
- Android应用安装过程及原理
- Android应用打包安装过程详解
- Android应用安装过程分析(一)
- Android应用安装过程分析(二)
- Android 应用安装过程源码解析
- MYSQL5.5和MYSQL5.1性能对比
- 程序员的视角:java GC
- C 语言中将字符串倒 输出
- MYSQL 5.1自动安装脚本
- 【暮色天】节节高升 旋律依旧(2.25)
- Android应用的安装过程
- UIBC 小註解
- [有奖征文] 大学毕业季之我的毕业心声征文活动火爆开启
- MYSQL管理之主从同步管理
- Android调用WebService
- ExtJs的CRUD操作及sync()的作用
- SQL四种语言:DDL,DML,DCL,TCL
- 阶乘取模
- 通过修改httpd.conf来生成多个二级域名访问