天天看点

Android下实现非启动界面Wifi连接

android的网络功能和一般的linux并无太大的区别,我原来以为在android上连接网络和普通的linux连接网络没有很大区别,事实上区别还是有一些的。

由于项目的需要,我的目标是在android的界面没有启动之前连接wifi,于是本来的期待是直接在init.rc中加入一些脚本调用即可,但研究了一会儿发现没有那么简单。

首先要感谢anly_jun@baidu贴吧的几篇博文,从http://hi.baidu.com/anly_jun/blog/item/8ecb92d593d144cf50da4b6e.html开始,一共有七篇关于android

wifi模块分析,其中有大量博主自己使用uml工具画的调用讲解,对于理解android wifi工作机制还是有很用的。其中最重要的是下面这几幅图。

Android下实现非启动界面Wifi连接

(转自http://hi.baidu.com/anly_jun/blog/item/65e26e117e09ebcda6ef3f56.html)

在想要对wifi硬件动作之前,需要做两件事情,一是要load wifi的driver,而是要打开wpa_supplicant,其实如果是连接没有加密的wifi,没有必要打开wpa_supplicant,但是为了讲问题化为熟知的问题,此处还是先按照提示调用wifi_load_driver()和wifi_start_supplicant()

按照上面的提示写出来的初始化代码如下:

int init_stage() { 

    //

load the wifi driver: insmod .ko 

    int ret = wifi_load_driver(); 

    if(ret < 0) { 

        loge("failed

to load wi-fi driver. %s",strerror(errno)); 

        return -1; 

    } 

start wpa_supplicant 

    ret =  wifi_start_supplicant(); 

to start supplicant daemon. %s",strerror(errno)); 

    return 0; 

接下来,便是连接的过程了,经过上面的步骤,wifi的driver已经载入,wpa_supplicant也已经打开,那咱们就可以开始连接无线了吧。后来证实这是错误的,因为anly_jun的这篇android wifi分析的粒度只在java层面的函数级别,因此有一些细节并没有提到。在下面我会提到这些细节。

按照一般的linux中连接wifi的步骤,这时候就可以直接调用一个程序来连接某个ssid的无线网络,然后调用dhcpd来分配ip了,我之前在eeepc上连接wifi就非常简单,调用iwconfig [ssid],再调用dhcpd就可以了。但很遗憾,android上并没有iwconfig这样方便的工具。

这下线索似乎就断了,天无绝人之路,既然在android的java code中都可以添加一个无线网络并且连接,那我们就去android的java源代码中找一找。在android中,程序员是使用wifimanager这个类来进行wifi操作的,其中关于添加一个网络的代码如下:

/** 

* add a new network description to the set of configured networks. 

* the {@code networkid} field of the supplied configuration object 

* is ignored. 

* <p/> 

* the new network will be marked disabled by default. to enable it, 

* called {@link #enablenetwork}. 

* @param config the set of variables that describe the configuration, 

*            contained in a {@link wificonfiguration} object. 

* @return the id of the newly created network description. this is used in 

*         other operations to specified the network to be acted upon. 

*         returns {@code -1} on failure. 

*/ 

public int addnetwork(wificonfiguration config) { 

    if (config == null) { 

    config.networkid = -1; 

    return addorupdatenetwork(config); 

* internal method for doing the rpc that creates a new network description 

* or updates an existing one. 

* @param config the possibly sparse object containing the variables that 

*         are to set or updated in the network description. 

* @return the id of the network on success, {@code -1} on failure. 

private int addorupdatenetwork(wificonfiguration config) { 

    try { 

        return mservice.addorupdatenetwork(config); 

    } catch (remoteexception e) { 

看其中确实是调用了service的函数,于是又在frameworks/base/services/java/com/android/server/wifiservice.java中找到了有关增加一个网络配置的相关代码

* see {@link android.net.wifi.wifimanager#addorupdatenetwork(wificonfiguration)} 

* @return the supplicant-assigned identifier for the new or updated 

* network if the operation succeeds, or {@code -1} if it fails 

public int addorupdatenetwork(wificonfiguration config) { 

    enforcechangepermission(); 

    /* 

     * if the supplied networkid is -1, we create a new empty 

     * network configuration. otherwise, the networkid should 

     * refer to an existing configuration. 

     */ 

    int netid = config.networkid; 

    boolean newnetwork = netid == -1; 

    boolean doreconfig = false; 

    // networkid of -1 means we want to create a new network

    synchronized (mwifistatetracker) { 

        if (newnetwork) { 

            netid = mwifistatetracker.addnetwork(); 

            if (netid < 0) { 

                if (dbg) { 

                    slog.d(tag, "failed to add a network!");

                } 

                return -1; 

            } 

            doreconfig = true; 

        } 

        mneedreconfig = mneedreconfig || doreconfig; 

    setvariables: { 

        /* 

         * note that if a networkid for a non-existent network 

         * was supplied, then the first setnetworkvariable()

         * will fail, so we don't bother to make a separate check 

         * for the validity of the id up front. 

         */ 

        if (config.ssid != null && 

                !mwifistatetracker.setnetworkvariable( 

                    netid, 

                    wificonfiguration.ssidvarname, 

                    config.ssid)) { 

            if (dbg) { 

                slog.d(tag, "failed to set ssid: "+config.ssid); 

            break setvariables; 

        if (config.bssid != null && 

                    wificonfiguration.bssidvarname, 

                    config.bssid)) { 

                slog.d(tag, "failed to set bssid: "+config.bssid); 

        }

我们来到frameworks/base/wifi/java/android/net/wifi/wifistatetracker.java中,看看addnetwork和setnetworkvariable函数都是什么样的。

* add a network 

* @return network id of the new network 

public synchronized int addnetwork() { 

    if (mwifistate.get() != wifi_state_enabled) { 

    return wifinative.addnetworkcommand(); 

* set network setting by name 

* @param netid network id of the network 

* @param name network variable key 

* @param value network variable value 

* @return {@code true} if the operation succeeds, {@code false} otherwise 

public synchronized boolean setnetworkvariable(int netid,string name, string value) { 

        return false; 

    return wifinative.setnetworkvariablecommand(netid, name,value); 

似乎wifinative就应该是最终boss了,于是来到frameworks/base/wifi/java/android/net/wifi/wifinative.java中一看,这两个函数都是这么定义的。

public native static int addnetworkcommand(); 

public native static boolean setnetworkvariablecommand(intnetid, string name, string value); 

熟悉android ndk开发的朋友都应该知道,这里的函数都是通过jni链接到c/c++代码中,于是我们稍微花了一点时间,找到了这个函数所对应的frameworks/base/core/jni/android_net_wifi_wifi.cpp

static int docommand(const char *cmd, char *replybuf, intreplybuflen) 

    size_t reply_len = replybuflen - 1; 

    if (::wifi_command(cmd, replybuf, &reply_len) != 0) 

    else { 

        // strip off trailing newline 

        if (reply_len > 0 && replybuf[reply_len-1] == '\n') 

            replybuf[reply_len-1] = '\0'; 

        else 

            replybuf[reply_len] = '\0'; 

        return 0; 

static jint dointcommand(const char *cmd) 

    char reply[256]; 

    if (docommand(cmd, reply, sizeof(reply)) != 0) { 

        return (jint)-1; 

    } else { 

        return (jint)atoi(reply); 

static jboolean dobooleancommand(const char *cmd, const char*expect) 

        return (jboolean)jni_false; 

        return (jboolean)(strcmp(reply, expect) == 0); 

…… 

static jint android_net_wifi_addnetworkcommand(jnienv* env,jobject clazz) 

    return dointcommand("add_network"); 

static jbooleanandroid_net_wifi_setnetworkvariablecommand(jnienv* env, 

                                                          jobject clazz, 

                                                          jint netid, 

                                                          jstring name, 

                                                          jstring value) 

    char cmdstr[256]; 

    jboolean iscopy; 

    const char *namestr = env->getstringutfchars(name,&iscopy); 

    const char *valuestr = env->getstringutfchars(value,&iscopy); 

    if (namestr == null || valuestr == null) 

        return jni_false; 

    int cmdtoolong = snprintf(cmdstr, sizeof(cmdstr),"set_network %d %s %s", 

                 netid, namestr, valuestr) >=(int)sizeof(cmdstr); 

    env->releasestringutfchars(name, namestr); 

    env->releasestringutfchars(value, valuestr); 

    return (jboolean)!cmdtoolong && dobooleancommand(cmdstr,"ok"); 

// ---------------------------------------------------------------------------- 

/* 

* jni registration. 

static jninativemethod gwifimethods[] = { 

    /* name, signature, funcptr */ 

    { "loaddriver", "()z",  (void*)android_net_wifi_loaddriver }, 

    { "unloaddriver", "()z",  (void*)android_net_wifi_unloaddriver }, 

    { "startsupplicant", "()z",  (void*)android_net_wifi_startsupplicant }, 

    { "stopsupplicant", "()z",  (void*)android_net_wifi_stopsupplicant }, 

    { "connecttosupplicant", "()z",  (void*)android_net_wifi_connecttosupplicant }, 

    { "closesupplicantconnection", "()v",  (void*)android_net_wifi_closesupplicantconnection }, 

    { "listnetworkscommand", "()ljava/lang/string;", 

        (void*) android_net_wifi_listnetworkscommand }, 

    { "addnetworkcommand", "()i", (void*)android_net_wifi_addnetworkcommand }, 

    { "setnetworkvariablecommand", "(iljava/lang/string;ljava/lang/string;)z", 

        (void*) android_net_wifi_setnetworkvariablecommand}, 

    { "getnetworkvariablecommand", "(iljava/lang/string;)ljava/lang/string;", 

        (void*) android_net_wifi_getnetworkvariablecommand}, 

看来这下终于找到干活儿的函数了,可喜可贺,于是我们得出结论:在android中,要连上一个无线网络,首先需要添加一个网络,然后设置这个网络的配置,而这些步骤实际上都是通过向wpa_supplicant发送命令实现的。命令的格式例外的很简单,比如添加一个网络配置是add_network,设置一个网络配置是set_network [netid] [varname] [var]。于是根据java代码我写出了配置环节的代码。

int config_stage(){    

    // add a network config to supplicant mode 

    int networkid = dointcommand("add_network"); // add a new network id 

    if(networkid < 0) { 

        loge("failed to add a network configuration. %s",strerror(errno)); 

    loge("add a network %d",networkid); 

    // set the ssid of the destination wifi adhoc 

    snprintf(cmdstr, sizeof(cmdstr), "set_network %d %s %s",networkid, ssid_name, ssid); 

    if(!dobooleancommand(cmdstr,"ok")) { 

        loge("failed to set network %d configuration ssid. %s", networkid, strerror(errno)); 

    return networkid; 

添加了一个配置的网络之后,选择某个网络配置的命令是select_network [netid]。在select_network之后,wpa_supplicant就尝试和该ssid的ap进行associate操作,在associate之后便调用dhcpd获取ip。select_network是立即返回的函数,那么何以得知已经和无线ap连接上了呢?android的界面里面连接/断开wifi都是可以被接受到的事件,在wifistatetracker中我们看到是由wifimonitor来监听由wpa_supplicant发过来的消息的,于是我们来到framework/base/wifi/java/android/net/wifi/wifimonitor.java,看到其中接受消息的monitor线程

class monitorthread extends thread { 

    public monitorthread() { 

        super("wifimonitor"); 

    public void run() { 

        if (connecttosupplicant()) { 

            // send a message indicating that it is now possible to send commands 

            // to the supplicant 

            mwifistatetracker.notifysupplicantconnection(); 

        } else { 

            mwifistatetracker.notifysupplicantlost(); 

            return; 

        //noinspection infiniteloopstatement 

        for (;;) { 

            string eventstr = wifinative.waitforevent(); 

            // skip logging the common but mostly uninteresting scan-results event 

            if (config.logd &&eventstr.indexof(scanresultsevent) == -1) { 

                log.v(tag, "event [" + eventstr + "]"); 

            if (!eventstr.startswith(eventprefix)) { 

                if (eventstr.startswith(wpaeventprefix) && 

                        0 <eventstr.indexof(passwordkeymaybeincorrectevent)) { 

                    handlepasswordkeymaybeincorrect(); 

                continue; 

            string eventname =eventstr.substring(eventprefixlen); 

            int nameend = eventname.indexof(' '); 

            if (nameend != -1) 

                eventname = eventname.substring(0, nameend);

            if (eventname.length() == 0) { 

                if (config.logd) log.i(tag, "received wpa_supplicant event with empty event

name"); 

wifinative.wairforevent在wifi.c中对应的函数是wifi_wait_event(),用来阻塞的等待wpa_supplicant发来的消息。当wpa_supplicant连接上之后,便会发出一个control-event-connected的消息,只要截获这个消息,我们便可以进行dhcp的操作了。

基于以上的考虑,我写了connect阶段的代码。这里我偷了一下懒,没有仔细的去分析过来的命令,只是匹配到有connected的命令,就当已经连接上了。

#define connected "connected" 

int connect_stage(int networkid) { 

    // enable the network 

    snprintf(cmdstr, sizeof(cmdstr), "select_network

%d",networkid); 

        loge("failed to select network %d. %s", networkid, strerror(errno)); 

    // wait for connect 

    char buf[256]; 

    while(1) { 

        int nread = wifi_wait_for_event(buf, sizeof(buf)); 

        if(nread > 0) { 

            loge("receive buf:\n %s\n",buf); 

            if(strstr(buf,connected) > 0) { 

                break; 

            // xxx danger of not going out of the loop!!! 

        continue; 

}

在wifistatetracer中我还找到了负责dhcp的dhcphandler,按照其中的函数写了dhcp阶段的代码

int dhcp_stage(){ 

    int result; 

    in_addr_t ipaddr, gateway, mask, dns1, dns2, server; 

    uint32_t lease; 

    char ifname[256]; 

    char mdns1name[256]; 

    char mdns2name[256]; 

    property_get("wifi.interface", ifname ,"eth0"); 

    snprintf(mdns1name, sizeof(mdns1name), "net.%s.dns1",ifname); 

    snprintf(mdns2name, sizeof(mdns2name), "net.%s.dns2",ifname); 

    result = dhcp_do_request(ifname, &ipaddr, &gateway, &mask, &dns1,&dns2, &server, &lease); 

    if(result != 0) { 

to dhcp on interface %s. %s", ifname, strerror(errno)); 

    struct in_addr dns_struct1, dns_struct2; 

    dns_struct1.s_addr = dns1; 

    dns_struct2.s_addr = dns2; 

    property_set(mdns1name,inet_ntoa(dns_struct1)); 

    property_set(mdns2name,inet_ntoa(dns_struct2)); 

于是我用无线路由器建立了一个最简单的无线网络,ssid=tsever,没有密码,按照上面步骤进行联网。结果在add_network的步骤就出错了,无法获得网络id,这是怎么一回事呢?看到dhcp时的代码要对特定的network interface做操作,突然想起来之前在代码中似乎看到有对interface做使能操作的代码——wifiservice在调用mwifistatetracker.enablenetwork之前,还调用了string ifname = mwifistatetracker.getinterfacename();

networkutils.enableinterface(ifname);这两句。于是在init阶段,加上了下面这段。

    ret = ifc_enable(ifname); 

        loge("failed to enable wifi interface %s. %s", ifname ,strerror(errno)); 

    }

但是运行起来还是出同样的错误,不过这次busybox ifconfig之后可以发现eth0的端口了 ,至少没有做无用功。倒回去看在wifimonitor的monitorthread中有connecttosupplicant()的调用,本以为此调用不是非常关键,然后去hardware/libhardware_legacy/wifi/wifi.c文件里面看到全局有一个static struct wpa_ctrl *ctrl_conn;并且在wifi_command中需要用到这个ctrl_conn,那么如果这个ctrl_conn是null的话,所有的wifi_command命令自然都无法完成了,于是又加上了

ret = wifi_connect_to_supplicant(); 

to connect supplicant daemon. %s",strerror(errno)); 

这样编译完运行之后,果然顺利的分配到id了。运行一下程序,还是出现了错误,输出的log如下。

i/wpa_supplicant(  618): ctrl-event-scan-results  ready

i/wpa_supplicant(  618): trying to associate with 00:24:b2:c1:16:b6 (ssid='tserver' freq=2462 mhz)

i/wpa_supplicant(  618): ctrl-event-state-change id=-1 state=3

e/        (  612): finished init stage.

e/        (  612): add a network 2

e/        (  612): finished config stage.

i/wpa_supplicant(  618): ctrl-event-state-change id=1 state=0

e/        (  612): receive buf:

e/        (  612):  ctrl-event-state-change id=1 state=0

i/wpa_supplicant(  618): ctrl-event-state-change id=-1 state=2

e/        (  612):  ctrl-event-state-change id=-1 state=2

e/        (  612):  ctrl-event-scan-results  ready

i/wpa_supplicant(  618): ctrl-event-state-change id=-1 state=4

e/        (  612):  ctrl-event-state-change id=-1 state=4

i/wpa_supplicant(  618): associated with 00:24:b2:c1:16:b6

e/        (  612):  associated with 00:24:b2:c1:16:b6

d/networklocationprovider(  127): oncelllocationchanged [4517,30785]

i/wpa_supplicant(  618): authentication with 00:24:b2:c1:16:b6 timed out.

e/        (  612):  authentication with 00:24:b2:c1:16:b6 timed out.

注意到红色的那句log,我的wifi并没有设置密码,为什么还需要authentication呢?想一想是不是wpa_supplicant的限制,于是去网上搜了到了这篇wpa_supplicant

工具使用,发现没有密码的情况下应该是这样的配置

bash-4.1# wificonnect

i/wpa_supplicant(  871): ctrl-event-scan-results  ready

i/wpa_supplicant(  871): trying to associate with 00:24:b2:c1:16:b6 (ssid='tserver' freq=2462 mhz)

i/wpa_supplicant(  871): ctrl-event-state-change id=-1 state=3

e/        (  865): finished init stage.

e/        (  865): add a network 2

e/        (  865): finished config stage.

i/wpa_supplicant(  871): ctrl-event-state-change id=1 state=0

e/        (  865): receive buf:

e/        (  865):  ctrl-event-state-change id=1 state=0

i/wpa_supplicant(  871): ctrl-event-state-change id=-1 state=2

e/        (  865):  ctrl-event-state-change id=-1 state=2

e/        (  865):  ctrl-event-scan-results  ready

e/        (  865):  trying to associate with 00:24:b2:c1:16:b6 (ssid='tserver' freq=2462 mhz)

e/        (  865):  ctrl-event-state-change id=-1 state=3

i/wpa_supplicant(  871): ctrl-event-state-change id=2 state=4

e/        (  865):  ctrl-event-state-change id=2 state=4

i/wpa_supplicant(  871): associated with 00:24:b2:c1:16:b6

i/wpa_supplicant(  871): ctrl-event-state-change id=2 state=7

i/wpa_supplicant(  871): ctrl-event-connected - connection to 00:24:b2:c1:16:b6 completed (auth) [id=2 id_str=]

e/        (  865):  associated with 00:24:b2:c1:16:b6

e/        (  865):  ctrl-event-state-change id=2 state=7

e/        (  865):  ctrl-event-connected - connection to 00:24:b2:c1:16:b6 completed (auth) [id=2 id_str=]

e/        (  865): finished dhcp stage.

    snprintf(cmdstr, sizeof(cmdstr), "set_network %d %s %s",networkid, key_mgmt ,pass); 

        loge("failed to set network %d configuration key_mgmr. %s", networkid, strerror(errno)); 

    snprintf(cmdstr, sizeof(cmdstr), "select_network %d",networkid); 

        loge("failed to select network %d. %s", networkid,strerror(errno)); 

    snprintf(mdns1name, sizeof(mdns1name),"net.%s.dns1",ifname); 

    snprintf(mdns2name, sizeof(mdns2name),"net.%s.dns2",ifname); 

    result = dhcp_do_request(ifname, &ipaddr, &gateway,&mask, &dns1, &dns2, &server, &lease); 

        loge("failed to dhcp on interface %s. %s", ifname,strerror(errno)); 

int main(int argc, char *argv[]) 

    int ret = init_stage(); 

        loge("failed init stage. %s",strerror(errno)); 

        exit(-1); 

    loge("finished init stage."); 

    ret = config_stage(); 

        loge("failed config stage. %s",strerror(errno)); 

    loge("finished config stage."); 

    ret = connect_stage(ret); 

        loge("failed connect stage. %s",strerror(errno)); 

    logv("finished connect stage."); 

    ret = dhcp_stage(); 

        loge("failed dhcp stage. %s",strerror(errno)); 

    loge("finished dhcp stage.");