Jimmy Chen

A Programmer

(原创)pm指令源码分析

  平时在开发调试的时候用pm指令用得蛮多的,pm指令可以列出系统支持的feature、permission和当前已安装的apk等信息。所以今天我们来阅读一下pm指令的代码,做下简单分析。

Android.mk

  首先我们来查看一下编译pm指令的Android.mk,看看pm指令包含哪些源码文件。Android.mk文件路劲为frameworks/base/cmds/pm

# Copyright 2007 The Android Open Source Project
#
LOCAL_PATH:= $(call my-dir)

# pmlib库由当前文件夹下所有java文件编译得到
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_MODULE := pmlib
# 用于指定目标文件名为pm.jar
LOCAL_MODULE_STEM := pm
include $(BUILD_JAVA_LIBRARY)

# pm指令使用的src文件是当前目录下的pm
include $(CLEAR_VARS)
LOCAL_MODULE := pm
# pm可执行文件
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_SRC_FILES := pm
# pm指令依赖pmlib
LOCAL_REQUIRED_MODULES := pmlib
include $(BUILD_PREBUILT)

pm指令源码

  从上面的Android.mk可以了解都pm是一个预编译的可执行文件,文件源码为pm,接下来我们查看该文件的源码

# Script to start "pm" on the device, which has a very rudimentary
# shell.
#
base=/system
export CLASSPATH=$base/framework/pm.jar
exec app_process $base/bin com.android.commands.pm.Pm "$@"

从注释处就可以看出,这是一个shell文件。因为shell在native层执行,所以需要在shell文件中通过app_process来创建一个Java进程,随后执行com.android.commands.pm.Pm包的main方法。接下来我们接着分析com.android.commands.pm.Pm的main方法。

pm.jar

  main方法如下:

public static void main(String[] args) {
    int exitCode = 1;
    try {
        // 转而调用run方法
        exitCode = new Pm().run(args);
    } catch (Exception e) {
        Log.e(TAG, "Error", e);
        System.err.println("Error: " + e);
        if (e instanceof RemoteException) {
            System.err.println(PM_NOT_RUNNING_ERR);
        }
    }
    System.exit(exitCode);
}

main方法只是个幌子,真正做事情的是run方法

public int run(String[] args) throws RemoteException {
    boolean validCommand = false;
    if (args.length < 1) {
        // 参数个数不对,显示pm的使用方法
        return showUsage();
    }
    // 获取AcountManagerService
    mAm = IAccountManager.Stub.asInterface(ServiceManager.getService(Context.ACCOUNT_SERVICE));
    // 获取UserManagerService
    mUm = IUserManager.Stub.asInterface(ServiceManager.getService(Context.USER_SERVICE));
    // 获取PackageManagerService
    mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));

    // 获取PackageInstaller
    if (mPm == null) {
        System.err.println(PM_NOT_RUNNING_ERR);
        return 1;
    }
    mInstaller = mPm.getPackageInstaller();

    mArgs = args;
    String op = args[0];
    mNextArg = 1;

    // 根据第一个参数判断需要调用的方法
    // 列出相关信息
    if ("list".equals(op)) {
        return runList();
    }

    if ("path".equals(op)) {
        return runPath();
    }

    if ("dump".equals(op)) {
        return runDump();
    }

    // 安装应用
    if ("install".equals(op)) {
        return runInstall();
    }

    if ("install-create".equals(op)) {
        return runInstallCreate();
    }

    if ("install-write".equals(op)) {
        return runInstallWrite();
    }

    if ("install-commit".equals(op)) {
        return runInstallCommit();
    }

    if ("install-abandon".equals(op) || "install-destroy".equals(op)) {
        return runInstallAbandon();
    }

    if ("set-installer".equals(op)) {
        return runSetInstaller();
    }

    // 卸载应用
    if ("uninstall".equals(op)) {
        return runUninstall();
    }

    // 下面还有一堆的指令可以用
    ................

    // 简化用法,例如: pm -lf和pm list packages -f是一样的
    try {
        if (args.length == 1) {
            if (args[0].equalsIgnoreCase("-l")) {
                validCommand = true;
                return runShellCommand("package", new String[] { "list", "package" });
            } else if (args[0].equalsIgnoreCase("-lf")) {
                validCommand = true;
                return runShellCommand("package", new String[] { "list", "package", "-f" });
            }
        } else if (args.length == 2) {
            if (args[0].equalsIgnoreCase("-p")) {
                validCommand = true;
                return displayPackageFilePath(args[1], UserHandle.USER_SYSTEM);
            }
        }
        return 1;
    } finally {
        if (validCommand == false) {
            if (op != null) {
                System.err.println("Error: unknown command '" + op + "'");
            }
            showUsage();
        }
    }
}

pm支持的功能用法很多,如果不清楚可以adb shell连接到手机后直接执行pm列出pm支持的用法。这一篇我们分析先分析pm isntall xxx.apkpm list packages -f方法。

pm install安装apk

  从上面run方法可知,pm install方法主要是调用runInstall方法

private int runInstall() throws RemoteException {
    long startedTime = SystemClock.elapsedRealtime();
    // 初始化安装需要使用的参数
    final InstallParams params = makeInstallParams();
    // 待安装应用的路径
    final String inPath = nextArg();
    if (params.sessionParams.sizeBytes == -1 && !STDIN_PATH.equals(inPath)) {
        // 根据文件创建文件解析
        File file = new File(inPath);
        if (file.isFile()) {
            try {
                // 解析APK文件,获取相关信息
                ApkLite baseApk = PackageParser.parseApkLite(file, 0);
                PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
                        null, null);
                // 计算APK所需空间大小
                params.sessionParams.setSize(
                        PackageHelper.calculateInstalledSize(pkgLite, false,
                        params.sessionParams.abiOverride));
            } catch (PackageParserException | IOException e) {
                System.err.println("Error: Failed to parse APK file: " + e);
                return 1;
            }
        } else {
            System.err.println("Error: Can't open non-file: " + inPath);
            return 1;
        }
    }

    // doCreateSession会调用Installers的createSession方法来创建SessionID
    final int sessionId = doCreateSession(params.sessionParams,
            params.installerPackageName, params.userId);

    try {
        if (inPath == null && params.sessionParams.sizeBytes == -1) {
            System.err.println("Error: must either specify a package size or an APK file");
            return 1;
        }
        // 这里也和PackageInstaller类似,将Package的信息写入到PackageInstaller的session里面
        if (doWriteSession(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
                false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
            return 1;
        }
        // 这里最后也是调用Session的commit方法,将安装工作交给PackageManagerService来处理
        Pair status = doCommitSession(sessionId, false /*logSuccess*/);
        if (status.second != PackageInstaller.STATUS_SUCCESS) {
            return 1;
        }
        // 按照成功,打印log
        Log.i(TAG, "Package " + status.first + " installed in " + (SystemClock.elapsedRealtime()
                - startedTime) + " ms");
        System.out.println("Success");
        return 0;
    } finally {
        try {
            mInstaller.abandonSession(sessionId);
        } catch (Exception ignore) {
        }
    }
}

如果有看博主前面对PackageInstaller源码分析的博文的话,对上面部分的代码就比较好了解了。所以这里也不多做解析。

pm list packages -f

  list功能走的是runList方法

/**
 * Execute the list sub-command.
 *
 * pm list [package | packages]
 * pm list permission-groups
 * pm list permissions
 * pm list features
 * pm list libraries
 * pm list instrumentation
 */
private int runList() {
    final String type = nextArg();
    if ("users".equals(type)) {
        return runShellCommand("user", new String[] { "list" });
    }
    return runShellCommand("package", mArgs);
}

  runList通过调用runShellCommand实现。

private int runShellCommand(String serviceName, String[] args) {
    // 消息处理线程
    final HandlerThread handlerThread = new HandlerThread("results");
    handlerThread.start();
    try {
        // 通过ServiceManager的getService方法获取到对应的service服务
        ServiceManager.getService(serviceName).shellCommand(
                FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
                args, new MyShellCallback(),
                new ResultReceiver(new Handler(handlerThread.getLooper())));
        return 0;
    } catch (RemoteException e) {
        e.printStackTrace();
    } finally {
        handlerThread.quitSafely();
    }
    return -1;
}

  上面通过ServiceManager的getService方法获取到对应的service,从前面的调用可以看到,调用的service是package。博主前面对PackageManagerService的介绍有提及,这个package服务就是PackageManagerService。所以这里应该调用的是PackageManagerService的shellCommand方法。按照binder调用过程列举如下:

  1. BinderProxy.shellCommand
  2. BinderProxy.transact
  3. transactNative
  4. 通过JNI调用到android_util_Binder.cpp中的android_os_BinderProxy_transact方法
  5. binder.transact
  6. binder.shellCommand
  7. binder.onShellCommand

具体的调用大家可以按照上面的过程查看,这里就省略掉不写了。按照最后的binder.onShellCommand方法,这个方法一般到了具体的service里面,都是会被重载的,所以我们可以直接看PackageManagerService的onShellCommand方法。

@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
        FileDescriptor err, String[] args, ShellCallback callback,
        ResultReceiver resultReceiver) {
    // 转而调用PackageManagerShellCommand的exec方法
    (new PackageManagerShellCommand(this)).exec(
            this, in, out, err, args, callback, resultReceiver);
}

  onShellCommand方法继承自ShellCommand方法,所以这里调用的是ShellCommand的exec方法。

public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
        String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
    String cmd;
    int start;
    if (args != null && args.length > 0) {
        cmd = args[0];
        start = 1;
    } else {
        cmd = null;
        start = 0;
    }
    // 初始化参数
    init(target, in, out, err, args, callback, start);
    // 记录参数
    mCmd = cmd;
    mResultReceiver = resultReceiver;

    if (DEBUG) Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget);
    int res = -1;
    try {
        // 调用onCommand方法
        res = onCommand(mCmd);
        if (DEBUG) Slog.d(TAG, "Executed command " + mCmd + " on " + mTarget);
    } catch (SecurityException e) {
        PrintWriter eout = getErrPrintWriter();
        eout.println("Security exception: " + e.getMessage());
    ...............
}

  紧接着调用onCommand方法进行处理。

@Override
public int onCommand(String cmd) {
    if (cmd == null) {
        return handleDefaultCommands(cmd);
    }

    final PrintWriter pw = getOutPrintWriter();
    try {
        switch(cmd) {
            case "install":
                return runInstall();
            case "install-abandon":
            case "install-destroy":
                return runInstallAbandon();
            case "install-commit":
                return runInstallCommit();
            case "install-create":
                return runInstallCreate();
            case "install-remove":
                return runInstallRemove();
            case "install-write":
                return runInstallWrite();
            case "install-existing":
                return runInstallExisting();
            case "compile":
                return runCompile();
            case "reconcile-secondary-dex-files":
                return runreconcileSecondaryDexFiles();
            case "bg-dexopt-job":
                return runDexoptJob();
            case "dump-profiles":
                return runDumpProfiles();
            case "list":
                return runList();
            case "uninstall":
                return runUninstall();
            case "resolve-activity":
                return runResolveActivity();
            case "query-activities":
                return runQueryIntentActivities();
            case "query-services":
                return runQueryIntentServices();
            case "query-receivers":
                return runQueryIntentReceivers();
            case "suspend":
                return runSuspend(true);
            case "unsuspend":
                return runSuspend(false);
            case "set-home-activity":
                return runSetHomeActivity();
            case "get-privapp-permissions":
                return runGetPrivappPermissions();
            case "get-privapp-deny-permissions":
                return runGetPrivappDenyPermissions();
            case "get-instantapp-resolver":
                return runGetInstantAppResolver();
            case "has-feature":
                return runHasFeature();
            default:
                return handleDefaultCommands(cmd);
        }
    } catch (RemoteException e) {
        pw.println("Remote exception: " + e);
    }
    return -1;
}

又是各种判断后调用具体的方法进行处理。这里是runList

private int runList() throws RemoteException {
    final PrintWriter pw = getOutPrintWriter();
    final String type = getNextArg();
    if (type == null) {
        pw.println("Error: didn't specify type of data to list");
        return -1;
    }
    switch(type) {
        case "features":
            return runListFeatures();
        case "instrumentation":
            return runListInstrumentation();
        case "libraries":
            return runListLibraries();
        case "package":
        case "packages":
            return runListPackages(false /*showSourceDir*/);
        case "permission-groups":
            return runListPermissionGroups();
        case "permissions":
            return runListPermissions();
    }
    pw.println("Error: unknown list type '" + type + "'");
    return -1;
}

  紧接着是packages,所以调用runListPackages方法

private int runListPackages(boolean showSourceDir) throws RemoteException {
    final PrintWriter pw = getOutPrintWriter();
    int getFlags = 0;
    boolean listDisabled = false, listEnabled = false;
    boolean listSystem = false, listThirdParty = false;
    boolean listInstaller = false;
    boolean showUid = false;
    boolean showVersionCode = false;
    int uid = -1;
    int userId = UserHandle.USER_SYSTEM;
    try {
        String opt;
        // 对后续选项参数进行解析处理
        while ((opt = getNextOption()) != null) {
            switch (opt) {
                case "-d":
                    listDisabled = true;
                    break;
                case "-e":
                    listEnabled = true;
                    break;
                case "-f":
                    showSourceDir = true;
                    break;
                case "-i":
                    listInstaller = true;
                    break;
                case "-l":
                    // old compat
                    break;
                case "-s":
                    listSystem = true;
                    break;
                case "-U":
                    showUid = true;
                    break;
                case "-u":
                    getFlags |= PackageManager.MATCH_UNINSTALLED_PACKAGES;
                    break;
                case "-3":
                    listThirdParty = true;
                    break;
                case "--show-versioncode":
                    showVersionCode = true;
                    break;
                case "--user":
                    userId = UserHandle.parseUserArg(getNextArgRequired());
                    break;
                case "--uid":
                    showUid = true;
                    uid = Integer.parseInt(getNextArgRequired());
                    break;
                default:
                    pw.println("Error: Unknown option: " + opt);
                    return -1;
            }
        }
    } catch (RuntimeException ex) {
        pw.println("Error: " + ex.toString());
        return -1;
    }

    final String filter = getNextArg();

    @SuppressWarnings("unchecked")
    // 调用PackageManagerService的getInstalledPackages方法获取安装的应用列表
    final ParceledListSlice slice =
            mInterface.getInstalledPackages(getFlags, userId);
    final List packages = slice.getList();

    final int count = packages.size();
    // 遍历列表,按照上面的选项要求打印相关的应用信息
    for (int p = 0; p < count; p++) {
        final PackageInfo info = packages.get(p);
        if (filter != null && !info.packageName.contains(filter)) {
            continue;
        }
        if (uid != -1 && info.applicationInfo.uid != uid) {
            continue;
        }
        final boolean isSystem =
                (info.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0;
        if ((!listDisabled || !info.applicationInfo.enabled) &&
                (!listEnabled || info.applicationInfo.enabled) &&
                (!listSystem || isSystem) &&
                (!listThirdParty || !isSystem)) {
            pw.print("package:");
            if (showSourceDir) {
                pw.print(info.applicationInfo.sourceDir);
                pw.print("=");
            }
            pw.print(info.packageName);
            if (showVersionCode) {
                pw.print(" versionCode:");
                pw.print(info.applicationInfo.versionCode);
            }
            if (listInstaller) {
                pw.print("  installer=");
                pw.print(mInterface.getInstallerPackageName(info.packageName));
            }
            if (showUid) {
                pw.print(" uid:");
                pw.print(info.applicationInfo.uid);
            }
            pw.println();
        }
    }
    return 0;
}

这篇就到这里了,pm的源码看起来还是比较简单的,但是却也是挺有用的,后续如果需要开发类似的工具,这里很多代码都是可以借鉴的。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d 博主赞过: