二維碼是搭載資訊的一種載體,通過二維碼可以傳遞名片、網址、商品資訊等,本文講到二維碼的另外一種妙用:通過二維碼實作wifi賬号和密碼分享。
關于二維碼的基礎知識,請通路:二維碼的生成細節和原理
試想這樣一種場景:一夥人去同一餐館或者在一起開會,有的人之前去過且已經使用過那個場景的wifi賬号,是以一去手機就能夠直接連上那裡的wifi,但有的同學是第一次去,也有連接配接wifi的需求,這時我們一般是通過别人告知wifi賬号和密碼然後手動登陸,但問題是有時候已經連上wifi的人也不記得wifi的密碼了,本文結合這個需求場景,做了一個wifi賬号和密碼分享的小demo,前提是雙方都需要安裝這個app,并且分享wifi的一方需要有root權限(目前是,也可以将分享wifi的用戶端作為熱點,然後将熱點分享給其他人,這個可以自行研究),這樣減少了手動輸入密碼的麻煩。
本文主要介紹通過二維碼實作wifi分享的兩個核心功能:
1、擷取本機已經連接配接上的wifi賬号、密碼和加密方式;
2、給定指定内容生成二維碼,掃描二維碼解析出其中搭載的資訊。
1、擷取本機已經連接配接上的wifi賬号、密碼和加密方式:
所有的事情都可以通過下面這個類搞定:
public class WifiAdmin {
public WifiAdmin(Context context) {
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mWifiInfo = mWifiManager.getConnectionInfo();
}
// 打開WIFI
public void openWifi() {
if (!mWifiManager.isWifiEnabled()) {
mWifiManager.setWifiEnabled(true);
}
}
// 關閉WIFI
public void closeWifi() {
if (mWifiManager.isWifiEnabled()) {
mWifiManager.setWifiEnabled(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);
}
public void startScan() {
mWifiManager.startScan();
// 得到掃描結果
mWifiList = mWifiManager.getScanResults();
// 得到配置好的網絡連接配接
mWifiConfiguration = mWifiManager.getConfiguredNetworks();
}
// 得到網絡清單
public List<ScanResult> getWifiList() {
return mWifiList;
}
// 檢視掃描結果
@SuppressLint("UseValueOf")
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;
}
// 得到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();
}
// 得到連接配接的ID
public int getNetworkId() {
return (mWifiInfo == null) ? 0 : mWifiInfo.getNetworkId();
}
// 得到WifiInfo的所有資訊包
public WifiInfo getWifiInfo() {
return mWifiInfo;
}
// 添加一個網絡并連接配接
public void addNetwork(WifiConfiguration wcg) {
int wcgID = mWifiManager.addNetwork(wcg);
boolean b = mWifiManager.enableNetwork(wcgID, true);
System.out.println("a--" + wcgID);
System.out.println("b--" + b);
}
// 斷開指定ID的網絡
public void disconnectWifi(int netId) {
mWifiManager.disableNetwork(netId);
mWifiManager.disconnect();
}
// 然後是一個實際應用方法,隻驗證過沒有密碼的情況:
// 分為三種情況:1沒有密碼2用wep加密3用wpa加密
public WifiConfiguration CreateWifiInfo(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 + "\"";
WifiConfiguration tempConfig = this.IsExsits(SSID);
if (tempConfig != null) {
mWifiManager.removeNetwork(tempConfig.networkId);
}
if (type == 1) {
// WIFICIPHER_NOPASS
config.wepKeys[0] = "";
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
config.wepTxKeyIndex = 0;
}
if (type == 2) {
// 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;
}
if (type == 3) {
// 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;
}
private WifiConfiguration IsExsits(String SSID) {
List<WifiConfiguration> existingConfigs = mWifiManager.getConfiguredNetworks();
if (null == existingConfigs) {
return null;
}
for (WifiConfiguration existingConfig : existingConfigs) {
System.out.println("existingConfig == " + existingConfig.toString());
if (existingConfig.SSID.equals("\"" + SSID + "\"")) {
System.out.println("existingConfig.SSID == " + existingConfig.SSID + " SSID == " + SSID);
return existingConfig;
}
}
return null;
}
public List<UserWifiInfo> getUserWifiInfoList( ) throws Exception {
List<UserWifiInfo> wifiInfos=new ArrayList<UserWifiInfo>();
Process process = null;
DataOutputStream dataOutputStream = null;
DataInputStream dataInputStream = null;
StringBuffer wifiConf = new StringBuffer();
try {
process = Runtime.getRuntime().exec("su");
dataOutputStream = new DataOutputStream(process.getOutputStream());
dataInputStream = new DataInputStream(process.getInputStream());
dataOutputStream.writeBytes("cat /data/misc/wifi/*.conf\n");
dataOutputStream.writeBytes("exit\n");
dataOutputStream.flush();
InputStreamReader inputStreamReader = new InputStreamReader(
dataInputStream, "UTF-8");
BufferedReader bufferedReader = new BufferedReader(
inputStreamReader);
String line = null;
while ((line = bufferedReader.readLine()) != null) {
wifiConf.append(line);
}
bufferedReader.close();
inputStreamReader.close();
process.waitFor();
} catch (Exception e) {
throw e;
} finally {
try {
if (dataOutputStream != null) {
dataOutputStream.close();
}
if (dataInputStream != null) {
dataInputStream.close();
}
process.destroy();
} catch (Exception e) {
throw e;
}
}
Pattern network = Pattern.compile("network=\\{([^\\}]+)\\}", Pattern.DOTALL);
Matcher networkMatcher = network.matcher(wifiConf.toString() );
while (networkMatcher.find() ) {
String networkBlock = networkMatcher.group();
Pattern ssid = Pattern.compile("ssid=\"([^\"]+)\"");
Matcher ssidMatcher = ssid.matcher(networkBlock);
if (ssidMatcher.find() ) {
UserWifiInfo wifiInfo=new UserWifiInfo();
wifiInfo.Ssid=ssidMatcher.group(1);
Pattern psk = Pattern.compile("psk=\"([^\"]+)\"");
Matcher pskMatcher = psk.matcher(networkBlock);
if (pskMatcher.find() ) {
wifiInfo.Password=pskMatcher.group(1);
} else {
wifiInfo.Password="無密碼";
}
wifiInfos.add(wifiInfo);
}
}
return wifiInfos;
}
private WifiManager mWifiManager = null;
private WifiInfo mWifiInfo = null;
private List<ScanResult> mWifiList = null;
private List<WifiConfiguration> mWifiConfiguration = null;
private WifiLock mWifiLock = null;
}
使用到wifi的各種功能,記得增加相應地權限:
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" >
</uses-permission>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" >
</uses-permission>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" >
</uses-permission>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" >
</uses-permission>
2、給定指定内容生成二維碼,掃描二維碼解析出其中搭載的資訊:
本文使用到了開源二維碼項目zxing,關于zxing的更多介紹請參看:二維碼、條形碼掃描——使用Google ZXing
(1)将指定内容生成二維碼的方法如下:
@SuppressWarnings("deprecation")
private void generate( String result ){
if( TextUtils.isEmpty( result ) ){
return;
}
try{
//判斷result合法性
if (result == null || "".equals(result) || result.length() < 1){
return;
}
Hashtable<EncodeHintType, String> hints = new Hashtable<EncodeHintType, String>();
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
//圖像資料轉換,使用了矩陣轉換
BitMatrix bitMatrix = new QRCodeWriter().encode(result, BarcodeFormat.QR_CODE, QR_WIDTH, QR_HEIGHT, hints);
int[] pixels = new int[QR_WIDTH * QR_HEIGHT];
//下面這裡按照二維碼的算法,逐個生成二維碼的圖檔,
//兩個for循環是圖檔橫列掃描的結果
for (int y = 0; y < QR_HEIGHT; y++){
for (int x = 0; x < QR_WIDTH; x++){
if (bitMatrix.get(x, y)){
pixels[y * QR_WIDTH + x] = 0xff000000;
}else{
pixels[y * QR_WIDTH + x] = 0xffffffff;
}
}
}
//生成二維碼圖檔的格式,使用ARGB_8888
Bitmap bitmap = Bitmap.createBitmap(QR_WIDTH, QR_HEIGHT, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, QR_WIDTH, 0, 0, QR_WIDTH, QR_HEIGHT);
//顯示到一個ImageView上面
mResultImg.setBackground( new BitmapDrawable( bitmap ) );
decodeBitmap( bitmap );
}catch (WriterException e) {
e.printStackTrace();
}
}
private String decodeBitmap( Bitmap bitmap ){
MultiFormatReader multiFormatReader = new MultiFormatReader();
// 解碼的參數
Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>(2);
// 可以解析的編碼類型
Vector<BarcodeFormat> decodeFormats = new Vector<BarcodeFormat>();
if (decodeFormats == null || decodeFormats.isEmpty()) {
decodeFormats = new Vector<BarcodeFormat>();
// 這裡設定可掃描的類型,我這裡選擇了都支援
decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS);
decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);
}
hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
// 設定繼續的字元編碼格式為UTF8
// hints.put(DecodeHintType.CHARACTER_SET, "UTF8");
// 設定解析配置參數
multiFormatReader.setHints(hints);
// 開始對圖像資源解碼
try {
Result rawResult = multiFormatReader.decodeWithState(new BinaryBitmap(new HybridBinarizer( new com.uperone.zxing.decoding.BitmapLuminanceSource(bitmap))));
mDecodeReslutTxt.setText(new StringBuilder().append("包括内容:")
.append(rawResult.getText()).append("\n編碼方式:")
.append(rawResult.getBarcodeFormat()).toString());
} catch (NotFoundException e) {
e.printStackTrace();
}
return null;
}
(2)解析出二維碼中搭載的内容方法如下:
/**
* Decode the data within the viewfinder rectangle, and time how long it took. For efficiency,
* reuse the same reader objects from one decode to the next.
*
* @param data The YUV preview frame.
* @param width The width of the preview frame.
* @param height The height of the preview frame.
*/
private void decode(byte[] data, int width, int height) {
long start = System.currentTimeMillis();
Result rawResult = null;
PlanarYUVLuminanceSource source = CameraManager.get().buildLuminanceSource(data, width, height);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
try {
rawResult = multiFormatReader.decodeWithState(bitmap);
} catch (ReaderException re) {
// continue
} finally {
multiFormatReader.reset();
}
if (rawResult != null) {
long end = System.currentTimeMillis();
Log.d(TAG, "Found barcode (" + (end - start) + " ms):\n" + rawResult.toString());
Message message = Message.obtain(activity.getHandler(), R.id.decode_succeeded, rawResult);
Bundle bundle = new Bundle();
bundle.putParcelable(DecodeThread.BARCODE_BITMAP, source.renderCroppedGreyscaleBitmap());
message.setData(bundle);
//Log.d(TAG, "Sending decode succeeded message...");
message.sendToTarget();
} else {
Message message = Message.obtain(activity.getHandler(), R.id.decode_failed);
message.sendToTarget();
}
}
@Override
public void handleMessage(Message message) {
switch (message.what) {
case R.id.auto_focus:
//Log.d(TAG, "Got auto-focus message");
// When one auto focus pass finishes, start another. This is the closest thing to
// continuous AF. It does seem to hunt a bit, but I'm not sure what else to do.
if (state == State.PREVIEW) {
CameraManager.get().requestAutoFocus(this, R.id.auto_focus);
}
break;
case R.id.restart_preview:
Log.d(TAG, "Got restart preview message");
restartPreviewAndDecode();
break;
case R.id.decode_succeeded:
Log.d(TAG, "Got decode succeeded message");
state = State.SUCCESS;
Bundle bundle = message.getData();
Bitmap barcode = bundle == null ? null :
(Bitmap) bundle.getParcelable(DecodeThread.BARCODE_BITMAP);
activity.handleDecode((Result) message.obj, barcode);
break;
case R.id.decode_failed:
// We're decoding as fast as possible, so when one decode fails, start another.
state = State.PREVIEW;
CameraManager.get().requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
break;
case R.id.return_scan_result:
Log.d(TAG, "Got return scan result message");
activity.setResult(Activity.RESULT_OK, (Intent) message.obj);
activity.finish();
break;
case R.id.launch_product_query:
Log.d(TAG, "Got product query message");
String url = (String) message.obj;
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
activity.startActivity(intent);
break;
}
}
public void handleDecode(final Result obj, Bitmap barcode){
inactivityTimer.onActivity();
playBeepSoundAndVibrate();
String result = obj.getText();
if(!TextUtils.isEmpty( result ) ){
System.out.println( "result == " + result );
String[] results = result.split( "#" );
String user = results[ 0 ];
String password = results[ 1 ];
int type = Integer.parseInt( results[ 2 ] );
WifiAdmin wifiAdmin = new WifiAdmin(this);
wifiAdmin.openWifi();
wifiAdmin.addNetwork(wifiAdmin.CreateWifiInfo(user, password, type));
Toast.makeText( this, result, Toast.LENGTH_LONG ).show( );
}
}
本例在掃描二維碼時使用到了相機功能,是以需要加上關于camera的一些權限:
<uses-permission android:name="android.permission.CAMERA" >
</uses-permission>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" >
</uses-permission>
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FLASHLIGHT" />
關于demo的詳細功能和各功能的詳細代碼可到這裡下載下傳demo體驗。