CodeCrafts是什么?
CodeCrafts下载安装:
Github地址下载, Gitee地址下载(需要登录gitee)
如果你的App有多个进程,你是否被子进程如何使用断点来调试Application.onCreate() 等进程启动前期的初始化代码困扰过?
我就是被这个问题困扰过很长一段时间,起初我都是在想要断点的地方手动加上Debug.waitForDebugger(),然后重新编译,最后再完成调试后再手动删除Debug.waitForDebugger()。繁琐低效不说,如果一不小心把它提交到代码仓库那就很尴尬了。
CodeCrafts的断点调试可以在进程启动前,提前将进程设置为断点调试状态,这样就能解决子进程的一些初始化代码不方便调试的问题。这是Android Studio无法做到的,先看图
实现原理
原理其实很简单,就是利用am set-debug-app,
set-debug-app这个名称并不准确,导致这个命令容易被用错。
set-debug-app和clear-debug-app
set-debug-app: 设置待调试的App, 实际上这个命令的名字和描述都不太准确,应该叫set-debug-process才对,因为set-debug-app 命令最后的参数是processName 并不是packageName。
clear-debug-app: 相反,是清除待调试的App
set-debug-app和clear-debug-app是am下面的一个子命令, 看下am的定义
# adb shell am
...
set-debug-app [-w] [--persistent] <PACKAGE> // <PACKAGE> 应该是Process, 不是Package
Set application <PACKAGE> to debug. Options are:
-w: wait for debugger when application starts
--persistent: retain this value
clear-debug-app
Clear the previously set-debug-app.
...
为什么说set-debug-app 命令的最后的参数是processName而不是packageName, 看一下它在源码中的实现就知道了
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
private boolean attachApplicationLocked(@NonNull IApplicationThread thread, int pid, int callingUid, long startSeq) { ....
try {
int testMode = ApplicationThreadConstants.DEBUG_OFF;
// mDebugApp是命令后的入参,与之判断相等的是processName,而不是packageName
if (mDebugApp != null && mDebugApp.equals(processName)) {
testMode = mWaitForDebugger
? ApplicationThreadConstants.DEBUG_WAIT
: ApplicationThreadConstants.DEBUG_ON;
// app是个ProcessRecord对象,这里将这个进程打上Debugging标记
app.setDebugging(true);
if (mDebugTransient) {
mDebugApp = mOrigDebugApp;
mWaitForDebugger = mOrigWaitForDebugger;
}
}
....
}
如果你真的输入packageName, 那么就没办法为子进程设置调试了。
CodeCrafts的断点调试实现
CodeCrafts有Shell权限,当然可以直接执行am set-debug-app来设置要对App的哪个进程来进行断点。 但这样有点简单粗暴,执行结果也很难控制。
set-debug-app是am下的一个子命令,因此推断它的实现肯定是在ActivityManagerService(后面简称AMS)中, 最终很容易找到了AMS的setDebugApp方法。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public void setDebugApp(String packageName, boolean waitForDebugger,
boolean persistent) {
enforceCallingPermission(android.Manifest.permission.SET_DEBUG_APP,
"setDebugApp()");
final long ident = Binder.clearCallingIdentity();
try {
// Note that this is not really thread safe if there are multiple
// callers into it at the same time, but that's not a situation we
// care about.
if (persistent) {
final ContentResolver resolver = mContext.getContentResolver();
Settings.Global.putString(
resolver, Settings.Global.DEBUG_APP,
packageName);
Settings.Global.putInt(
resolver, Settings.Global.WAIT_FOR_DEBUGGER,
waitForDebugger ? 1 : 0);
}
synchronized (this) {
if (!persistent) {
mOrigDebugApp = mDebugApp;
mOrigWaitForDebugger = mWaitForDebugger;
}
mDebugApp = packageName;
mWaitForDebugger = waitForDebugger;
mDebugTransient = !persistent;
if (packageName != null) {
forceStopPackageLocked(packageName, -1, false, false, true, true,
false, UserHandle.USER_ALL, "set debug app");
}
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
但是ActivityManager(后面简称AM)中并没有开放这个接口, 普通的App没有办法直接调用这个系统未公开的API。
那有没有间接一点的方法呢?
我们知道AM是AMS在应用进程中的代理,他们之间是通过Binder通信的,Binder通信一般需要定义一个aidl,IActivityManager.aidl 就是他们之间的接口定义, 而且IActivityManager.aidl中也有定义setDebugApp方法。
frameworks/base/core/java/android/app/IActivityManager.aidl
interface IActivityManager {
....
void setDebugApp(in String packageName, boolean waitForDebugger, boolean persistent);
....
}
IActivityManager.aidl 会在编译期间会生成
IActivityManager.java
,IActivityManager.java中当然也会有setDebugApp接口方法。
虽然AM没有开放setDebugApp接口,但是根据aidl的实现规则, IActivityManager.java中必定有个Proxy类,并且Proxy必定实现了IActivityManager接口,我们只要拿到这个Proxy类的对象就能调用到setDebugApp方法。
如何拿到这个Proxy类的对象呢?
frameworks/base/core/java/android/app/ActivityManagerNative.java
public abstract class ActivityManagerNative {
....
/**
* Cast a Binder object into an activity manager interface, generating
* a proxy if needed.
*
* @deprecated use IActivityManager.Stub.asInterface instead.
*/
@UnsupportedAppUsage
static public IActivityManager asInterface(IBinder obj) {
return IActivityManager.Stub.asInterface(obj);
}
/**
* Retrieve the system's default/global activity manager.
*
* @deprecated use ActivityManager.getService instead.
*/
@UnsupportedAppUsage
static public IActivityManager getDefault() {
return ActivityManager.getService();
}
....}
ActivityManagerNative提供的getDefault()方法获取的正是这个Proxy类。
虽然ActivityManagerNative和IActivityManager都是隐藏类App无法直接访问,但是我们只需要定义这两个类的同包名、同类名的文件即可解决编译问题。 根据类加载双亲委派机制,会自动忽略这两个文件。这块不是重点,有空再细讲。
使用说明
断点调试功能是CodeCrafts v1.0.15中新增功能
CodeCrafts下载安装:
Github地址下载, Gitee地址下载(需要登录gitee)