本文首先介绍系统的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文件是如何生成的呢?其过程用一张图总结:

   

android系统自带的apk路径 安卓apk安装路径_xml

   由于本文主旨不在APK文件的生成,所以关于这方面的就说这么多了。

APK文件的安装主要流程

    一个apk文件在安装的过程中会按照如下步骤,一步一步完成:

  

android系统自带的apk路径 安卓apk安装路径_xml_02

      我们的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包名为名字的文件夹。如下图是图解:

 

android系统自带的apk路径 安卓apk安装路径_xml文件_03

                                                    (注:此图引用自相关文档资料,出处连接未知)



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实例:


      

android系统自带的apk路径 安卓apk安装路径_android系统自带的apk路径_04

    有一点需要注意,我省略了一个重载的方法parsePackage(),经过以上的流程,一个APK的AndroidManifest文件就被成功解析成PackageParser.Package实例了。

    从以上源码分析可以看出来,当安装一个apk的时候首先要做的就是将其AndroidMainfest.xml文件进行解析。解析其中注册的所有权限和组件等。