从Android 8.0源码的角度剖析APK打包、安装过程

APK,全称Android Application Package,即Android应用程序包,是Android系统使用的一种应用程序包文件格式,它的作用是将Android程序资源整合在一起,以便Android程序能在Android设备上正常运行。简单地说,就是一个Android应用程序的代码要想在Android设备上运行,必须先进行编译,然后被打包成一个被Android系统所能识别的文件才可以被运行,而这种能被Android系统识别并运行的文件格式就是"APK",而文件后缀为".apk"。

1. APK打包过程

1.1 APK文件结构

 APK文件本质上是一个压缩文件,我们将一个APK文件进行解压,它主要包含以下文件:
在这里插入图片描述

  • assets目录:存放原生的静态资源文件,如图片、JSON配置文件、二进制数据、HTML5等。系统在编译时不会编译该目录下的资源,因此不会像res目录样能被直接通过R.xxx.xxx进行访问,而是需要通过AssetManager以二进制流的形式来读取资源。注意:res/raw目录存储的也是原生的资源文件,但是它能够被Android映射成R.xxx.xxx资源,它们的不同之处在于assets目录下的文件结构支持树形结构目录,而res/raw不支持。另外,assets目录下的单个文件尽量在1MB以下,而res/raw目录下的单个文件可以任意MB。
// 访问assets目录下的资源
AssetManager manager = this.getAssets();
InputStream in = manager.open("image/logo.jpg");//注意路径
Bitmap bitmap = BitmapFactory.decodeStream(in);

// 访问res/raw目录下的资源
InputStream is = this.getResources().openRawResource(R.raw.logo);
Bitmap image = BitmapFactory.decodeStream(is);
  • lib目录:存放应用程序依赖的不同架构(ABI)的.so文件。
  • res目录:存放应用的资源文件,包括图片、字符串、颜色、尺寸、动画文件、布局文件等资源。这个目录下的所有资源都会出现在资源清单文件R.java的索引中。
  • MERA-INF目录:保存应用程序的签名信息,签名信息可以验证APK文件的完整性。
    • MANIFEST.MF:该文件保存了APK包中每个文件得名字及其SHA1哈希值;
    • CERT.SF:该文件保存了MANIFEST.MF文件的哈希值及其文件中每一个哈希项的哈希值;
    • CERT.RSA:该文件保存了APK包的签名和证书的公钥信息;
  • AndroidManifest.xml:Android 应用的配置文件,用于描述 Android 应用的整体情况。每个 Android 应用必须包含一个 AndroidManifest.xml 文件。AndroidManifest.xml 包含了 Android 四大组件的注册信息,权限声明以及 Android SDK 版本信息等,该文件将会被编译成二进制文件。
  • classes.dex:应用程序可执行文件,即Dalvik字节码文件。APK中可能包含多个dex文件,具体要看Android程序的所有方法数是否超过65535,如果超过就进行分包处理,就会出现多个dex文件的情况。
  • Resources.arsc:资源配置文件。该文件用于记录资源文件位置和资源ID之间的映射关系,以便系统能够根据ID去寻找对应的资源路径。该ID实际上与R.java中存储的是一样的,但是R.java只是便于开发者调用资源且保证编译程序不报错,而实际上在程序运行时系统需要根据这个ID从Resources.arsc文件保存的对应的路径中获取资源文件。

1.2 APK打包过程

 一个Android项目主要由Java代码AIDL文件AndroidManifest.xml第三方库.class文件以及res目录资源文件(如图片、字符串、尺寸、布局…)等模块组成,因此对于APK的打包过程,主要就是对这些内容进行编译打包。下图是Google官方提供的详细的APK构建过程:
在这里插入图片描述
 从上图可知,APK的构建过程主要经历七个阶段,其中浅色方框表示每个阶段的输入或输出内容,绿色椭圆框表示所用到的工具,这些工具大部分位于Android SDK目录的build-tools\版本号目录下。下面我们对上述七个阶段作详细的解释:

  • (1) aapt.exe,打包资源

 该阶段的目的是打包res目录资源文件AndroidManifest.xml,使用的工具是aapt.exe,最终生成的中间文件为R.java类、二进制的resource.arscres文件夹(包括二进制的xml、没被改变的图片和res/raw文件等)、二进制的AndroidManifest.xml文件、没有改变的assets文件夹。另外,aapt在打包资源文件之前会检测AndroidManifest.xml、res目录下资源文件名的合法性,如果这两部分出现不符合要求情况话,Android Studio会直接报错无法编译。因此,如果我们遇到编译错误带有aapt信息时,基本上可以确定问题为AndroidManifest.xml配置错误,或者res目录下资源命名不合法或不存在。

  • (2) aidl.exe,将.aidl文件转换为.java文件

 该阶段的目的是将Android工程src/main/aidl目录下的所有.aidl文件转换为.java文件,生成的.java文件位于build/generated/source/aidl目录下,使用的工具是aidl.exe。在从Android6.0源码的角度剖析Binder工作原理一文中,我们曾介绍到AIDL(全称Android Interface Definition Language,Android接口定义语言),是Android系统为了便于开发具备跨进程通信的应用,专门提供的且用于自动生成Java层Binder通信框架的技术。因此,转换得到的Java代码将用于进程间通信的C/S端。

  • (3) javac.exe,编译Java源文件为字节码文件

 该阶段的目的是将所有模块src/main/java目录下的java源文件、aapt生成的R.java文件、aidl生成的.java文件以及BuildConfig.java文件编译成对应的.class字节码文件,使用的工具是jdk/bin/javac.exe。其中,BuildConfig.java文件是根据build.gradle配置自动生成的,我们可以使用它的DEBUG字段来控制日志的输出。BuildConfig.java源码如下:

//..\app\build\generated\source\buildConfig\...\BuildConfig.java
public final class BuildConfig {
  // 该在开发中可控制日志输出
  // 不需要自己手动添加Debug开发,系统会依据BUILD_TYPE自动设定
  // BUILD_TYPE="release"时,DEBUG=false
  public static final boolean DEBUG = Boolean.parseBoolean("true");  
  // 包名
  public static final String APPLICATION_ID = "com.jiangdg.jjusbcamera";
  // 当前编译类型,debug或release
  public static final String BUILD_TYPE = "debug";
  // 产品(渠道包的名称)
  public static final String FLAVOR = "";
  // 版本号
  public static final int VERSION_CODE = 2;
  // 版本名称
  public static final String VERSION_NAME = "1.0.1.20191104";
}
  • (4) dx.bat,将所有.class文件转换为.dex文件

 该阶段的目的是将所有的.class字节码文件(包括第三方库)转换为一个.dex字节码文件,使用的工具是dx.bat。从Android性能优化(2)一文中可知,由于.class文件存在较大的冗余、各个类相互独立且结构也不紧凑,不适合内存和处理器速度有限的系统,因此Android系统选用专门的虚拟机Dalvik或ART来执行专门的字节码文件,这类字节码文件即为.dex文件,它是在.class字节码文件的基础上经过DEX工具压缩、优化后得到的,适用于内存和处理器速度有限的系统。.class文件和.dex文件结构对比图:
在这里插入图片描述

  • (5) apkbuilder,打包生成APK

 该阶段目的是将前几步生成的.dex文件、resources.arsc文件、被编译后的res目录(res/raw目录下的资源没有被编译)和AndroidManifest.xml以及其他资源文件(assets文件夹)打包生成一个.apk文件,使用的工具是sdkpath/tools/sdklib-xxx.jar

  • (6) apksigner.bat,对APK文件签名

 该阶段是对打包生成的.apk文件进行签名,得到的是被签名后的.apk文件,使用的工具是apksigner.bat。签名是一个apk身份的证明,Android系统在安装apk的时候,首先会检验apk的签名,如果发现签名文件不存在或者校验签名失败,就会拒绝安装。对一个apk文件签名后,apk文件根目录下回增加META-INF目录,该目录下有三个文件:CERT.RSA、CERT.SF和MANIFEST.MF。

  • (7) zipalign.exe,对齐优化

 该阶段的目的是对签名后的.apk文件进行对齐处理,得到的是对齐优化后的.apk文件,使用的工具是zipalign.exe。需要注意的是,对齐优化存在于在release打包时,Zipalign是一个android平台上整理APK文件的工具,它对apk中未压缩的数据进行4字节对齐,对齐后就可以使用mmap函数读取文件,可以像读取内存一样对普通文件进行操作。如果没有4字节对齐,就必须显式的读取,这样比较缓慢并且会耗费额外的内存。

2. APK安装过程

 在Android设备上安装一个APK,是从执行以下一段程序开始的,该段程序采用隐式Intent的方式,通过指定intent的type属性为"application/vnd.android.package-archive"来启动Android系统中一个名为PackageInstaller的系统应用,PackageInstaller是系统内置的应用程序,用于安装和卸载应用。

// 启动PackageInstaller系统应用
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.fromFile(new File(apkPath)), 
                      "application/vnd.android.package-archive");
context.startActivity(intent);

// Android8.0\packages\apps\PackageInstaller\AndroidManifest.xml
...
<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>
...

 从PackageInstaller的AndroidManifest.xml可知,在PackageInstaller中能够隐式匹配application/vnd.android.package-archive的Activity为InstallStart,也就是说InstallStartPackageInstaller应用的入口。InstallStart并没有显示任何界面,而是直接启动了一个名为InstallStaging的Activity,它首先会将content协议的Uri转换为File协议,然后再调起PackageInstallerActivity进入安装流程。因为对于Android7.0以上系统来说,会使用FileProvider来处理URI ,FileProvider会隐藏共享文件的真实路径,将路径转换成content://Uri路径。PackageInstallerActivity就是我们安装APK时弹出来的询问是否安装对话框,当点击安装或"OK"时,它会启动名为InstallInstalling的Activity,该Activity就是我们看到的安装进度对话框。InstallInstalling主要用于向包管理器发送安装包的信息并处理包管理的回调,即通过IO流的形式将APK的信息写入到PackageInstaller.Session中,PackageInstaller.Session可理解为PackageInstallerSession代理对象,真正的实现位于PackageInstallerSession对象中,也就是说,这个发送APK信息的过程实质上是一个基于Binder机制的跨进程通信。至此,APK安装执行流程进入进入Framework层,PackageInstaller进程的初始化工作就执行完毕了。在PackageInstallerSession中,它会最终将APK信息交给PackageManagerService(PMS)处理,PMS通过向PackageHandler发送消息来驱动APK的拷贝和安装工作,其中APK的拷贝由DefaultContainerService完成,而安装由PackageManagerService完成。PackageInstaller进程初始化过程时序图如下:
在这里插入图片描述
 这里总结下PackageInstaller进程初始化工作有哪些:

  • 向用户提供APK安装的交互界面,比如安装确认、安装进度;
  • 将content协议的Uri转换为File协议,以获取APK存储的真实路径;
  • 将APK文件以IO流的形式提交给PMS,进入下一步处理;

2.1 拷贝APK

 APK拷贝过程时序图如下:
在这里插入图片描述
 从上述时序图可知,APK的拷贝过程是从PMS的installStage方法开始的,该方法会向PackageHandler发送一个INIT_COPY消息并传输一个InstallParams对象(这个对象后面有用),PackageHandler是PMS的一个内部类,专门用于处理PMS中发送过来的消息,而接收处理消息的方法PackageHandler的handleMessage,该方法会继续调用doHandleMessage方法实现具体的处理操作。PackageHandler接收并处理INIT_COPY消息源码如下:

// PackageManagerService.installStage
void installStage(String packageName, File stagedDir, String stagedCid,
                  IPackageInstallObserver2 observer, PackageInstaller.SessionParams 	
            sessionParams,String installerPackageName, int installerUid, UserHandle user,
                  Certificate[][] certificates) {
    ...
        final Message msg = mHandler.obtainMessage(INIT_COPY);
    final int installReason = fixUpInstallReason(installerPackageName, installerUid,
                                                 sessionParams.installReason);
    // 创建InstallParams
    final InstallParams params = new InstallParams(..);
    msg.obj = params;
    // 发送拷贝消息INIT_COPY
    mHandler.sendMessage(msg);
}

// PackageManagerService.PackageHandler内部类
class PackageHandler extends Handler {
    ...
    private boolean connectToService() {
        if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to bind to" +
                                    " DefaultContainerService");
        // 绑定启动com.android.defcontainer.DefaultContainerService服务
        // 并注册连接事件监听器mDefContainerConn
        // 该服务的目的是拷贝APK
        Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
        Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
        if (mContext.bindServiceAsUser(service, mDefContainerConn, // 注释3
                                       Context.BIND_AUTO_CREATE, UserHandle.SYSTEM)) {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            mBound = true;
            return true;
        }
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        return false;
    }
	...
        
    // 处理Handler消息
    public void handleMessage(Message msg) {
        try {
            doHandleMessage(msg);
        } finally {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        }
    }

    void doHandleMessage(Message msg) {
        switch (msg.what) {
            // INIT_COPY=5
            // 处理拷贝消息
            case INIT_COPY: {
                HandlerParams params = (HandlerParams) msg.obj;
                int idx = mPendingInstalls.size();
                // mBound服务绑定状态,默认为false
                if (!mBound) {  // 注释1
                    // 连接到服务
                    if (!connectToService()) {  // 注释2
                        ...
                    }
                } 
            }
            ...
        }
}

 在PackageHandler的doHandleMessage方法中,当INIT_COPY消息到来时,会直接进入INIT_COPY消息处理流程。首先,注释1处会判断标志位mBound的状态,这个标志位代表绑定到DefaultContainerService服务的状态,默认值为false表示未绑定。如果绑定成功,mBound会被置true,这里我们只考虑未绑定的情况。然后,注释2处为当mBound为false时,会去PackageHandler的connectToService方法,该方法会去绑定启动DefaultContainerService服务并将mBound置true,如注释3处。由于APK拷贝是一个比较耗时的操作,因此这个DefaultContainerService运行在一个单独的进程之中,将由它来完成APK的检查与拷贝工作。另外,PMS为了监听绑定DefaultContainerService服务的启动状态,在调用bindServiceAsUser时注册了一个DefaultContainerConnection对象,该对象继承于ServiceConnection,当绑定服务成功后,其onServiceConnected方法会被调用。DefaultContainerConnection源码如下:

// PackageManagerService.DefaultContainerConnection
class DefaultContainerConnection implements ServiceConnection {
    // 与DefaultContainerService建立连接后被调用
    // 向mHandler发送MCS_BOUND消息
    public void onServiceConnected(ComponentName name, IBinder service) {
        if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceConnected");
        // DefaultContainerService的Binder代理对象
        final IMediaContainerService imcs = IMediaContainerService.Stub
            .asInterface(Binder.allowBlocking(service));
        mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs));
    }

    public void onServiceDisconnected(ComponentName name) {
        if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceDisconnected");
    }
}

 当PMS绑定启动DefaultContainerService服务后,DefaultContainerConnection连接事件监听器的onServiceConnected方法会被回调,这就意味着SystemManager进程(PMS服务由此进程启动)和DefaultContainerService进程之间基于Binder机制的跨进程通信链路建立完毕。在onServiceConnected方法中,它首先获取IMediaContainerService.Stub这个实体Binder代理对象imcs,然后再向PackageHandler发送一个MCS_BOUND消息。接下来,我们看PackageHandler接收到MCS_BOUND消息后,做了些什么。

// PackageManagerService.PackageHandler.doHandleMessage
void doHandleMessage(Message msg) {
    switch (msg.what) {
		case MCS_BOUND: {
            // 取出IMediaContainerService.Stub的Binder代理对象
            if (msg.obj != null) {
                mContainerService = (IMediaContainerService) msg.obj;
            }
            // mContainerService不为null
            // 发起远程拷贝
            if (mContainerService == null) {
                ...
            } else if (mPendingInstalls.size() > 0) {
                HandlerParams params = mPendingInstalls.get(0);
                if (params != null) {
                    // 调用HandlerParams的startCopy方法
                    if (params.startCopy()) {
                        ...
                    }
                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                }
            } 
            break;
        }
    }
}

// PackageManagerService.HandlerParams.startCopy
final boolean startCopy() {
    // InstallParams继承于HandlerParams
    // 调用InstallParams的handleStartCopy
    handleStartCopy();
    
    // 调用InstallParams的handleReturnCode,开始解析APK
    handleReturnCode();
    return res;
}

 从上述源码可知,当PackageHandler接收到MCS_BOUND消息后,会首先缓存IMediaContainerService.Stub实体Binder代理对象mContainerService,然后调用HandlerParams的startCopy方法,该方法主要执行两步操作,一是调用InstallParams的handleStartCopy,二是调用InstallParams的handleReturnCode继续拷贝完毕后的下一步工作,即解析APK。这里为什么是InstallParams?在前面我们提到过这个类,是之前传进来的且继承于HandlerParams。handleStartCopy等部分源码如下:

// InstallParams.handleStartCopy
public void handleStartCopy() throws RemoteException {
    ...
    final InstallArgs args = createInstallArgs(this);
    mArgs = args;
    ...
    ret = args.copyApk(mContainerService, true);
}

// PackageManagerService.createInstallArgs
private InstallArgs createInstallArgs(InstallParams params) {
    if (params.move != null) {
        return new MoveInstallArgs(params);
    } else if (installOnExternalAsec(params.installFlags) || params.isForwardLocked()) {
        // 安装到SDCard
        return new AsecInstallArgs(params);
    } else {
        // 安装到data分区(内部存储器)
        return new FileInstallArgs(params);
    }
}

// PackageManagerService.FileInstallArgs.copyAPK
int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyApk");
    try {
        return doCopyApk(imcs, temp);
    } finally {
        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    }
}
// PackageManagerService.FileInstallArgs.doCopyAPK
private int doCopyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
    try {
        final boolean isEphemeral = (installFlags & 
                                     PackageManager.INSTALL_INSTANT_APP) != 0;
        //创建临时文件存储目录
        // /data/app/vmdl1223.tmp
        final File tempDir =
            mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);
        codeFile = tempDir;
        resourceFile = tempDir;
        } catch (IOException e) {
            Slog.w(TAG, "Failed to create copy file: " + e);
            return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
        }
    ...
    // 调用远程DefaultContainerService实体Binder的copyPackage
    // 执行真正的拷贝操作
    ret = imcs.copyPackage(origin.file.getAbsolutePath(), target);
    if (ret != PackageManager.INSTALL_SUCCEEDED) {
        Slog.e(TAG, "Failed to copy package");
        return ret;
    }
    ...
}

 首先,InstallParams.handleStartCopy会获取一个InstallArgs对象,这个InstallArgs是一个抽象类,通过查看PMS的createInstallArgs方法得值,它有三个子类,即MoveInstallArgs、AsecInstallArgs、FileInstallArgs,其中,FileInstallArgs用于处理安装到非ASEC的存储空间的APK,也就是内部存储空间(Data分区),AsecInstallArgs用于处理安装到ASEC中(mnt/asec)即SD卡中的APK。由于现在大部分手机都直接将APK安装到内部存储器中,这里我们以分析FileInstallArgs为例。然后,InstallParams.handleStartCopy接着会调用FileInstallArgs的copyApk方法,该方法继续调用doCopyApk方法并在这个方法中完成临时文件存储目录的创建,和调用远程Binder(DefaultContainerService.mBinder)的copyPackage方法,最终将拷贝任务交给远程DefaultContainerService服务进程处理。DefaultContainerService部分源码如下:

// DefaultContainerService.mBinder
private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
    ...
    @Override
    public int copyPackage(String packagePath, IParcelFileDescriptorFactory target) {
        if (packagePath == null || target == null) {
            return PackageManager.INSTALL_FAILED_INVALID_URI;
        }

        PackageLite pkg = null;
        try {
            final File packageFile = new File(packagePath);
            // 轻度解析APK
            // 首先,判断是单个APK还是APK目录;
            // 然后,将一个APkLite(轻量级包信息)或多个ApkLite封装为一个PackageLite返回
            pkg = PackageParser.parsePackageLite(packageFile, 0);
            // 拷贝APK到指定目录
            // /data/app/com.example/base.apk
			// /data/app/com.example/split_foo.apk
            return copyPackageInner(pkg, target);
        } catch (PackageParserException | IOException | RemoteException e) {
            Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e);
            return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
        }
    }
}

 最终,在DefaultContainerService服务中完成以下两个任务:

1)轻度解析APK,得到一个轻量级包信息PackageLite,为后续解析APK做准备工作。

2)拷贝APK到/data/app目录下。这里需要注意的是,为了解决65536上线以及APK安装包越来越大的问题,Android 5.0引入了Split APK机制,该机制允许将一个APK拆分成多个独立的APK。因此,拷贝APK需要分两种类型,即安装的是一个完整的APK,即base APK,Android称其为Monolithic;安装文件在一个文件目录中,这些APK由一个base APK和一个或多个split APK组成,Android称其为Cluster。比如:

/data/app/com.example/base.apk
/data/app/com.example/split_foo.apk

Android不同的目录存放不同类型的应用,如下所示:

/system/framwork:保存的是资源型的应用程序,它们用来打包资源文件。
/system/app:保存系统自带的应用程序。
/data/app:保存用户安装的应用程序。
/data/app-private:保存受DRM保护的私有应用程序。
/vendor/app:保存设备厂商提供的应用程序。

2.2 解析APK

 APK解析过程时序图如下:
在这里插入图片描述
 在1.3.1中我们谈到,当APK拷贝操作完成后会调用PackageManagerService.InstallParams的handleReturnCode方法进入APK解析流程。handleReturnCode方法很简单,只是调用了PMS的processPendingInstall方法,从该方法源码可知,由于APK安装解析是一个耗时的操作,因此它通过异步任务的形式将首先在安装前删除一些残留文件,然后开始解析APK,再然后删除安装后残留文件,最后向PackageHandler发送一个POST_INSTALL消息。PackageHandler接收到POST_INSTALL会发送一个removed广播,并请求运行授权。PMS.processPendingInstall方法部分源码如下:

private void processPendingInstall(final InstallArgs args, final int currentStatus) {
    ...
    mHandler.post(new Runnable() {
       public void run() {
           if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
               // 1. 安装前操作,删除安装残留文件
               args.doPreInstall(res.returnCode);
               // 2 .对APK进行解析
               synchronized (mInstallLock) {
                   installPackageTracedLI(args, res);
               }
               // 3. 删除安装后的残余文件
               args.doPostInstall(res.returnCode, res.uid);
            }
            ...
            // 4. 发送信息POST_INSTALL
            Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
            mHandler.sendMessage(msg);
       }
    }
}

 接下来,我们通过PMS的installPackageTracedLI详细分析APK的解析安装过程。在PMS的installPackageTracedLI方法中,它的实现很简单,只是调用了PMS的installPackageLI方法,该方法很长很长,但主要做了如下工作:

  • 解析AndroidManifest.xml文件得到应用信息、各组件信息和权限等,创建PackageParser.Package对象;
  • 检查APK是否已经安装,并进行签名校验;
  • 对dex文件进行优化,需要注意的是,不同的虚拟机操作会有所不同,如果是Davlik虚拟机,那么dex文件将被优化生成odex.dex文件,保存在/data/dalvik-cache目录下;如果是ART虚拟机,将生成oat文件;
  • 将复制的APK文件目录,重命名以包名为目录的文件;
  • 替换APK或者安装新的APK;

PMS.installPackageTracedLI源码如下:

private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
    ...
    // 1. 解析package信息,主要是AndroidManifest.xml
    //  最终调用的是PackageParser.ParseBaseApkCommon方法
    final PackageParser.Package pkg;
    try {
        pkg = pp.parsePackage(tmpPackageFile, parseFlags);
    } catch (PackageParserException e) {
        res.setError("Failed parse during installPackageLI", e);
        return;
    } finally {
        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    }
    // 2. 签名校验
    try {
        verifySignaturesLP(signatureCheckPs, pkg);
    } catch (PackageManagerException e) {
        res.setError(e.error, e.getMessage());
        return;
    }
    // 3. 对dex进行优化
    mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles,
                                       null , false ,
                                       getCompilerFilterForReason(REASON_INSTALL),
                                       getOrCreateCompilerPackageStats(pkg),
                                       mDexManager.isUsedByOtherApps(pkg.packageName));
    // 4. 将/data/app/vmdl18300388.tmp/base.apk,重命名为/data/app/包名-1/base.apk。
    if (!args.doRename(res.returnCode, pkg, oldCodePath)) {
        res.setError(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");
        return;
    }
    // 5. 替换或新安装APK
    if (replace) {
    	replacePackageLIF(pkg, parseFlags, scanFlags, args.user,
                installerPackageName, res, args.installReason); 
    } else {
    	installNewPackageLIF(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
            args.user, installerPackageName, volumeUuid, res, args.installReason); 
	}
}

 在PMS.installNewPackageLIF方法中,主要做了如下工作:

  • 调用PMS.scanPackageTracedLI方法重新扫描apk文件,该方法继续调用PMS的scanPackageLI方法,而scanPackageLI又调用了PMS的scanPackageDityLI,由它来完成向PSM注册四大组件信息等操作;
  • 更新PackageSettings,该类记录了APK配置的动态信息;
  • 安装完成后,为APP准备数据;

PMS.installNewPackageLIF方法部分源码如下:

private void installNewPackageLIF(PackageParser.Package pkg, final int policyFlags,
                                  int scanFlags, UserHandle user, String 
                                  installerPackageName, String volumeUuid,
                                  PackageInstalledInfo res, int installReason) {
    String pkgName = pkg.packageName;
    ...
    // 1. 调用scanPackageTracedLI扫描APK
    PackageParser.Package newPackage = scanPackageTracedLI(pkg,policyFlags,
                       scanFlags,System.currentTimeMillis(), user);
    // 2. 更新PackageSettings
    updateSettingsLI(newPackage, installerPackageName, null, 
                     res, user, installReason);
    // 3.执行到这里说明APP已经安装完毕
    // 准备APP数据
    if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
        prepareAppDataAfterInstallLIF(newPackage);

    } 
    ...
}

APK安装大致流程图:

img

发布了83 篇原创文章 · 获赞 293 · 访问量 30万+

猜你喜欢

转载自blog.csdn.net/AndrExpert/article/details/103318853