前言
最近在使用flutter写桌面端的一个adb工具,可以使用adb命令无线连接设备,需要电脑和手机在同一局域网内,但是需要手机的ip地址。于是我想到写一个android桌面小组件,点一下就获取WiFi的ipv4地址并显示出来,先去找gpt问了一下,告诉我使用WifiManager,浅看一下逻辑非常简单
但是…用起来才发现被弃用了,本着遵循官方的建议,还是去寻找一下替代的方法吧
实现思路
去官网翻文档发现WifiInfo被移动到ConnectivityManager中的NetworkCapabilities#getTransportInfo(),官方还提示你可以继续用这个被弃用的api,但是这个api不会被支持新的功能了
而获取IP地址的方法被移动到了android.net.LinkProperties这个类中
官方还贴了一个例子,大体思路就是先获取ConnectivityManager的实例,然后创建一个networkRequest请求,注册networkcallback的监听,就可以在wifi变化时监听到网络信息了
使用callback监听网络信息
MainActivity.kt
import android.content.Context
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.LinkProperties
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.util.Log
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private val TAG = "wifiState"
var handler = object : Handler(Looper.getMainLooper()){
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when (msg.what) {
0 -> {
val linkProperties = msg.obj as LinkProperties
findViewById<TextView>(R.id.ip).text = "${linkProperties.linkAddresses[1]}"
}
1->{
findViewById<TextView>(R.id.ip).text = "无连接"
}
}
}
}
private val networkCallback = object : NetworkCallback(){
override fun onAvailable(network: Network) {
super.onAvailable(network)
Log.w(TAG, "onAvailable: " )
}
override fun onLinkPropertiesChanged(
network: Network,
linkProperties: LinkProperties
) {
super.onLinkPropertiesChanged(network, linkProperties)
Log.w(TAG, "onLinkPropertiesChanged: ${linkProperties.linkAddresses}" )
handler.sendMessage(Message.obtain(handler,0,linkProperties))
}
override fun onUnavailable() {
super.onUnavailable()
Log.w(TAG, "onUnavailable: " )
}
override fun onLost(network: Network) {
super.onLost(network)
handler.sendMessage(Message.obtain(handler,1))
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onStart() {
super.onStart()
registerWifiState()
}
override fun onStop() {
super.onStop()
unregisterWifiState()
}
private fun registerWifiState(){
val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val request = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build()
connectivityManager.registerNetworkCallback(request, networkCallback)
}
private fun unregisterWifiState(){
val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager.unregisterNetworkCallback(networkCallback)
}
}
在onStart中注册了一个networkcallback回调,就可以在networkcallback中的onLinkPropertiesChanged中拿到IP地址并且传给id为ip的TextView,如果断开wifi的时候就会设置为无连接。
实现显示IP地址的小组件
目前为止我们已经成功在App内获取到了ip地址,是使用callback的办法,那能不能通过点击获取实时的网络ip呢,当然可以,但我们要使用小组件的方式来实现,就需要先写一个简单的小组件出来
先写一个小组件广播
WifiIpWidget.kt
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.wifi.WifiManager
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.widget.RemoteViews
import demo.tdsss.wifistate.R
/**
* @author TDSSS
* @datetime 2023/11/22 17:47
*/
class WifiIpWidget : AppWidgetProvider() {
private val TAG = "wifi state widget"
override fun onEnabled(context: Context?) {
super.onEnabled(context)
updateInfo(context)
Log.w(TAG, "onEnabled: " )
}
override fun onUpdate(
context: Context?,
appWidgetManager: AppWidgetManager?,
appWidgetIds: IntArray?
) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
Log.w(TAG, "onUpdate: " )
updateInfo(context)
}
override fun onAppWidgetOptionsChanged(
context: Context?,
appWidgetManager: AppWidgetManager?,
appWidgetId: Int,
newOptions: Bundle?
) {
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions)
Log.w(TAG, "onAppWidgetOptionsChanged: " )
val intent = Intent(context,this.javaClass)
intent.action = "touch"
val remoteViews = RemoteViews(context?.packageName, R.layout.widget_layout).also {
it.setOnClickPendingIntent(
R.id.wifi_widget,
PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
))
}
appWidgetManager!!.partiallyUpdateAppWidget(appWidgetId, remoteViews)
}
override fun onDisabled(context: Context?) {
super.onDisabled(context)
Log.w(TAG, "onDisabled: " )
}
override fun onDeleted(context: Context?, appWidgetIds: IntArray?) {
super.onDeleted(context, appWidgetIds)
Log.w(TAG, "onDeleted: ", )
}
override fun onReceive(context: Context?, intent: Intent?) {
super.onReceive(context, intent)
// Log.w(TAG, "onReceive: " )
// Log.w(TAG, "action: ${intent?.action}" )
// updateInfo(context)
if(intent?.action == "touch"){
Log.w(TAG, "onReceive: action == touch" )
updateInfo(context)
}
if (intent?.action != null && intent.action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION) && Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
Log.w(TAG, "onReceive: NETWORK_STATE_CHANGED_ACTION" )
updateInfo(context)
}
}
private fun updateInfo(context : Context?){
val connectivityManager = context?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkInfo = connectivityManager.activeNetwork
val capabilities = connectivityManager.getNetworkCapabilities(networkInfo)
if(capabilities == null || !(capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))){
val appWidgetManager = AppWidgetManager.getInstance(context)
val ids = appWidgetManager.getAppWidgetIds(ComponentName(context,this.javaClass))
val remoteViews = RemoteViews(context.packageName, R.layout.widget_layout).also {
it.setTextViewText(R.id.wifiAddress, "未连接wifi")
}
appWidgetManager.partiallyUpdateAppWidget(ids, remoteViews)
return
}
val linkProperties = connectivityManager.getLinkProperties(networkInfo)
val address = linkProperties?.linkAddresses
Log.w(TAG, "onReceive address: $address" )
val appWidgetManager = AppWidgetManager.getInstance(context)
val ids = appWidgetManager.getAppWidgetIds(ComponentName(context,this.javaClass))
val intent = Intent(context,this.javaClass)
intent.action = "touch"
val remoteViews = RemoteViews(context.packageName, R.layout.widget_layout).also {
it.setTextViewText(R.id.wifiAddress, "${address?.get(1)}")
it.setOnClickPendingIntent(
R.id.wifi_widget,
PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
))
}
appWidgetManager.partiallyUpdateAppWidget(ids, remoteViews)
}
}
核心逻辑其实就在updateInfo()中,先通过connectivityManager获取当前的networkInfo,然后根据networkInfo获取NetworkCapabilities,判断网络连接类型是否为WiFi,如果不仅仅想获取WiFi的就不用判断类型
然后通过connectivityManager和networkInfo获取LinkProperties,再从LinkProperties的实例中获取linkAddresses就可以啦,这里的linkAddresses其实是一个List类型,里面可能会有多端IP地址,包含IPv6和IPv4的,看你需要什么就取什么。
小组件布局很简单,就两行文字
widget_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:background="@color/white"
android:id="@+id/wifi_widget"
>
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击获取Wi-Fi地址:" />
<TextView
android:id="@+id/wifiAddress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="未连接" />
</LinearLayout>
别忘了在AndroidManifest.xml中注册小组件广播以及权限添加
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<receiver android:name=".widget.WifiIpWidget"
android:exported="false"
>
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.net.wifi.STATE_CHANGE"/>
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/wifi_ip_widget_info" />
</receiver>
注:这里我们虽然静态注册了"android.net.wifi.STATE_CHANGE"的wifi变化广播监听,但其实在Android O(8.0)即API 26之后,静态广播就受到限制,如果想让小组件在onReceiver中实时监听网络信息需要修改target Sdk 为26以下(不包含26),详细信息可以查看文档↓
广播概览 | Android 开发者 | Android Developers
最后我们安装完App,把小组件添加到桌面上,点击就会实时获取当前WiFi的局域网IP地址啦
源码地址:https://github.com/TDSSSzero/AndroidWifiStateWidget