小文字 吃饭,睡觉,溜狗头

获取进程名的几种方式

img

背景

获取Android指定pid的进程名该怎么操作?如果你曾经遇到过类似需求一定知道有多种办法,那么这些获取进程名的方法速度上有什么差异么?用那种方式最合适呢?

本文存粹为了对比集中获取进程的方式,做一个简单分析。

0x1 ActivityManager

第一种通过系统的ActivityManager来获取当前运行的所有进程,通过遍历比较pid来找出目标进程名。

private String processNameFromAMS(int pid) {
    String processName = "";
    ActivityManager manager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
    for (ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) {
        if (processInfo.pid == pid) {
            processName = processInfo.processName;
            break;
        }
    }
    return processName;
}

不过系统自5.x之后开始限制getRunningAppProcesses,只能返回当前app的相关进程。 ActivityManager.html#getRunningAppProcesses()

除了ActivityManager之外,类似的也可以去找其他的Manager

0x2 /proc/pid/cmdline

另外一种比较通用的方式是读取Linux的系统文件,因为Android本身是基于Linux的,而在Linux中一切都是文件,所以通过进程相关的文件就可以读取到指定进程名。

private String processNameFromLinuxFile(int pid) {
    String processName = "";
    BufferedReader cmdlineReader = null;
    try {
        cmdlineReader = new BufferedReader(new InputStreamReader(
                new FileInputStream("/proc/" + pid + "/cmdline"),
                "iso-8859-1"));
        int c;
        StringBuilder builder = new StringBuilder();
        while ((c = cmdlineReader.read()) > 0) {
            builder.append((char) c);
        }
        builder.trimToSize();
        processName = builder.toString();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (cmdlineReader != null) {
            try {
                cmdlineReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return processName;
}

0x3 ActivityThread

个人认为通过Linux文件读取是最好的办法,稳定且不受版本限制,但是读取一个文件还是有开销的,即使进行线程名缓存复用,还是免不了首次开销。笔者在mi 4上测试了一下大概2毫秒左右。

其实除了通过Linux文件还可以通过ActivityThread的成员变量来获取,不过需要借助反射技术.

Application/mLoadedApk/mActivityThread#getProcessName

private String processNameFromReflect(Application app) {
    String processName = null;
    try {
        Field loadedApkField = app.getClass().getField("mLoadedApk");
        loadedApkField.setAccessible(true);
        Object loadedApk = loadedApkField.get(app);

        Field activityThreadField = loadedApk.getClass().getDeclaredField("mActivityThread");
        activityThreadField.setAccessible(true);
        Object activityThread = activityThreadField.get(loadedApk);

        Method getProcessName = activityThread.getClass().getDeclaredMethod("getProcessName", null);
        processName = (String) getProcessName.invoke(activityThread, null);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return processName;
}

好奇的你一定想知道为什么ActivityThread可以变相获取到进程名?

0x4 ActivityThread 跟踪

这个实际上有点意思,app启动后开辟了一个进程,在经过一系列的套路之后,会把当前的进程名字回调给Java层保存。

android/app/Application.java

public class Application extends ContextWrapper implements ComponentCallbacks2 {
    private ArrayList<ComponentCallbacks> mComponentCallbacks =
            new ArrayList<ComponentCallbacks>();
    private ArrayList<ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
            new ArrayList<ActivityLifecycleCallbacks>();
    private ArrayList<OnProvideAssistDataListener> mAssistCallbacks = null;

    /** @hide */
    public LoadedApk mLoadedApk;
    // ...

android/app/LoadedApk.java

public final class LoadedApk {

    private static final String TAG = "LoadedApk";

    private final ActivityThread mActivityThread;
    final String mPackageName;
    private ApplicationInfo mApplicationInfo;
    // ...
}

android/app/ActivityThread.java

AppBindData mBoundApplication;

public String getProcessName() {
    return mBoundApplication.processName;
}

所以我们反射取得就是这个mBoundApplication.processName的值,接下可以再翻一下源代码,看看这个赋值是在handleBindApplication中:

private void handleBindApplication(AppBindData data) {
    // Register the UI Thread as a sensitive thread to the runtime.
    VMRuntime.registerSensitiveThread();
    if (data.trackAllocation) {
        DdmVmInternal.enableRecentAllocations(true);
    }

    // Note when this process has started.
    Process.setStartTimes(SystemClock.elapsedRealtime(), SystemClock.uptimeMillis());

    mBoundApplication = data;
    mConfiguration = new Configuration(data.config);
    mCompatConfiguration = new Configuration(data.config);

    mProfiler = new Profiler();
    if (data.initProfilerInfo != null) {
        mProfiler.profileFile = data.initProfilerInfo.profileFile;
        mProfiler.profileFd = data.initProfilerInfo.profileFd;
        mProfiler.samplingInterval = data.initProfilerInfo.samplingInterval;
        mProfiler.autoStopProfiler = data.initProfilerInfo.autoStopProfiler;
    }

    // send up app name; do this *before* waiting for debugger
    Process.setArgV0(data.processName);
    android.ddm.DdmHandleAppName.setAppName(data.processName,
                                            UserHandle.myUserId());
    // ...
}

调用是在我们的Handler中, 同时我们也可以知道,Android内的组件基本都是通过这个H内部类来实现消息分发,比如Activity的创建,销毁,Window的展示,Application的绑定:

android/app/ActivityThread.java

final ApplicationThread mAppThread = new ApplicationThread();
final Looper mLooper = Looper.myLooper();
final H mH = new H();
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
// List of new activities (via ActivityRecord.nextIdle) that should
// be reported when next we idle.
ActivityClientRecord mNewActivities = null;

android/app/ActivityThread.java

private class H extends Handler {
    public static final int LAUNCH_ACTIVITY         = 100;
    // ...
    public static final int DESTROY_ACTIVITY        = 109;
    public static final int BIND_APPLICATION        = 110;
    public static final int EXIT_APPLICATION        = 111;
    // ...
    public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            // ...
            case DESTROY_ACTIVITY:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
                handleDestroyActivity((IBinder)msg.obj, msg.arg1 != 0,
                        msg.arg2, false);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case BIND_APPLICATION:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                AppBindData data = (AppBindData)msg.obj;
                handleBindApplication(data);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case EXIT_APPLICATION:
                if (mInitialApplication != null) {
                    mInitialApplication.onTerminate();
                }
                Looper.myLooper().quit();
                break;
            // ...
        }
    }
}

接下来就是看看什么地方发送了Handler的BIND_APPLICATION消,相关代码仍然在android/app/ActivityThread.java中。 有一个内部类叫做ApplicationThread:

android/app/ActivityThread.java

private class ApplicationThread extends ApplicationThreadNative {

    public final void bindApplication(String processName, ApplicationInfo appInfo,
            List<ProviderInfo> providers, ComponentName instrumentationName,
            ProfilerInfo profilerInfo, Bundle instrumentationArgs,
            IInstrumentationWatcher instrumentationWatcher,
            IUiAutomationConnection instrumentationUiConnection, int debugMode,
            boolean enableBinderTracking, boolean trackAllocation,
            boolean isRestrictedBackupMode, boolean persistent, Configuration config,
            CompatibilityInfo compatInfo, Map<String, IBinder> services, Bundle coreSettings) {

        if (services != null) {
            // Setup the service cache in the ServiceManager
            ServiceManager.initServiceCache(services);
        }

        setCoreSettings(coreSettings);

        AppBindData data = new AppBindData();
        data.processName = processName;
        data.appInfo = appInfo;
        data.providers = providers;
        data.instrumentationName = instrumentationName;
        data.instrumentationArgs = instrumentationArgs;
        data.instrumentationWatcher = instrumentationWatcher;
        data.instrumentationUiAutomationConnection = instrumentationUiConnection;
        data.debugMode = debugMode;
        data.enableBinderTracking = enableBinderTracking;
        data.trackAllocation = trackAllocation;
        data.restrictedBackupMode = isRestrictedBackupMode;
        data.persistent = persistent;
        data.config = config;
        data.compatInfo = compatInfo;
        data.initProfilerInfo = profilerInfo;
        sendMessage(H.BIND_APPLICATION, data);
    }

    public final void scheduleExit() {
        sendMessage(H.EXIT_APPLICATION, null);
    }
}

bindApplication是定义在IApplicationThread接口中一个方法,由ApplicationThread实现,他继承自ApplicationThreadNative,ApplicationThreadNative本身是一个Binder, 会在onTransact中根据code的取值来判断是否是BIND_APPLICATION_TRANSACTION

@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
        throws RemoteException {
    switch (code) {
    // ...
    case BIND_APPLICATION_TRANSACTION:
    {
        data.enforceInterface(IApplicationThread.descriptor);
        String packageName = data.readString();
        ApplicationInfo info =
            ApplicationInfo.CREATOR.createFromParcel(data);
        List<ProviderInfo> providers =
            data.createTypedArrayList(ProviderInfo.CREATOR);
        ComponentName testName = (data.readInt() != 0)
            ? new ComponentName(data) : null;
        ProfilerInfo profilerInfo = data.readInt() != 0
                ? ProfilerInfo.CREATOR.createFromParcel(data) : null;
        Bundle testArgs = data.readBundle();
        IBinder binder = data.readStrongBinder();
        IInstrumentationWatcher testWatcher = IInstrumentationWatcher.Stub.asInterface(binder);
        binder = data.readStrongBinder();
        IUiAutomationConnection uiAutomationConnection =
                IUiAutomationConnection.Stub.asInterface(binder);
        int testMode = data.readInt();
        boolean enableBinderTracking = data.readInt() != 0;
        boolean trackAllocation = data.readInt() != 0;
        boolean restrictedBackupMode = (data.readInt() != 0);
        boolean persistent = (data.readInt() != 0);
        Configuration config = Configuration.CREATOR.createFromParcel(data);
        CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
        HashMap<String, IBinder> services = data.readHashMap(null);
        Bundle coreSettings = data.readBundle();
        bindApplication(packageName, info, providers, testName, profilerInfo, testArgs,
                testWatcher, uiAutomationConnection, testMode, enableBinderTracking,
                trackAllocation, restrictedBackupMode, persistent, config, compatInfo, services,
                coreSettings);
        return true;
    }
    // ...
    }
}

可以看到一个进程名的传递最后涉及到了Binder,最终回调到Java层的ActivityThread中。