AndroidTV Wifi开发(一)


概述

 WIFI就是一种无线联网技术,常见的是使用无线路由器。那么在这个无线路由器的信号覆盖的范围内都可以采用WIFI连接的方式进行联网。如果无线路由器连接了一个ADSL线路或其他的联网线路,则又被称为“热点”。
在实际使用中,我们进入wifi列表后可以看到下面这样
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_LOCATIONACCESS_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_LOCATIONACCESS_FINE_LOCATION 权限。
  • 应用拥有 CHANGE_WIFI_STATE 权限。
  • 设备已启用位置信息服务(位于设置 > 位置信息下)。

 若要成功调用 WifiManager.getScanResults(),请确保满足以下所有条件:

  • 如果您的应用以 Android 10(API 级别 29)SDK 或更高版本为目标平台,应用需要拥有
    ACCESS_FINE_LOCATION 权限。
  • 如果您的应用以低于 Android 10(API 级别 29)的 SDK 为目标平台,应用需要拥有
    ACCESS_COARSE_LOCATIONACCESS_FINE_LOCATION 权限。
  • 应用拥有 ACCESS_WIFI_STATE 权限。
  • 设备已启用位置信息服务(位于设置 > 位置信息下)。

 如果调用应用无法满足上述所有要求,调用将失败,并显示 SecurityException

节流

 使用 WifiManager.startScan() 扫描的频率适用以下限制。

Android 8.0 和 Android 8.1:

 每个后台应用可以在 30 分钟内扫描一次。

Android 9:

 每个前台应用可以在 2 分钟内扫描四次。这样便可在短时间内进行多次扫描。

 所有后台应用总共可以在 30 分钟内扫描一次。

Android 10 及更高版本:

 适用 Android 9 的节流限制。新增一个开发者选项,用户可以关闭节流功能以便进行本地测试(位于开发者选项 > 网络 > WLAN 扫描调节下)。

实现

扫描流程分为三步:

  1. SCAN_RESULTS_AVAILABLE_ACTION注册一个广播监听器,系统会在完成扫描请求时调用此监听器,提供其成功/失败状态。对于搭载 Android 10(API 级别29)及更高版本的设备,系统将针对平台或其他应用在设备上执行的所有完整 WLAN扫描发送此广播。应用可以使用广播被动监听设备上所有扫描的完成情况,无需发出自己的扫描。
  2. 使用 WifiManager.startScan() 请求扫描。请务必检查方法的返回状态,因为调用可能因以下任一原因失败:
     由于短时间扫描过多,扫描请求可能遭到节流。
     设备处于空闲状态,扫描已停用。
     WLAN 硬件报告扫描失败。
  3. 使用 WifiManager.getScanResults() 获取扫描结果。系统返回的扫描结果为最近更新的结果,但如果当前扫描尚未完成或成功,可能会返回以前扫描的结果。也就是说,如果在收到成功的 SCAN_RESULTS_AVAILABLE_ACTION 广播前调用此方法,您可能会获得较旧的扫描结果。

下面看代码部分的实现,主要代码如下

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ......
        //注册监听
		initListener();
		//在活动创建时开始扫描WiFi
        wifiManager.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 {
        @Override
        public 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);
        }

        @Override
        public 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;
		......

        @Override
        public 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;
                // 重新获取 viewHolder
                viewHolder = (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">
    <TextView
        android: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"/>
    <TextView
        android: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"/>
    <TextView
        android: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.Barrier
        android: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"/>

    <TextView
        android: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"/>

    <ImageView
        android: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上开发测试的,安卓模拟器可能无法扫描。建议真机模拟实验,另各家厂商的手机可能有修改本文的方法不一定生效。

如果想要了解更多,请参考官方文档

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值