Android WiFi熱點完全研究(自定義創建、跳轉系統界面設置、讀取配置、切換,Android6.0適配)


前言: WiFi熱點設置頁面的安全性選項在Android 4.x上有“無”、“WPA PSK”、“WPA2 PSK”三個選項,在Android 5.0(含)之后去掉了WPA PSK選項(部分手機廠家會修改ROM,有些手機4.4就沒有這個選項了,安全性選項下拉選項是在packages/apps/Settings/res/values/arrays.xml這個文件的wifi_ap_security數組中定義的),當安全性為“無”時連接熱點不需要密碼,其他兩種都是要輸入密碼才能連接的。本文將講解用代碼自動創建、跳轉系統熱點設置頁手動創建兩種方式創建熱點,以及當targetSdkVersion設為23以上時如何處理權限問題。

熱點的安全性選項對應的Java類是在WifiConfiguration.java中定義的(源碼路徑:/frameworks/base/wifi/java/android/net/wifi/WifiConfiguration.java,API 25,不同的API內容有差異)

 1     public static class KeyMgmt {
 2         private KeyMgmt() { }
 3 
 4         /** WPA is not used; plaintext or static WEP could be used. */
 5         public static final int NONE = 0;
 6         /** WPA pre-shared key (requires {@code preSharedKey} to be specified). */
 7         public static final int WPA_PSK = 1;
 8         /** WPA using EAP authentication. Generally used with an external authentication server. */
 9         public static final int WPA_EAP = 2;
10         /** IEEE 802.1X using EAP authentication and (optionally) dynamically
11          * generated WEP keys. */
12         public static final int IEEE8021X = 3;
13 
14         /** WPA2 pre-shared key for use with soft access point
15           * (requires {@code preSharedKey} to be specified).
16           * @hide
17           */
18         @SystemApi
19         public static final int WPA2_PSK = 4;
20         /**
21          * Hotspot 2.0 r2 OSEN:
22          * @hide
23          */
24         public static final int OSEN = 5;
25 
26         public static final String varName = "key_mgmt";
27 
28         public static final String[] strings = { "NONE", "WPA_PSK", "WPA_EAP", "IEEE8021X",
29                 "WPA2_PSK", "OSEN" };
30     }

可以看到,無、WPA PSK、WPA2 PSK這三個選項分別對應到KeyMgmt.NONE、KeyMgmt.WPA_PSK、KeyMgmt.WPA2_PSK,最后一個WPA2_PSK添加了@SystemApi標記,我們不可以直接訪問的,怎么創建這三種形式的熱點呢?

  • 編碼實現

添加需要的權限

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

Android並沒有公開創建WiFi熱點的API,所以我們只能通過反射的方式實現,創建安全性為“無”的代碼如下:

 1     /**
 2      * 自定義wifi熱點
 3      *
 4      * @param enabled 開啟or關閉
 5      * @return
 6      */
 7     private boolean setWifiApEnabled(boolean enabled) {
 8         boolean result = false;
 9         WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
10         if (enabled) {
11             //wifi和熱點不能同時打開,所以打開熱點的時候需要關閉wifi
12             if (wifiManager.isWifiEnabled()) {
13                 wifiManager.setWifiEnabled(false);
14             }
15         }
16         try {
17             //熱點的配置類
18             WifiConfiguration apConfig = new WifiConfiguration();
19             //配置熱點的名稱
20             apConfig.SSID = "ap_test";
21             //配置熱點的密碼,至少八位
22             apConfig.preSharedKey = "";
23             //配置熱點安全性選項
24             apConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
25             //通過反射調用設置熱點
26             Method method = wifiManager.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, boolean.class);
27             //返回熱點打開狀態
28             result = (Boolean) method.invoke(wifiManager, apConfig, enabled);
29             if (!result) {
30                 Toast.makeText(this, "熱點創建失敗,請手動創建!", Toast.LENGTH_SHORT).show();
31                 openAPUI();
32             }
33         } catch (Exception e) {
34             Toast.makeText(this, "熱點創建失敗,請手動創建!", Toast.LENGTH_SHORT).show();
35             openAPUI();
36         }
37         return result;
38     }

創建安全性為“WPA PSK”的熱點需要修改22、24行,需要注意的是:創建熱點后,在不支持WPA_PSK的手機上(Android 4.3以上版本)打開系統熱點頁面,“安全性”將顯示為“無”,看不到密碼文本框,但是連接此類熱點還是需要輸入密碼的。

1 //配置熱點的密碼,至少八位
2 apConfig.preSharedKey = "12345678";
3 //配置熱點安全性
4 apConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);

創建安全性選項為WPA2 PSK只需將上面代碼中第4行改為

apConfig.allowedKeyManagement.set(4);

直接將值指定為4,原因上面講過,這里還有另外一個坑:在MIUI系統上,WPA2 PSK這個值為6,如果在MIUI上運行需要設為6才可以,我們可以判斷一下手機ROM的版本,根據不同的ROM設為4或6。(后面有另外一種判斷方式)

 1     /**
 2      * 判斷是否為MIUI系統,參考http://blog.csdn.net/xx326664162/article/details/52438706
 3      *
 4      * @return
 5      */
 6     public static boolean isMIUI() {
 7         try {
 8             String KEY_MIUI_VERSION_CODE = "ro.miui.ui.version.code";
 9             String KEY_MIUI_VERSION_NAME = "ro.miui.ui.version.name";
10             String KEY_MIUI_INTERNAL_STORAGE = "ro.miui.internal.storage";
11             Properties prop = new Properties();
12             prop.load(new FileInputStream(new File(Environment.getRootDirectory(), "build.prop")));
13 
14             return prop.getProperty(KEY_MIUI_VERSION_CODE, null) != null
15                     || prop.getProperty(KEY_MIUI_VERSION_NAME, null) != null
16                     || prop.getProperty(KEY_MIUI_INTERNAL_STORAGE, null) != null;
17         } catch (IOException e) {
18             return false;
19         }
20     }
apConfig.allowedKeyManagement.set(isMIUI() ? 6 : 4);

至於MIUI為什么設為6就無從得知了,我嘗試在MIUI論壇發帖詢問原因,帖子一直沒有審核通過,由此可見使用系統非公開API是有一定風險的。

20170914更新

經過研究,可以從WifiConfiguration.KeyMgmt.strings這個數組中讀取所有的安全性選項(MIUI的安全性選項為NONE, WPA_PSK, WPA_EAP, IEEE8021X, WAPI_PSK, WAPI_CERT, WPA2_PSK, OSEN,多了WAPI_PSK, WAPI_CERT這兩個),我們只要從中找到WPA2_PSK的索引值就可以了,不需要根據ROM類型判斷,所以上述代碼可以修改為

1             int indexOfWPA2_PSK = 4;
2             //從WifiConfiguration.KeyMgmt數組中查找WPA2_PSK的值
3             for (int i = 0; i < WifiConfiguration.KeyMgmt.strings.length; i++) {
4                 if (WifiConfiguration.KeyMgmt.strings[i].equals("WPA2_PSK")) {
5                     indexOfWPA2_PSK = i;
6                     break;
7                 }
8             }
9             apConfig.allowedKeyManagement.set(indexOfWPA2_PSK);

如果自動創建熱點失敗,我們也可以跳轉系統設置頁讓用戶手動創建。 Android同樣沒有提供跳轉熱點設置頁面的Intent,我們可以打開熱點設置頁面后,通過使用adb shell actives去查找相應的APP包名、類,打開系統熱點設置頁的代碼如下:

    /**
     * 打開網絡共享與熱點設置頁面
     */
    private void openAPUI() {
        Intent intent = new Intent();
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        //打開網絡共享與熱點設置頁面
        ComponentName comp = new ComponentName("com.android.settings", "com.android.settings.Settings$TetherSettingsActivity");
        intent.setComponent(comp);
        startActivity(intent);
    }
  • 讀取熱點配置信息

直接貼代碼吧,判斷熱點是否打開可以參考源碼中的isWifiApEnabled()方法

 1     /**
 2      * 讀取熱點配置信息
 3      */
 4     private void getWiFiAPConfig() {
 5         try {
 6             WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
 7             Method method = wifiManager.getClass().getMethod("getWifiApConfiguration");
 8             WifiConfiguration apConfig = (WifiConfiguration) method.invoke(wifiManager);
 9             if (apConfig == null) {
10                 tvInfo.setText("未配置熱點");
11                 return;
12             }
13             tvInfo.setText(String.format("熱點名稱:%s\r\n", apConfig.SSID));
14             tvInfo.append(String.format("密碼:%s\n", apConfig.preSharedKey));
15             //使用apConfig.allowedKeyManagement.toString()返回{0}這樣的格式,需要截取中間的具體數值
16             //下面幾種寫法都可以
17             //int index = Integer.valueOf(apConfig.allowedKeyManagement.toString().substring(1, 2));
18             //int index = Integer.valueOf(String.valueOf(apConfig.allowedKeyManagement.toString().charAt(1)));
19             //int index = Integer.valueOf(apConfig.allowedKeyManagement.toString().charAt(1)+"");
20             int index = apConfig.allowedKeyManagement.toString().charAt(1) - '0';
21             //從KeyMgmt數組中取出對應的文本
22             String apType = WifiConfiguration.KeyMgmt.strings[index];
23             tvInfo.append(String.format(Locale.getDefault(), "WifiConfiguration.KeyMgmt:%s\r\n", Arrays.toString(WifiConfiguration.KeyMgmt.strings)));
24             tvInfo.append(String.format(Locale.getDefault(), "安全性:%d,%s\r\n", index, apType));
25             isOpen = isWifiApEnabled();
26             tvInfo.append("是否已開啟:" + isOpen);
27         } catch (NoSuchMethodException e) {
28             e.printStackTrace();
29         } catch (IllegalAccessException e) {
30             e.printStackTrace();
31         } catch (InvocationTargetException e) {
32             e.printStackTrace();
33         }
34     }

系統會自動創建一個默認的熱點,如果我們不需要自定義熱點參數的話,可以直接讀取系統的熱點參數,請參考源碼中的switchWifiApEnabled(boolean enabled)方法

  • Android 6.0權限適配

如果APP的targetSdkVersion為23以上,需要在清單文件中添加android.permission.WRITE_SETTINGS權限,並在運行時申請,跳轉系統設置頁由用戶開啟。

 1     private boolean isHasPermissions() {
 2         boolean result = false;
 3         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 4             if (!Settings.System.canWrite(this)) {
 5                 Toast.makeText(this, "打開熱點需要啟用“修改系統設置”權限,請手動開啟", Toast.LENGTH_SHORT).show();
 6 
 7                 //清單文件中需要android.permission.WRITE_SETTINGS,否則打開的設置頁面開關是灰色的
 8                 Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
 9                 intent.setData(Uri.parse("package:" + this.getPackageName()));
10                 //判斷系統能否處理,部分ROM無此action,如魅族Flyme
11                 if (intent.resolveActivity(getPackageManager()) != null) {
12                     startActivity(intent);
13                 } else {
14                     //打開應用詳情頁
15                     intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
16                     intent.setData(Uri.parse("package:" + this.getPackageName()));
17                     if (intent.resolveActivity(getPackageManager()) != null) {
18                         startActivity(intent);
19                     }
20                 }
21             } else {
22                 result = true;
23             }
24         } else {
25             result = true;
26         }
27         return result;
28     }

項目源碼:https://github.com/fly263/APDemo

PS:布局文件使用了ConstraintLayout,如果沒有安裝,需要在Android Studio中點擊File-Settings-System Settings-Android SDK,切換到SDK Tools標簽頁,下載ConstraintLayout for Android、Solver for ConstraintLayout兩個支持庫


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM