有这样的需求:通过扫描二维码获取Wi-Fi
的ssid
和密码,然后自动连接。我们先后出了两个版本,第一个版本,是通过网络搜索出来融合的初略版本,代码比较简陋,效果就是很多用户反馈联网的问题;第二个版本是我写的,基于充分了解联网逻辑的基础上,用kotlin
实现的,效果马马虎虎,不过时不时还是有用户反馈联网的问题,大概1%
左右吧;这次痛定思痛,采用反射调用系统framework
的隐藏方法来实现连接wifi
的功能。
因为是反射调用隐藏的方法 connect
,且用到了一个隐藏的回调接口类ActionListener
,所以在调用connect()
方法前,需要先实现该接口,这就用到了委托、代理的原理
Note: 本文所有代码均是基于kotlin
语言实现
1.实现 ActionListener 接口类
系统提供的连接wifi
的方法有两个,分别是:
public void connect(WifiConfiguration config, ActionListener listener)
public void connect(int networkId, ActionListener listener)
- 1
- 2
可以看到,两个重载方法都需要传递一个ActionListener
接口参数,这个接口的源代码如下:
/**
* Interface for callback invocation on an application action
* @hide
*/
public interface ActionListener {
/** The operation succeeded */
public void onSuccess();
/**
* The operation failed
* @param reason The reason for failure could be one of
* {@link #ERROR}, {@link #IN_PROGRESS} or {@link #BUSY}
*/
public void onFailure(int reason);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
简单来讲,接口类ActionListener
有两个回调方法,分别是成功回调onSuccess
和失败回调onFailure
,跟我们平时使用的网络访问回调很像,麻烦在于它是隐藏的。
第一步,使用反射和代理实现 ActionListener 接口类,代码如下:
//通过反射获取 ActionListener 类
val actionListener = Class.forName("android.net.wifi.WifiManager\$ActionListener")
//实现 InvocationHandler 接口,所有的我们需要的回调方法,都是通过这个代理回调来实现的
//通过第二个参数可以判断回调的是什么方法
val invocationHandler = InvocationHandler { proxy, method, args ->
Log.i("test", "代理回调: ${args.joinToString()}")
//通过方法的名称判断回调的是什么方法
if (method.name == "onSuccess") {
Log.i("test", "联网成功")
//等待获取ip地址
Log.i("test", "wait ip address available")
} else {
Log.i("test", "联网失败: $args")
}
}
//创建接口代理类,可以理解这个就是接口实现类(实现了接口 ActionListener 的类)
val proxy = Proxy.newProxyInstance(app.classLoader, arrayOf(actionListener), invocationHandler)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
Note: ActionListener
中有两个回调方法,这个时候我们可以通过第二个参数的属性值method.name
来判断回调的是哪个方法
总结一下,创建代理的步骤如下:
1. 获取接口类的类型。如果是公开的(public),就直接类::java.class
就行,比如:ActionListener::class.java
,如果是隐藏的,就通过反射获取。
2. 实现接口InvocationHandler
,ActionListener
中的回调方法的代码逻辑也是在里边实现。
3. 创建代理,关联ActionListener
和InvocationHandler
。此时的代理可以认为是ActionListener
的实现类
一语概之,先通过反射获取隐藏接口类 ActionListener
,然后通过代理实现该接口
2.获取 connect 方法实现联网操作
接下来就简单了,获取隐藏方法connect()
连接Wi-Fi
,代码如下:
//通过反射获取隐藏方法`connect()`
val connectMethod = context.wifiManager.javaClass.getDeclaredMethod("connect", WifiConfiguration::class.java, actionListener)
//第二个参数需要自己创建,具体的可以参考网络
connectMethod.invoke(context.wifiManager, config, proxy)
- 1
- 2
- 3
- 4
通过反射获取其中一个连接Wi-Fi的方法:public void connect(WifiConfiguration config, ActionListener listener)
,然后调用。需要注意的是,该方法的第一个参数是WifiConfiguration
类型的,所以需要各位自己去创建了,创建过程比较麻烦,可以参考网络,我有空的话再写一篇文章,哈哈。
3.总结
好了,具体实现方法已经记录完毕,剩下的估计就是一些参数的创建,和回调方法中的具体代码逻辑了,最后,再归纳一下步骤吧:
1. 获取接口类的类型,方法:反射获取或者类::java.class
2. 继承实现接口InvocationHandler
3. 创建代理Proxy
,作用是关联第一步中的接口类和第二步中的InvocationHandler
4. 反射获取隐藏方法connect()
并调用