前语
跟着Android版别的更新,现在最新的版别是Android 13,而且现已有部分国产手机更新了此版别,关于Android开发者来说,改变其实不那么大,而关于本文章来说就有一些改变。
正文
在Android 12版别中,添加了关于蓝牙操作的动态权限,而在Android 13中,添加了关于WIFI操作的动态权限,日常工作生活中,咱们用到WIFI功用是很多的,例如手机、电脑、电视等设备。而运用WIFI是一回事,WIFI开发又是另一回事,和蓝牙是一个道理,它们之间也有很多类似的地方。
一、创立项目
首要创立项目,这儿我运用的Android Studio版别为Android Studio Electric Eel | 2022.1.1,创立一个名为Android13Wifi的项目。
项目创立好之后,最低的API为24对应Android 7,最高33对应Android 13,Gradle插件版别为:7.5。
二、装备项目
作为WIFI项目咱们首要要装备项目的静态权限,在AndroidManifest.xml
中添加如下代码:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
在 Android 13 中,Google 将 Wi-Fi 扫描与方位相关内容分离, Android 13 为办理设备与周围 Wi-Fi 热点衔接的运用添加 NEARBY_WIFI_DEVICES
运转时权限 (归于 NEARBY_DEVICES
权限组),从而在不需求 ACCESS_FINE_LOCATION
权限的状况下,也能够让运用拜访附近的 Wi-Fi 设备。
这和Android 12中添加的三个蓝牙权限千篇一律,此前扫描蓝牙和WIFI需求定位权限一直是Google的痛点,也一直被诟病。
所以关于仅需求衔接 Wi-Fi 设备,但实际上并不需求了解设备方位的运用来说,以 Android 13 (33)为目标渠道的运用现在能够经过 “neverForLocation
” 特点来完善申请 NEARBY_WIFI_DEVICES
权限。
同时咱们还应该关注Android 13以下的设备运用,因此ACCESS_FINE_LOCATION
权限也要装备,在AndroidManifest.xml
中添加如下代码:
<!--Android 6 ~ 12 运用定位权限-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
tools:ignore="CoarseFineLocation" />
<!--Android 13及以上运用权限-->
<uses-permission
android:name="android.permission.NEARBY_WIFI_DEVICES"
android:usesPermissionFlags="neverForLocation"
tools:targetApi="Tiramisu" />
然后能够敞开ViewBinding,在app下的build.gradle的android{}闭包中添加如下代码:
buildFeatures {
viewBinding true //敞开ViewBinding
}
添加方位如下图所示:
然后咱们修正以下activity_main.xml
,里面的代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/btn_open_wifi"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:text="翻开WIFI"
app:layout_constraintEnd_toStartOf="@+id/btn_scan_wifi"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_scan_wifi"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:text="扫描WIFI"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/btn_open_wifi"
app:layout_constraintTop_toTopOf="@+id/btn_open_wifi" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_wifi"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#EEE"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_open_wifi" />
</androidx.constraintlayout.widget.ConstraintLayout>
就只有两个按钮(用于翻开/封闭WIFI,扫描WIFI),一个列表(显现WIFI设备,衔接WIFI)。现在xml现已有了,咱们先搞定ViewBinding,修正MainActivity
的代码如下所示:
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
}
}
下面便是写功用了,首要是WIFI的翻开和封闭,在此之前需求获取WIFI的开关状况。
三、WIFI开关
在运用Wifi之前,咱们首要要翻开Wifi,而翻开Wifi在不同的版别上办法不同,首要在MainActivity
中声明变量
private WifiManager wifiManager;//Wifi办理者
private ActivityResultLauncher<Intent> openWifi; //翻开Wifi目的
然后经过体系Wifi服务获取wifiManager,在onCreate()
办法中添加如下代码:
wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
经过wifiManager.getWifiState()
能够得到Wifi的状况,所以能够在MainActivity中 checkWifiState()
办法,代码如下所示:
public void checkWifiState() {
String msg;
switch (wifiManager.getWifiState()) {
case WifiManager.WIFI_STATE_DISABLING:
msg = "Wifi正在封闭";
break;
case WifiManager.WIFI_STATE_DISABLED:
msg = "Wifi现已封闭";
binding.btnOpenWifi.setText("翻开Wifi");
break;
case WifiManager.WIFI_STATE_ENABLING:
msg = "Wifi正在敞开";
break;
case WifiManager.WIFI_STATE_ENABLED:
msg = "Wifi现已敞开";
binding.btnOpenWifi.setText("封闭Wifi");
break;
default:
msg = "没有获取到WiFi状况";
break;
}
showMsg(msg);
}
这儿我在Wifi敞开和封闭的时候修正了按钮的文字,由于涉及到Android版别的判别,所以在MainActivity中添加isAndroidTarget()
办法,代码如下所示:
private boolean isAndroidTarget(int targetVersion) {
return Build.VERSION.SDK_INT >= targetVersion;
}
经过这个办法咱们只要传递目标Android版别进去即可,最终再添加一个showMsg()
办法,用于弹出Toast进行提示。
private void showMsg(CharSequence msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
在Android10及以上版别翻开蓝牙开关需求进行一个目的处理,这儿咱们经过Activity Result API来进行处理,在MainActivity中声明变量:
private ActivityResultLauncher<Intent> openWifi; //翻开Wifi目的
然后新增一个registerIntent()
办法,代码如下所示:
private void registerIntent() {
//翻开Wifi开关
openWifi = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
checkWifiState();
});
}
在回来成果时,调用checkWifiState()
办法检查Wifi的状况,registerIntent()
办法需求在Activity创立之前进行调用,修正onCreate()
办法,代码如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
//注册目的
registerIntent();
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
//经过Wifi服务获取wifi办理者目标
wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
//初始化视图
initView();
}
这儿有一个initView()
办法,在MainActivity中创立它,在此办法中将关于按钮的点击事情进行处理,代码如下所示:
private void initView() {
//翻开/封闭Wifi
binding.btnOpenWifi.setOnClickListener(v -> {
//Android10及以上版别
if (isAndroidTarget(Build.VERSION_CODES.Q)) {
openWifi.launch(new Intent(Settings.Panel.ACTION_WIFI));
} else {
wifiManager.setWifiEnabled(!wifiManager.isWifiEnabled());
checkWifiState();
}
});
}
这儿在点击事情中判别了当时的Android版别,10及以上版别选用目的的办法,以下的版别选用wifiManager.setWifiEnabled
API的办法,下面咱们运转一下:
四、WIFI扫描
WIFI开关搞定之后,咱们来做WIFI的扫描,这儿的WIFI扫描是经过播送来接纳成果,成果目标是ScanResult
,这个姓名和蓝牙扫描的ScanResult
相同,不要导错了包,扫描的成果以列表的办法展示,所以咱们能够依据这个成果目标来写一个Wifi适配器,适配器中就显现Wifi的称号,状况,信号强度信息。这儿会用到比较多的图片资源,用来标识信号强度等级的,从我的源码中去获取即可。
依据Wifi的加密与否,分为两种:加密与敞开,每一种有五个图标来分别表示不同的信号强度,这儿我做了两个level-list,是wifi_level.xml和wifi_lock_level.xml,在代码中能够经过信号强度得到不同的level,然后依据加密状况设置level资源图标即可。
① Wifi适配器
要创立适配器,首要要创立item的xml,在layout下创立一个item_wifi_rv.xml,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="1dp"
android:background="@color/white">
<TextView
android:id="@+id/tv_wifi_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="Wifi 称号"
android:textColor="@color/black"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_wifi_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="16dp"
android:text="Wifi 状况"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_wifi_name" />
<ImageView
android:id="@+id/iv_signal"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="@+id/tv_wifi_state"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/tv_wifi_name"
android:src="@drawable/wifi_level" />
</androidx.constraintlayout.widget.ConstraintLayout>
如我前面所说的,便是三个内容
下面写适配器,在com.llw.wifi下创立一个WifiAdapter类,代码如下所示:
public class WifiAdapter extends RecyclerView.Adapter<WifiAdapter.ViewHolder> {
private final List<ScanResult> lists;
public WifiAdapter(List<ScanResult> lists) {
this.lists = lists;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemWifiRvBinding binding = ItemWifiRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
//Wifi称号
holder.binding.tvWifiName.setText(lists.get(position).SSID);
//Wifi功用
String capabilities = lists.get(position).capabilities;
//Wifi状况标识 true:加密,false:敞开
boolean wifiStateFlag = capabilities.contains("WEP") || capabilities.contains("PSK") || capabilities.contains("EAP");
//Wifi状况描绘
String wifiState = wifiStateFlag ? "加密" : "敞开";
holder.binding.tvWifiState.setText(wifiState);
//信号强度
int imgLevel;
int level = lists.get(position).level;
if (level <= 0 && level >= -50) {
imgLevel = 5;
} else if (level < -50 && level >= -70) {
imgLevel = 4;
} else if (level < -70 && level >= -80) {
imgLevel = 3;
} else if (level < -80 && level >= -100) {
imgLevel = 2;
} else {
imgLevel = 1;
}
//依据是否加密设置不同的图片资源
holder.binding.ivSignal.setImageResource(wifiStateFlag ? R.drawable.wifi_lock_level : R.drawable.wifi_level);
//设置图片等级
holder.binding.ivSignal.setImageLevel(imgLevel);
}
@Override
public int getItemCount() {
return lists.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public ItemWifiRvBinding binding;
public ViewHolder(@NonNull ItemWifiRvBinding itemWifiRvBinding) {
super(itemWifiRvBinding.getRoot());
binding = itemWifiRvBinding;
}
}
}
这儿就简略用了一下ViewBinding,中心的内容在onBindViewHolder()
办法中,这儿咱们依据扫描成果进行三个内容的烘托。
② 扫描成果处理
下面咱们回到MainActivity,声明变量:
private final List<ScanResult> wifiList = new ArrayList<>(); //Wifi成果列表
private WifiAdapter wifiAdapter; //Wifi适配器
扫描成功或许适配都能够处理,在MainActivity中新增如下办法代码:
private void scanSuccess() {
//扫描成功:新的扫描成果
wifiList.clear();
wifiList.addAll(wifiManager.getScanResults());
wifiAdapter.notifyDataSetChanged();
}
private void scanFailure() {
// 处理失利:新的扫描没有成功,运用旧的扫描成果
wifiList.clear();
wifiList.addAll(wifiManager.getScanResults());
wifiAdapter.notifyDataSetChanged();
}
你会发现两个办法的内容是相同的,可是意义不同,当然你假如了解的话,就运用一个办法也行。下面在MainActivity中新增如下代码:
/**
* Wifi扫描播送接纳器
*/
private final BroadcastReceiver wifiScanReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context c, Intent intent) {
boolean success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false);
if (success) {
scanSuccess();
} else {
scanFailure();
}
}
};
经过接纳播送来刷新数据,在MainActivity中再添加一个initScan()
,代码如下所示:
private void initScan() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
registerReceiver(wifiScanReceiver, intentFilter);
}
在页面初始化之后就注册播送接纳器,在onCreate()中调用。
下面需求装备一下适配器和RecyclerView,在initView()办法中添加如下代码:
wifiAdapter = new WifiAdapter(wifiList);
binding.rvWifi.setLayoutManager(new LinearLayoutManager(this));
binding.rvWifi.setAdapter(wifiAdapter);
③ 发动扫描
发动扫描之前要做的是权限的处理,在MainActivity中声明变量:
private ActivityResultLauncher<String[]> requestPermission; //恳求权限目的
然后在registerIntent()办法中添加如下代码:
requestPermission = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> {
if (Boolean.TRUE.equals(result.get(Manifest.permission.NEARBY_WIFI_DEVICES))
|| Boolean.TRUE.equals(result.get(Manifest.permission.ACCESS_FINE_LOCATION))) {
//扫描Wifi
showMsg(wifiManager.startScan() ? "扫描Wifi中" : "敞开扫描失利");
} else {
showMsg("扫描设备需求此权限");
}
});
最终便是扫描Wifi按钮的点击事情,同样是在initView()办法中添加,代码如下:
//扫描Wifi 按钮点击事情
binding.btnScanWifi.setOnClickListener(v -> {
//是否翻开Wifi
if (wifiManager.getWifiState() != WifiManager.WIFI_STATE_ENABLED) return;
//Android13及以上版别
if (isAndroidTarget(Build.VERSION_CODES.TIRAMISU)) {
if (!hasPermission(Manifest.permission.NEARBY_WIFI_DEVICES) && !hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
requestPermission.launch(new String[]{Manifest.permission.NEARBY_WIFI_DEVICES, Manifest.permission.ACCESS_FINE_LOCATION});
return;
}
} else {
if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
requestPermission.launch(new String[]{Manifest.permission.ACCESS_FINE_LOCATION});
return;
}
}
//扫描Wifi
showMsg(wifiManager.startScan() ? "扫描Wifi中" : "敞开扫描失利");
});
这儿我在Android 13以上版别同时恳求了定位和Wifi权限,假如不这么做的话,调用wifiManager.startScan()
就会回来false,而在Android 13以下就只恳求定位权限即可,这儿还需求给MainActivity添加一个@SuppressLint("MissingPermission")
注解,如下图所示:
这样在api 33中运用wifi相关的api时就不会提示错误了,不过你得注意一点,便是你在运用之前确保权限现已获取到,否则会报错闪退。wifiManager.startScan()
调用会发动体系扫描,经过体系在扫描结束后,会发出WifiManager.SCAN_RESULTS_AVAILABLE_ACTION
的播送,当咱们的接纳者接纳到这个播送的时候,经过WifiManager的getScanResults()
就能获取到扫描成果的集合了。假如扫描失利就会回来之前的值,成功最近最新的值。
下面咱们运转看一下:
这样看起来仍是不错吧,现在有一个问题,便是这个扫描的wifi没有排序,同时没有wifi称号的咱们应该过滤掉。
④ 排序与过滤
现在咱们现已知道扫描成功和失利的成果区别了,所以就合并以下,同时添加过滤掉空称号的WIFI兵器信号强度进行排序,修正一下播送接纳器中的代码,如下所示:
private final BroadcastReceiver wifiScanReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context c, Intent intent) {
boolean success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false);
Log.e(TAG, "onReceive: " + (success ? "成功" : "失利") );
//处理扫描成果
wifiList.clear();
for (ScanResult scanResult : wifiManager.getScanResults()) {
if (!scanResult.SSID.isEmpty()) {
wifiList.add(scanResult);
}
}
sortByLevel(wifiList);
wifiAdapter.notifyDataSetChanged();
}
};
这儿的scanSuccess()和scanFailure()就能够删掉了,再添加一个sortByLevel()
办法,代码如下:
private void sortByLevel(List<ScanResult> list) {
Collections.sort(list, (lhs, rhs) -> rhs.level - lhs.level);
}
下面咱们再运转一下看看:
五、WIFI衔接
Wifi的衔接相对扫描来说复杂一点,假设现在有三个Wifi,分别是A、B、C。刚开始三个Wifi都没有衔接过,在第一次衔接A的时候,咱们需求输入Wifi暗码,暗码正确才会树立衔接,衔接成功后,咱们衔接B,同样输入暗码,此刻A就会断开,衔接B成功,此刻我再回头去衔接A,由于之前成功衔接过,有保存记录,所以再衔接A的时候直接衔接就能够了,不再需求暗码了。
① Wifi衔接东西类
依据这个状况我写了一个东西类,在com.llw.wifi下新建一个EasyWifi类,代码如下所示:
public class EasyWifi {
private static final String TAG = EasyWifi.class.getSimpleName();
private final ConnectivityManager connectivityManager;//衔接办理者
private final WifiManager wifiManager;//Wifi办理者
private WifiConnectCallback wifiConnectCallback;
@SuppressLint("StaticFieldLeak")
private static volatile EasyWifi mInstance;
private final Context mContext;
public EasyWifi(Context context) {
mContext = context;
wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
connectivityManager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
}
public static EasyWifi initialize(Context context) {
if (mInstance == null) {
synchronized (EasyWifi.class) {
if (mInstance == null) {
mInstance = new EasyWifi(context);
}
}
}
return mInstance;
}
public void setWifiConnectCallback(WifiConnectCallback wifiConnectCallback) {
this.wifiConnectCallback = wifiConnectCallback;
}
/**
* 衔接Wifi
*
* @param scanResult 扫描成果
* @param password 暗码
*/
public void connectWifi(ScanResult scanResult, String password) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
connectByNew(scanResult.SSID, password);
} else {
connectByOld(scanResult, password);
}
}
/**
* Android 10 以下运用
*
* @param scanResult 扫描成果
* @param password 暗码
*/
private void connectByOld(ScanResult scanResult, String password) {
String ssid = scanResult.SSID;
boolean isSuccess;
WifiConfiguration configured = isExist(ssid);
if (configured != null) {
//在装备表中找到了,直接衔接
isSuccess = wifiManager.enableNetwork(configured.networkId, true);
} else {
WifiConfiguration wifiConfig = createWifiConfig(ssid, password, getCipherType(scanResult.capabilities));
int netId = wifiManager.addNetwork(wifiConfig);
isSuccess = wifiManager.enableNetwork(netId, true);
}
Log.d(TAG, "connectWifi: " + (isSuccess ? "成功" : "失利"));
}
/**
* Android 10及以上版别运用此办法衔接Wifi
*
* @param ssid 称号
* @param password 暗码
*/
@SuppressLint("NewApi")
private void connectByNew(String ssid, String password) {
WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier.Builder()
.setSsid(ssid)
.setWpa2Passphrase(password)
.build();
//网络恳求
NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
.addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
.setNetworkSpecifier(wifiNetworkSpecifier)
.build();
//网络回调处理
ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(@NonNull Network network) {
super.onAvailable(network);
if (wifiConnectCallback != null) {
wifiConnectCallback.onSuccess(network);
}
}
@Override
public void onUnavailable() {
super.onUnavailable();
if (wifiConnectCallback != null) {
wifiConnectCallback.onFailure();
}
}
};
//恳求衔接网络
connectivityManager.requestNetwork(request, networkCallback);
}
@SuppressLint("NewApi")
private void connectBySug(String ssid, String password) {
WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
.setSsid(ssid)
.setWpa2Passphrase(password)
.setIsAppInteractionRequired(true)
.build();
List<WifiNetworkSuggestion> suggestionList = new ArrayList<>();
suggestionList.add(suggestion);
int status = wifiManager.addNetworkSuggestions(suggestionList);
if (status != WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS) {
return;
}
IntentFilter intentFilter = new IntentFilter(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION);
BroadcastReceiver wifiScanReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!intent.getAction().equals(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION)) {
return;
}
}
};
mContext.registerReceiver(wifiScanReceiver, intentFilter);
}
/**
* 创立Wifi装备
*
* @param ssid 称号
* @param password 暗码
* @param type 类型
*/
private WifiConfiguration createWifiConfig(String ssid, String password, WifiCapability type) {
WifiConfiguration config = new WifiConfiguration();
config.allowedAuthAlgorithms.clear();
config.allowedGroupCiphers.clear();
config.allowedKeyManagement.clear();
config.allowedPairwiseCiphers.clear();
config.allowedProtocols.clear();
config.SSID = "\"" + ssid + "\"";
WifiConfiguration configured = isExist(ssid);
if (configured != null) {
wifiManager.removeNetwork(configured.networkId);
wifiManager.saveConfiguration();
}
//不需求暗码的场景
if (type == WifiCapability.WIFI_CIPHER_NO_PASS) {
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
//以WEP加密的场景
} else if (type == WifiCapability.WIFI_CIPHER_WEP) {
config.hiddenSSID = true;
config.wepKeys[0] = "\"" + password + "\"";
config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
config.wepTxKeyIndex = 0;
//以WPA加密的场景,自己测试时,发现热点以WPA2树立时,同样能够用这种装备衔接
} else if (type == WifiCapability.WIFI_CIPHER_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.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
config.status = WifiConfiguration.Status.ENABLED;
}
return config;
}
/**
* 网络是否衔接
*/
public static boolean isNetConnected(ConnectivityManager connectivityManager) {
return connectivityManager.getActiveNetwork() != null;
}
/**
* 衔接网络类型是否为Wifi
*/
public static boolean isWifi(ConnectivityManager connectivityManager) {
if (connectivityManager.getActiveNetwork() == null) {
return false;
}
NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.getActiveNetwork());
if (networkCapabilities != null) {
return false;
}
return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
}
/**
* 装备表是否存在对应的Wifi装备
* @param SSID
* @return
*/
@SuppressLint("MissingPermission")
private WifiConfiguration isExist(String SSID) {
List<WifiConfiguration> existingConfigs = wifiManager.getConfiguredNetworks();
for (WifiConfiguration existingConfig : existingConfigs) {
if (existingConfig.SSID.equals("\"" + SSID + "\"")) {
return existingConfig;
}
}
return null;
}
private WifiCapability getCipherType(String capabilities) {
if (capabilities.contains("WEB")) {
return WifiCapability.WIFI_CIPHER_WEP;
} else if (capabilities.contains("PSK")) {
return WifiCapability.WIFI_CIPHER_WPA;
} else if (capabilities.contains("WPS")) {
return WifiCapability.WIFI_CIPHER_NO_PASS;
} else {
return WifiCapability.WIFI_CIPHER_NO_PASS;
}
}
/**
* wifi衔接回调接口
*/
public interface WifiConnectCallback {
void onSuccess(Network network);
void onFailure();
}
public enum WifiCapability {
WIFI_CIPHER_WEP, WIFI_CIPHER_WPA, WIFI_CIPHER_NO_PASS
}
}
这儿关于Wifi的处理,主要是衔接方面的,你当然也能够把扫描wifi放进来,关于wifi的衔接,需求区分版别进行不同的处理,Android 10 及以上和Android 10以下是不同的办法,下面咱们来运用这个东西类。
② 适配器点击处理
下面在WifiAdapter中添加一个接口,代码如下所示:
public interface OnItemClickListener {
void onItemClick(int position);
}
然后供给一个set办法,供运用的地方进行回调处理,代码如下:
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
然后在onCreateViewHolder()办法中添加接口办法的运用,代码如下:
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemWifiRvBinding binding = ItemWifiRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
ViewHolder viewHolder = new ViewHolder(binding);
//添加视图点击事情
binding.getRoot().setOnClickListener(v -> {
if (listener != null) {
listener.onItemClick(viewHolder.getAdapterPosition());
}
});
connectivityManager = (ConnectivityManager) parent.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
return viewHolder;
}
最终回到MainActivity中进行注册监听
然后完成办法:
@Override
public void onItemClick(int position) {
ScanResult scanResult = wifiList.get(position);
//获取Wifi扫描成果
String capabilities = scanResult.capabilities;
//Wifi状况标识 true:加密,false:敞开
boolean wifiStateFlag = capabilities.contains("WEP") || capabilities.contains("PSK") || capabilities.contains("EAP");
if (wifiStateFlag) {
} else {
}
}
在这个办法中咱们依据是否需求暗码进行不同的处理,先看不需求暗码的处理,咱们这儿需求运用东西类,在MainActivity中声明变量:
private EasyWifi easyWifi;
然后在onCreate()办法中进行初始化和设置衔接监听。
easyWifi = EasyWifi.initialize(this);
easyWifi.setWifiConnectCallback(this);
然后完成回调办法。
@Override
public void onSuccess(Network network) {
showMsg("衔接成功");
}
@Override
public void onFailure() {
showMsg("衔接失利");
}
这儿咱们仅仅提示一下衔接成功和失利。现在便是不需求暗码时的处理了,在修正适配器Item点击事情中的if判别,代码如下:
if (wifiStateFlag) {
} else {
easyWifi.connectWifi(scanResult,"");
}
这儿不需求暗码,而需求暗码则麻烦一些,咱们需求写一个弹窗来输入暗码。
③ 暗码弹窗
首要咱们需求创立弹窗所需求的布局文件,在layout下新建一个dialog_connect_wifi.xml文件,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/materialToolbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/white"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:title="Wifi称号"
app:titleCentered="true"
app:titleTextColor="@color/black" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/pwd_layout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:textColorHint="#989898"
app:boxStrokeColor="@color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/materialToolbar">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_pwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="暗码"
android:inputType="textPassword|textCapCharacters"
android:lines="1"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="撤销"
app:cornerRadius="24dp"
app:layout_constraintEnd_toStartOf="@+id/btn_connect"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="@+id/pwd_layout"
app:layout_constraintTop_toTopOf="@+id/btn_connect" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_connect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:text="衔接"
app:cornerRadius="24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/pwd_layout"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/btn_cancel"
app:layout_constraintTop_toBottomOf="@+id/pwd_layout" />
</androidx.constraintlayout.widget.ConstraintLayout>
这个布局很简略,就一个输入框,两个按钮,下面咱们回到MainActivity中,添加如下办法代码:
private void showConnectWifiDialog(ScanResult scanResult) {
BottomSheetDialog dialog = new BottomSheetDialog(this);
DialogConnectWifiBinding binding = DialogConnectWifiBinding.inflate(LayoutInflater.from(this), null, false);
binding.materialToolbar.setTitle(scanResult.SSID);
binding.btnCancel.setOnClickListener(v -> dialog.dismiss());
binding.btnConnect.setOnClickListener(v -> {
String password = binding.etPwd.getText().toString().trim();
if (password.isEmpty()) {
showMsg("请输入暗码");
return;
}
easyWifi.connectWifi(scanResult, password);
dialog.dismiss();
});
dialog.setContentView(binding.getRoot());
dialog.show();
}
这个办法便是显现暗码输入弹窗,当输入暗码之后就衔接wifi,衔接过程中就会触发之前东西类中的回调,下面咱们需求调用这个衔接办法,仍是之前的那个if语句,代码如下所示:
if (wifiStateFlag) {
showConnectWifiDialog(scanResult);
} else {
easyWifi.connectWifi(scanResult,"");
}
现在能够运转了,由于Wifi衔接涉及到隐私信息,所以我就不做动图演示了,衔接成功之后会有提示,然后你翻开体系Wifi页面会看到如下图所示的:
你会看到这儿衔接的wifi下面提示了是经过Android13Wifi这个软件进行的wifi衔接,当咱们的程序被杀死,wifi就会断连,这是由于咱们走的不是体系的wifi衔接的办法。
六、源码
文章中的wifi运用仍是比较浅显的,简略了解一下,而假如你是专门从事WIFI运用开发的话,则需求花心思去研究了,不能流于表面,或许全部靠别人来帮你处理,能帮你的只有自己,山高水长,后会有期~
假如对你有帮助的话,不妨Star一下~
源码地址 :Android13Wifi