android udp 广播通信,Android开启热点进行UDP通信中的坑

f18f2d82cd06

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);

}

}

需要用到权限:

这里来说坑,经过测试发现在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” 么?不是的,这里就要提一下子网掩码了。先看一下图片:

f18f2d82cd06

子网掩码1

f18f2d82cd06

子网掩码2

通过上边的图片大家可以发现,并不是所有的子网掩码都是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 en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {

NetworkInterface intf = en.nextElement();

List 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了,遇到的问题都贴出来代码了。希望可以帮到小伙伴们。哦,如果文中有错误,尤其是对子网掩码的解释,欢迎大牛们提出批评意见!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值