ADB(七)_USB连接 (ABD通过USB连接的流程分析)

10 篇文章 2 订阅
8 篇文章 13 订阅

前言

前文

ADB(一)_概况了解
ADB(二)_ADBD_main()函数代码梳理
ADB(三)_ADBD_adbd_main()函数代码梳理
ADB(四)_host端的启动流程代码梳理
ADB(五)_host端adb server相关的代码梳理
ADB(六)_调试ADB(ADB设置自身日志的代码梳理和设置ADB自身日志可见)

首先,我们知道,开发中在使用ADB的时经常使用的是USB连接Android开发设备。我们今天就主要对USB通信进行分析;不过我们不会对USB本身的协议、原理、实现等方面进行说明,那些不在我们讨论的范围内,我们只是就USB通信在ADB的的使用进行源代码层面的梳理。

我们的USB通信是连接host端的adb程序和Android端的adbd程序,所以我们需要分别对adb 和adbd 的运行过程有所了解,具体可以参考前面讨论过的文章;

Android设备端的usb初始化和usb消息处理流程

1.usb_init()

在adbd的初始化流程中,我们在adbd_main()函数中看到有调用usb_init()函数,usb_init()函数就是usb初始化的入口。我们先看看usb_init()函数是如何被调用的:

    bool is_usb = false;
    if (access(USB_FFS_ADB_EP0, F_OK) == 0) {
        // Listen on USB.
        usb_init();
        is_usb = true;
    }

首先,会有个判断,主要是通过调用access()函数,这个access()函数是用于检查调用进程是否可以对指定的文件执行某种操作。这里简单的先把这个函数介绍一下:
函数定义在:unistd.h中,

/* Test for access to NAME using the real UID and real GID.  */
extern int access (const char *__name, int __type) __THROW __nonnull ((1));
。。。
/* Values for the second argument to access.
   These may be OR'd together.  */
#define	R_OK	4		/* Test for read permission.  */
#define	W_OK	2		/* Test for write permission.  */
#define	X_OK	1		/* Test for execute permission.  */
#define	F_OK	0		/* Test for existence.  */

  • 参数一: *__name : 需要测试的文件路径名
  • 参数二 : __type :是否允许的操作,可以是一个值或者是多个值组合,具体的可能值参见上面的定义

在当前的程序代码中,access()函数的需要检测的参数是USB_FFS_ADB_EP0,这USB_FFS_ADB_EP0又是什么呢?
我们就从定义出看看:/system/core/adb/adb.h

...
#if !ADB_HOST
#define USB_FFS_ADB_PATH "/dev/usb-ffs/adb/"
#define USB_FFS_ADB_EP(x) USB_FFS_ADB_PATH #x
#define USB_FFS_ADB_EP0 USB_FFS_ADB_EP(ep0)
...

经过上述的宏定义,我们知道USB_FFS_ADB_EP0等价于/dev/usb-ffs/adb/ep0;所以判断当前/dev/usb-ffs/adb/ep0文件【USB节点】是否存在,如果存在就调用usb_init()函数:

void usb_init() {
    ...
    usb_ffs_init();
}

2.usb_ffs_init()

usb_init()函数里面主要通过调用usb_ffs_init()函数,我们就需要到usb_ffs_init()中去,

static void usb_ffs_init() {
    D("[ usb_init - using FunctionFS ]");
    usb_handle* h = new usb_handle();

    if (android::base::GetBoolProperty("sys.usb.ffs.aio_compat", false)) {
        // Devices on older kernels (< 3.18) will not have aio support for ffs
        // unless backported. Fall back on the non-aio functions instead.
        h->write = usb_ffs_write;
        h->read = usb_ffs_read;
    } else {
        h->write = usb_ffs_aio_write;
        h->read = usb_ffs_aio_read;
        aio_block_init(&h->read_aiob);
        aio_block_init(&h->write_aiob);
    }
    h->kick = usb_ffs_kick;
    h->close = usb_ffs_close;

    D("[ usb_init - starting thread ]");
    std::thread(usb_ffs_open_thread, h).detach();
}

如上所示是usb_ffs_init()函数的定义,在一开始,我们首先创建一个usb_handle的,这个usb_handle是个与USB连接先关的struct。

2.1 usb_handle

struct usb_handle {
    usb_handle() : kicked(false) {
    }

    std::condition_variable notify;
    std::mutex lock;
    std::atomic<bool> kicked;
    bool open_new_connection = true;

    int (*write)(usb_handle* h, const void* data, int len);
    int (*read)(usb_handle* h, void* data, int len);
    void (*kick)(usb_handle* h);
    void (*close)(usb_handle* h);

    // FunctionFS
    int control = -1;
    int bulk_out = -1; /* "out" from the host's perspective => source for adbd */
    int bulk_in = -1;  /* "in" from the host's perspective => sink for adbd */
    // Access to these blocks is very not thread safe. Have one block for both the
    // read and write threads.
    struct aio_block read_aiob;
    struct aio_block write_aiob;
};

2.2 android::base::GetBoolProperty(“sys.usb.ffs.aio_compat”, false)

在常见一个新的usb_handle后,程序会获取Android系统属性sys.usb.ffs.aio_compat判断,根据属性值分别对usb_handle内部的变量进行赋值;

3 usb_ffs_open_thread

紧接着,就会创建并启动一个新的线程去执行sb_ffs_open_thread()函数,并把前面创建的并赋值好变量的结构体usb_handle传进去;我们跳转到usb_ffs_open_thread()中继续分析:

std::thread(usb_ffs_open_thread, h).detach();

static void usb_ffs_open_thread(void* x) {
    struct usb_handle* usb = (struct usb_handle*)x;
    adb_thread_setname("usb ffs open");
    while (true) {
        // wait until the USB device needs opening
        std::unique_lock<std::mutex> lock(usb->lock);
        while (!usb->open_new_connection) {
            usb->notify.wait(lock);
        }
        usb->open_new_connection = false;
        lock.unlock();
        while (true) {
            if (init_functionfs(usb)) {
                LOG(INFO) << "functionfs successfully initialized";
                break;
            }
            std::this_thread::sleep_for(1s);
        }
        LOG(INFO) << "registering usb transport";
        register_usb_transport(usb, 0, 0, 1);
    }

    // never gets here
    abort();
}

如上所述,usb_ffs_open_thread()函数会进入一个while死循环,在外面大的while循环中:

  • 首先判断usb->open_new_connection变量是否为true。这个在usb_handle结构体定义时就将open_new_connection默认赋值为true,并且在前面的usb_ffs_close()函數中將open_new_connection再次赋值为true,所以这里就不会调用wait()方法进入阻塞状态,代码继续运行;
  • 然后第二个循环中while循环中,一直调用init_functionfs()函数来初始化usb,如果初始化完成,即init_functionfs()函数返回true,就会跳出循环,否则程序会休眠1秒,然后继续调用init_functionfs()函数来初始化。

在usb初始化完成后,会调用register_usb_transport()函数来注册usb传输;

4. register_usb_transport(usb, 0, 0, 1)

void register_usb_transport(usb_handle* usb, const char* serial, const char* devpath,
                            unsigned writeable) {
    atransport* t = new atransport((writeable ? kCsOffline : kCsNoPerm));
    init_usb_transport(t, usb);
	...
	{
       std::lock_guard<std::recursive_mutex> lock(transport_lock);
        pending_list.push_front(t);
    }
    register_transport(t);
}

register_usb_transport()函数中首先创建一个新的atransport实例,当前的writeable=1,所以类atransport的成员变量connection_state_ = kCsOffline;

4.1 init_usb_transport(t, usb);

紧接着调用init_usb_transport()函数;

void init_usb_transport(atransport* t, usb_handle* h) {
    D("transport: usb");
    t->connection.reset(new UsbConnection(h));
    t->sync_token = 1;
    t->type = kTransportUsb;
}

init_usb_transport()函数主要就是将atransport内部的相关成员变量初始化;然后将当前的atransport示例装到pending_list列表中,只要是保存待处理消息的。pending_list定义如下:

static auto& pending_list = *new std::list<atransport*>();

最后会调用register_transport().方法,进行注册传输.

5. register_transport(t);

/* the fdevent select pump is single threaded */
static void register_transport(atransport* transport) {
    tmsg m;
    m.transport = transport;
    m.action = 1;
    D("transport: %s registered", transport->serial);
    if (transport_write_action(transport_registration_send, &m)) {
        fatal_errno("cannot write transport registration socket\n");
    }
}

register_transport()函数主要作用就是封装起一个tmsg,然后通过transport_write_action()函数写入到transport_registration_send中,然后在程序中的另一端进行处理:
程序利用poll()机制,可以很方便的监听众多的fd.一旦当前的transport_registration_send中有数据的写入,poll()就会通知程序去处理;

接下来,我们需要把重点放到transport_registration_send中去,看看它是怎么将当前分行装好的消息传递到相关的函数中进行处理的.

6 init_transport_registration();

我们先回到 adbd_main()函数中,在调用usb_init()函数前调用了init_transport_registration()函数;我们来看看:

void init_transport_registration(void) {
    int s[2];
    if (adb_socketpair(s)) {
        fatal_errno("cannot open transport registration socketpair");
    }
    D("socketpair: (%d,%d)", s[0], s[1]);
    transport_registration_send = s[0];
    transport_registration_recv = s[1];
    fdevent_install(&transport_registration_fde, transport_registration_recv,
                    transport_registration_func, 0);
    fdevent_set(&transport_registration_fde, FDE_READ);
}

这个函数我们在前篇的文章中遇到过,这里为了更好的理解,我再次再简单说一下流程.

  • adb_socketpair()创建两个socket.这俩个socket是这样的:在s[0]中写数据,就可以在s[1]中读取到;相反也行【就是全双工的通道】。
  • 然后调用fdevent_install()函数,将其中的transport_registration_recv【s[1]】和transport_registration_func()函数【这就是处理s[1]中数据的函数】封装到transport_registration_fde【fdevent】; 紧接着就会将transport_registration_fde以transport_registration_recv为key,PollNode(fde)为value装到g_poll_node_map中去。
void fdevent_install(fdevent* fde, int fd, fd_func func, void* arg) {
    ...
    memset(fde, 0, sizeof(fdevent));
    fde->state = FDE_ACTIVE;
    fde->fd = fd;
    fde->func = func;
    fde->arg = arg;
    ...
    auto pair = g_poll_node_map.emplace(fde->fd, PollNode(fde));
   ...
}
  • 最后,调用fdevent_set()函数,将transport_registration_fde放到我们poll()监听列表里面.

7 transport_registration_func();

经过前面的分析,我们就知道了, **5. register_transport(t)**中的消息将会在transport_registration_func()函数中进行处理;我们就来看看是怎么进行处理的吧:

static void transport_registration_func(int _fd, unsigned ev, void* data) {
    tmsg m;
    int s[2];
    atransport* t;
    ...
    if (transport_read_action(_fd, &m)) {
        fatal_errno("cannot read transport registration socket");
    }
    t = m.transport;
    ...
    /* don't create transport threads for inaccessible devices */
    if (t->GetConnectionState() != kCsNoPerm) {
        /* initial references are the two threads */
        t->ref_count = 2;
        if (adb_socketpair(s)) {
            fatal_errno("cannot open transport socketpair");
        }
        D("transport: %s socketpair: (%d,%d) starting", t->serial, s[0], s[1]);
        t->transport_socket = s[0];
        t->fd = s[1];
        fdevent_install(&(t->transport_fde), t->transport_socket, transport_socket_events, t);
        fdevent_set(&(t->transport_fde), FDE_READ);
        std::thread(write_transport_thread, t).detach();
        std::thread(read_transport_thread, t).detach();
    }
    {
        std::lock_guard<std::recursive_mutex> lock(transport_lock);
        pending_list.remove(t);
        transport_list.push_front(t);
    }
    update_transports();
}

这里我先概括一下transport_registration_func()函数的逻辑:

  • 首先,从transport_registration_recv中将消息取出,通过调用函数transport_read_action(_fd, &m)来实现的
  • 然后,有通过adb_socketpair()函数创建了一对全双工的socket,一个用于usb传输通道的句柄【 s[1]】,另一个封装到新的fdevent中去【 s[0]】.负责监听usb传输通道是否有数据发生,然后调用
  • 接着我们看这次又封装了怎样的新的fdevent.
fdevent_install(&(t->transport_fde), t->transport_socket, transport_socket_events, t);

8. transport_socket_events

这次封装的处理函数是transport_socket_events(),我们就要看看当usb传输通道中有数据时,会怎么进行处理的;

static void transport_socket_events(int fd, unsigned events, void* _t) {
    atransport* t = reinterpret_cast<atransport*>(_t);
    D("transport_socket_events(fd=%d, events=%04x,...)", fd, events);
    if (events & FDE_READ) {
        apacket* p = 0;
        if (read_packet(fd, t->serial, &p)) {
            D("%s: failed to read packet from transport socket on fd %d", t->serial, fd);
            return;
        }
        handle_packet(p, (atransport*)_t);
    }
}

8.1read_packet

static int read_packet(int fd, const char* name, apacket** ppacket) {
    ...
    char* p = reinterpret_cast<char*>(ppacket); /* really read a packet address */
    int len = sizeof(apacket*);
    while (len > 0) {
     	int r = adb_read(fd, p, len);
 	 ...
    return 0;
}

read_packet()很简单,就是从发生t->transport_socket中读取出数据,并转换成为消息;在这之后,就会调用handle_packet()来处理usb传输通道中的消息了,我们进入handle_packet()函数中看看:

8.2 handle_packet

void handle_packet(apacket *p, atransport *t)
{
    ...
    switch(p->msg.command){
    case A_SYNC:
    ...
    case A_CNXN:  // CONNECT(version, maxdata, "system-id-string")
    ...
    case A_AUTH:
        switch (p->msg.arg0) {
			...
            case ADB_AUTH_SIGNATURE:
              ...
            case ADB_AUTH_RSAPUBLICKEY:
            ...
        }
    case A_OPEN: /* OPEN(local-id, 0, "destination") */
       ...
    case A_OKAY: /* READY(local-id, remote-id, "") */
       ...
    case A_CLSE: /* CLOSE(local-id, remote-id, "") or CLOSE(0, remote-id, "") */
      ...

    case A_WRTE: /* WRITE(local-id, remote-id, <data>) */
        if (t->online && p->msg.arg0 != 0 && p->msg.arg1 != 0) {
        ...
    default:
        printf("handle_packet: what is %08x?!\n", p->msg.command);
    }
    put_apacket(p);
}

handle_packet()内部处理逻辑也是很清晰,
首先根据消息的组成,逐步解析,【USB传输的消息的组成如下所示】;根据不同的command和来调用相应的函数进行不同的处理。,在消息处理完后,就会调用put_apacket()函数将当前消息清除,清空内存空间。

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

到此,我们终于弄明白了Android设备端的usb通信的初始化和usb消息的处理流程,

host端的usb初始化和USB消息处理流程

在梳理完Android端的USB初始化后,我们就着手看看host端,首先我们还是要从程序的启动流程开始说起,同样我们先来到host端的adb的adb_server_main()方法中,

1 adb_server_main()

}

int adb_server_main(int is_daemon, const std::string& socket_spec, int ack_reply_fd) {
    ...
    usb_init();
    ...
    return 0;
}

在adb_server_main()函数中调用了usb_init()函数来初始化usb,我们就跳转到usb_init()中去看看:

2. usb_init()

void usb_init() {
    if (should_use_libusb()) {
        LOG(DEBUG) << "using libusb backend";
        libusb::usb_init();
    } else {
        LOG(DEBUG) << "using native backend";
        native::usb_init();
    }
}

usb_init()函数中首先根据环境决定最后调用那个初始化方案,这里通过 should_use_libusb()函数来判断的

2.1 should_use_libusb()

bool should_use_libusb() {
#if !ADB_HOST
    return false;
#else
    static bool enable = getenv("ADB_LIBUSB") && strcmp(getenv("ADB_LIBUSB"), "1") == 0;
    return enable;
#endif
}

在should_use_libusb()函数中,首先是通过宏ADB_HOST来确定当前代码运行的环境,因为当前在host端,所以这里的!ADB_HOST为false,程序就不直接返回,而是根据环境变量"ADB_LIBUSB"是否存在并且环境变量设置的值为"1"来返回一个bool值.由于当前host并没有设置"ADB_LIBUSB",所以我们返回的是false.

再回到usb_init()中,should_use_libusb()应该返回的是false,那么就会调用native::usb_init(); 所以我们就会调用native命名空间中的usb_init()函数.
【说明:不同的host端系统的native命名空间不一样,对应的函数也不相同,当前系统是linux,我们就会选择usb_linux.cpp中的的native命名空间】

3.native::usb_init()

void usb_init() {
    struct sigaction actions;
    memset(&actions, 0, sizeof(actions));
    sigemptyset(&actions.sa_mask);
    actions.sa_flags = 0;
    actions.sa_handler = [](int) {};
    sigaction(SIGALRM, &actions, nullptr);

    std::thread(device_poll_thread).detach();
}

这里顺便了解一下 sigaction:

3.1 struct sigaction

struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
}
  • sa_handler此参数和signal()的参数handler相同,代表新的信号处理函数
  • sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置
  • sa_flags 用来设置信号处理的其他相关操作,下列的数值可用。
    • SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL
    • SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
    • SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号

3.2 sigaction()

sigaction()函数主要:进程在接受到相应的信号后会改变处理动作,

 #include <signal.h>
      int sigaction(int signum, const struct sigaction *act,
                   struct sigaction *oldact);
  • signum:接受到的有效信号
  • *act: 新的信号处理方式
  • *oldact:原来的信号处理方式

这里的sigaction()函数主要是对SIGALRM修信号修改新的处理动做,
usb_init()最后是通过创建一个子线程,传入device_poll_thread()函数来处理usb初始化的,我们就要到找这个device_poll_thread()函数中一窥究竟.

4. device_poll_thread

static void device_poll_thread() {
    ...
    while (true) {
        // TODO: Use inotify.
        find_usb_device("/dev/bus/usb", register_device);
        kick_disconnected_devices();
        std::this_thread::sleep_for(1s);
    }
}

device_poll_thread()函数内部是一个死循环,并且没有设置退出,所以我们就可以猜到,host端对于usb的状态应该是实时跟新的,我们就先抱着猜想去代码中验证一下吧:

4.1 find_usb_device()

首先都会调用find_usb_device()函数,传入一个路径和一个函数指针,find_usb_devic()函数内部实现代码较多,具体实现细节就不赘述,主要就是做了以下两件事:

  • 首先从传入的"/dev/bus/usb"路径下读取usb节点信息
  • 然后调用传进的register_device()函数并将usb节点信息传入

5 register_device()

接下来我们就要看最后发现设备后调用的register_device()函数:
首先,我们从函数内部的注解中了解到:linux系统在设备打开之后是不会在重新分配设备ID的,所以我们呢这里可以创建一个list用于存放已经打开的设备,在设备关闭时将其从列表中删除.并且这里我们会列表中匹配的打开的设备分配一个相应的usb处理:

static void register_device(const char* dev_name, const char* dev_path, unsigned char ep_in,
                            unsigned char ep_out, int interface, int serial_index,
                            unsigned zero_mask, size_t max_packet_size) {
    ...
    std::unique_ptr<usb_handle> usb(new usb_handle);
    usb->path = dev_name;
    usb->ep_in = ep_in;
    usb->ep_out = ep_out;
    usb->zero_mask = zero_mask;
    usb->max_packet_size = max_packet_size;
    //对 usb_handl进行初始化设置
    ...
    // Read the device's serial number.
    std::string serial_path = android::base::StringPrintf(
        "/sys/bus/usb/devices/%s/serial", dev_path + 4);
    //从传入的路径下读取usb设备信息
    ..
    // Add to the end of the active handles.
    usb_handle* done_usb = usb.release();
    {
        std::lock_guard<std::mutex> lock(g_usb_handles_mutex);
        g_usb_handles.push_back(done_usb);
        //g_usb_handles列表会在设备关闭时删除当前添加的usb_handle
    }
    //最后调用register_usb_transport()函数,
    register_usb_transport(done_usb, serial.c_str(), dev_path, done_usb->writeable);
}

register_device()函数的处理步骤为:

  • 首先获取一个usb_handle.并进行初始化设置,
  • 从传进的路径下读取usb设备信息,
  • 将当前的usb_handle放进一个list中,
  • 调用register_usb_transport()函数进行usb传输注册

6 register_usb_transport()

顺着程序代码往下走,接下来我们就要看看这个register_usb_transport()函数:

void register_usb_transport(usb_handle* usb, const char* serial, const char* devpath,
                            unsigned writeable) {
    atransport* t = new atransport((writeable ? kCsOffline : kCsNoPerm));

    D("transport: %p init'ing for usb_handle %p (sn='%s')", t, usb, serial ? serial : "");
    init_usb_transport(t, usb);
    ...
    {
        std::lock_guard<std::recursive_mutex> lock(transport_lock);
        pending_list.push_front(t);
    }
    register_transport(t);
}

如上述代码所示,这个register_usb_transport()函数

  • 首先会啊初始化一个atransport实例,这就代表了一个传输事件,然后将这个atransport实例放进一个pending_list中,
  • 最后调用register_transport()注册传输事件.

7 register_transport()

/* the fdevent select pump is single threaded */
static void register_transport(atransport* transport) {
    tmsg m;
    m.transport = transport;
    m.action = 1;
    D("transport: %s registered", transport->serial);
    if (transport_write_action(transport_registration_send, &m)) {
        fatal_errno("cannot write transport registration socket\n");
    }
}

在前面的register_usb_transport()函数中,最后会调用调用register_transport()注册传输事件,register_transport()函数中,将atranspor封装成tmsg,然后就是调用transport_write_action()函数将tmsg写入到transport_registration_send中去,和Android设备端类似,我们就需要看看在host端的transport_registration_send是在哪里创建和使用的;

8.init_transport_registration()

我们在host端的代码中,发现在usb_init()函数调用之前,也会调用init_transport_registration()函数,在Android端的的分析中,我们就说到过这个init_transport_registration;辞旧不在赘述.

int adb_server_main(int is_daemon, const std::string& socket_spec, int ack_reply_fd) {
    ...
    init_transport_registration();
    ...
    usb_init();
    ...
    return 0;

}

分析到这里,我们会发现host端的USB初始函数调用和Android设备端的usb初始化的调用的函数相同了,具体可参考文章上半部分的Android设备端的usb初始化和usb消息处理流程 6. init_transport_registration ()函数及以后的分析梳理

  • 4
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
win10_adb_fastboot驱动_usb_driver是指用于在Windows 10操作系统上安装和管理Android设备的ADB(Android Debug Bridge)和Fastboot驱动,以便进行开发者选项和设备连接ADB驱动是Android开发工具包(SDK)中的一个组件,它允许开发者通过电脑上的命令行界面与Android设备进行通信。借助ADB驱动,开发者可以在电脑上安装、调试和测试应用程序,还可以利用ADB命令来获取设备信息、传输文件等。 Fastboot驱动是Android设备上的引导程序,它提供了一种引导设备到Fastboot模式的方式。Fastboot模式是一种拥有高级权限的启动模式,使用Fastboot驱动可以在设备上执行一系列高级操作,如刷写系统分区、解锁引导程序等。 在Windows 10操作系统上安装和配置win10_adb_fastboot驱动_usb_driver一般分为以下几个步骤: 1. 下载ADB驱动程序。例如,可以从官方的Android开发者网站下载最新版本的ADB驱动。 2. 解压下载的驱动文件。将解压后的文件夹保存到一个容易访问的位置。 3. 启动设备管理器。在Windows 10上,可以通过按下Windows键 + X打开快速访问菜单,然后选择“设备管理器”来打开设备管理器。 4. 连接Android设备。使用USB数据线将Android设备连接到电脑上。确保设备处于开发者模式,并已启用USB调试选项。 5. 在设备管理器中找到Android设备。通常,Android设备会出现在“其他设备”或“便携式设备”下面,它可能带有一个黄色的感叹号图标。 6. 右键单击Android设备,选择“更新驱动程序”。 7. 在更新驱动程序向导中,选择“浏览计算机以查找驱动程序软件”。 8. 浏览到之前下载和解压的ADB驱动文件夹,并选择相应的驱动程序。 9. 完成驱动程序的安装过程后,设备管理器中的Android设备应该显示为正常状态。 安装和配置win10_adb_fastboot驱动_usb_driver后,开发者就可以开始使用ADB和Fastboot命令与Android设备进行交互,进一步开发、调试和测试Android应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值