1. 程式人生 > >Android掃描wifi二維碼自動連線wifi

Android掃描wifi二維碼自動連線wifi

現在二維碼的用處越來越多,微信掃二維碼加好友,商場掃二維碼關注公眾號、得優惠券,就連一些小餐館吃個飯,掃碼自動跳轉到線上(餓了麼,百度外賣)的店鋪.....而這些地方因為人多,擔心使用者等待時間過長就會走掉,怎麼辦?得有空調、wifi、西瓜,這樣才能留住一部分客人,而wifi密碼一般是直接問店裡服務員獲取,但是時間一長,人一多,店裡老闆和員工也煩了,乾脆直接把wifi密碼貼出來。但是這些都不夠,我認為未來微信、QQ得加入(擴充套件)這個功能,掃描這個wifi二維碼,詢問使用者是否連線上此wifi。

不說了,看原始碼吧,還是挺簡單的。

原理是 基礎的掃描二維碼功能,識別二維碼後,解析其內容,開啟我們的wifi管理器,加入此wifi。

一般標準的wifi二維碼的格式是這樣的:

 WIFI:S:arg1;P:arg2;T:arg3; 其中 arg1是wifi名(也就是SSID);arg2是密碼,當然也可以為空,就是沒有密碼;arg3是網路的加密型別,一般有3種,無密碼、Wpa/wap2、Wep。

二維碼掃描的部分就不說了,可以參考我前一篇的文章Android二維碼識別與生成,或者嫌前一篇麻煩的,後面我會給出此Demo的原始碼。

當我們掃描到二維碼後,會在handleDecode中去處理掃描結果,處理完畢後會通過setResult方式,將Intent和結果傳回MainActivty的onActivityResult中,然後通過requestCode和resultCode去判斷來自於哪個activity的Intent,接著取出值。

其解析處理結果

 switch (requestCode) {
		case SCANNIN_GREQUEST_CODE:
			if(resultCode == RESULT_OK){
				Bundle bundle = data.getExtras();
				//顯示掃描到的內容
				mTextView.setText(bundle.getString("result"));
				//顯示
				//mImageView.setImageBitmap((Bitmap) data.getParcelableExtra("bitmap"));
			
				String strResult = bundle.getString("result");
				if (strResult.contains("P:") && strResult.contains("T:")) {// 自動連線wifi
					Log.e("掃描返回的結果----->", strResult);// 還是要判斷
					String passwordTemp = strResult.substring(strResult
							.indexOf("P:"));
					password = passwordTemp.substring(2,
							passwordTemp.indexOf(";"));
					String netWorkTypeTemp = strResult.substring(strResult
							.indexOf("T:"));
					netWorkType = netWorkTypeTemp.substring(2,
							netWorkTypeTemp.indexOf(";"));
					String netWorkNameTemp = strResult.substring(strResult
							.indexOf("S:"));
					netWorkName = netWorkNameTemp.substring(2,
							netWorkNameTemp.indexOf(";"));

					if (!wifiAdmin.mWifiManager.isWifiEnabled()) {
						Toast.makeText(this, "開啟wifi設定", Toast.LENGTH_LONG)
								.show();
						wifiAdmin.openWifi();
					}
					Dialog alertDialog = new AlertDialog.Builder(this)
							.setTitle("掃描到可用wifi")
							.setIcon(R.drawable.ic_launcher)
							.setMessage("wifi名:" + netWorkName)
							.setNegativeButton("取消",
									new DialogInterface.OnClickListener() {

										@Override
										public void onClick(
												DialogInterface dialog,
												int which) {
											// TODO Auto-generated method stub
										}
									})
							.setPositiveButton("加入此wifi ",
									new DialogInterface.OnClickListener() {
										@Override
										public void onClick(
												DialogInterface dialog,
												int which) {
											int net_type = 0x13;
											if (netWorkType
													.compareToIgnoreCase("wpa") == 0) {
												net_type = WifiAdmin.TYPE_WPA;// wpa
											} else if (netWorkType
													.compareToIgnoreCase("wep") == 0) {
												net_type = WifiAdmin.TYPE_WEP;// wep
											} else {
												net_type = WifiAdmin.TYPE_NO_PASSWD;// 無加密
											}
											wifiAdmin.addNetwork(netWorkName,
													password,
													net_type);
											Log.e("解析的資料----->",
													"networkname: "
															+ netWorkName + " "
															+ "password: "
															+ password
															+ " netWorkType: "
															+ net_type);
										}
									}).create();
					alertDialog.show();
			
			}
			break;
		}
    }	

WifiAdmin.java的程式碼如下

import android.content.Context;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.util.Log;

import java.util.List;

/**
 * Created by cmos.
 */
public  class WifiAdmin {
	private static String TAG="WifiManger";
    public static final int TYPE_NO_PASSWD = 0x11;
    public static final int TYPE_WEP = 0x12;
    public static final int TYPE_WPA = 0x13;
    public WifiManager mWifiManager;
    private WifiInfo mWifiInfo;
    public WifiAdmin(Context context){
        mWifiManager=  (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
       // mWifiInfo = mWifiManager.getConnectionInfo();
    }
    
    // 新增一個網路並連線 
    public void addNetwork(WifiConfiguration wifi){
        int netId=mWifiManager.addNetwork(wifi);
        mWifiManager.enableNetwork(netId,true);
       
    }
    public void addNetwork(String ssid, String passwd, int type) {
        if (ssid == null || passwd == null || ssid.equals("")) {
            Log.e(TAG, "addNetwork() ## nullpointer error!");
            return;
        }

        if (type != TYPE_NO_PASSWD && type != TYPE_WEP && type != TYPE_WPA) {
            Log.e(TAG, "addNetwork() ## unknown type = " + type);
        }

        addNetwork(createWifiInfo(ssid, passwd, type));
    }

    public WifiConfiguration createWifiInfo(String SSID, String password, int type) {

        Log.e(TAG, "SSID = " + SSID + "## Password = " + password + "## Type = " + type);

        WifiConfiguration config = new WifiConfiguration();
        config.allowedAuthAlgorithms.clear();
        config.allowedGroupCiphers.clear();
        config.allowedKeyManagement.clear();
        config.allowedPairwiseCiphers.clear();
        config.allowedProtocols.clear();
        config.SSID = "\"" + SSID + "\"";

      
        WifiConfiguration tempConfig = this.IsExsits(SSID);
        if (tempConfig != null) {
            //如果已存在該SSID的wifi
            mWifiManager.removeNetwork(tempConfig.networkId);
        }

        // 分為三種情況:1沒有密碼2用wep加密3用wpa加密
        if (type == TYPE_NO_PASSWD) {// 沒有密碼
            config.wepKeys[0] = "";
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
            config.wepTxKeyIndex = 0;

        } else if (type == TYPE_WEP) {  //  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;
        } else if (type == TYPE_WPA) {   // 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;
        }

        return config;
    }

    //檢測該SSID是否已存在
    private WifiConfiguration IsExsits(String SSID) {
        List<WifiConfiguration> existingConfigs = mWifiManager.getConfiguredNetworks();
        for (WifiConfiguration existingConfig : existingConfigs) {
            if (existingConfig.SSID.equals("\"" + SSID + "\"")) {
                return existingConfig;
            }
        }
        return null;
    }

    // 開啟WIFI
    public void openWifi() {
        if (!mWifiManager.isWifiEnabled()) {
            mWifiManager.setWifiEnabled(true);
        }
    }

    // 關閉WIFI
    public void closeWifi() {
        if (mWifiManager.isWifiEnabled()) {
            mWifiManager.setWifiEnabled(false);
        }
    }
}


最後,我們得加上處理wifi相應的許可權。

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

好了,是不是很簡單。demo下載地址  點我下載。

最後的思考:

其實我也在想這麼一個需求,既然能識別,那也得加上一鍵生成我的wifi二維碼功能啊,結合前篇的二維碼生成,這樣就實現wifi共享了,免得別人問我密碼。但是,但是這個目前實現不了,準確的說是在非root功能上的手機實現不了,因為這就等效於“怎麼獲取連線wifi的密碼”的問題了,假如我們可以通過程式碼能獲取到密碼的話,那麼按照格式就可以自動生成了。但是通過收集資料,發現這個實現不了,想一下,目前市場上存在的wifi萬能鑰匙一類的app在使用時,如果要顯示密碼的話,就得獲取root許可權,所以說這個是暫時沒有辦法解決的。當然如果你有好的、通用的解決方法,歡迎留言給個思路。

======2018.1.18更新=====

最近回過頭來,看這些問題,突然發現有部分人反應6.0以上系統,這個方法就有時不行,經過我這2天的初步瞭解,算找到了一部分的原因。原來是Android系統在6.0以上的系統對wifi模組做了修改。以下是官方的原文:

Wi-Fi and Networking Changes
1、This release introduces the following behavior changes to the Wi-Fi and networking APIs.
Your apps can now change the state of WifiConfiguration objects only if you created these objects. You are not permitted to modify or delete WifiConfiguration objects created by the user or by other apps.
2、Previously, if an app forced the device to connect to a specific Wi-Fi network by using enableNetwork() with the disableAllOthers=true setting, the device disconnected from other networks such as cellular data. 
In This release, the device no longer disconnects from such other networks. If your app’s targetSdkVersion is “20” or lower, it is pinned to the selected Wi-Fi network. If your app’s targetSdkVersion is “21” or higher, 
use the multinetwork APIs (such as openConnection(), bindSocket(), and the new bindProcessToNetwork() method) to ensure that its network traffic is sent on the selected network.

翻譯過來就是這樣的(下圖來自於這裡


簡單點說,就是android6.0 只允許操作自己建立的WifiConfiguration。

自己建立的:就是自己的app通過程式碼來建立的

使用者或其他應用建立的:就是自己通過在系統設定裡面選擇ssid後,手動輸入密碼的;和其他應用通過程式碼來建立的

對wifi的操作
使用者或其他應用建立的wifi:雖然可以通過wifiManager的savedConfiguration方法獲取到指定wifi對應的Configuration,但是並不能進行具體的操作,add該wifi,會直接返回-1(addNetwork(WifiConfigurationconfig)=-1),enableNetwork(config)時會導致app無響應,removeNetwork(config)=false,移除系統wifi也不會成功。

自己建立的:可以進行add、enable、remove操作。

下面上傳一個,我測試通過的可以在6.0以上系統適配的程式碼:

import android.content.Context;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.text.TextUtils;
import android.util.Log;

import java.util.List;

public class WifiAutoConnectManager {
    private static final String TAG = WifiAutoConnectManager.class
            .getSimpleName();

    WifiManager wifiManager;

    // 定義幾種加密方式,一種是WEP,一種是WPA,還有沒有密碼的情況
    public enum WifiCipherType {
        WIFICIPHER_WEP, WIFICIPHER_WPA, WIFICIPHER_NOPASS, WIFICIPHER_INVALID
    }

    // 建構函式
    public WifiAutoConnectManager(Context mContext) {
        this.wifiManager = (WifiManager) mContext.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
    }

    // 提供一個外部介面,傳入要連線的無線網
    public void connect(String ssid, String password, WifiCipherType type) {
        Thread thread = new Thread(new ConnectRunnable(ssid, password, type));
        thread.start();
    }

    // 檢視以前是否也配置過這個網路
    private WifiConfiguration isExsits(String SSID) {
        List<WifiConfiguration> existingConfigs = wifiManager
                .getConfiguredNetworks();
        for (WifiConfiguration existingConfig : existingConfigs) {
            if (existingConfig.SSID.equals("\"" + SSID + "\"")) {
                return existingConfig;
            }
        }
        return null;
    }

    private WifiConfiguration createWifiInfo(String SSID, String Password,
                                             WifiCipherType Type) {
        WifiConfiguration config = new WifiConfiguration();
        config.allowedAuthAlgorithms.clear();
        config.allowedGroupCiphers.clear();
        config.allowedKeyManagement.clear();
        config.allowedPairwiseCiphers.clear();
        config.allowedProtocols.clear();
        config.SSID = "\"" + SSID + "\"";
        // config.SSID = SSID;
        // nopass
        if (Type == WifiCipherType.WIFICIPHER_NOPASS) {
            // config.wepKeys[0] = "";
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
            // config.wepTxKeyIndex = 0;
        }
        // wep
        if (Type == WifiCipherType.WIFICIPHER_WEP) {
            if (!TextUtils.isEmpty(Password)) {
                if (isHexWepKey(Password)) {
                    config.wepKeys[0] = Password;
                } else {
                    config.wepKeys[0] = "\"" + Password + "\"";
                }
            }
            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
            config.wepTxKeyIndex = 0;
        }
        // wpa
        if (Type == WifiCipherType.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;

        }
        return config;
    }

    // 開啟wifi功能
    private boolean openWifi() {
        boolean bRet = true;
        if (!wifiManager.isWifiEnabled()) {
            bRet = wifiManager.setWifiEnabled(true);
        }
        return bRet;
    }

    // 關閉WIFI
    private void closeWifi() {
        if (wifiManager.isWifiEnabled()) {
            wifiManager.setWifiEnabled(false);
        }
    }

    private class ConnectRunnable implements Runnable {
        private String ssid;

        private String password;

        private WifiCipherType type;

        public ConnectRunnable(String ssid, String password, WifiCipherType type) {
            this.ssid = ssid;
            this.password = password;
            this.type = type;
        }

        @Override
        public void run() {
            // 開啟wifi
            openWifi();
            // 開啟wifi功能需要一段時間(一般需要1-3秒左右),所以要等到wifi
            // 狀態變成WIFI_STATE_ENABLED的時候才能執行下面的語句

            while (wifiManager.getWifiState() == WifiManager.WIFI_STATE_ENABLING) {
                try {
                    // 為了避免程式一直while迴圈,讓它睡個200毫秒檢測……
                    Thread.sleep(200);

                } catch (InterruptedException ie) {
                    Log.e(TAG, ie.toString());
                }
            }

            WifiConfiguration tempConfig = isExsits(ssid);

            if (tempConfig != null) {
                // wifiManager.removeNetwork(tempConfig.networkId);此方法只能刪除自己應用建立的wifi

                wifiManager.enableNetwork(tempConfig.networkId,
                        true);
            } else {
                WifiConfiguration wifiConfig = createWifiInfo(ssid, password,
                        type);
                //
                if (wifiConfig == null) {
                    Log.d(TAG, "wifiConfig is null!");
                    return;
                }

                int netID = wifiManager.addNetwork(wifiConfig);
                boolean enabled = wifiManager.enableNetwork(netID, true);
                Log.d(TAG, "enableNetwork status enable=" + enabled);
                boolean connected = wifiManager.reconnect();
                Log.d(TAG, "enableNetwork connected=" + connected);
            }

        }
    }

    private static boolean isHexWepKey(String wepKey) {
        final int len = wepKey.length();

        // WEP-40, WEP-104, and some vendors using 256-bit WEP (WEP-232?)
        if (len != 10 && len != 26 && len != 58) {
            return false;
        }

        return isHex(wepKey);
    }

    private static boolean isHex(String key) {
        for (int i = key.length() - 1; i >= 0; i--) {
            final char c = key.charAt(i);
            if (!(c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a'
                    && c <= 'f')) {
                return false;
            }
        }

        return true;
    }

    // 獲取ssid的加密方式

    public static WifiCipherType getCipherType(Context context, String ssid) {
        WifiManager wifiManager = (WifiManager) context.getApplicationContext()
                .getSystemService(Context.WIFI_SERVICE);

        List<ScanResult> list = wifiManager.getScanResults();

        for (ScanResult scResult : list) {

            if (!TextUtils.isEmpty(scResult.SSID) && scResult.SSID.equals(ssid)) {
                String capabilities = scResult.capabilities;
                // Log.i("hefeng","capabilities=" + capabilities);

                if (!TextUtils.isEmpty(capabilities)) {

                    if (capabilities.contains("WPA")
                            || capabilities.contains("wpa")) {
                        return WifiCipherType.WIFICIPHER_WPA;
                    } else if (capabilities.contains("WEP")
                            || capabilities.contains("wep")) {
                        return WifiCipherType.WIFICIPHER_WEP;
                    } else {
                        return WifiCipherType.WIFICIPHER_NOPASS;
                    }
                }
            }
        }
        return WifiCipherType.WIFICIPHER_INVALID;
    }
}

大家可以拿過去測試下,如果有問題的,歡迎留言討論

======2018.1.18更新=====