赞
踩
在我们日常的开发Debug过程中,经常需要去加一些Log进行打印。
APP层面的还好,但是Framework的层面,编译时长,版本匹配,OAT的Push,经常会带来较大的困扰。
为了方便Debug,Android系统本身设计了dumpsys系统,来帮助开发者进行快速定位。
可以使用这个命令,检查系统运行的services,运行时的一些变量,图层等内容。
熟练的使用dumpsys命令,可以极大的减少我们的开发成本,节约开发时间。
本文我们将会讲解dumpsys的使用,以及其详细的实现。
首先我们来看一些系统的使用帮助:
可以使用adb shell dumpsys -h
命令来进行操作,可以获得如下提示:
usage: dumpsys
To dump all services.
or:
dumpsys [-t TIMEOUT] [–priority LEVEL] [–pid] [–help | -l | --skip SERVICES | SERVICE [ARGS]]
–help: shows this help
-l: only list services, do not dump them
-t TIMEOUT_SEC: TIMEOUT to use in seconds instead of default 10 seconds
-T TIMEOUT_MS: TIMEOUT to use in milliseconds instead of default 10 seconds
–pid: dump PID instead of usual dump
–proto: filter services that support dumping data in proto format. Dumps
will be in proto format.
–priority LEVEL: filter services based on specified priority
LEVEL must be one of CRITICAL | HIGH | NORMAL
–skip SERVICES: dumps all services but SERVICES (comma-separated list)
SERVICE [ARGS]: dumps only service SERVICE, optionally passing ARGS to it
针对上述命令的解释如下:
dumpsys -l --> 将会列出系统中当前正在运行的services.
dumpsys -t --> 将10s的默认超时时间设置为-t后所跟时间,单位seconds
dumpsys -T --> 将10s的默认超时时间设置为-T后所跟时间,单位milliseconds
dumpsys --pid --> 将会打印系统中的services及其对应的PID
dumpsys --proto --> 将会调用系统中services的dumpProto方法,并进行Log输出
dumpsys --priority --> 将会根据后面跟的priority的等级,打印对应的services的信息
dumpsys --skip --> 将会在打印
dumpsys [SERVICE] --> 打印对应Services的DUMP信息**
举例:
adb shell dumpsys adb
查看adb的dump信息。
Current Battery Service state:
AC powered: false
USB powered: true
Wireless powered: false
Max charging current: 500000
Max charging voltage: 5000000
Charge counter: 4020390
status: 5
health: 2
present: true
level: 100
scale: 100
voltage: 4372
temperature: 260
而这个显示对应的代码在如下的位置:
frameworks/base/services/core/java/com/android/server/BatteryService.java
Code的实现为:
private final class BinderService extends Binder {
@Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
if (args.length > 0 && "--proto".equals(args[0])) {
dumpProto(fd);
} else {
dumpInternal(fd, pw, args);
}
}
@Override public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
(new Shell()).exec(this, in, out, err, args, callback, resultReceiver);
}
}
因为我们没有去加–proto的参数,所以会走dumpInternal的方法。
private void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) {
synchronized (mLock) {
if (args == null || args.length == 0 || "-a".equals(args[0])) {
pw.println("Current Battery Service state:");
if (mUpdatesStopped) {
pw.println(" (UPDATES STOPPED -- use 'reset' to restart)");
}
pw.println(" AC powered: " + mHealthInfo.chargerAcOnline);
pw.println(" USB powered: " + mHealthInfo.chargerUsbOnline);
pw.println(" Wireless powered: " + mHealthInfo.chargerWirelessOnline);
pw.println(" Max charging current: " + mHealthInfo.maxChargingCurrent);
pw.println(" Max charging voltage: " + mHealthInfo.maxChargingVoltage);
pw.println(" Charge counter: " + mHealthInfo.batteryChargeCounter);
pw.println(" status: " + mHealthInfo.batteryStatus);
pw.println(" health: " + mHealthInfo.batteryHealth);
pw.println(" present: " + mHealthInfo.batteryPresent);
pw.println(" level: " + mHealthInfo.batteryLevel);
pw.println(" scale: " + BATTERY_SCALE);
pw.println(" voltage: " + mHealthInfo.batteryVoltage);
pw.println(" temperature: " + mHealthInfo.batteryTemperature);
pw.println(" technology: " + mHealthInfo.batteryTechnology);
} else {
Shell shell = new Shell();
shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null));
}
}
}
代码路径:
frameworks/native/cmds/dumpsys/
我们先看一下bp文件中对dumpsys
的定义:
cc_binary {
name: "dumpsys",
defaults: ["dumpsys_defaults"],
srcs: [
"main.cpp",
],
}
从这里我们可以看到,dumpsys是被编译成了一个可执行的文件,并放在了系统中。
我们在手机中,执行adb shell启动命令端,用which dumpsys可以看到具体可执行文件的路径。
adb shell which dumpsys
/system/bin/dumpsys
那么,我们接下来主要看的是main.cpp的实现了。
int main(int argc, char* const argv[]) {
signal(SIGPIPE, SIG_IGN);
sp<IServiceManager> sm = defaultServiceManager();
fflush(stdout);
if (sm == nullptr) {
ALOGE("Unable to get default service manager!");
std::cerr << "dumpsys: Unable to get default service manager!" << std::endl;
return 20;
}
Dumpsys dumpsys(sm.get());
return dumpsys.main(argc, argv);
}
main函数的实现也非常简单,首先获取了IServiceManager对象。
获取"IServiceManager对象"的目的是为了和"ServiceManager进程"进行通信。
然后就去初始化了dumpsys的对象。
explicit Dumpsys(android::IServiceManager* sm) : sm_(sm) {
}
将获取到的IServiceManager对象,复制到Dumpsys的构造函数,并赋给sm_.
接下来就是dumpsys.main(argc, argv)的执行了。
int Dumpsys::main(int argc, char* const argv[]) {
Vector<String16> services;
Vector<String16> args;
String16 priorityType;
Vector<String16> skippedServices;
Vector<String16> protoServices;
bool showListOnly = false;
bool skipServices = false;
bool asProto = false;
Type type = Type::DUMP;
int timeoutArgMs = 10000;
int priorityFlags = IServiceManager::DUMP_FLAG_PRIORITY_ALL;
static struct option longOptions[] = {{"pid", no_argument, 0, 0},
{"priority", required_argument, 0, 0},
{"proto", no_argument, 0, 0},
{"skip", no_argument, 0, 0},
{"help", no_argument, 0, 0},
{0, 0, 0, 0}};
// Must reset optind, otherwise subsequent calls will fail (wouldn't happen on main.cpp, but
// happens on test cases).
optind = 1;
while (1) {
int c;
int optionIndex = 0;
c = getopt_long(argc, argv, "+t:T:l", longOptions, &optionIndex);
if (c == -1) {
break;
}
switch (c) {
case 0:
if (!strcmp(longOptions[optionIndex].name, "skip")) {
skipServices = true;
} else if (!strcmp(longOptions[optionIndex].name, "proto")) {
asProto = true;
} else if (!strcmp(longOptions[optionIndex].name, "help")) {
usage();
return 0;
} else if (!strcmp(longOptions[optionIndex].name, "priority")) {
priorityType = String16(String8(optarg));
if (!ConvertPriorityTypeToBitmask(priorityType, priorityFlags)) {
fprintf(stderr, "\n");
usage();
return -1;
}
} else if (!strcmp(longOptions[optionIndex].name, "pid")) {
type = Type::PID;
}
break;
case 't':
{
char* endptr;
timeoutArgMs = strtol(optarg, &endptr, 10);
timeoutArgMs = timeoutArgMs * 1000;
if (*endptr != '\0' || timeoutArgMs <= 0) {
fprintf(stderr, "Error: invalid timeout(seconds) number: '%s'\n", optarg);
return -1;
}
}
break;
case 'T':
{
char* endptr;
timeoutArgMs = strtol(optarg, &endptr, 10);
if (*endptr != '\0' || timeoutArgMs <= 0) {
fprintf(stderr, "Error: invalid timeout(milliseconds) number: '%s'\n", optarg);
return -1;
}
}
break;
case 'l':
showListOnly = true;
break;
default:
fprintf(stderr, "\n");
usage();
return -1;
}
}
for (int i = optind; i < argc; i++) {
if (skipServices) {
skippedServices.add(String16(argv[i]));
} else {
if (i == optind) {
services.add(String16(argv[i]));
} else {
const String16 arg(argv[i]);
args.add(arg);
// For backward compatible, if the proto argument is passed to the service, the
// dump request is also considered to use proto.
if (!asProto && !arg.compare(String16(PriorityDumper::PROTO_ARG))) {
asProto = true;
}
}
}
}
if ((skipServices && skippedServices.empty()) ||
(showListOnly && (!services.empty() || !skippedServices.empty()))) {
usage();
return -1;
}
if (services.empty() || showListOnly) {
services = listServices(priorityFlags, asProto);
setServiceArgs(args, asProto, priorityFlags);
}
const size_t N = services.size();
if (N > 1) {
// first print a list of the current services
std::cout << "Currently running services:" << std::endl;
for (size_t i=0; i<N; i++) {
sp<IBinder> service = sm_->checkService(services[i]);
if (service != nullptr) {
bool skipped = IsSkipped(skippedServices, services[i]);
std::cout << " " << services[i] << (skipped ? " (skipped)" : "") << std::endl;
}
}
}
if (showListOnly) {
return 0;
}
for (size_t i = 0; i < N; i++) {
const String16& serviceName = services[i];
if (IsSkipped(skippedServices, serviceName)) continue;
if (startDumpThread(type, serviceName, args) == OK) {
bool addSeparator = (N > 1);
if (addSeparator) {
writeDumpHeader(STDOUT_FILENO, serviceName, priorityFlags);
}
std::chrono::duration<double> elapsedDuration;
size_t bytesWritten = 0;
status_t status =
writeDump(STDOUT_FILENO, serviceName, std::chrono::milliseconds(timeoutArgMs),
asProto, elapsedDuration, bytesWritten);
if (status == TIMED_OUT) {
std::cout << std::endl
<< "*** SERVICE '" << serviceName << "' DUMP TIMEOUT (" << timeoutArgMs
<< "ms) EXPIRED ***" << std::endl
<< std::endl;
}
if (addSeparator) {
writeDumpFooter(STDOUT_FILENO, serviceName, elapsedDuration);
}
bool dumpComplete = (status == OK);
stopDumpThread(dumpComplete);
}
}
return 0;
}
这个函数有一些长,我们来对其进行分解。
分为三部分来看:
我们在前文提到了dumpsys可以传递的参数,那么这边就是处理的地方。
再重温一下刚才的一些关键命令:
dumpsys -l --> 将会列出系统中当前正在运行的services.
dumpsys -t --> 将10s的默认超时时间设置为-t后所跟时间,单位seconds
dumpsys -T --> 将10s的默认超时时间设置为-T后所跟时间,单位milliseconds
dumpsys --pid --> 将会打印系统中的services及其对应的PID
dumpsys --proto --> 将会调用系统中services的dumpProto方法,并进行Log输出
dumpsys --priority --> 将会根据后面跟的priority的等级,打印对应的services的信息
dumpsys --skip --> 将会在打印
dumpsys [SERVICE] --> 打印对应Services的DUMP信息**
这里面,当我们使用不同参数的时候,会首先判断是不是使用了 -l,-t,-T。
并且是否有longOptions的参数:
static struct option longOptions[] = {{"pid", no_argument, 0, 0},
{"priority", required_argument, 0, 0},
{"proto", no_argument, 0, 0},
{"skip", no_argument, 0, 0},
{"help", no_argument, 0, 0},
{0, 0, 0, 0}};
对参数的详细处理,可以看到下面的swich case中进行完成。
switch (c) {
case 0:
if (!strcmp(longOptions[optionIndex].name, "skip")) {
skipServices = true;
} else if (!strcmp(longOptions[optionIndex].name, "proto")) {
asProto = true;
} else if (!strcmp(longOptions[optionIndex].name, "help")) {
usage();
return 0;
} else if (!strcmp(longOptions[optionIndex].name, "priority")) {
priorityType = String16(String8(optarg));
if (!ConvertPriorityTypeToBitmask(priorityType, priorityFlags)) {
fprintf(stderr, "\n");
usage();
return -1;
}
} else if (!strcmp(longOptions[optionIndex].name, "pid")) {
type = Type::PID;
}
break;
case 't':
{
char* endptr;
timeoutArgMs = strtol(optarg, &endptr, 10);
timeoutArgMs = timeoutArgMs * 1000;
if (*endptr != '\0' || timeoutArgMs <= 0) {
fprintf(stderr, "Error: invalid timeout(seconds) number: '%s'\n", optarg);
return -1;
}
}
break;
case 'T':
{
char* endptr;
timeoutArgMs = strtol(optarg, &endptr, 10);
if (*endptr != '\0' || timeoutArgMs <= 0) {
fprintf(stderr, "Error: invalid timeout(milliseconds) number: '%s'\n", optarg);
return -1;
}
}
break;
case 'l':
showListOnly = true;
break;
default:
fprintf(stderr, "\n");
usage();
return -1;
}
}
我们在传递dumpsys的services时,提到了可以skip一些services的操作。
也可以再dumpsys后,跟一些services来进行特定的打印。
那么就需要将其用标志位标记,并且放到相应的list中进行保存。
详细逻辑如下:
for (int i = optind; i < argc; i++) {
if (skipServices) {
skippedServices.add(String16(argv[i]));
} else {
if (i == optind) {
services.add(String16(argv[i]));
} else {
const String16 arg(argv[i]);
args.add(arg);
// For backward compatible, if the proto argument is passed to the service, the
// dump request is also considered to use proto.
if (!asProto && !arg.compare(String16(PriorityDumper::PROTO_ARG))) {
asProto = true;
}
}
}
}
if ((skipServices && skippedServices.empty()) ||
(showListOnly && (!services.empty() || !skippedServices.empty()))) {
usage();
return -1;
}
if (services.empty() || showListOnly) {
services = listServices(priorityFlags, asProto);
setServiceArgs(args, asProto, priorityFlags);
}
const size_t N = services.size();
if (N > 1) {
// first print a list of the current services
std::cout << "Currently running services:" << std::endl;
for (size_t i=0; i<N; i++) {
sp<IBinder> service = sm_->checkService(services[i]);
if (service != nullptr) {
bool skipped = IsSkipped(skippedServices, services[i]);
std::cout << " " << services[i] << (skipped ? " (skipped)" : "") << std::endl;
}
}
}
if (showListOnly) {
return 0;
}
这里需要注意的是:我们会在这边打印出所有便利出来的services。
std::cout << “Currently running services:” << std::endl;
std::cout << " " << services[i] << (skipped ? " (skipped)" : “”) << std::endl;
因为dumpsys会有一个参数是-l,即只列出对应的services list。
检查之前的swtich case的函数,可以看到-l的定义为:
case 'l':
showListOnly = true;
break;
设置了showListOnly为true,那么在这边我们就不需要执行后续的操作了。
即在本文的第二段,进行了return 0的操作。
if (showListOnly) {
return 0;
}
接下来的第三段,其实也比较简单。
for (size_t i = 0; i < N; i++) {
const String16& serviceName = services[i];
if (IsSkipped(skippedServices, serviceName)) continue;
if (startDumpThread(type, serviceName, args) == OK) {
bool addSeparator = (N > 1);
if (addSeparator) {
writeDumpHeader(STDOUT_FILENO, serviceName, priorityFlags);
}
std::chrono::duration<double> elapsedDuration;
size_t bytesWritten = 0;
status_t status =
writeDump(STDOUT_FILENO, serviceName, std::chrono::milliseconds(timeoutArgMs),
asProto, elapsedDuration, bytesWritten);
if (status == TIMED_OUT) {
std::cout << std::endl
<< "*** SERVICE '" << serviceName << "' DUMP TIMEOUT (" << timeoutArgMs
<< "ms) EXPIRED ***" << std::endl
<< std::endl;
}
if (addSeparator) {
writeDumpFooter(STDOUT_FILENO, serviceName, elapsedDuration);
}
bool dumpComplete = (status == OK);
stopDumpThread(dumpComplete);
}
}
首先会使用startDumpThread去检查是否状态值为OK,实现如下:
status_t Dumpsys::startDumpThread(Type type, const String16& serviceName,
const Vector<String16>& args) {
sp<IBinder> service = sm_->checkService(serviceName);
if (service == nullptr) {
std::cerr << "Can't find service: " << serviceName << std::endl;
return NAME_NOT_FOUND;
}
int sfd[2];
if (pipe(sfd) != 0) {
std::cerr << "Failed to create pipe to dump service info for " << serviceName << ": "
<< strerror(errno) << std::endl;
return -errno;
}
redirectFd_ = unique_fd(sfd[0]);
unique_fd remote_end(sfd[1]);
sfd[0] = sfd[1] = -1;
// dump blocks until completion, so spawn a thread..
activeThread_ = std::thread([=, remote_end{std::move(remote_end)}]() mutable {
status_t err = 0;
switch (type) {
case Type::DUMP:
err = service->dump(remote_end.get(), args);
break;
case Type::PID:
err = dumpPidToFd(service, remote_end);
break;
default:
std::cerr << "Unknown dump type" << static_cast<int>(type) << std::endl;
return;
}
if (err != OK) {
std::cerr << "Error dumping service info status_t: " << statusToString(err) << " "
<< serviceName << std::endl;
}
});
return OK;
}
首先会去再检查一遍当前的services是否存在。
sm_->checkService(serviceName);
使用管道创建进程间的通信通道。
pipe(sfd) != 0
启用线程完成dump的打印
// dump blocks until completion, so spawn a thread..
activeThread_ = std::thread([=, remote_end{std::move(remote_end)}]() mutable {
status_t err = 0;
switch (type) {
case Type::DUMP:
err = service->dump(remote_end.get(), args);
break;
case Type::PID:
err = dumpPidToFd(service, remote_end);
break;
default:
std::cerr << "Unknown dump type" << static_cast<int>(type) << std::endl;
return;
}
if (err != OK) {
std::cerr << "Error dumping service info status_t: " << statusToString(err) << " "
<< serviceName << std::endl;
}
});
真正的打印会通过如下方法,来调用对应services的dump函数。
service->dump(remote_end.get(), args);
接下来回到之前的函数中,当读取ok后,我们要检查一些状态,比如输出超时,即会有:
if (status == TIMED_OUT) {
std::cout << std::endl
<< "*** SERVICE '" << serviceName << "' DUMP TIMEOUT (" << timeoutArgMs
<< "ms) EXPIRED ***" << std::endl
<< std::endl;
}
以上,就是dumpsys的实现原理,和具体使用。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。