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内容有差异)
public static class KeyMgmt {
private KeyMgmt() { } /** WPA is not used; plaintext or static WEP could be used. */
public static final int NONE = 0;
/** WPA pre-shared key (requires {@code preSharedKey} to be specified). */
public static final int WPA_PSK = 1;
/** WPA using EAP authentication. Generally used with an external authentication server. */
public static final int WPA_EAP = 2;
/** IEEE 802.1X using EAP authentication and (optionally) dynamically
* generated WEP keys. */
public static final int IEEE8021X = 3; /** WPA2 pre-shared key for use with soft access point
* (requires {@code preSharedKey} to be specified).
* @hide
*/
@SystemApi
public static final int WPA2_PSK = 4;
/**
* Hotspot 2.0 r2 OSEN:
* @hide
*/
public static final int OSEN = 5; public static final String varName = "key_mgmt"; public static final String[] strings = { "NONE", "WPA_PSK", "WPA_EAP", "IEEE8021X",
"WPA2_PSK", "OSEN" };
}
可以看到,无、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,所以我们只能通过反射的方式实现,创建安全性为“无”的代码如下:
/**
* 自定义wifi热点
*
* @param enabled 开启or关闭
* @return
*/
private boolean setWifiApEnabled(boolean enabled) {
boolean result = false;
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
if (enabled) {
//wifi和热点不能同时打开,所以打开热点的时候需要关闭wifi
if (wifiManager.isWifiEnabled()) {
wifiManager.setWifiEnabled(false);
}
}
try {
//热点的配置类
WifiConfiguration apConfig = new WifiConfiguration();
//配置热点的名称
apConfig.SSID = "ap_test";
//配置热点的密码,至少八位
apConfig.preSharedKey = "";
//配置热点安全性选项
apConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
//通过反射调用设置热点
Method method = wifiManager.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, boolean.class);
//返回热点打开状态
result = (Boolean) method.invoke(wifiManager, apConfig, enabled);
if (!result) {
Toast.makeText(this, "热点创建失败,请手动创建!", Toast.LENGTH_SHORT).show();
openAPUI();
}
} catch (Exception e) {
Toast.makeText(this, "热点创建失败,请手动创建!", Toast.LENGTH_SHORT).show();
openAPUI();
}
return result;
}
创建安全性为“WPA PSK”的热点需要修改22、24行,需要注意的是:创建热点后,在不支持WPA_PSK的手机上(Android 4.3以上版本)打开系统热点页面,“安全性”将显示为“无”,看不到密码文本框,但是连接此类热点还是需要输入密码的。
//配置热点的密码,至少八位
apConfig.preSharedKey = "12345678";
//配置热点安全性
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。(后面有另外一种判断方式)
/**
* 判断是否为MIUI系统,参考http://blog.csdn.net/xx326664162/article/details/52438706
*
* @return
*/
public static boolean isMIUI() {
try {
String KEY_MIUI_VERSION_CODE = "ro.miui.ui.version.code";
String KEY_MIUI_VERSION_NAME = "ro.miui.ui.version.name";
String KEY_MIUI_INTERNAL_STORAGE = "ro.miui.internal.storage";
Properties prop = new Properties();
prop.load(new FileInputStream(new File(Environment.getRootDirectory(), "build.prop"))); return prop.getProperty(KEY_MIUI_VERSION_CODE, null) != null
|| prop.getProperty(KEY_MIUI_VERSION_NAME, null) != null
|| prop.getProperty(KEY_MIUI_INTERNAL_STORAGE, null) != null;
} catch (IOException e) {
return false;
}
}
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类型判断,所以上述代码可以修改为
int indexOfWPA2_PSK = 4;
//从WifiConfiguration.KeyMgmt数组中查找WPA2_PSK的值
for (int i = 0; i < WifiConfiguration.KeyMgmt.strings.length; i++) {
if (WifiConfiguration.KeyMgmt.strings[i].equals("WPA2_PSK")) {
indexOfWPA2_PSK = i;
break;
}
}
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()方法
/**
* 读取热点配置信息
*/
private void getWiFiAPConfig() {
try {
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
Method method = wifiManager.getClass().getMethod("getWifiApConfiguration");
WifiConfiguration apConfig = (WifiConfiguration) method.invoke(wifiManager);
if (apConfig == null) {
tvInfo.setText("未配置热点");
return;
}
tvInfo.setText(String.format("热点名称:%s\r\n", apConfig.SSID));
tvInfo.append(String.format("密码:%s\n", apConfig.preSharedKey));
//使用apConfig.allowedKeyManagement.toString()返回{0}这样的格式,需要截取中间的具体数值
//下面几种写法都可以
//int index = Integer.valueOf(apConfig.allowedKeyManagement.toString().substring(1, 2));
//int index = Integer.valueOf(String.valueOf(apConfig.allowedKeyManagement.toString().charAt(1)));
//int index = Integer.valueOf(apConfig.allowedKeyManagement.toString().charAt(1)+"");
int index = apConfig.allowedKeyManagement.toString().charAt(1) - '0';
//从KeyMgmt数组中取出对应的文本
String apType = WifiConfiguration.KeyMgmt.strings[index];
tvInfo.append(String.format(Locale.getDefault(), "WifiConfiguration.KeyMgmt:%s\r\n", Arrays.toString(WifiConfiguration.KeyMgmt.strings)));
tvInfo.append(String.format(Locale.getDefault(), "安全性:%d,%s\r\n", index, apType));
isOpen = isWifiApEnabled();
tvInfo.append("是否已开启:" + isOpen);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
系统会自动创建一个默认的热点,如果我们不需要自定义热点参数的话,可以直接读取系统的热点参数,请参考源码中的switchWifiApEnabled(boolean enabled)方法
- Android 6.0权限适配
如果APP的targetSdkVersion为23以上,需要在清单文件中添加android.permission.WRITE_SETTINGS权限,并在运行时申请,跳转系统设置页由用户开启。
private boolean isHasPermissions() {
boolean result = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.System.canWrite(this)) {
Toast.makeText(this, "打开热点需要启用“修改系统设置”权限,请手动开启", Toast.LENGTH_SHORT).show(); //清单文件中需要android.permission.WRITE_SETTINGS,否则打开的设置页面开关是灰色的
Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
intent.setData(Uri.parse("package:" + this.getPackageName()));
//判断系统能否处理,部分ROM无此action,如魅族Flyme
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
} else {
//打开应用详情页
intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + this.getPackageName()));
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
}
} else {
result = true;
}
} else {
result = true;
}
return result;
}
项目源码:https://github.com/fly263/APDemo
PS:布局文件使用了ConstraintLayout,如果没有安装,需要在Android Studio中点击File-Settings-System Settings-Android SDK,切换到SDK Tools标签页,下载ConstraintLayout for Android、Solver for ConstraintLayout两个支持库
Android WiFi热点完全研究(自定义创建、跳转系统界面设置、读取配置、切换,Android6.0适配)的更多相关文章
- Android WiFi热点7.1以上版本适配
代码地址如下:http://www.demodashi.com/demo/13907.html 一.准备工作 开发环境: jdk1.8 AS(3.0.1) 运行环境: 华为V10(Android ...
- android wifi热点 socket通信
1.首先建立wifi热点服务器 wifi客户端连接 2.开启一个子线程循环监听某个端口,进行数据流输入输出 /* 服务器 接收数据 */ class Receiver extends Thread ...
- android wifi 热点、socket通讯
WiFi管理工具类 package com.wyf.app.common; import java.lang.reflect.InvocationTargetException; import jav ...
- android WIFI的一些属性
package com.example.wifitest; import java.util.List; import android.content.Context; import android. ...
- Win7下设置WiFi热点
Win7下设置WiFi热点 今天研究了下Win7设置WIFI热点,Connectify神马的都是浮云~亲測可用,现拿出来分享下~ 1.点击"開始",再点击"执行" ...
- windows 7 wifi热点配置
自我总结,有什么不足或更好的解决方案,请告知,感激不尽! 目的:闲来无事的童鞋,可以试一试自己配置wifi热点. ps:其实wifi热点配置是系统存在的功能,只不过需要配置. 现在win桌面wifi热 ...
- Android 开发 创建WiFi、WiFi热点 ---开发集合
WIFI 权限 <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> < ...
- [Android源码]Android源码之高仿飞鸽传书WIFI热点搜索与创建(一)
(本文详情来源:android源码 http://www.eoeandroid.com/thread-296427-1-1.html 转载请注明出处!) [Android源码分享]飞鸽传书的An ...
- Android 连接Wifi和创建Wifi热点 demo
android的热点功能不可见,用了反射的技术搞定之外. Eclipse设置语言为utf-8才能查看中文注释 上代码: MainActivity.java package com.widget.hot ...
随机推荐
- day36 类的三大特性---封装以及Property特性
目录 类的封装 如果真的要拿 类的property特性 setter & deleter 类属性用法 类与对象的绑定方法和非绑定方法 对象方法&类方法&静态方法 隐藏模块内的函 ...
- 15.5.2 【Task实现细节】骨架方法的结构
尽管骨架方法中的代码非常简单,但它暗示了状态机的职责.代码清单15-11生成的骨架方 法如下所示: [DebuggerStepThrough] [AsyncStateMachine(typeof(De ...
- 回文词(Palindromes, UVa401)
输入一个字符串,判断它是否为回文串以及镜像串.输入字符串保证不含数字0. 所谓 回文串,就是反转以后和原串相同,如abba和madam. 所谓镜像串,就是左右镜像之后和原串相同,如2S和3AIAE. ...
- SpringBoot 读取配置文件的值 赋给静态变量
需求:写了一个工具类,但是工具类中的一些变量需要放到配置文件中,而这个工具类中的变量与方法都是静态的,这个时候我需要一个办法将配置文件中的相关配置读取过来赋值给这些静态变量.找了一些文章,试了一些方法 ...
- 洛谷 P2056 BZOJ 2743 [HEOI2012]采花
//表示真的更喜欢洛谷的题面 题目描述 萧芸斓是 Z国的公主,平时的一大爱好是采花. 今天天气晴朗,阳光明媚,公主清晨便去了皇宫中新建的花园采花.花园足够大,容纳了 n 朵花,花有 c 种颜色(用整数 ...
- POJ 1329
模板题,注意一下输出就可以. #include <iostream> #include <cstdio> #include <cmath> #include < ...
- GMGDC专訪戴亦斌:具体解释QAMAster全面測试服务6大功能
GMGDC专訪戴亦斌:具体解释QAMAster全面測试服务6大功能 2014/10/10 · Testin · 业界资讯 在9月24-25日第三届全球移动游戏开发人员大会上,Testin云測COO戴亦 ...
- 数据结构之---C语言实现共享栈
所谓共享栈是两个栈在一个顺序的存储空间中.两个栈的栈底各自是存储空间的首尾地址. 如图我们能够将两个栈构造成一个: 如图: 从这里也就能够分析出来,栈1为空时,就是top1等于-1时.而当top2等于 ...
- 改你MB需求!
改你MB需求! 原创 2015-12-08 尖峰视界 尖峰视界 我敏锐的觉察到,产品经理的头像開始闪动了.在0.1秒的时间内,我全身的血液都冲向了大脑.果然.右上角弹出了文件传输窗体. "最 ...
- GET,POST,PUT,DELETE的区别 和 用法
Http定义了与服务器交互的不同方法,最基本的方法有4种,分别是GET,POST,PUT,DELETE.URL全称是资源描述符,我们可以这样认为:一个URL地址,它用于描述一个网络上的资源,而HTTP ...