Android PackageManager Service 学习笔记

PMS 主要包含三部分的内容

  • 提供一个能够根据Intent 匹配找到Activity,Provider, Service ,即通过intent来找到具体的组件
  • 进行权限的检查,当应用程序需要调用一个需要权限的函数的时候,系统能够判断当前调用者是否具有改该权限。
  • 提供安装删除应用程序的接个口

PMS服务运行时使用两个目录下的xml文件保存相关的包管理信息,
第一个目录是/system/etc/premissions该目录下的xml用于permission的管理,具体包含两个,第一个是定义系统中都包含了那些featrue, 应用程序可以在AndroidManifest.xml中使用 use-featrue 声明程序需要哪些freatrue
例如:
该标签表示应用程序需要使用这个功能,如果required = true, 但是设备上没有wifi,那么程序将无法安装

设备都包含了那些feature就是在该目录下声明的,如果用户给系统添加了GPS或者蓝牙的功能,则必须在该目录下声明相关的xml文件来告诉framework。

该目录下还有一个platform.xml文件,该文件为一些特别的uid和gid分配了一些默认的权限,

第二个目录是/data/system/packages.xml,该文件保存了所有安装程序的基本信息,类似注册表。
下面是从文件里面拷贝出来的一段

<package name="xxxxxxx.xxx" codePath="/data/app/xxxxxxx" nativeLibraryPath="/data/app/com.tools.freereminder-1/lib" primaryCpuAbi="armeabi-v7a" flags="4767300" ft="15a1c7833a0" it="15a1c783548" ut="15a1c783548" version="170208" userId="10223">
    <sigs count="1">
        <cert index="12" key="xxxxxx" />
    </sigs>
    <perms>
        <item name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
        <item name="android.permission.WRITE_SETTINGS" />
        <item name="android.permission.SYSTEM_ALERT_WINDOW" />
        <item name="android.permission.RECEIVE_BOOT_COMPLETED" />
        <item name="android.permission.GET_TASKS" />
        <item name="android.permission.INTERNET" />
        <item name="android.permission.READ_EXTERNAL_STORAGE" />
        <item name="android.permission.ACCESS_COARSE_LOCATION" />
        <item name="android.permission.READ_PHONE_STATE" />
        <item name="android.permission.ACCESS_NETWORK_STATE" />
        <item name="android.permission.DISABLE_KEYGUARD" />
        <item name="android.permission.GET_ACCOUNTS" />
        <item name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <item name="android.permission.VIBRATE" />
        <item name="android.permission.ACCESS_WIFI_STATE" />
        <item name="com.android.launcher.permission.INSTALL_SHORTCUT" />
        <item name="android.permission.WAKE_LOCK" />
    </perms>
    <proper-signing-keyset identifier="322" />
    <signing-keyset identifier="322" />
</package>

PMS 在启动的时候,会从这两个目录中解析相关的xml文件,从而建立一个包信息树,应用程序可以间接从这个信息树中查询所有所需的包信息。

除了PMS外,还有两个辅助系统服务用于程序安装,一个是DefaultContainerService,该服务主要把安装程序复制到程序目录中;另一个是installer服务,该服务实际上不是一个binder,而是一个socket客户端,PMS直接和Socket客户端交互,Socket的服务端主要完成程序文件的解压工作及数据目录的创建,比如从APK文件中提取出dex文件,删除delvik-cache目前下的dex文件,创建程序专属的数据目录。

就像所有的操作系统一样,Android中的程序文件也由相关的程序文件组成,这些程序文件可以分为三个部分。
第一部分:程序文件,所有的系统程序文件保存在/system/app下,所有第三方的应用程序保存在/data/app下。/data/dalvik-cache目录保存了程序中执行的代码,PMS会从APK中提取出dex保存在该目录下,以便以后能快速的运行。
第二部分:framework 库文件,这些库文件位于/system/framework/ 目录下,库文件类型是apk或者jar,系统开机之后,dalvik虚拟机会加载这些库文件,而在PMS启动的时候如果这些jar或者APK还没有被转换为dex文件,PMS会将这些库文件转为dex文件,并且保存到/data/dalvik-cache目录下。(目前为止,我将dex导出之后,从反编译工具去打开这个dex文件,提示不是一个dex文件)
第三部分:程序自身的数据文件.

系统安装一个apk的过程分析

入口方法在于PMS的installPackageAsUser 方法。

在方法的开始会进行一系列的权限检查,然后相关的信息封装成一个message 通过自定义的packagehandler 发送出去,关键代码如下

final File originFile = new File(originPath);
final OriginInfo origin = OriginInfo.fromUntrustedFile(originFile);
final Message msg = mHandler.obtainMessage(INIT_COPY);
final VerificationInfo verificationInfo = new VerificationInfo(
        null , null, -1, callingUid);
final InstallParams params = new InstallParams(origin, null, observer,
        installFlags, installerPackageName, null, verificationInfo, user,
        null, null,null);
params.setTraceMethod("installAsUser").setTraceCookie(System.identityHashCode(params));
msg.obj = params;
Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "installAsUser",
        System.identityHashCode(msg.obj));
Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
        System.identityHashCode(msg.obj));
mHandler.sendMessage(msg);

处理消息的代码可以在PackgetHanlder当中找到,定义在doHandler当中,之前发送的消息类型是INIT_COPY。

处理的代码为下面一段

HandlerParams params = (HandlerParams) msg.obj;
                int idx = mPendingInstalls.size();
                if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params);
                // If a bind was already initiated we dont really
                // need to do anything. The pending install
                // will be processed later on.
                if (!mBound) {
                    Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
                            System.identityHashCode(mHandler));
                    // If this is the only one pending we might
                    // have to bind to the service again.
                    if (!connectToService()) {
                        Slog.e(TAG, "Failed to bind to media container service");
                        params.serviceError();
                        Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
                                System.identityHashCode(mHandler));
                        if (params.traceMethod != null) {
                            Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, params.traceMethod,
                                    params.traceCookie);
                        }
                        return;
                    } else {
                        // Once we bind to the service, the first
                        // pending request will be processed.
                        mPendingInstalls.add(idx, params);
                    }
                } else {
                    mPendingInstalls.add(idx, params);
                    // Already bound to the service. Just make
                    // sure we trigger off processing the first request.
                    if (idx == 0) {
                        mHandler.sendEmptyMessage(MCS_BOUND);
                    }
                }
                break;

首先会去检查一个mBound的状态,这个状态表示和系统service的connect结果,PackageHanlder在内部定义了一个方法,connectToService(), 这个service是DefaultContainerService. 在这个类的说明上可以知道这个类主要是做了关于移动存储设备上文件检查和复制的工作。如果这个时候已经成功绑定了服务,那么可以将安装的请求添加到 mPendingInstalls 这个集合当中去。

接下来发送的消息类型是MSC_BOUND
在这个消息的处理中还是先判断绑定服务的状态,如果判断当前的状态是没有连接,那么这个时候调用prams的serviceError方法,表示安装失败,这个时候也会清空 mPendingInstalls
如果这个时候服务绑定成功,并且mPendingInstalls中有安装请求,这个时候会从集合中拿到第一个安装请求参数,然后开始调用startCopy方法

final boolean startCopy() {
        boolean res;
        try {
            if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);
            if (++mRetries > MAX_RETRIES) {
                Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
                mHandler.sendEmptyMessage(MCS_GIVE_UP);
                handleServiceError();
                return false;
            } else {
                handleStartCopy();
                res = true;
            }
        } catch (RemoteException e) {
            if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT");
            mHandler.sendEmptyMessage(MCS_RECONNECT);
            res = false;
        }
        handleReturnCode();
        return res;
    }

里面加了重试的逻辑,如果重试之后还是失败,那么将进行安装失败通知,核心copy的逻辑在handleStartCopy,这个方法是一个抽象方法,具体的实现看InstallParams。

// If we're already staged, we've firmly committed to an install location
       if (origin.staged) {
           if (origin.file != null) {
               installFlags |= PackageManager.INSTALL_INTERNAL;
               installFlags &= ~PackageManager.INSTALL_EXTERNAL;
           } else if (origin.cid != null) {
               installFlags |= PackageManager.INSTALL_EXTERNAL;
               installFlags &= ~PackageManager.INSTALL_INTERNAL;
           } else {
               throw new IllegalStateException("Invalid stage location");
           }
       }

方法之中首先开始检查安装的位置,如果找到不到将抛出异常。
在检查完安装位置之后,开始安装的的空间是否足够,如果不够,系统会尝试清除一些缓存,再来判断是否可以安装。

如果在上面的检查当中没有出现问题,那么接下来获取安装的位置,并且在这个过程中判断安装是否有版本的回退,如果这个情况也没有,那么将会再设置一次installFlags.

接下来会去检查package verification 是否开启了,如果没有开启将会调用远程服务开始拷贝文件的过程。
关于拷贝的过程可以看InstallArgs其中一个子类的实现,FileInstallArgs, copyApk的真正过程在doCopyApk方法当中。

在doCopyApk的方法当中,首先调用了

File tempDir = mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);

这个方法用来指定res和code存放的临时目录,在这个步骤中创建了一些临时文件,之所以这么做是因为内部文件系统会为临时文件分配内存,提高效率。

imcs.copyPackage(origin.file.getAbsolutePath(), target);

这个imcs的实现在DefaultContainerService当中,安装apk的时候第一步就是检查到这个service的连接。

所以实际上imcs就是 IMediaContainerService.Stub的一个引用,copyPackage 之中又调用了copyPackageInner方法,方法的代码如下

private int copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target)
        throws IOException, RemoteException {
    copyFile(pkg.baseCodePath, target, "base.apk");
    if (!ArrayUtils.isEmpty(pkg.splitNames)) {
        for (int i = 0; i < pkg.splitNames.length; i++) {
            copyFile(pkg.splitCodePaths[i], target, "split_" + pkg.splitNames[i] + ".apk");
        }
    }
    return PackageManager.INSTALL_SUCCEEDED;
}

在copyFile这个方法当中看到了一个熟悉的名字“base.apk”,我们在/data/app/包名下同样有这个文件名的存在,在这里就完成了apk的拷贝。

接下来就是拷贝二进制文件的过程了。