前语

  跟着Android版别的更新,现在最新的版别是Android 13,而且现已有部分国产手机更新了此版别,关于Android开发者来说,改变其实不那么大,而关于本文章来说就有一些改变。

正文

  在Android 12版别中,添加了关于蓝牙操作的动态权限,而在Android 13中,添加了关于WIFI操作的动态权限,日常工作生活中,咱们用到WIFI功用是很多的,例如手机、电脑、电视等设备。而运用WIFI是一回事,WIFI开发又是另一回事,和蓝牙是一个道理,它们之间也有很多类似的地方。

一、创立项目

  首要创立项目,这儿我运用的Android Studio版别为Android Studio Electric Eel | 2022.1.1,创立一个名为Android13Wifi的项目。

Android WIFI使用简述

项目创立好之后,最低的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
    }

添加方位如下图所示:

Android WIFI使用简述

然后咱们修正以下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.setWifiEnabledAPI的办法,下面咱们运转一下:

Android WIFI使用简述

四、WIFI扫描

  WIFI开关搞定之后,咱们来做WIFI的扫描,这儿的WIFI扫描是经过播送来接纳成果,成果目标是ScanResult,这个姓名和蓝牙扫描的ScanResult相同,不要导错了包,扫描的成果以列表的办法展示,所以咱们能够依据这个成果目标来写一个Wifi适配器,适配器中就显现Wifi的称号,状况,信号强度信息。这儿会用到比较多的图片资源,用来标识信号强度等级的,从我的源码中去获取即可。

Android 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>

如我前面所说的,便是三个内容

Android WIFI使用简述

下面写适配器,在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()中调用。

Android WIFI使用简述

下面需求装备一下适配器和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")注解,如下图所示:

Android WIFI使用简述

  这样在api 33中运用wifi相关的api时就不会提示错误了,不过你得注意一点,便是你在运用之前确保权限现已获取到,否则会报错闪退。wifiManager.startScan()调用会发动体系扫描,经过体系在扫描结束后,会发出WifiManager.SCAN_RESULTS_AVAILABLE_ACTION的播送,当咱们的接纳者接纳到这个播送的时候,经过WifiManager的getScanResults()就能获取到扫描成果的集合了。假如扫描失利就会回来之前的值,成功最近最新的值。

下面咱们运转看一下:

Android WIFI使用简述

  这样看起来仍是不错吧,现在有一个问题,便是这个扫描的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);
    }

下面咱们再运转一下看看:

Android WIFI使用简述

五、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中进行注册监听

Android WIFI使用简述

Android WIFI使用简述

然后完成办法:

    @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);

Android WIFI使用简述

然后完成回调办法。

    @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页面会看到如下图所示的:

Android WIFI使用简述

  你会看到这儿衔接的wifi下面提示了是经过Android13Wifi这个软件进行的wifi衔接,当咱们的程序被杀死,wifi就会断连,这是由于咱们走的不是体系的wifi衔接的办法。

六、源码

  文章中的wifi运用仍是比较浅显的,简略了解一下,而假如你是专门从事WIFI运用开发的话,则需求花心思去研究了,不能流于表面,或许全部靠别人来帮你处理,能帮你的只有自己,山高水长,后会有期~

假如对你有帮助的话,不妨Star一下~

源码地址 :Android13Wifi