AndroidTV Wifi开发(一)
Wifi扫描
- 概述
- 限制
- 权限
- 节流
- 实现
- 总结
概述
WIFI就是一种无线联网技术,常见的是使用无线路由器。那么在这个无线路由器的信号覆盖的范围内都可以采用WIFI连接的方式进行联网。如果无线路由器连接了一个ADSL线路或其他的联网线路,则又被称为“热点”。
在实际使用中,我们进入wifi列表后可以看到下面这样
那wifi列表里面的数据是怎么来的?这里就是本文章主要讲的内容wifi如何进行扫描以及如何去更新数据。
限制
权限
在我们开始使用前需要在AndroidManifest.xml中加入wifi相关的权限:
//用于扫描结束后读取wifi信息<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />//用于扫描WiFi列表<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
Android 8.0 & 8.1
成功调用WifiManager.getScanResults() 方法必须具备以下权限之一,否则会抛异常SecurityException。或者,在搭载 Android 8.0(API 级别 26)及更高版本的设备上,您可以使用 CompanionDeviceManager 代表应用对附近的配套设备执行扫描,而不需要位置权限。
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /><uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
实际从Android6.0以上调用getScanResults()获取WiFi列表,就必须要打开GPS才能获取,否则为空。是因为6.0之前,APP在不开启GPS定位的情况下,依然可以调用getScanResults获取周边Wifi的相关信息,比如SSID、BSSID、level等,去请求谷歌位置服务器(“http://www.google.com/loc/json”),获取用户所在的位置信息;这显得GPS定位开关毫无意义,显然不合理,是位置权限设计的Bug;Google出于设计考虑,6.0之后版本,获取WiFi列表必须要设备开启了GPS定位,并且APP具备位置权限,才能获取WiFi列表。
Android 9
成功调用 WifiManager.startScan() 需要满足以下所有条件:
- 应用拥有 ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION 权限。
- 应用拥有 CHANGE_WIFI_STATE 权限。
- 设备已启用位置信息服务(位于设置 > 位置信息下)。
Android 10(API 级别 29)及更高版本:
成功调用 WifiManager.startScan() 需要满足以下所有条件:
- 如果您的应用以 Android 10(API 级别 29)SDK 或更高版本为目标平台,应用需要拥有
ACCESS_FINE_LOCATION 权限。 - 如果您的应用以低于 Android 10(API 级别 29)的 SDK 为目标平台,应用需要拥有
ACCESS_COARSE_LOCATION 或 ACCESS_FINE_LOCATION 权限。 - 应用拥有 CHANGE_WIFI_STATE 权限。
- 设备已启用位置信息服务(位于设置 > 位置信息下)。
若要成功调用 WifiManager.getScanResults(),请确保满足以下所有条件:
- 如果您的应用以 Android 10(API 级别 29)SDK 或更高版本为目标平台,应用需要拥有
ACCESS_FINE_LOCATION 权限。 - 如果您的应用以低于 Android 10(API 级别 29)的 SDK 为目标平台,应用需要拥有
ACCESS_COARSE_LOCATION 或 ACCESS_FINE_LOCATION 权限。 - 应用拥有 ACCESS_WIFI_STATE 权限。
- 设备已启用位置信息服务(位于设置 > 位置信息下)。
如果调用应用无法满足上述所有要求,调用将失败,并显示 SecurityException。
节流
使用 WifiManager.startScan() 扫描的频率适用以下限制。
Android 8.0 和 Android 8.1:
每个后台应用可以在 30 分钟内扫描一次。
Android 9:
每个前台应用可以在 2 分钟内扫描四次。这样便可在短时间内进行多次扫描。
所有后台应用总共可以在 30 分钟内扫描一次。
Android 10 及更高版本:
适用 Android 9 的节流限制。新增一个开发者选项,用户可以关闭节流功能以便进行本地测试(位于开发者选项 > 网络 > WLAN 扫描调节下)。
实现
扫描流程分为三步:
- 为 SCAN_RESULTS_AVAILABLE_ACTION注册一个广播监听器,系统会在完成扫描请求时调用此监听器,提供其成功/失败状态。对于搭载 Android 10(API 级别29)及更高版本的设备,系统将针对平台或其他应用在设备上执行的所有完整 WLAN扫描发送此广播。应用可以使用广播被动监听设备上所有扫描的完成情况,无需发出自己的扫描。
- 使用 WifiManager.startScan() 请求扫描。请务必检查方法的返回状态,因为调用可能因以下任一原因失败:
由于短时间扫描过多,扫描请求可能遭到节流。
设备处于空闲状态,扫描已停用。
WLAN 硬件报告扫描失败。 - 使用 WifiManager.getScanResults() 获取扫描结果。系统返回的扫描结果为最近更新的结果,但如果当前扫描尚未完成或成功,可能会返回以前扫描的结果。也就是说,如果在收到成功的 SCAN_RESULTS_AVAILABLE_ACTION 广播前调用此方法,您可能会获得较旧的扫描结果。
下面看代码部分的实现,主要代码如下
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);......//注册监听initListener();//在活动创建时开始扫描WiFiwifiManager.startScan();}private void initListener() {//注册相关广播的监听IntentFilter scanFilter = new IntentFilter();scanFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);if (wifiListReceiver == null) {wifiListReceiver = new WifiListReceiver();}registerReceiver(wifiListReceiver, scanFilter);......}private class WifiListReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {String action = intent.getAction();//接收到扫描完成的广播if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {//开始过滤数据,筛选有效数据。getRealWifi();Message message = Message.obtain();message.what = MSG_SCAN_WIFI;//因为Wifi的信号是实时变化的,这里我们在拿到上一个数据后再过10s开始下一次的扫描wifiHandler.sendMessageDelayed(message, 10000);}}}public void getRealWifi() {//获取wifi扫描结果wifiList = wifiManager.getScanResults();if (wifiList.size() == 0) {return;}//新建一个list存储有效wifi,有些WiFi是无效的需要过滤if (realWifiList == null) {realWifiList = new ArrayList();} else {realWifiList.clear();}for (ScanResult result : wifiList) {if (result.SSID == null || result.SSID.length() == 0 || result.capabilities.contains("[IBSS]")) {continue;}boolean found = false;for (ScanResult item : realWifiList) {if (item.SSID.equals(result.SSID) && item.capabilities.equals(result.capabilities)) {found = true;break;}}if (!found) {realWifiList.add(result);}}if (realWifiList.size() != 0) {currentWifiInfo = wifiManager.getConnectionInfo();if (wifiAdapter == null) {//第一次扫描列表wifiAdapter = new MyAdapter(WifiActivity.this, realWifiList);wifiListView.setAdapter(wifiAdapter);} else {//更新列表数据wifiAdapter.list = realWifiList;wifiAdapter.notifyDataSetChanged();}}}//负责相关消息处理private static class WifiHandler extends Handler {final WeakReference<WifiActivity> mActivity;private WifiHandler(WifiActivity activity) {mActivity = new WeakReference<WifiActivity>(activity);}@Overridepublic void handleMessage(Message msg) {WifiActivity currentActivity = mActivity.get();if (currentActivity == null) {return;}//开始下一次的扫描switch (msg.what) {case MSG_SCAN_WIFI:currentActivity.wifiManager.startScan();break;}}}
通过以上几个方法我们就可以拿到扫描到的有效WiFi数据。然后我们就可以通过Listview或Recyclerview去加载数据。这里主要看下加载布局的部分:
public class MyAdapter extends BaseAdapter {LayoutInflater inflater;List<ScanResult> list;......@Overridepublic View getView(int position, View convertView, ViewGroup parent) {View view = null;VIewHolder viewHolder;ScanResult scanResult = getItem(position);if (convertView == null) {view = LayoutInflater.from(getBaseContext()).inflate(R.layout.item_wifi_list, parent, false);viewHolder = new VIewHolder();viewHolder.textView = (TextView) view.findViewById(R.id.item_title);viewHolder.keyType = (TextView) view.findViewById(R.id.item_key_type);viewHolder.wifiState = (TextView) view.findViewById(R.id.item_state);viewHolder.tag = (TextView) view.findViewById(R.id.tag);viewHolder.imageView = (ImageView) view.findViewById(R.id.item_level);// 将 viewHolder 存储在 View 中view.setTag(viewHolder);} else {view = convertView;// 重新获取 viewHolderviewHolder = (VIewHolder) view.getTag();}viewHolder.textView.setText(scanResult.SSID);viewHolder.keyType.setText(WifiUtil.getEncrypt(scanResult));//判断信号强度,显示对应的指示图标,数值越小代表信号越高。if (Math.abs(scanResult.level) > 100) {viewHolder.imageView.setImageDrawable(getResources().getDrawable(R.drawable.stat_wifi_signal_1));} else if (Math.abs(scanResult.level) > 80) {viewHolder.imageView.setImageDrawable(getResources().getDrawable(R.drawable.stat_wifi_signal_2));} else if (Math.abs(scanResult.level) > 70) {viewHolder.imageView.setImageDrawable(getResources().getDrawable(R.drawable.stat_wifi_signal_3));} else if (Math.abs(scanResult.level) > 60) {viewHolder.imageView.setImageDrawable(getResources().getDrawable(R.drawable.stat_wifi_signal_4));} else {viewHolder.imageView.setImageDrawable(getResources().getDrawable(R.drawable.stat_wifi_signal_5));}return view;}class VIewHolder {TextView textView;TextView keyType;TextView wifiState;TextView tag;ImageView imageView;}}
item_wifi_list.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="@dimen/x300"android:layout_height="wrap_content"xmlns:app="http://schemas.android.com/apk/res-auto"><TextViewandroid:id="@+id/item_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="test"android:textSize="@dimen/x16"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toTopOf="parent"/><TextViewandroid:id="@+id/item_key_type"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="NONE"android:textSize="@dimen/x16"android:layout_marginTop="@dimen/x3"app:layout_constraintLeft_toLeftOf="@+id/item_title"app:layout_constraintTop_toBottomOf="@+id/item_title"/><TextViewandroid:id="@+id/tag"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="@dimen/x45"android:visibility="gone"android:text="|"android:textSize="@dimen/x18"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintBaseline_toBaselineOf="@+id/item_key_type"/><androidx.constraintlayout.widget.Barrierandroid:id="@+id/item_barrier"android:layout_width="wrap_content"android:layout_height="wrap_content"app:barrierDirection="right"app:constraint_referenced_ids="item_title,item_key_type"/><TextViewandroid:id="@+id/item_state"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text=""android:textSize="@dimen/x16"android:layout_marginStart="@dimen/x60"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintBaseline_toBaselineOf="@+id/item_key_type"/><ImageViewandroid:id="@+id/item_level"android:layout_width="@dimen/x32"android:layout_height="@dimen/x32"android:src="@drawable/ic_back"android:layout_marginRight="@dimen/x20"app:layout_constraintTop_toTopOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintBottom_toBottomOf="parent"/></androidx.constraintlayout.widget.ConstraintLayout>
总结
Wifi扫描的代码流程上不难,主要是权限方面的问题。保证权限申请正确的前提下以上流程就可以扫描到正确的WiFi.
PS:本文是在Android TV上开发测试的,安卓模拟器可能无法扫描。建议真机模拟实验,另各家厂商的手机可能有修改本文的方法不一定生效。
如果想要了解更多,请参考官方文档。
AndroidTV Wifi开发(一)相关推荐
- Android WiFi开发教程(三)——WiFi热点数据传输
在上一篇文章中介绍了WiFi的搜索和连接,如果你还没阅读过,建议先阅读上一篇Android WiFi开发教程(二)--WiFi的搜索和连接.本篇接着简单介绍手机上如何通过WiFi热点进行数据传输. 跟 ...
- Android中WIFI开发总结(一)
WIFI就是一种无线联网技术,常见的是使用无线路由器.那么在这个无线路由器的信号覆盖的范围内都可以采用WIFI连接的方式进行联网.如果无线路由器连接了一个ADSL线路或其他的联网线路,则又被称为&qu ...
- android Wifi开发相关内容
今天,简单讲讲android里如何使用WifiManager. 之前,我看代码时,看到了wifi相关的代码,发现自己对于这个内容的使用还很不熟悉,所以在网上查找资料,最终解决了问题.这里记录一下. 移 ...
- linux wifi开发书籍,Android WIFI开发介绍.pdf
Android WIFI开发介绍: WifiStateTracker 会创建WifiMonitor 接收来自底层的事件,WifiService 和WifiMonitor 是整个模块的核心.WifiSe ...
- Android WiFi开发
概述 介绍Android WiFi的扫描.连接.信息.以及WiFi热点等等的实现,并用代码实现. 详细 代码下载:http://www.demodashi.com/demo/10660.html 一. ...
- android获取wifi开关,Android WiFi开发(一)--WiFi开关与状态监听
之前开发了一个WiFi,热点相关的应用.因为对这方面也不熟悉,刚开始找资料看书,但看明白实现时,发现随着android版本更新,相关api有较大改动,之前的代码不能用.经过一番探索,最后实现出来了,现 ...
- WiFi开发|WiFi无线技术介绍
WiFi无线技术介绍 1. WiFi技术概述 WLAN是无线局域网络的简称,全称为Wireless Local Area Networks,是一种利用无线技术进行数据传输的系统,该技术的出现能够弥补有 ...
- 基于Wio RP2040迷你无线WiFi开发板的硬件接口技术及MicroPython控制编程基础
Wio RP2040迷你无线WiFi开发板(Wio RP2040 mini Dev Board)是Seeed Studio公司于2021年5月推出的一款迷你无线WiFi开发板,它集成了Wio RP20 ...
- 树莓派Pico W无线WiFi开发板使用方法及MicroPython网络编程实践
树莓派Pico W开发板是树莓派基金会于2022年6月底推出的一款无线WiFi开发板,它支持C/C++和MicroPython编程.本文介绍树莓派Pico W无线WiFi开发板的使用方法及MicroP ...
最新文章
- 计算机也可以看“视频”,理解“视频”
- 学习dos批处理,再也不怕老板安排一些重复性高的工作了,几行代码就搞定!
- 【OS】期末总结复习
- LeetCode 之 JavaScript 解答第141题 —— 环形链表 I(Linked List Cycle I)
- SAP UI5 Nav container - how the inner control is added
- pythrch 启动 visdom可视化
- 2021年中国乙烯基一次性手套市场趋势报告、技术动态创新及2027年市场预测
- PAT 1084. 外观数列 (20) - 乙级
- Git基础教程(三)
- python socket编程(tcp/udp)
- 如何应对当下的 996?
- kindle刷机ttl_亚马逊卡大树kindle voyage修复刷机救砖KV死机变砖忘记密码维修
- SIAMfc++:采用目标估计准则,实现稳健和准确的视觉跟踪
- c语言单片机矩阵键盘,51单片机矩阵键盘的C语言程序与分析
- Android__逆向__xpose使用
- Oracle 误删恢复
- 回顾总结-----第九届中国云计算大会,量子计算机为最大亮点
- 与Anthony Baldino一起塑造声音
- 凡科小程序服务器域名,小程序支付申请及配置教程
- Lua语言实现游戏动作