参考博文
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()进行获取,
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()
- 信号强度
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的获取可参考前两个博客
- 连接速度
WifiInfo info = mAccessPoint.getInfo();
if (info != null && info.getLinkSpeed() != -1) {
..........
}
可以看出连接速度是通过WiFiinfo的实例来获取的,开发者可以通过调用如下代码获取实例
代码语言:javascript复制WifiInfo info = mWifiManager.getConnectionInfo();
获取到info后获取连接速度代码就如上所示了
- 安全性
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地址
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);
}
}