android adb 流程原理代码分析(一)

由于要用到adb的知识,但是对adb啥也不了解,看了下android的代码,adb的源码在system/core/adb下面,然后网上搜下了资料,发现很多大神的源码分析,瞬间信心爆棚,把大神写的博客都浏览了一遍,然后手动运行了下adb命令,顺便跟踪了下过程,发现原来还是很好的理解,源码的各种线程创建,函数回调,对于我这种基础不咋好的,,还是看的晕晕呼呼,现在把我自己的理解给大家分享,有理解错误的还请多多指正

一般直接上代码,可能看官都走了一大,我们逆向的看,先看结果,再看过程,最后再看代码。从简单的入手。

理解:

(1)adb的本质,就是socket的通信,通过secket传送数据及文件

(2)adb传送是以每个固定格式的包发送的数据,包的格式如下:

#define A_SYNC 0x434e5953
#define A_CNXN 0x4e584e43
#define A_OPEN 0x4e45504f
#define A_OKAY 0x59414b4f
#define A_CLSE 0x45534c43
#define A_WRTE 0x45545257
#define A_AUTH 0x48545541

struct amessage {
    unsigned command;       /* command identifier constant      */
    unsigned arg0;          /* first argument                   */
    unsigned arg1;          /* second argument                  */
    unsigned data_length;   /* length of payload (0 is allowed) */
    unsigned data_check;    /* checksum of data payload         */
    unsigned magic;         /* command ^ 0xffffffff             */
};

struct apacket
{
    apacket *next;

    unsigned len;
    unsigned char *ptr;

    amessage msg;
    unsigned char data[MAX_PAYLOAD];
};

发送的包格式为apacket格式,其中msg为消息部分,data为数据部分。msg的消息类型有很多种,包括A_SYNC, A_CNXN, A_OPEN, A_OKAY等等。

到此,我们对adb的理解就是,一种socket通信,每次发送apacket格式的数据包,好吧,我们在这样的认知下继续学习adb。

(3)adb给我们预留了调试的信息,我们只需要在adb.h中定义指定的宏,即可看到每次数据的传输过程:

#define DEBUG_PACKETS 1

(4)我们使用adb push命令,来跟踪分析下这个apacket数据是怎样传输的:

我们以adb push profile /命令为例,在串口我们可以看见如下详细的传输信息:

  status     command    arg0              arg1           len          data

recv:     OPEN     00141028     00000000     0006     "sync:."

send:     OKAY     0000003e     00141028     0000     ""

recv:     WRTE     00141028     0000003e     0009     "STAT..../"

send:     OKAY     0000003e     00141028     0000     ""

send:     WRTE     0000003e     00141028     0010     "STAT.A......[oHZ"

recv:     OKAY     00141028     0000003e     0000      ""

recv:     WRTE     00141028     0000003e     0027     "SEND..../profile,33206DATA....2D

send:     OKAY     0000003e     00141028     0000     ""

send:     WRTE     0000003e     00141028     0008     "OKAY...."

recv:     OKAY     00141028     0000003e      0000     ""

recv:     WRTE     00141028     0000003e       0008     "QUIT...."

send:     OKAY     0000003e     00141028     0000     ""

send:     CLSE     00000000     00141028     0000     ""

recv:      CLSE     00141028     0000003e     0000     ""

以上recv表示接收的数据包,send表示回传的数据包。后面五个分别为数据包的数据字段值(command arg0 arg1 len data),这样数据我们还是不够直观,我们翻译成更加直接的数据辅以文字解释

这样是不是容易理解多了呢,经过这样的数据发送,我们就通过adb push命令把本地的profile文件推送到远程设备的根目录了。哇..... 原来这么简单,一个profile文件就传输了。流程理解了,我们再来看代码,现在结果你知道了,流程你也懂了,再来看源码,是不是容易理解了呢。

(5)同样,我们看代码也是逆向的看,这样利于我们理解,不会被源码看到晕乎乎,上面流程懂了,知道了每次是以apacket的格式发送的,我们先来研究这个apacket的接收与发送函数。

接收函数handle_packet

void handle_packet(apacket *p, atransport *t)
{
    asocket *s;

    D("handle_packet() %c%c%c%c\n", ((char*) (&(p->msg.command)))[0],
            ((char*) (&(p->msg.command)))[1],
            ((char*) (&(p->msg.command)))[2],
            ((char*) (&(p->msg.command)))[3]);
    print_packet("recv", p);

    switch(p->msg.command){
    case A_SYNC:
        if(p->msg.arg0){
            send_packet(p, t);
            if(HOST) send_connect(t);
        } else {
            t->connection_state = CS_OFFLINE;
            handle_offline(t);
            send_packet(p, t);
        }
        return;

    case A_CNXN: /* CONNECT(version, maxdata, "system-id-string") */
            /* XXX verify version, etc */
        if(t->connection_state != CS_OFFLINE) {
            t->connection_state = CS_OFFLINE;
            handle_offline(t);
        }

        parse_banner((char*) p->data, t);

        if (HOST || !auth_enabled) {
            handle_online(t);
            if(!HOST) send_connect(t);
        } else {
            send_auth_request(t);
        }
        break;

    case A_AUTH:
        if (p->msg.arg0 == ADB_AUTH_TOKEN) {
            t->key = adb_auth_nextkey(t->key);
            if (t->key) {
                send_auth_response(p->data, p->msg.data_length, t);
            } else {
                /* No more private keys to try, send the public key */
                send_auth_publickey(t);
            }
        } else if (p->msg.arg0 == ADB_AUTH_SIGNATURE) {
            if (adb_auth_verify(t->token, p->data, p->msg.data_length)) {
                adb_auth_verified(t);
                t->failed_auth_attempts = 0;
            } else {
                if (t->failed_auth_attempts++ > 10)
                    adb_sleep_ms(1000);
                send_auth_request(t);
            }
        } else if (p->msg.arg0 == ADB_AUTH_RSAPUBLICKEY) {
            adb_auth_confirm_key(p->data, p->msg.data_length, t);
        }
        break;

    case A_OPEN: /* OPEN(local-id, 0, "destination") */
        if (t->online) {
            char *name = (char*) p->data;
            name[p->msg.data_length > 0 ? p->msg.data_length - 1 : 0] = 0;
            s = create_local_service_socket(name);
            if(s == 0) {
                send_close(0, p->msg.arg0, t);
            } else {
                s->peer = create_remote_socket(p->msg.arg0, t);
                s->peer->peer = s;
                send_ready(s->id, s->peer->id, t);
                s->ready(s);
            }
        }
        break;

    case A_OKAY: /* READY(local-id, remote-id, "") */
        if (t->online) {
            if((s = find_local_socket(p->msg.arg1))) {
                if(s->peer == 0) {
                    s->peer = create_remote_socket(p->msg.arg0, t);
                    s->peer->peer = s;
                }
                s->ready(s);
            }
        }
        break;

    case A_CLSE: /* CLOSE(local-id, remote-id, "") */
        if (t->online) {
            if((s = find_local_socket(p->msg.arg1))) {
                s->close(s);
            }
        }
        break;

    case A_WRTE:
        if (t->online) {
            if((s = find_local_socket(p->msg.arg1))) {
                unsigned rid = p->msg.arg0;
                p->len = p->msg.data_length;

                if(s->enqueue(s, p) == 0) {
                    D("Enqueue the socket\n");
                    send_ready(s->id, rid, t);
                }
                return;
            }
        }
        break;

    default:
        printf("handle_packet: what is %08x?!\n", p->msg.command);
    }

    put_apacket(p);
}

哇,这个函数好像不复杂,一个函数,然后解析apacket *p数据,根据msg.command的命令值, 然后对应不同的case,有着不同的响应。事实上也就是这样,这个函数主要就是根据不同的消息类型,来处理这个apacket的数据。
    上面不是有adb push命令吗,我们根据这个流程,看看handle_packet是否是跟我们预期的响应流程一样。

  (5.1)OPEN响应

recv:     OPEN     00141028     00000000     0006     "sync:."

send:   OKAY   0000003e   00141028   0000   ""

接收到了OPEN的消息,然后附带了一个sync的数据,我们看看是如何响应的。

    case A_OPEN: /* OPEN(local-id, 0, "destination") */
        if (t->online) {
            char *name = (char*) p->data;
            name[p->msg.data_length > 0 ? p->msg.data_length - 1 : 0] = 0;
            s = create_local_service_socket(name);
            if(s == 0) {
                send_close(0, p->msg.arg0, t);
            } else {
                s->peer = create_remote_socket(p->msg.arg0, t);
                s->peer->peer = s;
                send_ready(s->id, s->peer->id, t);
                s->ready(s);
            }
        }
        break;

调用create_local_service_socket(“sync”);

fd = service_to_fd(name);

//创建本地socket,并为这个socket创建数据处理线程file_sync_service

ret = create_service_thread(file_sync_service, NULL);

//把这个本地socket关联到结构asocket *s

s = create_local_socket(fd);

调用create_remote_socket(p->msg.arg0, t); //把远程的socket也与这个结构体asocket 关联。

如上两个函数调用,主要是初始化本地的socket对,本地socket用来跟后台服务线程之间的通信,以及跟对应命令的后台服务线程通信。初始化adb通信的环境。其中asocket *s为本地socket与远程socket的一个关联结构体,其中s保存的是本地socket的信息,s->peer保存的是远程socket相关的信息。

send_ready(s->id, s->peer->id, t); 然后发送OKAY给PC端。

static void send_ready(unsigned local, unsigned remote, atransport *t)
{
    D("Calling send_ready \n");
    apacket *p = get_apacket();
    p->msg.command = A_OKAY;
    p->msg.arg0 = local;
    p->msg.arg1 = remote;
    send_packet(p, t);
}

这个与我们看到的流程相符合。接收到OPEN的消息,初始化一些状态,然后返回一个OKAY的状态

(5.2)WRITE响应

recv:   WRTE  00141028   0000003e   0009   "STAT..../"

send:   OKAY   0000003e   00141028   0000   ""

send:   WRTE   0000003e   00141028   0010   "STAT.A......[oHZ"

接收到了WRITE的消息,顺带了一个查询STAT的数据,我们看看是如何响应的:

    case A_WRTE:
        if (t->online) {
            if((s = find_local_socket(p->msg.arg1))) {
                unsigned rid = p->msg.arg0;
                p->len = p->msg.data_length;

                if(s->enqueue(s, p) == 0) {
                    D("Enqueue the socket\n");
                    send_ready(s->id, rid, t);
                }
                return;
            }
        }
        break;

先通过参数p->msg.arg1找到我们在OPEN的时候建立的结构体信息asocket *s, 然后处理本地socket队列中的数据(s为本地,s->peer为远程)

s->enqueue(s, p)即为之前 关联的函数local_socket_enqueue,其在create_local_socket(fd);    的时候设置。

static int local_socket_enqueue(asocket *s, apacket *p)
{
    D("LS(%d): enqueue %d\n", s->id, p->len);

    p->ptr = p->data;

        /* if there is already data queue‘d, we will receive
        ** events when it‘s time to write.  just add this to
        ** the tail
        */
    if(s->pkt_first) {
        goto enqueue;
    }

        /* write as much as we can, until we
        ** would block or there is an error/eof
        */
    while(p->len > 0) {
        int r = adb_write(s->fd, p->ptr, p->len);
        if(r > 0) {
            p->len -= r;
            p->ptr += r;
            continue;
        }
        if((r == 0) || (errno != EAGAIN)) {
            D( "LS(%d): not ready, errno=%d: %s\n", s->id, errno, strerror(errno) );
            s->close(s);
            return 1; /* not ready (error) */
        } else {
            break;
        }
    }

    if(p->len == 0) {
        put_apacket(p);
        return 0; /* ready for more data */
    }

enqueue:
    p->next = 0;
    if(s->pkt_first) {
        s->pkt_last->next = p;
    } else {
        s->pkt_first = p;
    }
    s->pkt_last = p;

        /* make sure we are notified when we can drain the queue */
    fdevent_add(&s->fde, FDE_WRITE);

    return 1; /* not ready (backlog) */
}

我们通过adb_write(s->fd, p->ptr, p->len)把要处理的数据,写入到本地socket对应的fd中,等待处理。

然后调用send_ready(s->id, rid, t);返回一个OKAY的状态

我们把待处理的数据adb_write之后,又是在哪里处理的呢,我们之前在创建本地socket的时候,就创建了一个线程,对应的处理socket数据的函数file_sync_service

我们来看看file_sync_service函数是如何处理的

void file_sync_service(int fd, void *cookie)
{
    syncmsg msg;
    char name[1025];
    unsigned namelen;

    char *buffer = malloc(SYNC_DATA_MAX);
    if(buffer == 0) goto fail;

    for(;;) {
        D("sync: waiting for command\n");

        if(readx(fd, &msg.req, sizeof(msg.req))) {
            fail_message(fd, "command read failure");
            break;
        }
        namelen = ltohl(msg.req.namelen);
        if(namelen > 1024) {
            fail_message(fd, "invalid namelen");
            break;
        }
        if(readx(fd, name, namelen)) {
            fail_message(fd, "filename read failure");
            break;
        }
        name[namelen] = 0;

        msg.req.namelen = 0;
        D("sync: ‘%s‘ ‘%s‘\n", (char*) &msg.req, name);

        switch(msg.req.id) {
        case ID_STAT:
            if(do_stat(fd, name)) goto fail;
            break;
        case ID_LIST:
            if(do_list(fd, name)) goto fail;
            break;
        case ID_SEND:
            if(do_send(fd, name, buffer)) goto fail;
            break;
        case ID_RECV:
            if(do_recv(fd, name, buffer)) goto fail;
            break;
        case ID_QUIT:
            goto fail;
        default:
            fail_message(fd, "unknown command");
            goto fail;
        }
    }

fail:
    if(buffer != 0) free(buffer);
    D("sync: done\n");
    adb_close(fd);
}

原来在这里处理的数据,终于找到你, 我们收到的消息是查看路径是否存在,这里对应的就是ID_STAT,还有其他的消息处理,比如ID_SEND,ID_RECV,ID_QUIT,望文生义,我们就不具体解释了。我们还是看看ID_STAT对应的处理吧do_stat(fd, name)。

static int do_stat(int s, const char *path)
{
    syncmsg msg;
    struct stat st;

    msg.stat.id = ID_STAT;

    if(lstat(path, &st)) {
        msg.stat.mode = 0;
        msg.stat.size = 0;
        msg.stat.time = 0;
    } else {
        msg.stat.mode = htoll(st.st_mode);
        msg.stat.size = htoll(st.st_size);
        msg.stat.time = htoll(st.st_mtime);
    }

    return writex(s, &msg.stat, sizeof(msg.stat));
}

这里就是判断路径是否存在的逻辑了,这个就是我们想要的,我们把判断的结果存储在msg.stat, 然后把对应的结果写回去writex。

我们把检测的状态writex之后,但是这个数据还没有发送回PC端啊,是在哪里发送回去的呢,我们继续跟踪         我们在create_local_socket创建本地socket的时候,顺便还注册了一个回调函数local_socket_event_func

static void local_socket_event_func(int fd, unsigned ev, void *_s)
{
    asocket *s = _s;

    D("LS(%d): event_func(fd=%d(==%d), ev=%04x)\n", s->id, s->fd, fd, ev);

    /* put the FDE_WRITE processing before the FDE_READ
    ** in order to simplify the code.
    */
    if(ev & FDE_WRITE){
        apacket *p;

        while((p = s->pkt_first) != 0) {
            while(p->len > 0) {
                int r = adb_write(fd, p->ptr, p->len);
                if(r > 0) {
                    p->ptr += r;
                    p->len -= r;
                    continue;
                }
                if(r < 0) {
                    /* returning here is ok because FDE_READ will
                    ** be processed in the next iteration loop
                    */
                    if(errno == EAGAIN) return;
                    if(errno == EINTR) continue;
                }
                D(" closing after write because r=%d and errno is %d\n", r, errno);
                s->close(s);
                return;
            }

            if(p->len == 0) {
                s->pkt_first = p->next;
                if(s->pkt_first == 0) s->pkt_last = 0;
                put_apacket(p);
            }
        }

            /* if we sent the last packet of a closing socket,
            ** we can now destroy it.
            */
        if (s->closing) {
            D(" closing because ‘closing‘ is set after write\n");
            s->close(s);
            return;
        }

            /* no more packets queued, so we can ignore
            ** writable events again and tell our peer
            ** to resume writing
            */
        fdevent_del(&s->fde, FDE_WRITE);
        s->peer->ready(s->peer);
    }

    if(ev & FDE_READ){
        apacket *p = get_apacket();
        unsigned char *x = p->data;
        size_t avail = MAX_PAYLOAD;
        int r;
        int is_eof = 0;

        while(avail > 0) {
            r = adb_read(fd, x, avail);
            D("LS(%d): post adb_read(fd=%d,...) r=%d (errno=%d) avail=%d\n", s->id, s->fd, r, r<0?errno:0, avail);
            if(r > 0) {
                avail -= r;
                x += r;
                continue;
            }
            if(r < 0) {
                if(errno == EAGAIN) break;
                if(errno == EINTR) continue;
            }

                /* r = 0 or unhandled error */
            is_eof = 1;
            break;
        }
        D("LS(%d): fd=%d post avail loop. r=%d is_eof=%d forced_eof=%d\n",
          s->id, s->fd, r, is_eof, s->fde.force_eof);
        if((avail == MAX_PAYLOAD) || (s->peer == 0)) {
            put_apacket(p);
        } else {
            p->len = MAX_PAYLOAD - avail;

            r = s->peer->enqueue(s->peer, p);
            D("LS(%d): fd=%d post peer->enqueue(). r=%d\n", s->id, s->fd, r);

            if(r < 0) {
                    /* error return means they closed us as a side-effect
                    ** and we must return immediately.
                    **
                    ** note that if we still have buffered packets, the
                    ** socket will be placed on the closing socket list.
                    ** this handler function will be called again
                    ** to process FDE_WRITE events.
                    */
                return;
            }

            if(r > 0) {
                    /* if the remote cannot accept further events,
                    ** we disable notification of READs.  They‘ll
                    ** be enabled again when we get a call to ready()
                    */
                fdevent_del(&s->fde, FDE_READ);
            }
        }
        /* Don‘t allow a forced eof if data is still there */
        if((s->fde.force_eof && !r) || is_eof) {
            D(" closing because is_eof=%d r=%d s->fde.force_eof=%d\n", is_eof, r, s->fde.force_eof);
            s->close(s);
        }
    }

    if(ev & FDE_ERROR){
            /* this should be caught be the next read or write
            ** catching it here means we may skip the last few
            ** bytes of readable data.
            */
//        s->close(s);
        D("LS(%d): FDE_ERROR (fd=%d)\n", s->id, s->fd);

        return;
    }
}

这个函数内容就比较多了,我们看后面if(ev & FDE_READ)部分:

adb_read(fd, x, avail);把数据读出来,然后调用r = s->peer->enqueue(s->peer, p);,即把数据发送给远程socket的队列处理。(s->speer即远程端,之前已经说明)

s->peer->enqueue函数即remote_socket_enqueue:

static int remote_socket_enqueue(asocket *s, apacket *p)
{
    D("entered remote_socket_enqueue RS(%d) WRITE fd=%d peer.fd=%d\n",
      s->id, s->fd, s->peer->fd);
    p->msg.command = A_WRTE;
    p->msg.arg0 = s->peer->id;
    p->msg.arg1 = s->id;
    p->msg.data_length = p->len;
    send_packet(p, s->transport);
    return 1;
}

这样我们就把STAT的结果,通过WRITE返回给了PC端

        这个与我们看到的流程也是相符的,接收到WRITE(STAT)的消息,先返回一个OKAY的状态,在返回WRITE(STAT)的结果

我们可以观察之前的数据接收及发送流程,可以发现每次一个WRITE消息,后面都是返回一个OKAY WRITE消息。

贴了这么多的代码,是不是有点晕了,再贴就真的看不下去了,我们下面重新来理一理思路。

1. adb其实就是个socket通信,数据发过来发过去。

2. adb每次都是发送的一个数据包,数据结构是struct apacket,其中包含msg消息部分,及data数据部分。

3. 从PC跟device通信的过程,有一条协议流程,通过不断的数据交互发送,实现数据文件传递。

4. 我们可以定义 #define DEBUG_PACKETS 1 这样可以看到socket通信的数据发送过程。

5. socket数据建立传输过程,会创建socket,创建事件监听线程,注册回调响应函数,乱七八糟的......

6. 然后就是一系列的代码流程了,头晕了,我们下节再来详细理一理这个,为啥要按照你说的这个流程走,为啥你说走到这里,调用这个函数,凭啥相信你,下节见......

原文地址:https://www.cnblogs.com/codeking100/p/10344069.html

时间: 01-31

android adb 流程原理代码分析(一)的相关文章

例子Architecting Android…The clean way?----代码分析

Presention层: 整个应用启动的时候,就执行依赖的初始化.编译项目之后,Dagger依赖框架使用ApplicationComponent生成一个DaggerApplicationCOmponent. 1. 首先进行依赖的生成 在Application中,调用initializeInjector()就会促使Dagger框架进行依赖生成. ApplicationComponent 对其它Component提供Context,ThreadExecutor,PostExecutionThread

Android加壳原理分析

0x00 阅读本文前,建议读者首先阅读Android加壳原理,参考文章Android中的Apk的加固(加壳)原理解析和实现.如果没有看过这篇文章,本文理解起来比较困难. 0x01 下面我们来分析脱壳代码为什么要这样写,核心脱壳代码在ProxyApplication类里面,首先执行成员方法attachBaseContext,然后执行成员方法onCreate. 那么attachBaseContext是什么时候被执行的呢,为什么先于onCreate执行呢?那就需要看Android的源码了,我们选用的是

android usb adb流程[转]

android adb 概述 android adb的代码分为两部分: kernel层的代码在如下路径: drivers/usb/gadget/f_adb.c drivers/usb/gadget/android.c 他吐给上层应用的是如下的设备节点:/dev/android_adb 应用层的代码在如下路径: system/core/adb目录 针对device,该目录编译的输出是adbd 控制台上手动启动平板adb的功能的方法如下: step1:在init.rc中申明adbd服务 [cpp] 

Java NIO原理 图文分析及代码实现

Java NIO原理 图文分析及代码实现 博客分类: java底层 java NIO原理阻塞I/O非阻塞I/O Java NIO原理图文分析及代码实现 前言:  最近在分析hadoop的RPC(Remote Procedure Call Protocol ,远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议.可以参考:http://baike.baidu.com/view/32726.htm )机制时,发现hadoop的RPC机制的实现主要用到了两个技术

[JavaEE]Java NIO原理图文分析及代码实现

转http://weixiaolu.iteye.com/blog/1479656 目录: 一.java NIO 和阻塞I/O的区别      1. 阻塞I/O通信模型      2. java NIO原理及通信模型 二.java NIO服务端和客户端代码实现 具体分析: 一.java NIO 和阻塞I/O的区别 1. 阻塞I/O通信模型 假如现在你对阻塞I/O已有了一定了解,我们知道阻塞I/O在调用InputStream.read()方法时是阻塞的,它会一直等到数据到来时(或超 时)才会返回:同

Android系统启动流程分析

随着Android版本的升级,aosp项目中的代码也有了些变化,本文基于Android 7.0分析Android系统启动流程. 简单来说Android系统启动大体如下: init进程 和所有Linux系统一样,Android系统的启动同样是从init进程启动.init进程会解析init.rc文件(关于init.rc中的语法,可以参见我之前写的深入分析AIL语言及init.rc文件),加载相关目录,并启动相关服务 init进程在/system/core/init/init.c init.rc文件在

【转】android SystemUI 流程分析

android4 SystemUI 流程分析 什么是SystemUI? 对于Phone来说SystemUI指的是:StatusBar(状态栏).NavigationBar(导航栏).而对于Tablet或者是TV来说SystemUI指的是:CombinedBar(包括了StatusBar和NavigationBar). 启动后Phone界面上的信号,蓝牙标志,Wifi标志等等这些状态显示标志都会在StatusBar上显示.当我们的设备开机后,首先需要给用户呈现的就是各种界面同时也包括了我们的Sys

adb通信原理分析

关于这个问题,自己研究了一下,没有研究出来 在网络上搜罗了一下,基本上关于ADB的原理用了两张图表示:        我表示没有看懂这两个图, 又开始查阅一些一些资料: 首先知道adb的通信有Socket,那么socket是什么呢? 套接字(socket)概念  套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元.它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的

TeamTalk Android代码分析(业务流程篇)

TeamTalk Android代码分析(业务流程篇) 1.1 总体结构 1.总体结构有点类似MVC的感觉,模块结构从上向下大体是: UI层:Activity和Fragment构成,期间包括常用的一些开源控件如:imageloader,speedx,gifview等,和下层数据变更通知通过总线事件完成(EventBus) 管理层:Service(即:imservice,下文均采用此称呼)和一些按照业务划分的Manager(loginmanager,contactmanager,sessionma

【Heritrix源代码分析4】开始一个爬虫抓取的全流程代码分析

在创建一个job后,就要开始job的运行,运行的全流程如下: 1.在界面上启动job 2.index.jsp 查看上述页面对应的源代码 <a href='"+request.getContextPath()+"/console/action.jsp?action=start'>Start</a> 3.action.jsp String sAction = request.getParameter("action"); if(sAction !