打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Android开启热点进行UDP通信中的坑

1、写在前面:

2018年的第一篇文章,最近在使用UDP协议进行硬件通信,大家都知道UDP协议通信必须在同一个局域网内,但是每个用户家的wifi都是不一样的,硬件设备是无法只值连接到用户家的wifi的。所以为了解决这个问题,提出一个思路,让手机开启热点,然后把硬件链接到手机的热点上。再由手机告诉硬件去链接用户家里的wifi,这样手机和设备就都能连接到用户家的wifi了,就能愉快的进行通信了。那么怎么解决这个问题呢?继续往下看!

2、实现思路:

  • 1、获取当前网络wifi名称
  • 2、开启热点
  • 3、让用户输入wifi密码
  • 4、获取当前网络的广播地址,扫描设备
  • 5、给设备发命令,配置信息
  • 6、把绑定的设备存起来
  • 7、循环4-6直到没有新设备了
  • 8、退出的时候先把设备信息提交
  • 9、关闭热点、打开wifi

3、中间遇到坑:

测试真机: 魅族4 Android5.1 、小米5 Android 7.0
这里就不说怎么进行UDP通信了,只说在中间遇到的问题。两个坑吧,一个是开启热点兼容6.x+,另一个是获取广播地址,兼容wifi环境,以太网环境,无网络环境。

3.1 开启热点,兼容android6.x

这里先提供一个开启/关闭热点的工具类WifiUtils:

import android.net.wifi.WifiConfiguration;import android.net.wifi.WifiManager;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;/** * 作者:dell or Xiaomi Li * 时间: 2018/1/17 * 内容:打开/关闭热点 * 最后修改: */public class WifiUtils {    private final static String APName = "XiaomiLi8";    private final static String APPassword = "5311925577";    /**     * 创建热点     *     * @return     */    public static boolean CreatHotspot(WifiManager wifiManager) {        boolean request;        //开启热点        if (wifiManager.isWifiEnabled()) {            //如果wifi处于打开状态,则关闭wifi,            wifiManager.setWifiEnabled(false);        }        WifiConfiguration config = new WifiConfiguration();        config.SSID = APName;        config.preSharedKey = APPassword;        config.hiddenSSID = false;//是否隐藏网络        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;        //通过反射调用设置热点        try {            Method method = wifiManager.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, Boolean.TYPE);            boolean enable = (Boolean) method.invoke(wifiManager, config, true);            if (enable) {                request = true;            } else {                request = false;                LogUtils.Loge("创建失败");            }        } catch (Exception e) {            e.printStackTrace();            LogUtils.Loge(e.toString() + "创建失败");            request = false;        }        return request;    }    /**     * 关闭热点,并开启wifi     */    public static void closeWifiHotspot(WifiManager wifiManager) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {        Method method = wifiManager.getClass().getMethod("getWifiApConfiguration");        method.setAccessible(true);        WifiConfiguration config = (WifiConfiguration) method.invoke(wifiManager);        Method method2 = wifiManager.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, boolean.class);        method2.invoke(wifiManager, config, false);        //开启wifi        wifiManager.setWifiEnabled(true);    }}

需要用到权限:

<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"/><uses-permission android:name="android.permission.INTERNET"/><uses-permission android:name="android.permission.WRITE_SETTINGS" />

这里来说坑,经过测试发现在Android6.0一下的手机是可以正常开启热点的,但是在6.0以上的手机。开启热点会报如下错误:

java.lang.reflect.InvocationTargetException

大家都能猜到是权限的问题,确实如此,经过查找资料很多博客也都说是权限的问题,也有人说直接把版本设置在22,这样就不用管权限问题了,但是这种方法也行可以归纳为不正常手段。所以这里就不用这种方法了。然后继续查询,发现是android.permission.WRITE_SETTINGS这个权限发生的错误,既然是权限问题,那就去动态申请权限就好了。然后就会发现,申请之后根本没有用。还是没有权限。那这是为何呢?
因为在android 6.0及以后,WRITE_SETTINGS权限的保护等级已经由原来的dangerous升级为signature,这意味着我们的APP需要用系统签名或者成为系统预装软件才能够申请此权限,并且还需要提示用户跳转到修改系统的设置界面去授予此权限。所以我们动态申请权限是没有用的。这里先给出参考地址:http://blog.csdn.net/XieGaoXiong/article/details/52317155 然后给出申请的方法,如下:

    /**     * WIFI设置请求码     */    private final int REQUEST_CODE_ASK_WRITE_SETTINGS = 0X1;    /**     * 请求权限     */    private void getWifiPreMission() {        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {            if (!Settings.System.canWrite(this)) {                Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS,                        Uri.parse("package:" + getPackageName()));                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);                startActivityForResult(intent, REQUEST_CODE_ASK_WRITE_SETTINGS);            } else {                //有了权限去做什么呢?                getConnectWifiSsid();            }        } else {            getConnectWifiSsid();        }    }

然后在Activity的onActivityResult()方法中去操作一波:

    @Override    protected void onActivityResult(int requestCode, int resultCode, Intent data) {        super.onActivityResult(requestCode, resultCode, data);        if (requestCode == REQUEST_CODE_ASK_WRITE_SETTINGS || requestCode == PreMissionDialog.ACTION_APPLICATION_DETAILS_SETTINGS) {            if (!Settings.System.canWrite(this)) {                //如果还是没有权限,就弹框提醒用户                PreMissionDialog.showPermissionDialog(SearchEqListActivity.this, "系统设置");            } else {                getConnectWifiSsid();            }        }    }

这里把提醒用户的弹框也给出来,可以拿去用,觉得丑可以自己写一个:

import android.app.Activity;import android.content.DialogInterface;import android.content.Intent;import android.net.Uri;import android.provider.Settings;import android.support.v7.app.AlertDialog;/** * 作者:dell or Xiaomi Li * 时间: 2018/1/17 * 内容:提醒用户开启权限弹框 * 最后修改: */public class PreMissionDialog {    public final static int ACTION_APPLICATION_DETAILS_SETTINGS = 0x100;    /**     * 申请权限     *     * @param message     */    public static void showPermissionDialog(final Activity mActivity, String message) {        AlertDialog.Builder dialog = new AlertDialog.Builder(mActivity);        dialog.setTitle("权限提醒")                .setMessage("请在权限管理中允许" + message + "权限")                .setPositiveButton("权限设置", new DialogInterface.OnClickListener() {                    @Override                    public void onClick(DialogInterface dialog, int which) {                        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);                        Uri uri = Uri.fromParts("package", mActivity.getPackageName(), null);                        intent.setData(uri);                        mActivity.startActivityForResult(intent, ACTION_APPLICATION_DETAILS_SETTINGS);                    }                })                .setNegativeButton("取消", null)                .create()                .show();    }}

进行上述操作,就可以成功开启热点了。

3.2 获取当前网络的广播地址。兼容wifi环境/以太网环境/无网络环境下。

UDP发送一个广播命令,需要有一个广播地址。先说一下广播地址是怎么组成的。通常大家连接到的wifi是IP地址是:1xx.1xx.2xx.45 这样样子的。那么广播地址就是:1xx.1xx.2xx.255 这样。那么问题来了,这个广播地址是怎么来的呢?是用IP地址的最后一个“.”后边的数字改成“255” 么?不是的,这里就要提一下子网掩码了。先看一下图片:

通过上边的图片大家可以发现,并不是所有的子网掩码都是255.255.255.0 这样子的。那么广播地址和子网掩码又有什么关系呢?其实广播地址是当前网段的最后一个地址,而通过子网掩码就能看出来当前网段有多多少个地址。像最后一个是0的,就是有1-255个地址,最后一个.255就是广播地址。而第二个240的呢,就是有1-15个地址,最后一个.15就是广播地址。
也就是说用255-子网掩码的最后一个值就是广播地址。
那么结论就出来了,最后的广播地址等于IP地址的前三个+(255-子网掩码的最后一个)。这样拼起来就是真正的广播地址。(这里不知道解释的清楚不清楚,或者说的就有错误,欢迎各位大牛提意见!)

然后咱们就去去子网掩码和IP地址就好了。那么坑又来了。在wifi环境下,取到这两个值是没问题的,但是在以太网环境下,怎么获取IP地址呢,是不是需要必须有网络呢?为什么在手机信息里边看到的IP地址和电脑链接手机热点的IP地址不在一个网段呢?唉,问题还真多!直接放一个工具类出来好了,通过这个工具类就能截至获取到广播地址了,包括wifi环境和以太网环境!代码如下:

import android.content.Context;import android.net.DhcpInfo;import android.net.wifi.WifiInfo;import android.net.wifi.WifiManager;import java.net.InetAddress;import java.net.InterfaceAddress;import java.net.NetworkInterface;import java.net.SocketException;import java.util.Enumeration;import java.util.List;/** * 类描述:获取ip * 作  者:Admin or 李小米 * 时  间:2018/1/11 * 修改备注: */public class IPUtils {    public static String getIp(Context mContext) throws SocketException {        String ip = "";        //获取wifi服务        WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);        if (wifiManager.isWifiEnabled()) {            WifiInfo wifiInfo = wifiManager.getConnectionInfo();            int ipAddress = wifiInfo.getIpAddress();            DhcpInfo dhcpInfo = wifiManager.getDhcpInfo();            String dhcpInfos = intToIp(dhcpInfo.netmask);            String[] split = intToIp(ipAddress).split("\\.");            ip = split[0] + "." + split[1] + "." + split[2] + "." + (255 - Integer.parseInt(dhcpInfos.split("\\.")[3]));//根据子网掩码获取广播的IP地址        } else {            String asd = getInfo();            String[] split = asd.split(",");            String ipStr = split[0];            String NetMask = split[1];            String[] split1 = ipStr.split("\\.");            ip = split1[0] + "." + split1[1] + "." + split1[2] + "." + (255 - Integer.parseInt(NetMask.split("\\.")[3]));//根据子网掩码获取广播的IP地址        }        return ip;    }    private static String intToIp(int paramInt) {        return (paramInt & 0xFF) + "." + (0xFF & paramInt >> 8) + "." + (0xFF & paramInt >> 16) + "."                + (0xFF & paramInt >> 24);    }    public static String getInfo() throws SocketException {        String ipAddress = "";        String maskAddress = "";        for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {            NetworkInterface intf = en.nextElement();            List<InterfaceAddress> mList = intf.getInterfaceAddresses();            for (InterfaceAddress l : mList) {                InetAddress inetAddress = l.getAddress();                if (!inetAddress.isLoopbackAddress()) {                    String hostAddress = inetAddress.getHostAddress();                    if (hostAddress.indexOf(":") > 0) {                        continue;                    } else {                        ipAddress = hostAddress;                        maskAddress = calcMaskByPrefixLength(l.getNetworkPrefixLength());                    }                }            }        }        return ipAddress + "," + maskAddress;    }    private static String calcMaskByPrefixLength(int length) {        int mask = -1 << (32 - length);        int partsNum = 4;        int bitsOfPart = 8;        int maskParts[] = new int[partsNum];        int selector = 0x000000ff;        for (int i = 0; i < maskParts.length; i++) {            int pos = maskParts.length - 1 - i;            maskParts[pos] = (mask >> (i * bitsOfPart)) & selector;        }        String result = "";        result = result + maskParts[0];        for (int i = 1; i < maskParts.length; i++) {            result = result + "." + maskParts[i];        }        return result;    }}

4、结语:

这里因为是直接在项目里写的,就不写demo了,遇到的问题都贴出来代码了。希望可以帮到小伙伴们。哦,如果文中有错误,尤其是对子网掩码的解释,欢迎大牛们提出批评意见!

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Android 监听wifi广播的两种方式
Android开发之WIFI网络操作
android Wifi模块分析(一)
wifi使用的相关流程
getSystemService原理
android系统 连接管理总结
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服