Android wifi通訊 開發(wifi列表 +連線具體的wifi熱點+socket的獲取) 之 解決熱點連成功的時延問題
寫這篇部落格前,說一些題外話:樓主是在一家做嵌入式研發的公司當然也有軟體研發,總之就是以軟硬體通訊為主把資料展示到前端,所
以不可避免的有 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通訊協議以及通訊中執行緒中處理業務的問題。如有疑問可以聯絡我。