Android ADB工具详解

来源:互联网 发布:后窗知乎 编辑:程序博客网 时间:2024/03/28 21:58

抱歉拖更

上篇回归博客里已经说到,完成周更计划,可是上一周周末我就水了,十分抱歉,临近毕业事物繁杂,这里补上一篇adb相关知识。


一、概念

adb是什么?:adb的全称为Android Debug Bridge,就是起到调试桥的作用。
通过adb我们可以在Eclipse中方面通过DDMS来调试Android程序,说白了就是debug工具。
adb的工作方式比较特殊,采用监听Socket TCP 5554等端口的方式让IDE和Qemu通讯,默认情况下adb会daemon相关的网络端口,所以当我们运行Eclipse时adb进程就会自动运行。

二、作用

adb有什么用?:借助adb工具,我们可以管理设备或手机模拟器的状态。还可以进行很多手机操作,如安装软件、系统升级、运行shell命令等等。其实简而言说,adb就是连接Android手机与PC端的桥梁,可以让用户在电脑上对手机进行全面的操作

三、adb的运行机制和框架结构

下面以几张图来进行分析:

这里写图片描述

该图表示,ADB的CS模型

这里写图片描述

该图表示,ADB内部实现流程机制

完整的数据处理流程:

1:PC端adb server发送cmd到小机端
2:adbd(adb daemon)的output thread从/dev/android_usb读取到cmd并解析
(adbd和内核usb driver通过/dev/android_usb进行数据交互)
3:output_thread将解析的cmd写到socketpair的A端
4:fdevent_loop通过select socketpair的B端,得知A端有数据写入,B端可读,然后读取cmd,
进行处理(handle_packet)
5:处理完后将结果写到socketpair的B端
6:Input_thread阻塞式读取socketpair的A端
7:Input_thread读取到数据后,写入/dev/android_usb
8:由usb driver将response发给pc端
output_thread负责读取/dev/android_usb,Input_thread负责写入

四、ADB底层实现机制

来看看整体流程
源码路径:~/system/core/adb/adb.c

adb_main==>init_transport_registration 注册transport登记接口,回调函数transport_registration_func   等待register_transport发送数据过来.==>local_init 对于pc上的host程序,执行client_socket_thread线程==>local_connect   优先扫描fd = socket_loopback_client(port, SOCK_STREAM);   本地port是否能打开,如果不能打开,那么尝试获取const char *host = getenv("ADBHOST");   环境变量,而我们恰恰定义了该环境变量export ADBHOST=192.168.100.2,   所以尝试打开手机端server监听的相应端口[我们定义的是#define ADB_LOCAL_TRANSPORT_PORT 5555,   这由运行在手机上的server_socket_thread线程创建],   fd = socket_network_client(host, port, SOCK_STREAM);打开成功后   调用register_socket_transport==>register_transport将该transport交由   上面的transport_registration_func函数继续处理,transport_registration_func函数将   该transport挂接到transport_list链表上,然后创建一对socketpair[这样就可以对其中   一个fd实行双向读写操作了,pipe只能单向操作,一个fd只读,另一个只写],   一端给t->transport_socket = s[0];另一端给t->fd = s[1];之后将t->transport_socket   添加到FDE_READ读epoll中,t->fd发过来的数据由transport_socket_events回调函数   调用handle_packet函数做进一步处理,之后创建2个线程input_thread和output_thread,   input_thread通过read_packet(t->fd, &p);接收host通过t->transport_socket发送的数据,   然后执行t->write_to_remote(p, t);将数据转发给手机上的server[192.168.100.2:5555]   output_thread通过t->read_from_remote(p, t);接收手机server[192.168.100.2:5555]   发送过来的数据,然后执行write_packet(t->fd, &p);转发给正在监听t->transport_socket   端口的回调函数transport_socket_events==>handle_packet,这样在该transport上进行数据   收发所需要的环境都已经建立完毕了[luther.gliethttp]==>install_listener("tcp:5037", "*smartsocket*", NULL);   这是运行在pc上的host程序所创建的一个监听端口5037[#define ADB_PORT 5037],   他将监听所有adb shell等本地命令数据[姑且把pc上执行adb shell等命令的操作为client端],   如果该本地命令host可以执行,那么直接返回结果给client端,   否则通过transport端口将该命令数据转发到手机上,由手机上的server进行处理[luther.gliethttp]   具体由l->fd = local_name_to_fd(local_name);语句   执行socket_loopback_server(port, SOCK_STREAM);创建[luther.gliethttp]   5037端口监听到的数据交由ss_listener_event_func回调函数做进一步处理,   如果是非"*smartsocket*"的listener那么执行listener_event_func回调函数[luther.gliethttp]   ss_listener_event_func==>   fd = adb_socket_accept(_fd, &addr, &alen);   s = create_local_socket(fd);设置该adb shell命令在host上连接处理函数local_socket_event_func   connect_to_smartsocket(s);    void connect_to_smartsocket(asocket *s)    {        D("Connecting to smart socket \n");        asocket *ss = create_smart_socket(smart_socket_action);         //将本地client命令,比如adb shell的peer设置为smartsocket        s->peer = ss;        ss->peer = s;        s->ready(s); // 调用local_socket_ready==>fdevent_add(&s->fde, FDE_READ);                     // 这样host将等待本地client命令发送数据过来,                     // 然后执行local_socket_event_func==>s->peer->enqueue(s->peer, p);                     // 即上面create_smart_socket创建的smartsocket==>smart_socket_enqueue                     // 处理来自client命令的数据[比如:adb shell命令发送过来的数据]    }==>对于host来说fprintf(stderr, "OK\n");// 将OK传递给执行adb start-server的parent程序,因为parent正执                                       // 行adb_read(fd[0], temp, 3);等待管道数据[luther.gleithttp]==>start_logging(); // 打开log文件,然后dup2到stdout和stderr,==>fdevent_loop==>fdevent_process // 进入event loop循环等待所有static void fdevent_process(){    struct epoll_event events[256];    fdevent *fde;    int i, n;    n = epoll_wait(epoll_fd, events, 256, -1); // 等待添加到epoll_fd中的各个fd对应event事件发生[luther.gliethttp]    ...    for(i = 0; i < n; i++) {        struct epoll_event *ev = events + i;        fde = ev->data.ptr;        if(ev->events & EPOLLIN) {            fde->events |= FDE_READ;        }        if(ev->events & EPOLLOUT) {            fde->events |= FDE_WRITE;        }        if(ev->events & (EPOLLERR | EPOLLHUP)) {            fde->events |= FDE_ERROR;        }        if(fde->events) {            if(fde->state & FDE_PENDING) continue; // 正在处理前一条信息            fde->state |= FDE_PENDING;            fdevent_plist_enqueue(fde); // 放入待处理的list链表上        }    }}==>比如client端执行adb shell命令==>adb shell ==> interactive_shell ==> fd = adb_connect("shell:"); ==> fd = _adb_connect("host:version");该   命令将触发pc daemon上install_listener("tcp:5037", "*smartsocket*", NULL);创建的监听端口发生READ事件,   执行ss_listener_event_func回调函数登记该socket连接的读回调函数local_socket_event_func,它将读取该socket数据,   然后交由s->peer->enqueue(s->peer, p);[这里就是smart_socket_enqueue函数]处理所有数据,之后所有shell命令   都通过该fd发送到pc daemon的local_socket_event_func处理函数进行处理,最终效果如下:   | client端 | pc daemon端 |   | | |   | "host:version" ==> | local_socket_event_func ==> smart_socket_enqueue[最终处理数据] |   当发送一个非service命令,即非"host:"开头的命令时,smart_socket_enqueue将把自己从s->peer中摘掉:   static int smart_socket_enqueue(asocket *s, apacket *p) {        ...        // s代表smart,s->peer就指向了pc daemon与client端建立的socket端口fd        s->peer->ready = local_socket_ready_notify; // ready时,同时发送"OKAY"字符串给client        s->peer->close = local_socket_close_notify; // close时,同时发送"closed"字符串给client        s->peer->peer = 0; // 表示client没有peer了,原来的peer smart没了[luther.gliethttp]            /* give him our transport and upref it */        s->peer->transport = s->transport; // 该client端在pc daemon上的socket端口fd,通过该transport通道与remote远端                                           // server[我们就是手机上的adb server]进行通信[luther.gliethttp]        connect_to_remote(s->peer, (char*) (p->data + 4)); // smart解除自己与client的peer关联之后,同时负责为client和        // 运行在手机上的server远端程序进行open衔接操作.        // s->peer将与remote进行p->data + 4命令连接[lutehr.gliethttp]        s->peer = 0; // ok,smart的peer清0        s->close(s); // 对应的该smart关闭        return 1;        ...   }void connect_to_remote(asocket *s, const char *destination){    D("Connect_to_remote call \n");    apacket *p = get_apacket();    int len = strlen(destination) + 1;    if(len > (MAX_PAYLOAD-1)) {        fatal("destination oversized");    }    D("LS(%d): connect('%s')\n", s->id, destination);    p->msg.command = A_OPEN;    p->msg.arg0 = s->id; // 告诉手机上的server,向pc daemon中id值为s->id的socket发送数据    // 手机server发送过来的数据被output_thread线程接收,执行write_packet(t->fd, &p);    // 转发给正在监听t->transport_socket端口的回调函数transport_socket_events==>handle_packet进一步处理    p->msg.data_length = len;    strcpy((char*) p->data, destination);    send_packet(p, s->transport);}==>transport_socket_events==>handle_packet    case A_OPEN: /* OPEN(local-id, 0, "destination") */    //手机端接收到connect_to_remote命令A_OPEN        if(t->connection_state != CS_OFFLINE) {            char *name = (char*) p->data;            name[p->msg.data_length > 0 ? p->msg.data_length - 1 : 0] = 0;            s = create_local_service_socket(name);//create_local_socket本地建立一个新的socket,用于name对应的service数据收发.//如果service有数据发送,那么将触发local_socket_event_func函数执行,进而按上面所讲的方式发送给 s->peer->enqueue(s->peer, p);在这里就是下面create_remote_socket创建的peer,对应的enqueue函数为 remote_socket_enqueue.//那么当对端remote来数据的话,又怎么分发给daemon所管理的那么多socket呢,即remote所期望通信的socket呢,这就需要上面的//s->id = local_socket_next_id++;//该socket在daemon的id标识//来进一步区分本地daemon中的socket了.            if(s == 0) {                send_close(0, p->msg.arg0, t);            } else {                s->peer = create_remote_socket(p->msg.arg0, t);//arg0就是remote端pc上daemon中socket的id标识,详细信息见上面[luther.gliethttp].                //这样s->peer->id等于p->msg.arg0,这样就记录下在pc上远端的socket的id标识了                s->peer->peer = s;                //发送本地s->id给remote端,让它执行A_OKAY处理,同样将本地id登记到remote上.                //remote端的id标识为s->peer->id,回应给pc端的daemon                send_ready(s->id, s->peer->id, t);//local_id=s->id, remote_id=s->peer->id,向pc daemon发送"A_OKAY"数据                s->ready(s); // 将s添加到READ事件中            }        }        break;    case A_OKAY: /* READY(local-id, remote-id, "") *///pc端的daemon接收到手机端send_ready(s->id, s->peer->id, t);回应信息.//因为pc端在上面smart_socket_enqueue==>s->peer->peer = 0;//将s->peer的peer清空//connect_to_remote之后smart将自己close了,内存也全部释放掉了[luther.gliethttp].//所以这里s->peer等于0        if(t->connection_state != CS_OFFLINE) {            if((s = find_local_socket(p->msg.arg1))) {//arg0=local_id, arg1=remote_id                if(s->peer == 0) {//生成以手机端socket id为s->id的pc daemon上的socket                    s->peer = create_remote_socket(p->msg.arg0, t);//创建对端socket,其在对端daemon下的id标识为msg.arg0[luther.gliethttp]                    s->peer->peer = s;                }                s->ready(s); // 调用local_socket_ready_notify,将s添加到READ事件中,同时发送"OKAY"字符串给client            }        }        break;    case A_WRTE:        if(t->connection_state != CS_OFFLINE) {            if((s = find_local_socket(p->msg.arg1))) {//根据s->id查找本地daemon[手机或pc]管理的相应socket处理函数                unsigned rid = p->msg.arg0;                p->len = p->msg.data_length;                if(s->enqueue(s, p) == 0) {//调用local的数据处理函数,处理来自remote端的数据                    D("Enqueue the socket\n");                    send_ready(s->id, rid, t);                }                return;            }        }        break;

五、ADB相关命令

ADB是android sdk里的一个工具,用这个工具可以直接操作管理android模拟器或者真实的andriod设备。

  1. 显示系统中全部Android平台:
    android list targets

  2. 显示系统中全部AVD(模拟器):
    android list avd

  3. 创建AVD(模拟器):
    android create avd –name 名称 –target 平台编号

  4. 启动模拟器:
    emulator -avd 名称 -sdcard ~/名称.img (-skin 1280x800)

  5. 删除AVD(模拟器):
    android delete avd –name 名称

  6. 创建SDCard:
    mksdcard 1024M ~/名称.img

  7. AVD(模拟器)所在位置:
    Linux(~/.android/avd)Windows(C:\DocumentsandSettings\Administrator.android\avd)

  8. 启动DDMS:
    ddms

  9. 显示当前运行的全部模拟器:
    adb devices

  10. 对某一模拟器执行命令:
    abd -s 模拟器编号 命令

  11. 安装应用程序:
    adb install
    +apk文件路径:这个命令将指定的apk文件安装到设备上
    adb install -r 应用程序.apk

  12. 获取模拟器中的文件:
    adb pull sdcard/<> <>
    获取模拟器中的文件:
    adb pull sdcard/<> <>

  13. 向模拟器中写文件:
    adb push/ <><>

  14. 进入模拟器的shell模式:
    adb shell
    退出用exit即可
    通过上面的命令,就可以进入设备或模拟器的shell环境中,在这个Linux Shell中,你可以执行各种Linux的命令,另外如果只想执行一条shell命令,可以采用以下的方式:
      adb shell [command]
    如:adb shell dmesg会打印出内核的调试信息。

  15. 启动SDK,文档,实例下载管理器:
    android

  16. 缷载apk包:
    adb uninstall <软件名>
      adb uninstall -k <软件名>
    如果加 -k 参数,为卸载软件但是保留配置和缓存文件.
    adb shell
    cd data/app
    rm apk包
    exit
    adb uninstall apk包的主包名

  17. 查看adb命令帮助信息:
    adb help

  18. 在命令行中查看LOG信息:

  19. adb shell后面跟的命令主要来自:
    源码\system\core\toolbox目录和源码\frameworks\base\cmds目录。

  20. 删除系统应用:
    adb remount (重新挂载系统分区,使系统分区重新可写)。
    adb shell
    cd system/app

  21. 获取管理员权限:
    adb root

  22. 启动Activity:

  23. 发布端口:
    你可以设置任意的端口号,做为主机向模拟器或设备的请求端口。如:
    adb forward tcp:5555 tcp:8000

  24. 获取设备的ID和序列号
    adb get-product
    adb get-serialno
    adb shell
    sqlite3

  25. 搜索模拟器/设备的实例:
    取得当前运行的模拟器/设备的实例的列表及每个实例的状态:
    adb devices

  26. 查看bug报告:
    adb bugreport
    cd system/sd/data //进入系统内指定文件夹
    ls //列表显示当前文件夹内容
    rm -r xxx //删除名字为xxx的文件夹及其里面的所有文件
    rm xxx //删除文件xxx
    rmdir xxx //删除xxx的文件夹

  27. 从电脑上发送文件到设备
    adb push <本地路径> <远程路径>
    用push命令可以把本机电脑上的文件或者文件夹复制到设备(手机)

  28. 从设备上下载文件到电脑
    adb pull <远程路径> <本地路径>
    用pull命令可以把设备(手机)上的文件或者文件夹复制到本机电脑

  29. 查看bug报告
    adb bugreport

  30. 记录无线通讯日志
    一般来说,无线通讯的日志非常多,在运行时没必要去记录,但我们还是可以通过命令,设置记录:
    adb shell
    logcat -b radio

六、使用logcat查看日志

Android日志系统提供了从众多应用程序和系统程序中收集和查看调试信息的机制,这些信息被收集到一系统循环缓冲区中,可以 logcat 命令查看和过滤。
使用adb logcat –help查看 对应的帮助命令提示,如下图:

这里写图片描述

选项解析 :

“-s”选项 : 设置输出日志的标签, 只显示该标签的日志;
“-f”选项 : 将日志输出到文件, 默认输出到标准输出流中, -f 参数执行不成功;
“-r”选项 : 按照每千字节输出日志, 需要 -f 参数, =
“-n”选项 : 设置日志输出的最大数目, 需要 -r 参数, =
“-v”选项 : 设置日志的输出格式, 注意只能设置一项;
“-c”选项 : 清空所有的日志缓存信息;
“-d”选项 : 将缓存的日志输出到屏幕上, 并且不会阻塞;
“-t”选项 : 输出最近的几行日志, 输出完退出, 不阻塞;
“-g”选项 : 查看日志缓冲区信息;
“-b”选项 : 加载一个日志缓冲区, 默认是 main, 下面详解;
“-B”选项 : 以二进制形式输出日志;

注:

(1)以上shell@pcpcshellshell@android 表示在手机shell中执行后边的命令l
(2)一定注意合适的时候需要停止掉以上命令,否则再次使用相同命令的时候,就会有两个logcat写同一个文件了

停止方法: adb shell kill -9
其中logcat_pid 通过 如下命令获取

adb shell ps | grep logcat # Linux 平台
adb shell ps | findstr “logcat” #Windows平台

1、adb logcat > D:/1.txt 把信息存放到D盘目录下的1.txt文件,方便后续查看。注意,adb logcat > C:/1.txt,这个C盘就不一定成功,会拒绝访问。

2、adb logcat|find “要显示包含的字符串” 网上说adb logcat|grep 是不能在win 环境用的!

有上面两个方法,基本可以满足要求使用了!

使用 logcat 命令
查看和跟踪系统日志缓冲区的命令logcat的一般用法是:
[adb] logcat [] … [] …
下文介绍过滤器和命令选项,详细内容可参见Listing of logcat Command Options。
可以在开发机中通过远程shell的方式使用logcat命令查看日志输出:
$ adb logcat
如果是在远程shell中可直接使用命令:

logcat

过滤日志输出
每一条日志消息都有一个标记和优先级与其关联。
l 标记是一个简短的字符串,用于标识原始消息的来源 (例如”View” 来源于显示系统)。
l 优先级是下面的字符,顺序是从低到高:
V — 明细 (最低优先级)
D — 调试
I — 信息
W — 警告
E — 错误
F — 严重错误
S — 无记载 (最高优先级,没有什么会被记载)
通过运行logcat ,可以获得一个系统中使用的标记和优先级的列表,观察列表的前两列,给出的格式是/。
这里是一个日志输出的消息,优先级是“I”,标记是“ActivityManager”:
I/ActivityManager( 585): Starting activity: Intent { action=android.intent.action…}
如果想要减少输出的内容,可以加上过滤器表达式进行限制,过滤器可以限制系统只输出感兴趣的标记-优先级组合。
过滤器表达式的格式是tag:priority … ,其中tag是标记, priority是最小的优先级, 该标记标识的所有大于等于指定优先级的消息被写入日志。也可以在一个过滤器表达式中提供多个这样的过滤,它们之间用空格隔开。
下面给出的例子是仅输出标记为“ActivityManager”并且优先级大于等于“Info”和标记为“MyApp”并且优先级大于等于“Debug”的日志:
adb logcat ActivityManager:I MyApp:D *:S
上述表达式最后的 :S 用于设置所有标记的日志优先级为S,这样可以确保仅有标记为“View”(译者注:应该为ActivityManager,原文可能是笔误)和“MyApp”的日志被输出,使用 :S 是可以确保输出符合指定的过滤器设置的一种推荐的方式,这样过滤器就成为了日志输出的“白名单”。
下面的表达是显示所有优先级大于等于“warning”的日志:
adb logcat *:W
如果在开发用电脑上运行 logcat (相对于运行运程shell而言),也可以通过ANDROID_LOG_TAGS环境变量设置默认的过滤器表达式:
export ANDROID_LOG_TAGS=”ActivityManager:I MyApp:D *:S”
需要注意的是,如果是在远程shell或是使用adb shell logcat 命令运行logcat , ANDROID_LOG_TAGS 不会导出到模拟器或手机设备上。

控制日志格式

日志消息在标记和优先级之外还有很多元数据字段,这些字段可以通过修改输出格式来控制输出结果, -v 选项加上下面列出的内容可以控制输出字段:

brief — 显示优先级/标记和原始进程的PID (默认格式)
process — 仅显示进程PID
tag — 仅显示优先级/标记
thread — 仅显示进程:线程和优先级/标记
raw — 显示原始的日志信息,没有其他的元数据字段
time — 显示日期,调用时间,优先级/标记,PID
long —显示所有的元数据字段并且用空行分隔消息内容
可以使用 -v启动 logcat来控制日志格式:
[adb] logcat [-v ]
例如使用 thread 输出格式:
adb logcat -v thread
注意只能在 -v 选项中指定一种格式。

Viewing Alternative Log Buffers
Android日志系统为日志消息保持了多个循环缓冲区,而且不是所有的消息都被发送到默认缓冲区,要想查看这些附加的缓冲区,可以使用-b 选项,以下是可以指定的缓冲区:
l radio — 查看包含在无线/电话相关的缓冲区消息
l events — 查看事件相关的消息
l main — 查看主缓冲区 (默认缓冲区)
-b 选项的用法:
[adb] logcat [-b ]
例如查看radio缓冲区:
adb logcat -b radio

查看stdout和stderr
默认的,Android系统发送 stdout 和 stderr (System.out 和 System.err) 输出到 /dev/null。 在 Dalvik VM进程,可以将输出复制到日志文件,在这种情况下,系统使用 stdout 和 stderr标记写入日志,优先级是I。
要想使用这种方式获得输出,需要停止运行中的模拟器或手机,然后使用命令 setprop 来允许输出重定位,示例如下:
adbshellstop adb shell setprop log.redirect-stdio true $ adb shell start
系统会保留这一设置直到模拟器或手机退出,也可以在设备中增加/data/local.prop以使得这一设备成为默认配置。

Logcat命令选项列表

选项描述
-b
加载不同的缓冲区日志,例如 event 或radio。main 缓冲区是默认项,参见Viewing Alternative Log Buffers.
-c
清空(刷新)所有的日志并且退出
-d
在屏幕上输出日志并退出
-f
将日志输出到文件
默认输出是stdout.
例如:adb logcat –d –f sdcard/log.txt
adb logcat –d –f sdcard/log.txt &这个表示后台进行
adb logcat > D:/1.txt
把信息存放到D盘目录下的1.txt文件,方便后续查看。
注意,adb logcat > C:/1.txt,这个C盘就不一定成功,会拒绝访问。
效果:

这里写图片描述

这里写图片描述

里面包含了手机所有的log信息
-g
输出日志的大小
-n
设置最大的循环数据,默认是4,需要-r选项
-r
每循环日志文件,默认是16,需要 -f 选项
-s
设置默认的过滤器为无输出
-v
设置输出格式,默认的是brief,支持的格式列表参见Controlling Log Output Format.

停止adb服务

在某些情况下,可能需要终止然后重启服务端进程,例如adb不响应命令的时候,可以通过重启解决问题。使用kill-server可以终止服务端,然后使用其他的adb命令重启。

0 0
原创粉丝点击