本文首先介绍系统的APK安装过程的主流程,然后会介绍第三方APK安装的详细过程。
Android系统的目录/文件介绍
在Android开发中,以及本章所涉及到的APK安装都需要了解Android的目录所存在的意义,以下就列出常见的目录:
(1)system/app:存放系统的apk,该目录可以通过Environment.getRootDirectory()+"/app"来获取。
(2)data/app:用户安装的app,该目录可以通过Environment.getDataDirectory()+"app"来获取。
(3)data/data:存放应用程序的数据。
(4)Data/dalvik-cache :将apk中的dexclass.dex文件复制到dalvik-cache下。
system/etc/permissions目录下存放的都是相关的权限xml文件。
(6)/data/system/packages.xml文件:此文件是由PackageManagerService.java文件生成,每当新的app被安装时都会在这个文件里注册,具体的参考此博文:
APK的安装途径
APK即Android Package的缩写,因此在Android中我们通常用的一个概念就是“包”,APK的安装也无非就是对手机设备上的“包”的解析、移动操作,在Android设备上安装主要有以下一种方式:
(1)系统APK安装:系统开机自动完成,由PackageManagerService完成,且没有安装界面。
(2)应用商店(如Google Play)下载的APK安装:由相应的market自动安装,没有安装界面。
(3)ADB工具安装:通常开发人员用这种方式比较多,没有安装界面。
此过程由系统应用packageinstaller.apk来完成。
APK安装主流程
APK文件的生成过程
当我们在开发Android应用时,最终会生成APK文件安装到Android设备上,那么次APK文件是如何生成的呢?其过程用一张图总结:
由于本文主旨不在APK文件的生成,所以关于这方面的就说这么多了。
APK文件的安装主要流程
一个apk文件在安装的过程中会按照如下步骤,一步一步完成:
我们的apk文件都是有PackageManagerService的进程完成的:
- Waiting:PMS处于等待状态。
- 为安装进程添加一个Package到队列。
- 确定包安装的合适位置。
- 确定是安装还是更新(将原来的包替换掉)。
- 将apk文件复制到指定目录。
- 为此app确定一个UID。
- 守护进程开始安装。
- 创建此应用的目录并且设置权限。
- 提取dex文件到缓存目录(dalvik-cache目录)。
- 反射packages.list文件并更新packages.xml文件的状态,即将此app的信息注册到packages.xml中去。
- 当安装器完成安装以后就将app有效的名称广播出去。
- 如果是新的app,广播Action就是Intent.ACTION_PACKAGE_ADDED,如果是更新app则广播Action是 ( Intent.ACTION_PACKAGE_REPLACED)。
我们从apk的复制说起!首先将apk拷贝到data/app目录下,解压取出dexclass.dex文件并复制到dalvik-cache目录,然后在data/data目录下创建以apk包名为名字的文件夹。如下图是图解:
(注:此图引用自相关文档资料,出处连接未知)
APK安装原理过程源码分析
首先我们看看在创建PackageManagerService的时候,其构造器完成了哪些事情,为了介绍主要的实现思路,就屏蔽掉了很多细节实现,以下就是代码实现:
(1)创建Handler、获取相关系统目录路径
Looper l = Looper.prepare();
mHandlerThread.start();
mHandler = new PackageHandler(mHandlerThread.getLooper());
// -------------------------- 以下获取系统的目录
File dataDir = Environment.getDataDirectory();
mAppDataDir = new File(dataDir, "data");
mAppInstallDir = new File(dataDir, "app");
mAppLibInstallDir = new File(dataDir, "app-lib");
mAsecInternalPath = new File(dataDir, "app-asec").getPath();
mUserAppDataDir = new File(dataDir, "user");
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
注意一下第二行和第三行,mHandlerThread是一个HandlerThread‘对象,用于帮我们快速创建Handler对象,我们都知道在创建Handler对象之前,在当前线程必须已经调用了Looper.prapare()方法,HandlerThread就帮我们简化了这些步骤,使得我们可以在任何时候地点创建Handler对象。
实例化Handler对象后就会获取一系列的相关系统目录路径:system/data、system/app等等,接下来我们主要看看AndroidMainfest.xml文件是如何被解析的。
(2)从system/etc/permission读取权限
void readPermissions() {
// Read permissions from .../etc/permission directory.
File libraryDir = new File(Environment.getRootDirectory(),
"etc/permissions");
// 首先判断是否存在,或者是否是目录
if (!libraryDir.exists() || !libraryDir.isDirectory()) {
Slog.w(TAG, "No directory " + libraryDir + ", skipping");
return;
}
// 是否可读
if (!libraryDir.canRead()) {
Slog.w(TAG, "Directory " + libraryDir + " cannot be read");
return;
}
// Iterate over the files in the directory and scan .xml files
for (File f : libraryDir.listFiles()) {
// 最后读取平台上的所有权限
if (f.getPath().endsWith("etc/permissions/platform.xml")) {
continue;
}
// 是否是xml文件
if (!f.getPath().endsWith(".xml")) {
Slog.i(TAG, "Non-xml file " + f + " in " + libraryDir
+ " directory, ignoring");
continue;
}
// xml文件是否可读
if (!f.canRead()) {
Slog.w(TAG, "Permissions library file " + f + " cannot be read");
continue;
}
readPermissionsFromXml(f);
}
// Read permissions from .../etc/permissions/platform.xml last so it
// will take precedence
// 最后读取平台的权限文件,这样他就有优先权了
final File permFile = new File(Environment.getRootDirectory(),
"etc/permissions/platform.xml");
readPermissionsFromXml(permFile);
}
platform.xml),包括我们在做app开发时常用的摄像头、定位、SD卡读写、电话状态监听等等权限。至于readPermissionsFromXml()方法读取XML的具体实现这里就不贴出来了,其实就是一个XML的Pull解析,这种解析在Android系统中很常见。
(3)加载系统system/framework下的jar包
// 以下加载system/framework中的各种jar包----------------------
String bootClassPath = System
.getProperty("java.boot.class.path");
if (bootClassPath != null) {
String[] paths = splitString(bootClassPath, ':');
for (int i = 0; i < paths.length; i++) {
try {
if (dalvik.system.DexFile.isDexOptNeeded(paths[i])) {
libFiles.add(paths[i]);
mInstaller.dexopt(paths[i], Process.SYSTEM_UID,
true);
didDexOpt = true;
}
} catch (FileNotFoundException e) {
Slog.w(TAG, "Boot class path not found: "
+ paths[i]);
} catch (IOException e) {
Slog.w(TAG,
"Cannot dexopt " + paths[i]
+ "; is it an APK or JAR? "
+ e.getMessage());
}
}
} else {
Slog.w(TAG, "No BOOTCLASSPATH found!");
}
(4)对system/app等文件夹进行监听
mFrameworkInstallObserver = new AppDirObserver(
mFrameworkDir.getPath(), OBSERVER_EVENTS, true);
// 为该目录添加监听,如果有文件被添加进来就
mFrameworkInstallObserver.startWatching();
// 扫描system/framework下的apk
scanDirLI(mFrameworkDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanMode
| SCAN_NO_DEX, 0);
// Collect all system packages.
mSystemAppDir = new File(Environment.getRootDirectory(), "app");
// 添加监听
mSystemInstallObserver = new AppDirObserver(
mSystemAppDir.getPath(), OBSERVER_EVENTS, true);
mSystemInstallObserver.startWatching();
// 扫描system/app目录下的apk
scanDirLI(mSystemAppDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);
// Collect all vendor packages.
mVendorAppDir = new File("/vendor/app");
mVendorInstallObserver = new AppDirObserver(
mVendorAppDir.getPath(), OBSERVER_EVENTS, true);
mVendorInstallObserver.startWatching();
(5)扫描APK文件并解析
private void scanDirLI(File dir, int flags, int scanMode, long currentTime) {
String[] files = dir.list();
if (files == null) {
Log.d(TAG, "No files in app dir " + dir);
return;
}
if (DEBUG_PACKAGE_SCANNING) {
Log.d(TAG, "Scanning app dir " + dir);
}
int i;
for (i = 0; i < files.length; i++) {
File file = new File(dir, files[i]);
// 放过非apk文件
if (!isPackageFilename(files[i])) {
// Ignore entries which are not apk's
continue;
}
//此方法会解析出Package实例
PackageParser.Package pkg = scanPackageLI(file, flags
| PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime,
null);
// Don't mess around with apps in system partition.
if (pkg == null
&& (flags & PackageParser.PARSE_IS_SYSTEM) == 0
&& mLastScanError == PackageManager.INSTALL_FAILED_INVALID_APK) {
// Delete the apk
Slog.w(TAG, "Cleaning up failed install of " + file);
file.delete();
}
}
}
实际上,在解析APK的过程中涉及到多各层次的方法的调用,并最终生成了Package实例:
有一点需要注意,我省略了一个重载的方法parsePackage(),经过以上的流程,一个APK的AndroidManifest文件就被成功解析成PackageParser.Package实例了。
从以上源码分析可以看出来,当安装一个apk的时候首先要做的就是将其AndroidMainfest.xml文件进行解析。解析其中注册的所有权限和组件等。