自定义View,指示wifi信号强度(菜鸟历险记)

我的Android是自学的,自学教材是李刚老师的《疯狂Android讲义》。因为之前是学C语言的,从事的嵌入式行业的开发,接触的是arm世界,用纯c语言写成的bin文件去驱动板子上的显示器及其它。后来有个项目要用Android开发,没有办法就开始自学,当时的打算是1个月的时间学会java,1个月的时间学会Android.因为觉得自己c语言还可以,特别是解决了指针这一个难题后,心里有信心学其它语言会比较容易。那好,java一个月是看了基础的内容,跟c差不多,但面向对象又有一点不一样,勉强觉得能接受。就看Android的书籍了,跟着书上的代码一遍一遍的敲,有的确实不能理解,就反复看,上厕所也捧着一本书看。由于性格问题,遇到事情我喜欢刨根问底,所以遇到很多我不明白的问题,我就拿着不放,后来我发现自己其实是走进了误区,当菜鸟的时候就要好好学习,好好学习最基本的知识,等基础扎实之后才能开始思考。不然只能是自己给自己较劲。 一个月后,我准备找新的工作,在网上记一些面试的题目就匆忙去面试了。只选了两家,第一家没有面试上,问了listview怎么优化?我一时语塞,没有答上来,我没有真实的Android开发经验,真的不知道。第二天后,面试第二家,做的题目都是基础的题目,我应该拿了满分,主管很年轻,我们相谈甚欢,然后我又进行了几轮面试,最终拿到offer。我也不想再去其它面试,于是就进了这一家。 新的项目是一个电视盒子上的Launcher,既然是Launcher就应该有Launcher的样子,我负责的模块有这么一个需求,在顶部状态栏显示时间、日期、以太网信号、usb状态、wifi信号强度。那么我就在下面讲我如何实现wifi信号强度的自定义view的。

有经验的开发者可以忽略我这篇文章。我只想给如当年自学如我的菜鸟一个提示,由其是从一个陌生领域进入Android开发的没有任何人能指点的菜鸟。

好的回归主题。

如何自定义view,并指示wifi信号强度?

我是菜鸟,这是我Android生涯的第一个难题。最大的原因是书上没有讲。是的我是看书自学的Android,在那个年代,没人讲过自定义view.怎么办呢?我在脑海中寻找答案,很可惜没有答案。我上网搜索,也没有相关的文章。关键的那个时候,我没有源码。 有源码,我也不知道上哪里查。我是菜鸟。我不知道。grepcode,也不知道androidxref. 因为我是菜鸟。 但菜鸟会有菜鸟的办法。 我记得手机上状态栏电池的图标是可以变动的,电量不同的时候,这个图标会显示不同的电量,其实我应该也知道状态栏上的wifi图标也是如此。 作为菜鸟的我开始了思考

既然系统能够实现这样的功能,那么我也可以实现。如果我还不能实现,是因为我不知他们用了什么办法。

作为菜鸟,别指望我去看源码。我压根就不知道在哪里,去哪里看。我的Android生涯还没有半个月,别跟我讲这么不切实际的话。u can u bb,u can u up.我不can我不bibi,我不can我也必须的up.

菜鸟程序员也是程序员,况且我此前写c语言的呢。我会用eclipse,用ctrl+鼠标左键就能点击进去查源码。我点击了TextClock这个系统控件,看看标准的Android控件长什么样子,因为时间也是变的,我看不到wifi的view,我看下TextClock还不行?

@RemoteView
public class TextClock extends TextView {
    ...

private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
                final String timeZone = intent.getStringExtra("time-zone");
                createTime(timeZone);
            }
            onTimeChanged();
        }
    };

public TextClock(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

      ...
    }
    ...

 @Override
 protected void onAttachedToWindow() {
       super.onAttachedToWindow();

        if (!mAttached) {
            mAttached = true;

            registerReceiver();

       }
    }

@Override
protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
...
        if (mAttached) {
           unregisterReceiver();
   ...       
           getHandler().removeCallbacks(mTicker);
   ...
        }
   ...
    }
}

在这之前,有人说了一个解决方案说,Activity接收wifi信号广播,然后在Activity中setImage给这些图标动态改变状态。这方案确实可行,如果我找不到答案,也许最后就是这样去解决。但我义正严辞拒绝了,这样怎么行呢?那么Activity会多累。我的想法是我要写一个View,像TextView这样一样,用到其它地方,甚至其他项目都可以直接用,我写的view就取名WifiStateView。

我有我的人格,我的自定义view有它的view格。

话说到我看了TextClock之后,给了我一些震撼。因为—- 没有人告诉我,没有书上说。一个View,它能有handler,它可以注册BroadcastReceiver.书上没有说,书上的demo,不都是讲handler在Activity用来 异步,BroadcastReceiver是四大组件么???

我之后便明白了,也许是找到了我的答案。

  1. 我可以在WifiStateView注册监听wifi相关的广播
  2. 我可以创建一个handler,或者用它的handler来更新backgroud。 但还有一个问题是我要考虑的,那就是省电的问题,虽然当时盒子是插电源的不是用电池,但我的志向是我的WifiStateView要能够移植,如果是在手机上我就不得不考虑电量的问题。我总不会让它一直去接收广播吧,况且没有打开wifi的时候,它要消失。但是很快我也找到了答案。 onAttachedToWindow() onDetachedFromWindow()

我在onAttachedToWindow方法中注册广播监听器,在onDetachedFromWindow的时候移除。这样省电的问题就解决了。 那好,我把关键字再重列一下: 广播监听器 handler onAttachedToWindow() onDetachedFromWindow()

剩下的就是与wifi有关的api了,这个借助百度很容易查到。

WifiManager.WIFI_STATE_CHANGED_ACTION消息,并实时处理wifi状态的变化。

wifi的消息一共有五种:

WifiManager.WIFI_STATE_DISABLED: //wifi不可用

WifiManager.WIFI_STATE_DISABLING://wifi 正在关闭或者断开

WifiManager.WIFI_STATE_ENABLED://wifi可用

WifiManager.WIFI_STATE_ENABLING://wifi正在打开或者连接

WifiManager.WIFI_STATE_UNKNOWN://未知消息

仅在wifi状态为WIFI_STATE_ENABLED的时候,才表示wifi已经连接成功。

这是状态问题,那么信息强度呢?

下面出自Android:通过WifiManager监听Wifi信号强弱

先来了解下Android如何获取wifi的信息: WifiManager wifi_service = (WifiManager)getSystemService(WIFI_SERVICE); WifiInfo wifiInfo = wifi_service.getConnectionInfo(); 其中WifiManager是管理wifi的最重要的类,详细请参考 http://developer.android.com/reference/android/net/wifi/WifiManager.html 其中wifiInfo有以下的方法: wifiinfo.getBSSID(); wifiinfo.getSSID(); wifiinfo.getIpAddress();获取IP地址。 wifiinfo.getMacAddress();获取MAC地址。 wifiinfo.getNetworkId();获取网络ID。 wifiinfo.getLinkSpeed();获取连接速度,可以让用户获知这一信息。 wifiinfo.getRssi();获取RSSI,RSSI就是接受信号强度指示。在这可以直 接和华为提供的Wi-Fi信号阈值进行比较来提供给用户,让用户对网络或地理位置做出调整来获得最好的连接效果。 这里得到信号强度就靠wifiinfo.getRssi();这个方法。得到的值是一个0到-100的区间值,是一个int型数据,其中0到-50表示信号最好,-50到-70表示信号偏差,小于-70表示最差,有可能连接不上或者掉线,一般Wifi已断则值为-200。

但是还不够的,wifiinfo.getRssi()是可以获取数值,但我想自己定义wifi等级,比如有的手机wifi只能显示3格,有的却可以显示5格。那用什么方法呢?

calculateSignalLevel(int rssi , int numLevels) 计算信号的等级

 /**
     * Calculates the level of the signal. This should be used any time a signal
     * is being shown.
     *
     * @param rssi The power of the signal measured in RSSI.
     * @param numLevels The number of levels to consider in the calculated
     *            level.
     * @return A level of the signal, given in the range of 0 to numLevels-1
     *         (both inclusive).
     */
    public static int calculateSignalLevel(int rssi, int numLevels) {
        if (rssi <= MIN_RSSI) {
            return 0;
        } else if (rssi >= MAX_RSSI) {
            return numLevels - 1;
        } else {
            float inputRange = (MAX_RSSI - MIN_RSSI);
            float outputRange = (numLevels - 1);
            return (int)((float)(rssi - MIN_RSSI) * outputRange / inputRange);
        }
    }

当然,这代码是我刚刚搜索到的,几年前我只知道calculateSignalLevel(int rssi , int numLevels)这个API就够了。第二个参数就是要自定义的等级。这里我将传进去5,第一个参数就是前面getRssi()的值。

那就开始写代码吧

1.继承自ImageView.(这是代价最少,最适合菜鸟的,什么onMeasure(),什么onLayout()都不需要管,因为用不上,我当时也不会写。) 2.WifiSateView中有一个广播接收器,接收WifiManager.WIFI_STATE_CHANGED_ACTION. WifiManager.RSSI_CHANGED_ACTION ConnectivityManager.CONNECTIVITY_ACTION 3.根据信号强度进行计算等级,用calculateSignalLevel。 4.通过handler来进行状态更换,也就是调用setImageResource方法。

Coding吧,菜鸟!!!

/**
 * Created by frank on 2016/3/27.
 */
public class WifiStateView extends ImageView {


    public static final String TAG = "WifiStateView";
    private static final int LEVEL_DGREE = 4;
    //定义wifi信号等级
    private static final int LEVEL_0 = 0;
    private static final int LEVEL_1 = 1;
    private static final int LEVEL_2 = 2;
    private static final int LEVEL_3 = 3;
    private static final int LEVEL_NONE = -1;

    WifiManager mWifiManager;
    WifiHandler mWifiHandler;
    ConnectivityManager mCM;

    //将handle设定为static,防止内存泄漏
    private static class WifiHandler extends Handler{
        WeakReference<WifiStateView> mView;
        public WifiHandler(WifiStateView view){
            mView = new WeakReference<WifiStateView>(view);

        }
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            if(mView.get() == null){
                return;
            }
            WifiStateView view = mView.get();
            Log.i(TAG, "handleMessage level " + msg.what);
            //根据wifi的信号强度等级,更换图标
            switch (msg.what) {
                case LEVEL_0:
                    view.setImageResource(R.drawable.wifi0);
                    break;
                case LEVEL_1:
                    view.setImageResource(R.drawable.wifi1);
                    break;
                case LEVEL_2:
                   view. setImageResource(R.drawable.wifi2);
                    break;

                case LEVEL_3:
                   view. setImageResource(R.drawable.wifi3);
                    break;

                case LEVEL_NONE:
                    view.setImageResource(R.drawable.wifinone);
                    break;

                default:
                    break;
            }
        }

    }


    private BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            //WifiManager.WIFI_STATE_CHANGED_ACTION
            Log.i(TAG, "onReceive "+action);
            if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
                //如果wifi没有连接成功,则显示wifi图标无连接的状态
                if (mWifiManager.getWifiState() == WifiManager.WIFI_STATE_DISABLED) {
                    mWifiHandler.sendEmptyMessage(LEVEL_NONE);
                    return;
                }

            }else if(action.equals(ConnectivityManager.CONNECTIVITY_ACTION)){

                NetworkInfo netInfo =  mCM.getActiveNetworkInfo();
                //当前网络无连接,当前网络不是wifi连接,当前网络是wifi但是没有连接,wifi图标都显示无连接
                if(netInfo == null || netInfo.getType() != ConnectivityManager.TYPE_WIFI){
                    mWifiHandler.sendEmptyMessage(LEVEL_NONE);
                }else if(netInfo != null && netInfo.getType() == ConnectivityManager.TYPE_WIFI && !netInfo.isConnected()){
                    mWifiHandler.sendEmptyMessage(LEVEL_NONE);

                }

            }else if(action.equals(WifiManager.RSSI_CHANGED_ACTION)){
                //当信号的rssi值发生变化时,在这里处理
                if (mWifiManager.getWifiState() == WifiManager.WIFI_STATE_DISABLED) {
                    mWifiHandler.sendEmptyMessage(LEVEL_NONE);
                    return;
                }
                WifiInfo info = mWifiManager.getConnectionInfo();
                //计算wifi的信号等级
                int level = WifiManager.calculateSignalLevel(info.getRssi(), LEVEL_DGREE);
                Log.i(TAG,"wifi rssi "+info.getRssi());
                mWifiHandler.sendEmptyMessage(level);
            }
        }
    };

    public WifiStateView(Context context) {
        this(context, null);
    }

    public WifiStateView(Context context, AttributeSet attrs) {
        this(context, null, 0);
    }

    public WifiStateView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
        mCM = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
        mWifiHandler = new WifiHandler(this);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        IntentFilter mFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
       // mFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
        mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
        //注册广播接收器
        getContext().registerReceiver(mWifiStateReceiver, mFilter);

    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mWifiHandler.removeCallbacksAndMessages(null);
        //注销广播接收器
        getContext().unregisterReceiver(mWifiStateReceiver);
    }


}

上面的R.drawable.wifi0这4张图是我自己用win10自带的附件画的,我不是美工,所以画的比较简陋。大家可以用自己的图片替换。

当然,还要配置AndroidMainefest.xml文件uses-permission

android:name=”android.permission.CHANGE_NETWORK_STATE” android:name=”android.permission.CHANGE_WIFI_STATE” android:name=”android.permission.ACCESS_NETWORK_STATE” android:name=”android.permission.ACCESS_WIFI_STATE”

代码十分简单,也很简陋。真实的情况肯定要复杂的多,有时间我会把它完善,但掌握了基本的方法,其它的也就那么些回事。WifiStateView搞定之后,那些以太网状态图标、USB状态图标就很容易了。我一举攻破。

最后,总结一下。WifiStateView核心内容就是自定义imageView,然后通过广播监听wifi信号变化,然后计算wifi强度值,再改变自身的图标。

Demo地址 请点击这里

希望广大Android菜鸟要对自己充满信心。无论面对什么情况,都要保持乐观,然后去想办法解决难题。因为脱离了菜鸟阶段后,Android世界仍然有许多奇怪的坑等待你填。但面对困难,迎难而上,根据自身条件去寻找解决办法的习惯我们应该培养然后一直保持下去。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券