Android WIFI使用简述(上)

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: Android WIFI使用简述(上)

前言


  随着Android版本的更新,目前最新的版本是Android 13,并且已经有部分国产手机更新了此版本,对于Android开发者来说,变化其实不那么大,而对于本文章来说就有一些变化。


正文


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


一、创建项目


  首先创建项目,这里我使用的Android Studio版本为Android Studio Electric Eel | 2022.1.1,创建一个名为Android13Wifi的项目。

2cf19005bd964316a19a690941587e1b.png

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


添加位置如下图所示:

2c836f4f94be4bde883aed8ff3c16df1.png

然后我们修改以下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的方式,下面我们运行一下:

4969a563024a4141ba593ff8c4c89ab1.gif


四、WIFI扫描


  WIFI开关搞定之后,我们来做WIFI的扫描,这里的WIFI扫描是通过广播来接收结果,结果对象是ScanResult,这个名字和蓝牙扫描的ScanResult一样,不要导错了包,扫描的结果以列表的形式展现,所以我们可以根据这个结果对象来写一个Wifi适配器,适配器中就显示Wifi的名称,状态,信号强度信息。这里会用到比较多的图片资源,用来标识信号强度等级的,从我的源码中去获取即可。

d2e809265677400abaa092c496a80c94.png

 根据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>


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

139a0722e7ca465bb02d8affbfbcba55.png

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

2c65989ecbda462787d0d8d095d0147e.png

下面需要配置一下适配器和RecyclerView,在initView()方法中增加如下代码:

  wifiAdapter = new WifiAdapter(wifiList);
  binding.rvWifi.setLayoutManager(new LinearLayoutManager(this));
    binding.rvWifi.setAdapter(wifiAdapter);


Android WIFI使用简述(下)https://developer.aliyun.com/article/1407537

相关文章
|
6月前
|
Android开发
Android 状态栏WiFi图标的显示逻辑
Android 状态栏WiFi图标的显示逻辑
160 0
|
6月前
|
Android开发
Android获取当前连接的wifi名称
Android获取当前连接的wifi名称
327 6
|
6月前
|
Android开发
android连接指定wifi
android连接指定wifi
105 0
|
6月前
|
Java Android开发
Android 9在连接以太网情况下 还能连接WiFi
Android 9在连接以太网情况下 还能连接WiFi
63 0
|
6月前
|
Android开发
Android12 ethernet和wifi共存
Android12 ethernet和wifi共存
368 0
|
6月前
|
Java Shell Android开发
Android11 有线网和wifi优先级设置
Android11 有线网和wifi优先级设置
502 0
|
6月前
|
Java Android开发 开发者
rk3399 android以太网和wifi共存
rk3399 android以太网和wifi共存
198 0
|
6月前
|
缓存 Java Android开发
Android 9.0 WiFi 扫描结果上报和获取流程
Android 9.0 WiFi 扫描结果上报和获取流程
286 0
|
6月前
|
Android开发
Android 获取Wifi开关状态、控制Wifi开关
Android 获取Wifi开关状态、控制Wifi开关
194 0
|
6月前
|
Android开发
android 获取wifi的协议标准
android 获取wifi的协议标准
97 0