Android4.4.2源码分析之WiFi模块(三)

2022-05-07 14:49:39 浏览数 (1)

参考博文

Android4.4.2源码分析之WiFi模块(一)

Android4.4.2源码分析之WiFi模块(二)

获取到WiFi列表后就是对WiFi进行连接,本博文分析WiFi列表的点击事件

Wifi列表中存有四中WiFi

  • 已连接WiFi
  • 未连接也无需输入密码的WiFi(程序不会对该WiFi进行保存)
  • 未连接但需要输入密码而且已保存的WiFi
  • 未连接但需要输入密码而且未保存的WiFi

分情况进行分析,

1,已连接的WiFi,点击弹出dialog显示WiFi信息

除了第二种情况无需密码的WiFi点击时直接连接,其他三种情况的点击事件的处理均是弹出dialog,只不过是根据不同情况去加载布局,说一下源码的实现思路,如果想做出这种效果可以增加代码的复用性可以参考源码实现思路。

showDialog时弹出WiFidialog对话框,对话框有title,content,以及button,在构造WiFidialog时会传入listener对button的事件进行处理,所以对于button的点击事件在WiFiSettings中进行处理。在WiFiDialog的onCreate方法中会调用如下代码

代码语言:javascript复制
mController = new WifiConfigController(this, mView, mAccessPoint, mEdit);

创建出WifiConfigController对象,并在wifiConfigController中进行title以及content以及button内容的显示。对于content的显示为动态添加的布局,每次加载dialog时会去判断所要加载的信息是否存在,如果存在就调用addRow(。。)加载布局,addRow方法内部的实现如下:

代码语言:javascript复制
 private void addRow(ViewGroup group, int name, String value) {
        View row = mConfigUi.getLayoutInflater().inflate(R.layout.wifi_dialog_row, group, false);
        ((TextView) row.findViewById(R.id.name)).setText(name);
        ((TextView) row.findViewById(R.id.value)).setText(value);
        group.addView(row);
    }

在该方法中动态添加两个textview,并将其添加至dialog。

已连接WiFi点击时所显示的信息是最全的,以此为例,该dialog包括以下几种信息(至于密码输入框在第四种情况进行介绍)

  • 状态信息:状态信息的获取可通过AccessPoint.getState()进行获取,
代码语言:javascript复制
mAccessPoint.getState();

但是在该类中只是对state进行获取,不对其进行赋值或者修改,state的赋值或者修改位于WifiSettings中,当接收到WifiManager.NETWORK_STATE_CHANGED_ACTION网络状态改变时的广播时,借助intent的值进行获取NetworkInfo实例,借助该实例可以获取到state

代码语言:javascript复制
 NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
                    WifiManager.EXTRA_NETWORK_INFO);


......

...
//利用NetworkInfo实例获取到state,进而去更新WiFi的Javabean数据AccessPoint
info.getDetailedState()
  • 信号强度
代码语言:javascript复制
int level = mAccessPoint.getLevel();

其方法内部又是怎么做的呢?

代码语言:javascript复制
 int getLevel() {
        if (mRssi == Integer.MAX_VALUE) {
            return -1;
        }
        return WifiManager.calculateSignalLevel(mRssi, 4);
    }

这些代码开发者是可以直接调用的,而且开发者可以看到wifimanager.calculateSignalLevel(。。)方法的具体实现,大致分析一下就是通过将mRssi与所规定的最大值和最小值进行比较并进行简单运算后获取到一个int型的数值,根据int型的数值来加载对应的drawable,这也是表示WiFi的信号强度图标实现的原理。举个例子,利用imageview加载信号强度图标的做法如下:

第一,在drawable文件夹下创建xml文件wif_level.xml

代码语言:javascript复制
<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/wifi_level_0" android:maxLevel="0"/>
    <item android:drawable="@drawable/wifi_level_1" android:maxLevel="1"/>
    <item android:drawable="@drawable/wifi_level_2" android:maxLevel="2"/>
    <item android:drawable="@drawable/wifi_level_3" android:maxLevel="3"/>
</level-list>

第二,在布局文件中,对imageview控件的drawable属性引用刚才所创建的xml文件

第三,在Java代码中对imageview进行set

代码语言:javascript复制
mWifiLevel.setImageLevel(WifiManager.calculateSignalLevel(mRssi,4))

至于对Rssi的获取可参考前两个博客

  • 连接速度
代码语言:javascript复制
WifiInfo info = mAccessPoint.getInfo();
            if (info != null && info.getLinkSpeed() != -1) {
                ..........
            }

可以看出连接速度是通过WiFiinfo的实例来获取的,开发者可以通过调用如下代码获取实例

代码语言:javascript复制
WifiInfo info = mWifiManager.getConnectionInfo();

获取到info后获取连接速度代码就如上所示了

  • 安全性
代码语言:javascript复制
mAccessPoint.getSecurityString(false)

该方法内部是根据security来返回不同的字符串,归根结底还是要查找security是如何获取的

代码语言:javascript复制
static int getSecurity(WifiConfiguration config) {
        if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
            return SECURITY_PSK;
        }
        if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) ||
                config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
            return SECURITY_EAP;
        }
        return (config.wepKeys[0] != null) ? SECURITY_WEP : SECURITY_NONE;
    }

security的获取借助于WifiConfiguration对象,config的获取参考博文二

  • IP地址
代码语言:javascript复制
for(InetAddress a : config.linkProperties.getAddresses()) {
....
          a.getHostAddress();
.....
               }

源码中获取WiFi的ip地址的方式是借助wificonfig实例对象去获取,但是config的linkProperties字段对开发者是hide的,不过我们可以借助WiFiinfo的实例去获取到已经连接的WiFi的ip地址

代码语言:javascript复制
info.getIpAddress();

以上信息大部分都是从AccessPont获取的,AccessPoint.java相当于存放WiFi的Javabean数据,但是对开发者是hide的,但开发者可以模拟AccessPoint去自己创建WiFi的Javabean数据的类

2,点击未连接无需密码的WiFi

点击该WiFi没有dialog弹出,直接进行WiFi的连接,

代码语言:javascript复制
if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE &&
                    mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) {
                mSelectedAccessPoint.generateOpenNetworkConfig();
                mWifiManager.connect(mSelectedAccessPoint.getConfig(), mConnectListener);

通过判断security属性来判断是否加密(security的来源是有Scanresult的capabilities决定的,可参考博文二),然后判断是否网络ID是否有效,如果以上两种条件均满足的话则可以调用WifiManager的connect()方法直接连接

3,点击未连接需要输入密码而且已保存的WiFi

代码语言:javascript复制
 protected void generateOpenNetworkConfig() {
        if (security != SECURITY_NONE)
            throw new IllegalStateException();
        if (mConfig != null)
            return;
        mConfig = new WifiConfiguration();
        mConfig.SSID = AccessPoint.convertToQuotedString(ssid);
        mConfig.allowedKeyManagement.set(KeyMgmt.NONE);
    }

连接WiF调用的是WifiManager的connect方法,但该方法是隐藏的,在进行APP编程时无法直接调用,但可以调用mWifiManager.enableNetwork(int netId,boolean disableOthers)进行连接.

对于button的点击大体上分两种情况:

代码语言:javascript复制
@Override
    public void onClick(DialogInterface dialogInterface, int button) {
        if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) {
            forget();//取消保存
        } else if (button == WifiDialog.BUTTON_SUBMIT) {
            if (mDialog != null) {
                submit(mDialog.getController());//连接
            }
        }
    }

对于取消保存只有WiFi在已保存的情况下才会出现,点击该按钮后会取消对WiFi的保存,即清除该WiFi的WiFiConfig信息

代码语言:javascript复制
mWifiManager.forget(mSelectedAccessPoint.networkId, mForgetListener);

对于submit按钮,分保存(添加网络时)和连接

代码语言:javascript复制
void submit(WifiConfigController configController) {

        final WifiConfiguration config = configController.getConfig();

        if (config == null) {
            if (mSelectedAccessPoint != null
                    && mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
                mWifiManager.connect(mSelectedAccessPoint.networkId,
                        mConnectListener);
            }
        } else if (config.networkId != INVALID_NETWORK_ID) {
            if (mSelectedAccessPoint != null) {
                mWifiManager.save(config, mSaveListener);
            }
        } else {
            if (configController.isEdit()) {//添加网络
              mWifiManager.save(config, mSaveListener);
            } else {
                mWifiManager.connect(config, mConnectListener);
            }
        }
。。。。。
}

4,点击未连接需要输入密码而且未保存的WiFi

当需要输入密码时会将xml文件中的edittext设置为可见,并添加监听watch

代码语言:javascript复制
if (mPasswordView == null) {
            mPasswordView = (TextView) mView.findViewById(R.id.password);
            mPasswordView.addTextChangedListener(this);
            ((CheckBox) mView.findViewById(R.id.show_password))
                .setOnCheckedChangeListener(this);

            if (mAccessPoint != null && mAccessPoint.networkId != INVALID_NETWORK_ID) {
                mPasswordView.setHint(R.string.wifi_unchanged);
            }
        }

0 人点赞