前言
关于开启热点,手机间通信,网上有很多相关的例子,但几乎所有的例子只在开启热点和进行socket连接部分有比较详细的描叙,实际上,这其中还有很多重要的细节,例如热点的IP如何获得, socket连接如何保证连接上等?
正文
两个Android手机要想相互间通信,可以通过蓝牙,也可以通过WIFI,相对而已WIFI的速度更快,但两个手机必须连接同一WIFI源才行。如果没有WIF源呢?Android 9提供了一个叫LocalOnlyHotspot的东西,其中一个手机开启热点,另一手机连接此热点,然后就可以通过socket通信。
实际上,这整个过程可以划分为三个独立部分开启热点、启动ServerSocket、连接热点并连接socket。但下面我是按相关技术的要点进行分段描叙的。
-
开启热点,获得热点名称,密码
这部分在网上有很多介绍,我就直接贴代码了:
private void startHotApp(final Intent intent) {
final WifiManager manager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
if (manager!= null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
manager.startLocalOnlyHotspot(new WifiManager.LocalOnlyHotspotCallback() {
@Override
public void onStarted(WifiManager.LocalOnlyHotspotReservation reservation) {
super.onStarted(reservation);
sReservation = reservation;
PendingIntent pendingIntent = intent.getParcelableExtra("pendingIntent");
Intent data = new Intent();
data.putExtra(Constants.KEY_SSID, reservation.getWifiConfiguration().SSID);
data.putExtra(Constants.KEY_PRESHARE, reservation.getWifiConfiguration().preSharedKey);
final String ipAddress = getLocalIpAddress();
data.putExtra(Constants.KEY_IP, ipAddress);
try {
DebugLog.info("pendingIntent.send:" + ipAddress);
if(pendingIntent != null) {
pendingIntent.send(HotService.this, 200, data);
} catch (PendingIntent.CanceledException e) {
e.printStackTrace();
@Override
public void onStopped() {
super.onStopped();
@Override
public void onFailed(int reason) {
super.onFailed(reason);
}, null);
- 获得热点的IP
这部分我要重点说说,网上很多通过WifiManager或者读取本地文件等方式来获取手机IP,其实都是不对的,我们手机连接别人的热点,才能通过这种方式获得IP,而我们手机自己开启的热点,这种方式要么获得的一直是0,要么就不对,可能是以前手机连接他人的热点形成的ip。
首先,我们来看看热点IP是如何生成的:
源代码网址:
http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
198 // TODO: Refactor this in terms of calls to InterfaceController.
199 private boolean configureIPv4(boolean enabled) {
200 if (VDBG) Log.d(TAG, "configureIPv4(" + enabled + ")");
202 // TODO: Replace this hard-coded information with dynamically selected
203 // config passed down to us by a higher layer IP-coordinating element.
204 String ipAsString = null;
205 int prefixLen = 0;
206 if (mInterfaceType == ConnectivityManager.TETHERING_USB) {
207 ipAsString = USB_NEAR_IFACE_ADDR;
208 prefixLen = USB_PREFIX_LENGTH;
209 } else if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) {
210 ipAsString = getRandomWifiIPv4Address();
211 prefixLen = WIFI_HOST_IFACE_PREFIX_LENGTH;
212 } else {
213 // Nothing to do, BT does this elsewhere.
214 return true;
215 }
217 final LinkAddress linkAddr;
218 try {
219 final InterfaceConfiguration ifcg = mNMService.getInterfaceConfig(mIfaceName);
220 if (ifcg == null) {
221 mLog.e("Received null interface config");
222 return false;
223 }
225 InetAddress addr = NetworkUtils.numericToInetAddress(ipAsString);
226 linkAddr = new LinkAddress(addr, prefixLen);
227 ifcg.setLinkAddress(linkAddr);
228 if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) {
229 // The WiFi stack has ownership of the interface up/down state.
230 // It is unclear whether the Bluetooth or USB stacks will manage their own
231 // state.
232 ifcg.ignoreInterfaceUpDownStatus();
233 } else {
234 if (enabled) {
235 ifcg.setInterfaceUp();
236 } else {
237 ifcg.setInterfaceDown();
238 }
239 }
240 ifcg.clearFlag("running");
241 mNMService.setInterfaceConfig(mIfaceName, ifcg);
242 } catch (Exception e) {
243 mLog.e("Error configuring interface " + e);
244 return false;
245 }
247 // Directly-connected route.
248 final RouteInfo route = new RouteInfo(linkAddr);
249 if (enabled) {
250 mLinkProperties.addLinkAddress(linkAddr);
251 mLinkProperties.addRoute(route);
252 } else {
253 mLinkProperties.removeLinkAddress(linkAddr);
254 mLinkProperties.removeRoute(route);
255 }
256 return true;
257 }
259 private String getRandomWifiIPv4Address() {
260 try {
261 byte[] bytes = NetworkUtils.numericToInetAddress(WIFI_HOST_IFACE_ADDR).getAddress();
262 bytes[3] = getRandomSanitizedByte(DOUG_ADAMS, asByte(0), asByte(1), FF);
263 return InetAddress.getByAddress(bytes).getHostAddress();
264 } catch (Exception e) {
265 return WIFI_HOST_IFACE_ADDR;
266 }
267 }
我们看到IP地址被保存到NetworkManagementService中,那么我们是不是可以通过NetworkManagementService来获得IP呢,很遗憾NetworkManagementService是hide的,那么是否可以通过反射方式的,于是试试:
private String getIpAddress() {
String address = null;
try {
//IBinder b = ServiceManager.getService(Context.NETWORK_STATS_SERVICE);
Class<?> serviceManager = Class.forName("android.os.ServiceManager");
Method method = serviceManager.getMethod("getService", String.class);
IBinder b = (IBinder)method.invoke(serviceManager.newInstance(), "network_management");
//INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
Class<?> stub = Class.forName("android.os.INetworkManagementService$Stub");
Method asInterface = stub.getMethod("asInterface", IBinder.class);
DebugLog.info("asInterface:" + asInterface);
Object service = asInterface.invoke(null, b);
DebugLog.info("service:" + service);
//InterfaceConfiguration ifcg = service.getInterfaceConfig("wlan0");
if(service != null) {
Method config = service.getClass().getMethod("getInterfaceConfig", String.class);
Object ifcfg = config.invoke(service, "wlan0");
//LinkAddress linkAddr = ifcg.getLinkAddress();
Method getLink = ifcfg.getClass().getMethod("getLinkAddress");
LinkAddress linkAddr = (LinkAddress)getLink.invoke(ifcfg);
DebugLog.info("linkAddr:" + linkAddr);
if (linkAddr != null) {
InetAddress Inetaddr = linkAddr.getAddress();
DebugLog.info("Inetaddr:" + Inetaddr);
if (Inetaddr != null) {
address = Inetaddr.getHostAddress();
DebugLog.info("address:" + address);
//InterfaceConfiguration ifcg = service.getInterfaceConfig("wlan0");
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
e.printStackTrace();
DebugLog.error("ClassNotFoundException:", e);
return address;
但是,我的天啦:
Neither user 10003 nor current process has android.permission.CONNECTIVITY_INTERNAL.
android.permission.CONNECTIVITY_INTERNAL只能给系统APP使用,即使我们添加了也没用。于是路堵上了。
我的神啦,咋办?只好去圣地StackOverFlow找找,果然还真找到了:
private String getLocalIpAddress() {
String ipAddress = null;
try {
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress()) {
ipAddress = inetAddress.getHostAddress();
if(ipAddress != null && ipAddress.startsWith(Constants.HOT_SERVER_IP_PREFIX)) {
return ipAddress;
} catch (SocketException ex) {
DebugLog.error(ex.toString());
return ipAddress;
参考TetherInterfaceStateMachine.java中的代码IP固定以192.168.43开头的。
Stackoverflow问题地址:how-to-get-my-ip-address
这下好了,热点的名称、密码、IP地址、端口(整个可以随便设置)都有了,通过蓝牙或者生成二维码的形式发给客户机,客户机就可以建立连接通信了。
- Socket 服务端
这个也简单,不再描叙,直接贴代码:
@Override
public void run() {
try{
if(sServerSocket != null) {
sServerSocket.close();
sServerSocket = null;
sServerSocket = new ServerSocket(Constants.HOT_SERVER_PORT);
Socket clientSocket = sServerSocket.accept();//wait here
String remoteIP = clientSocket.getInetAddress().getHostAddress();
int remotePort = clientSocket.getLocalPort();
DebugLog.info("remoteIP:" + remoteIP + " remotePort" + remotePort);
inputStream = clientSocket.getInputStream();
byte[] buffer = new byte[1024];
int bytes;
while(isRunning) {
bytes = inputStream.read(buffer);
if(bytes != -1) {
final byte[] data = new byte[bytes];
System.arraycopy(buffer, 0, data, 0, bytes);
final String content = new String(data);
DebugLog.info("data:" + content );
//dosomething
Thread.sleep(20);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
} finally{
close();
- 客服端
网上也有很多Socket客户端的代码,但是如果你直接照搬,你是不是发现,别人的正常,你的就是连接不上,或者说时好时不好?这其实是细节问题,连接上热点,网络不会马上就通,还需要等一会儿。具体过程可以这样描叙,连接热点—连通—分配IP—分配完成—开启socket连接—连接成功—开始发送数据,这其中都有一点等待时间,我这是通过轮询来保证连接成功的。
override fun run() {
//wifiManager.updateNetwork(createConfig(ssid,psw))
if (!ssid.equals("test")) {
wifiManager.addNetwork(createConfig(ssid, psw))
while (true) {//检测是否连接上热点
if (wifiManager.connectionInfo != null) {
var c_ssid = getSSID(wifiManager)
Log.i("wenpd", "connectionInfo:$c_ssid")
if (ssid.equals(c_ssid)) break;
Thread.sleep(500)
while (true) {//检测是否已获得IP地址
var ip = manager.connectionInfo.ipAddress
Log.d("wenpd", "ip is $ip")
if (ip != 0) {
var routeip = manager.dhcpInfo.gateway
Log.d("wenpd", "routeip is $routeip")
break
Thread.sleep(400)
val ipRoute = getWifiRouteIPAddress(wifiManager);
Log.d("wenpd", "connect:$ipRoute")
while (true){//检测网络是否已经通了
try {
socket = Socket(ipRoute, 50000)
break
} catch (se: SocketException) {
Thread.sleep(500)
var outputStream = socket.getOutputStream();
var i = 0;
val len = Constants.CONTENTS.size
while(isRunning) {
val content = Constants.CONTENTS.get(i);
outputStream.write(content.toByteArray())
Thread.sleep(40)
if(++i == len) {
i = 0;
这里需要注意的是,如果客服端有保存其他wifi热点wifiManager.addNetwork不一定连上我们需要的那个,所以需要手动把保存的热点都先取消。
前言关于开启热点,手机间通信,网上有很多相关的例子,但几乎所有的例子只在开启热点和进行socket连接部分有比较详细的描叙,实际上,这其中还有很多重要的细节,例如热点的IP如何获得, socket连接如何保证连接上等?正文两个Android手机要想相互间通信,可以通过蓝牙,也可以通过WIFI,相对而已WIFI的速度更快,但两个手机必须连接同一WIFI源才行。如果没有WIF源呢?Android...
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.So
1。这个都是自己慢慢摸索的。mono for andriod ,资料真的太少了。
2.正在做安卓手机的项目。选择这条路都不知道能不能坚持下去。
3.希望有同样迷惑的人,照亮一点前面的路,也希望能照亮自己。
4.下面的代码运行环境
VS2012 + mono for andriod 4.8
java -sdk 与 mono for andriod 4.8 见下面的网盘
希望到时,这个网盘没有被封。
http://pan.baidu.com/s/1sjqMsqT
http://www.51mono.com/article/show/172
http://www.51mono.com/article/show/96
安卓模拟器,推荐使用 Genymotion。
具体的安装教程,就请网上找吧。我实在没有力气了。
不过在模拟时,还是有点不够爽。
由于是 mono for andriod 专业破解版,所以可以直接使用 真机运行的。
我就是用 小米3 来运行。问题比较少。
另外要吐槽一下的是,开发这个手机APP,真的很耗内存。为此我不得不把我的老爷机换了4G的内存条。
由于是老爷机,内存条好贵的呀。
5,windows 开发环境 是 win8.1. 32位。
6.希望我啰嗦的这么多信息对你有用。
7.下面是正题了。
这个压缩包中,包含 C#写的服务端。 和 mono for andriod ---c# 写的手机客户端。
实现简单的 Socket --TCP/IP 通信。
其实,使用的类 和语法代码与 普通PC机的一样。但 手机客户端,还是有点点区别。
在pc客户端中,使用线程收取 信息,然后打印到UI控件上,是使用委托进行。
在手机APP中,有一个专门的方法来把这个信息寄送到 UI线程,然后在 UI控件上进行显示。
在例子中,有体现。
8.好了,就这么多了
最近把测试丢过来的种种BUG解决后,终于有时间去研究研究Socket通信,再加上以前做的WiFi连接和热点开启,于是有了现在的这篇博文:创建热点发送文件,让另一台手机连接热点接收文件。
两台设备是如何传输文件的:
发送端->创建WiFi热点
接收端->连接热点
发送端->发送文件列表
接收端->收到后展示文件列表,选择要接收的文件发送给发送端
发送端->发送所选文件
接收端->开始接收…
App端 打开wifi操作
private WifiManager mWifiManager;
mWifiManager = (WifiManager) mContext.getSystemService(WifiManager.class);\\获取WIFI的服务
mWifiManager.setWifiEnabled(true)
让我们看一下WifiManager设置的时候是如何操作
* 创建一个新的 WifiManager 实例。
* 应用程序几乎
andriod-studio-2021.1.1.21-windows.exe是一款Android开发集成环境软件,这个版本是2021年1月1日发布的Windows版本。它主要用于开发Android应用程序,包括Android手机应用、平板应用、智能手表应用等。这个软件具备一系列强大的开发工具,如代码编辑器、编译器、调试器等,在开发过程中能够提高开发效率和开发质量。此外,andriod-studio-2021.1.1.21-windows.exe还提供了丰富的模板和插件,方便开发者在开发过程中更快速地完成任务。同时,该软件也是谷歌公司发布的官方工具,因此能够更好地支持Android操作系统和相关技术。如果您是一位Android应用程序开发者,那么andriod-studio-2021.1.1.21-windows.exe将是您值得选择的一个优秀工具。