寫這篇博客前,說一些題外話:樓主是在一家做嵌入式研發的公司當然也有軟件研發,總之就是以軟硬件通信爲主把數據展示到前端,所
以不可避免的有 wifi 藍牙 網口 串口 等這些通信媒介,網上的wifi通信千篇一律下面我來總結我的wifi通信 ,可以負責的告訴大家這是公
司目前在用的,起碼穩定是可以維持住的,歡迎大家指證。
轉載請附上本文鏈接squery的博客鏈接地址: http://blog.csdn.net/shentanweilan9
wifi通信設計到以下3點, 當然 肯定要建立一個工具類來對外暴露這三個以上的方法
- wifi列表獲取展示
- wifi連接
- 獲取熱點ip建立socket
wifi連接通信的工具類WifiAdmin
這個工具類中主要方法如下:
- startScan() 掃描wifi
- getWifiList() 獲取wifi列表
- connectToTarget() 連接wifi 這裏包含兩個一種是不知道加密方式的 另一種加密方式固定 兩種都需要密碼
- intToIp() 轉換成ip地址
整個類代碼如下:
package com.pswx.squery.wifi_phone.utils;
import android.content.Context;
import android.net.DhcpInfo;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import java.lang.reflect.Method;
import java.util.List;
/**
* Created by squery on 2017/8/17.
*/
public class WifiAdmin {
public static final String SSID = "test";
public static final String PassWord = "12345678";
private final DhcpInfo mDhcpInfo;
private WifiManager mWifiManager;//wifimanager 對象
private WifiInfo mWifiInfo; // 定義WifiInfo對象
private List<ScanResult> mWifiList; // 掃描出的網絡連接列表
private List<WifiConfiguration> mWifiConfiguration; // 網絡連接列表
WifiManager.WifiLock mWifiLock; // 定義一個WifiLock
private static final int NOPASSWORD = 0;
private static final int PASSWORD_WPA = 1;
private static final int PASSWORD_WEP = 2;
private static final int PASSWORD_WPA2 = 3;
// 構造器
public WifiAdmin(Context context) {
// 取得WifiManager對象
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
// 取得WifiInfo對象
mWifiInfo = mWifiManager.getConnectionInfo();
mDhcpInfo = mWifiManager.getDhcpInfo();
}
//打開wifi
public void openWifi() {
if (!mWifiManager.isWifiEnabled()) {
mWifiManager.setWifiEnabled(true);
}
}
//關閉WIFI
public void closeWifi() {
if (mWifiManager.isWifiEnabled()) {
mWifiManager.setWifiEnabled(false);
}
}
//創建熱點
public void createAp() {
if (mWifiManager.isWifiEnabled()) {
mWifiManager.setWifiEnabled(false);
}
try {
WifiConfiguration apConfiguration = new WifiConfiguration();
apConfiguration.SSID = WifiAdmin.SSID;
apConfiguration.preSharedKey = WifiAdmin.PassWord;
apConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
Method method = mWifiManager.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, boolean.class);
method.invoke(mWifiManager, apConfiguration, true);
} catch (Exception e) {
e.printStackTrace();
}
}
//關閉WiFi熱點
public void closeWifiAp() {
if (isWifiApEnabled()) {
try {
Method method = mWifiManager.getClass().getMethod("getWifiApConfiguration");
method.setAccessible(true);
WifiConfiguration config = (WifiConfiguration) method.invoke(mWifiManager);
Method method2 = mWifiManager.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, boolean.class);
method2.invoke(mWifiManager, config, false);
} catch (Exception e) {
e.printStackTrace();
}
}
}
//熱點開關是否打開
public boolean isWifiApEnabled() {
try {
Method method = mWifiManager.getClass().getMethod("isWifiApEnabled");
method.setAccessible(true);
return (Boolean) method.invoke(mWifiManager);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
// 檢查當前WIFI狀態
public int checkState() {
return mWifiManager.getWifiState();
}
// 鎖定WifiLock
public void acquireWifiLock() {
mWifiLock.acquire();
}
// 解鎖WifiLock
public void releaseWifiLock() {
// 判斷時候鎖定
if (mWifiLock.isHeld()) {
mWifiLock.acquire();
}
}
// 創建一個WifiLock
public void creatWifiLock() {
mWifiLock = mWifiManager.createWifiLock("Test");
}
// 得到配置好的網絡
public List<WifiConfiguration> getConfiguration() {
return mWifiConfiguration;
}
// 指定配置好的網絡進行連接
public void connectConfiguration(int index) {
// 索引大於配置好的網絡索引返回
if (index > mWifiConfiguration.size()) {
return;
}
// 連接配置好的指定ID的網絡
mWifiManager.enableNetwork(mWifiConfiguration.get(index).networkId, true);
}
/**
* 掃描WIFI
*/
public void startScan() {
mWifiManager.startScan();
// 得到掃描結果
mWifiList = mWifiManager.getScanResults();
// 得到配置好的網絡連接
mWifiConfiguration = mWifiManager.getConfiguredNetworks();
}
// 得到網絡列表
public List<ScanResult> getWifiList() {
return mWifiList;
}
// 查看掃描結果
public StringBuilder lookUpScan() {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < mWifiList.size(); i++) {
stringBuilder.append("Index_" + new Integer(i + 1).toString() + ":");
// 將ScanResult信息轉換成一個字符串包
// 其中把包括:BSSID、SSID、capabilities、frequency、level
stringBuilder.append((mWifiList.get(i)).toString());
stringBuilder.append("/n");
}
return stringBuilder;
}
// 得到接入點的BSSID
public String getSSID() {
return (mWifiInfo == null) ? "NULL" : mWifiInfo.getSSID();
}
// 得到MAC地址
public String getMacAddress() {
return (mWifiInfo == null) ? "NULL" : mWifiInfo.getMacAddress();
}
// 得到接入點的BSSID
public String getBSSID() {
return (mWifiInfo == null) ? "NULL" : mWifiInfo.getBSSID();
}
// 得到IP地址
public int getIPAddress() {
return (mWifiInfo == null) ? 0 : mWifiInfo.getIpAddress();
}
public int getServerIPAddress() {
return (mWifiInfo == null) ? 0 : mDhcpInfo.serverAddress;
}
// 得到連接的ID
public int getNetworkId() {
return (mWifiInfo == null) ? 0 : mWifiInfo.getNetworkId();
}
// 得到WifiInfo的所有信息包
public String getWifiInfo() {
return (mWifiInfo == null) ? "NULL" : mWifiInfo.toString();
}
// 添加一個網絡並連接
public int addNetwork(WifiConfiguration wcg) {
int wcgID = mWifiManager.addNetwork(wcg);
boolean b = mWifiManager.enableNetwork(wcgID, true);
mWifiManager.reassociate();
LogUtils.e("b--" + b);
return wcgID;
}
// 創建wificonfig
public WifiConfiguration createWifiConfig(String SSID, String Password, int Type) {
WifiConfiguration config = new WifiConfiguration();
config.allowedAuthAlgorithms.clear();
config.allowedGroupCiphers.clear();
config.allowedKeyManagement.clear();
config.allowedPairwiseCiphers.clear();
config.allowedProtocols.clear();
config.SSID = "\"" + SSID + "\"";
//如果設備大於6.0配置的時候就不需要雙引號,加了就連接不上了
if (Build.VERSION.SDK_INT >= 23) {
config.SSID = SSID;
} else {
config.SSID = "\"" + SSID + "\"";
}
WifiConfiguration tempConfig = isExsits(SSID);
if (tempConfig != null) {// 去除自動保存的 wifi
disconnectWifi(tempConfig.networkId);
}
if (Type == NOPASSWORD) // WIFICIPHER_NOPASS
{
config.hiddenSSID = true;
//config.wepKeys[0] = "";
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
//config.wepTxKeyIndex = 0;
}
if (Type == PASSWORD_WPA) // WIFICIPHER_WPA
{
config.preSharedKey = "\"" + Password + "\"";
config.hiddenSSID = true;
config.allowedAuthAlgorithms
.set(WifiConfiguration.AuthAlgorithm.OPEN);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
config.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
config.status = WifiConfiguration.Status.ENABLED;
}
if (Type == PASSWORD_WEP) // WIFICIPHER_WEP
{
config.hiddenSSID = true;
config.wepKeys[0] = "\"" + Password + "\"";
config.allowedAuthAlgorithms
.set(WifiConfiguration.AuthAlgorithm.SHARED);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
config.allowedGroupCiphers
.set(WifiConfiguration.GroupCipher.WEP104);
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
config.wepTxKeyIndex = 0;
}
return config;
}
/**
* 判斷wifi是否存在
*
* @param SSID
* @return
*/
public WifiConfiguration isExsits(String SSID) {
List<WifiConfiguration> existingConfigs = mWifiManager.getConfiguredNetworks();
if (!ArrayUtils.isEmpty(existingConfigs)) {
for (WifiConfiguration existingConfig : existingConfigs) {
if (existingConfig.SSID.equals("\"" + SSID + "\"")) {
return existingConfig;
}
}
}
return null;
}
/**
* 連接目標熱點
*
* @param scanResult 熱點的加密方式
*/
public int connectToTarget(ScanResult scanResult, String password) {
int mNetworkID = 0;
int password_type = 0;
WifiConfiguration mTargetWifiCfg;
if (scanResult != null) {
if (scanResult.capabilities.contains("WPA") || scanResult.capabilities.contains("wpa")) {
password_type = PASSWORD_WPA;
} else if (scanResult.capabilities.contains("WEP") || scanResult.capabilities.contains("wep")) {
password_type = PASSWORD_WEP;
} else if (scanResult.capabilities.contains("WPA2") || scanResult.capabilities.contains("wpa2")) {
password_type = PASSWORD_WPA2;
} else {
password_type = NOPASSWORD;
}
}
//LogUtils.e(scanResult.SSID+"::::::::::::::::::::" + password_type); //password_type=1 WPA
mTargetWifiCfg = createWifiConfig(scanResult.SSID, password, password_type);// 獲得wificonfig
mNetworkID = addNetwork(mTargetWifiCfg);
return mNetworkID;
}
public int connectToTarget(String SSID, String password) {
int mNetworkID = 0;
int password_type = PASSWORD_WPA;
WifiConfiguration mTargetWifiCfg;
mTargetWifiCfg = createWifiConfig(SSID, password, password_type);// 獲得wificonfig
mNetworkID = addNetwork(mTargetWifiCfg);
return mNetworkID;
}
// 斷開指定ID的網絡
public void disconnectWifi(int netId) {
mWifiManager.disableNetwork(netId);
mWifiManager.disconnect();
mWifiManager.removeNetwork(netId);
}
/**
* 轉換IP地址
*
* @param i
* @return
*/
public String intToIp(int i) {
return (i & 0xFF) + "." + ((i >> 8) & 0xFF) + "." + ((i >> 16) & 0xFF)
+ "." + ((i >> 24) & 0xFF);
}
}
wifi列表獲取與展示
wifi列表的獲取,大致思路是:掃描附近wifi這個時候android系統會廣播一條通知SCAN_RESULTS_AVAILABLE_ACTION
注意:** 每掃描一次會廣播單條具體的通知,這個不同於wifi連接的通知(wifi連接通知 不同手機廣播的通知條數不同 通知類型也不同) ** 然後就是在廣播監聽裏面獲取到wifi列表 並更新adapter 也就是更新ui
發送廣播 可以在一進入頁面就去掃描 也可以通過按鈕點擊事件來觸發 代碼如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
setLayoutId(R.layout.activity_wifi_list);
super.onCreate(savedInstanceState);
initTitle(R.mipmap.left_arrow, R.string.back, "wifi列表", R.string.showelctric, 0);
mWifiAdmin.openWifi();// 第一次進來時候顯示列表
scanFlag = 1;
mWifiAdmin.startScan();//開始掃描 發送通知
AppUtils.getInstance().showLoading(this);
}
@OnClick({R.id.lear_left, R.id.btn_scan})
void onClicks(View v) {
int flag = 0;
switch (v.getId()) {
case R.id.lear_left:
mConnectThread = null;
finish();
break;
case R.id.btn_scan:
mWifiAdmin.openWifi();
mWifiAdmin.startScan();//開始掃描 發送通知
AppUtils.getInstance().showLoading(this);
scanFlag = 1;
break;
}
}
獲取wifi列表 並更新adapter 當然要註冊廣播監聽 頁面銷燬時候要註銷廣播監聽 代碼如下:
@Override
public void onResume() {// 註冊廣播
super.onResume();
registerReceiver(wifiScanReceiver, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
}
@Override
public void onPause() {// 註銷廣播
super.onPause();
unregisterReceiver(wifiScanReceiver);
}
private BroadcastReceiver wifiScanReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context arg0, Intent intent) {
if (intent.getAction().equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {//掃描完畢 更新Ui
AppUtils.getInstance().dismissLoading(WifiListActivity.this);
mWifiList.clear();
for (ScanResult scanResult : mWifiAdmin.getWifiList()) {
if (scanResult.SSID.startsWith("orbis_")) // 過濾掉其他的,添加自己需要的wifi熱點
mWifiList.add(new CustomScan(false, scanResult));
}
//LogUtils.e("掃描的wifi列表:::\t" + mWifiAdmin.lookUpScan().toString());
mCommonAdapter.notifyDataSetChanged();
}
}
};
wifi連接以及處理連接時延問題
wifi連接:就是連接具體的wifi熱點 這個很好理解
連接時延: 執行了連接代碼 但是系統反應需要一段時間並不是馬上連接上wifi熱點的,這個時間就是連接時延
注意:如果大家不去考慮這個時延,那樣會出現很大的問題,那樣我們通過ip獲取建立socket時候會報一個socket untouched異常 具體異常名字我沒有詳記
- 首先我們不能通過
mWifiAdmin.connectToTarget(customScan.getScanResult(), "123456789");
發送的通知 以及捕
獲具體通知進行wifi連接的處理 因爲上述我已經闡明 通知的種類以及條數都不是固定的 - 我們不能認爲
mWifiAdmin.connectToTarget(customScan.getScanResult(), "123456789");
執行完瞬間就可以連
接上wifi了不能單純的只根據這個 返回值是不是-1 來判斷連接wifi成功與否 當然返回-1 肯定是失敗 但是返回!-1的時
候,由於連接是需要一段時間的,所以我們要加一個循環判斷是否連接成功的線程 這樣就可以在連接成功後進行我們自己的業務處理了. - 循環判斷wifi連接成功的線程需要注意 1.循環次數 2.失敗後的界面反饋 可以起線程 那樣需要handler來傳遞
失敗後的信息 也可以用異步任務
1和2 有兩種可能 通過wifi列表連接具體wifi的 也可以 直接連接某個wifi熱點的 代碼如下:
@OnClick({R.id.btn_jump_temp, R.id.btn_scan})
void onClicks(View v) {
int flag = 0;
switch (v.getId()) {
case R.id.btn_scan:
mWifiAdmin.openWifi();
mWifiAdmin.startScan();//開始掃描 發送通知
AppUtils.getInstance().showLoading(this);
scanFlag = 1;
break;
case R.id.btn_jump_temp:
if (!ArrayUtils.isEmpty(mWifiList)) {
for (CustomScan customScan : mWifiList) {
if (customScan.isSelected()) {
flag = 1;
if (!TextUtils.isEmpty(mEdiPhoneNum.getText())) {
int i = mWifiAdmin.connectToTarget(customScan.getScanResult(), "123456789");// 發送通知
if (i != -1) {
AppUtils.getInstance().showLoading(WifiListActivity.this);
jumpNum = 1;
mConnectThread = new ConnectThread();
mConnectThread.start();
mLearRight.setClickable(true);
} else {
Toast.makeText(WifiListActivity.this, "連接失敗", Toast.LENGTH_LONG).show();
mLearRight.setClickable(true);
}
}
}
}
if (flag == 0) {
Toast.makeText(this, "請先選擇一個wifi熱點", Toast.LENGTH_LONG).show();
mLearRight.setClickable(true);
return;
}
} else {
Toast.makeText(this, "沒有wifi列表", Toast.LENGTH_LONG).show();
mLearRight.setClickable(true);
return;
}
break;
}
}
@OnClick({R.id.btn_jump_temp, R.id.btn_scan})
void onClicks(View v) {
int flag = 0;
switch (v.getId()) {
case R.id.btn_scan:
mWifiAdmin.openWifi();
mWifiAdmin.startScan();//開始掃描 發送通知
AppUtils.getInstance().showLoading(this);
scanFlag = 1;
break;
case R.id.btn_jump_temp:
if (!DeviceUtils.isWifiConnected(WifiListNullActivity.this)
|| TextUtils.isEmpty(PreferenceSettingUtils.getIP(this))) {
jumpNum = 1;
AppUtils.getInstance().showLoading(WifiListNullActivity.this, "分機連接中...");
int i = mWifiAdmin.connectToTarget(PreferenceSettingUtils.getWifiName(WifiListNullActivity.this),
PreferenceSettingUtils.getWifiPassword(WifiListNullActivity.this));// 發送通知
if (i == -1) {
Toast.makeText(WifiListNullActivity.this, "附近沒有找到指定的分機", Toast.LENGTH_LONG).show();
}
mConnectThread = new ConnectThread();
mConnectThread.start();
} else {
startActivity(new Intent(this, DetctTempActivity.class));
}
break;
}
}
3.也有兩種 起線程 和 異步任務 代碼如下:
public class ConnectThread extends Thread {// 連接Thread
@Override
public void run() {
int count = 0;
while (true) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (DeviceUtils.isWifiConnected(WifiListActivity.this)) {// 連接成功
WifiAdmin wifiAdmin = new WifiAdmin(WifiListActivity.this);
String ip = wifiAdmin.intToIp(wifiAdmin.getServerIPAddress());
PreferenceSettingUtils.setIP(WifiListActivity.this, ip);
// 啓動net work
try {
Message msg = new Message();
msg.obj = "WIFI連接成功!";
msg.what = 1;
LinkDetectedHandler.sendMessage(msg);
} catch (Exception e) {
Message msg = new Message();
msg.obj = "獲取socket失敗";
msg.what = 0;
LinkDetectedHandler.sendMessage(msg);
e.printStackTrace();
}
break;
}
if (count++ > 10) {
Message msg = new Message();
msg.obj = "WIFI連接失敗!";
msg.what = -1;
LinkDetectedHandler.sendMessage(msg);
break;
}
}
super.run();
}
}
public class SocketConnectTask extends AsyncTask<Integer, Void, Integer> {
//後面尖括號內分別是參數(線程休息時間),進度(publishProgress用到),返回值 類型
private Context mContext;
private Thread mThread;
public SocketConnectTask(Context context, Thread thread) {
mContext = context;
mThread = thread;
}
public SocketConnectTask(Context context) {
mContext = context;
}
/*
* 第一個執行的方法
* 執行時機:在執行實際的後臺操作前,被UI 線程調用
* 作用:可以在該方法中做一些準備工作,如在界面上顯示一個進度條,或者一些控件的實例化,這個方法可以不用實現。
* @see android.os.AsyncTask#onPreExecute()
*/
@Override
protected void onPreExecute() {
//AppUtils.getInstance().showLoading(mContext);
super.onPreExecute();
}
/*
* 執行時機:在onPreExecute 方法執行後馬上執行,該方法運行在後臺線程中
* 作用:主要負責執行那些很耗時的後臺處理工作。該方法是抽象方法,子類必須實現。
* @see android.os.AsyncTask#doInBackground(Params[])
*/
@Override
protected Integer doInBackground(Integer... params) {
int count = 0;
while (true) {
try {
Thread.sleep(2000);
if (DeviceUtils.isWifiConnected(mContext)) {
Thread.sleep(1000);
WifiAdmin wifiAdmin = new WifiAdmin(mContext);
String ip = wifiAdmin.intToIp(wifiAdmin.getServerIPAddress());
PreferenceSettingUtils.setIP(mContext, ip);
return 1;
}
if (count++ > 10) return -1;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/*
* 執行時機:在doInBackground 執行完成後,將被UI 線程調用
* 作用:後臺的計算結果將通過該方法傳遞到UI 線程,並且在界面上展示給用戶
* result:上面doInBackground執行後的返回值,所以這裏是"執行完畢"
* @see android.os.AsyncTask#onPostExecute(java.lang.Object)
*/
@Override
protected void onPostExecute(Integer result) {
//AppUtils.getInstance().dismissLoading(mContext);
switch (result) {
case 1:
if (mThread != null) {
mThread.start();
}
Toast.makeText(mContext, "wifi連接成功", Toast.LENGTH_SHORT).show();
break;
case -1:
Toast.makeText(mContext, "wifi連接異常", Toast.LENGTH_SHORT).show();
break;
}
super.onPostExecute(result);
}
}
總結:wifi連接就這些了 其主要問題 就是在連接到熱點 建立socket的時候一定要注意這個時延 不能去通過通知的捕獲來處理業務邏輯socket建立的成功與否 是在wifi熱點必須連接成功的前提下。下面章節我可能會敘述一些 wifi通信協議以及通信中線程中處理業務的問題。如有疑問可以聯繫我。