本文主要是记录实现面对面快传功能时遇到的一些较大的问题。 这里仿照的是“换机助手”的快传逻辑,传输内容只实现了传输一张图片。 ## 面对面快传逻辑: >我们需要使用面对面快传,就是一个手机开热点一个手机开wifi,组件成一个局域网,然后一个手机当client,一个手机当server 所以重点问题就是:开启热点、连接wifi、数据传输(这里使用socket传输) 1、发送方:(类似客户端) - 选择需要发送的文件(这里只发送一张图片) - 扫描接收方二维码(选择图片成功后跳转到扫描二维码界面) - 连接接收方热点(扫描二维码成功后获取到接收方的热点SSID和密码,以及IP,开始连接接收方的热点) - 连接接收方Socket服务(扫描二维码成功后获取到接收方的热点SSID和密码,以及IP,开始连接接收方Socket服务) - 发送文件(使用Socket服务传输文件) 2、接收方:(类似服务端) - 打开热点,将热点SSID、密码和IP信息生成一个二维码 - 打开Socket服务 - 生成二维码 - 接收文件  ## 所遇问题: ### 1、开启、关闭热点 已解决:开启、关闭热点的代码如下 [如何在Android 8.0(Oreo)中以编程方式打开/关闭wifi热点](https://code-examples.net/zh-CN/q/2bdaa59 "如何在Android 8.0(Oreo)中以编程方式打开/关闭wifi热点") ```java private WifiManager.LocalOnlyHotspotReservation mReservation; //开启热点 public void openWifiAp() { boolean i = wifiManager.isWifiEnabled(); Log.i(TAG, "wifi是否打开: " + i); if (i) { wifiManager.setWifiEnabled(false); } if (ActivityCompat.checkSelfPermission(ReceiveActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { //请求定位权限 PermissionUtils.request(this, PermissionUtils.LOCATION_PERMISSIONS, P_LOCATION); return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { //Android 8.0及以上 wifiManager.startLocalOnlyHotspot(new WifiManager.LocalOnlyHotspotCallback() { @Override public void onStarted(WifiManager.LocalOnlyHotspotReservation reservation) { super.onStarted(reservation); //热点开启成功 mReservation = reservation; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { binding.statusTv.setText("热点已开启"); wifiSSID = reservation.getWifiConfiguration().SSID; wifiPwd = reservation.getWifiConfiguration().preSharedKey; Log.i(TAG, "热点SSID:" + reservation.getWifiConfiguration().SSID); Log.i(TAG, "热点密码:" + reservation.getWifiConfiguration().preSharedKey); } } @Override public void onStopped() { super.onStopped(); Log.i(TAG, "onStopped: "); } @Override public void onFailed(int reason) { super.onFailed(reason); Log.i(TAG, "onFailed: "); } }, new Handler()); } } //关闭热点 public void closeWifiAp(){ if (mReservation != null){ mReservation.close(); } } ``` ### 2、连接指定WiFi 已解决:[Android开发——自动连接指定SSID的wifi热点(不加密/加密)](https://developer.aliyun.com/article/12841 "Android开发——自动连接指定SSID的wifi热点(不加密/加密)") 连接指定WiFi的工具类代码如下: ```java public class WifiAdmin { private final String TAG = "发送方"; // 定义WifiManager对象 private WifiManager mWifiManager; // 定义WifiInfo对象 private WifiInfo mWifiInfo; // 扫描出的网络连接列表 private List mWifiList; // 网络连接列表 private List mWifiConfiguration; // 定义一个WifiLock WifiManager.WifiLock mWifiLock; // 构造器 public WifiAdmin(Context context) { // 取得WifiManager对象 mWifiManager = (WifiManager) context .getSystemService(Context.WIFI_SERVICE); // 取得WifiInfo对象 mWifiInfo = mWifiManager.getConnectionInfo(); } // 打开WIFI public void openWifi() { if (!mWifiManager.isWifiEnabled()) { mWifiManager.setWifiEnabled(true); } } // 关闭WIFI public void closeWifi() { if (mWifiManager.isWifiEnabled()) { mWifiManager.setWifiEnabled(false); } } // 检查当前WIFI状态 public int checkState() { return mWifiManager.getWifiState(); } // 锁定WifiLock public void acquireWifiLock() { mWifiLock.acquire(); } // 解锁WifiLock public void releaseWifiLock() { // 判断时候锁定 if (mWifiLock.isHeld()) { mWifiLock.acquire(); } } // 创建一个WifiLock public void creatWifiLock() { mWifiLock = mWifiManager.createWifiLock("Test"); } // 得到配置好的网络 public List getConfiguration() { return mWifiConfiguration; } // 指定配置好的网络进行连接 public void connectConfiguration(int index) { // 索引大于配置好的网络索引返回 if (index > mWifiConfiguration.size()) { return; } // 连接配置好的指定ID的网络 mWifiManager.enableNetwork(mWifiConfiguration.get(index).networkId, true); } public void startScan() { mWifiManager.startScan(); // 得到扫描结果 mWifiList = mWifiManager.getScanResults(); // 得到配置好的网络连接 mWifiConfiguration = mWifiManager.getConfiguredNetworks(); } // 得到网络列表 public List getWifiList() { return mWifiList; } // 查看扫描结果 public StringBuilder lookUpScan() { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < mWifiList.size(); i++) { stringBuilder .append("Index_" + new Integer(i + 1).toString() + ":"); // 将ScanResult信息转换成一个字符串包 // 其中把包括:BSSID、SSID、capabilities、frequency、level stringBuilder.append((mWifiList.get(i)).toString()); stringBuilder.append("/n"); } return stringBuilder; } // 得到MAC地址 public String getMacAddress() { return (mWifiInfo == null) ? "NULL" : mWifiInfo.getMacAddress(); } // 得到接入点的BSSID public String getBSSID() { return (mWifiInfo == null) ? "NULL" : mWifiInfo.getBSSID(); } // 得到IP地址 public int getIPAddress() { return (mWifiInfo == null) ? 0 : mWifiInfo.getIpAddress(); } // 得到连接的ID public int getNetworkId() { return (mWifiInfo == null) ? 0 : mWifiInfo.getNetworkId(); } // 得到WifiInfo的所有信息包 public String getWifiInfo() { return (mWifiInfo == null) ? "NULL" : mWifiInfo.toString(); } // 添加一个网络并连接 public void addNetwork(WifiConfiguration wcg) { int wcgID = mWifiManager.addNetwork(wcg); boolean b = mWifiManager.enableNetwork(wcgID, true); Log.i(TAG, "a--" + wcgID); Log.i(TAG, "b--" + b); } // 断开指定ID的网络 public void disconnectWifi(int netId) { mWifiManager.disableNetwork(netId); mWifiManager.disconnect(); } //然后是一个实际应用方法,只验证过没有密码的情况: public WifiConfiguration CreateWifiInfo(String SSID, String Password, int Type) { WifiConfiguration config = new WifiConfiguration(); config.allowedAuthAlgorithms.clear(); config.allowedGroupCiphers.clear(); config.allowedKeyManagement.clear(); config.allowedPairwiseCiphers.clear(); config.allowedProtocols.clear(); config.SSID = "\"" + SSID + "\""; WifiConfiguration tempConfig = this.IsExsits(SSID); if(tempConfig != null) { mWifiManager.removeNetwork(tempConfig.networkId); } if(Type == 1) //WIFICIPHER_NOPASS { config.wepKeys[0] = ""; config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); config.wepTxKeyIndex = 0; } if(Type == 2) //WIFICIPHER_WEP { config.hiddenSSID = true; config.wepKeys[0]= "\""+Password+"\""; config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED); config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40); config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104); config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); config.wepTxKeyIndex = 0; } if(Type == 3) //WIFICIPHER_WPA { config.preSharedKey = "\""+Password+"\""; config.hiddenSSID = true; 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.allowedProtocols.set(WifiConfiguration.Protocol.WPA); config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP); config.status = WifiConfiguration.Status.ENABLED; } return config; } private WifiConfiguration IsExsits(String SSID) { List existingConfigs = mWifiManager.getConfiguredNetworks(); for (WifiConfiguration existingConfig : existingConfigs) { if (existingConfig.SSID.equals("\""+SSID+"\"")) { return existingConfig; } } return null; } } ``` 使用: ```java public class Test_wifiActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); WifiAdmin wifiAdmin = new WifiAdmin(this); wifiAdmin.openWifi(); wifiAdmin.addNetwork(wifiAdmin.CreateWifiInfo("XXX", "XXX", 3)); } } ``` ### 3、获取热点ip 在连接接收方开启的Socket服务时,接收方由于打开了热点,没有连接wifi,一直无法获取到正确的ip地址,导致发送方连接接收方socket服务时报错: `连接失败:java.net.ConnectException: failed to connect to /192.168.23.79 (port 9999) from /:: (port 39050): connect failed: ECONNREFUSED (Connection refused)` 原因:接收方打开热点后再打开socket服务,无法获取到正确的ip地址 已解决:[Android获取热点提供设备的IP地址](https://stackoverflow.com/questions/17302220/android-get-ip-address-of-a-hotspot-providing-device# "Android获取热点提供设备的IP地址") 使用下面的代码获取热点IP: ```java private String getIpAddress() { String ip = ""; try { Enumeration enumNetworkInterfaces = NetworkInterface .getNetworkInterfaces(); while (enumNetworkInterfaces.hasMoreElements()) { NetworkInterface networkInterface = enumNetworkInterfaces .nextElement(); Enumeration enumInetAddress = networkInterface .getInetAddresses(); while (enumInetAddress.hasMoreElements()) { InetAddress inetAddress = enumInetAddress.nextElement(); if (inetAddress.isSiteLocalAddress()) { ip += "SiteLocalAddress: " + inetAddress.getHostAddress() + "\n"; } } } } catch (SocketException e) { // TODO Auto-generated catch block e.printStackTrace(); ip += "Something Wrong! " + e.toString() + "\n"; } return ip; } ``` ### 4、传输数据 这里仅展示传输一张图片。 接收方(即服务端): ```java public String path = Environment.getExternalStorageDirectory().toString() + "/myHhhh"; private ServerSocket serverSocket; private Socket socket; // 端口号 private static final int PORT = 9999; //开启服务 public void startSocket(){ new Thread(() -> { //1、创建一个服务器Socket,即ServerSocket,绑定指定的端口,并监听此端口 try { serverSocket = new ServerSocket(PORT); serverSocket.setSoTimeout(10000); } catch (IOException e) { e.printStackTrace(); Log.i(TAG, "服务器创建失败: "+e); } if (serverSocket == null) return; while (!isFinishing()){ try { //2、调用accept()等待客户端连接 Log.i(TAG, "连接状态:等待远程连接..."+serverSocket.getLocalSocketAddress()); socket = serverSocket.accept();//接收客户端的请求,并且阻塞直到接收消息 //3、连接后获取输入流,读取客户端信息 Log.i(TAG, "连接状态:已连接 远程主机地址:"+socket.getRemoteSocketAddress()); //4、接收图片 DataInputStream in = new DataInputStream(socket.getInputStream()); //接收图片名称 String imgName = in.readUTF(); //创建保存图片的文件夹 File file = new File(path); /** *如果文件夹不存在就创建 */ if (!file.exists()) { file.mkdirs(); } File downloadFile = new File(path, imgName); FileOutputStream fileOutputStream = new FileOutputStream(downloadFile); //接收图片数据 byte[] inputByte = new byte[1024*8]; int length; while ((length = in.read(inputByte, 0, inputByte.length))>0){ Log.i(TAG, "正在接收数据..."+length); fileOutputStream.write(inputByte, 0, length); fileOutputStream.flush(); } //接收完成 Log.i(TAG, "图片接收完成"); runOnUiThread(new Runnable() { @Override public void run() { //将接收到的图片显示出来 Bitmap bitmap = null; try { bitmap = BitmapFactory.decodeStream(new FileInputStream(downloadFile)); } catch (FileNotFoundException e) { e.printStackTrace(); } binding.image.setImageBitmap(bitmap); binding.imageInfoTv.setText(downloadFile.getName()); Toast.makeText(ReceiveActivity.this, "接收成功!", Toast.LENGTH_SHORT).show(); } }); fileOutputStream.close(); in.close(); } catch (SocketTimeoutException e) { Log.i(TAG, "连接状态:服务超时。。。"); e.printStackTrace(); } catch (IOException e) { Log.i(TAG, "连接状态:无连接: "+e); e.printStackTrace(); } } }).start(); } ``` 发送方(即客户端): ```java private static final int PORT = 9999; private void startSocket(){ if (imageFile == null){ Toast.makeText(this, "请选择要发送的图片", Toast.LENGTH_SHORT).show(); return; } //开启连接 new Thread(() -> { try { //1、创建客户端Socket,指定服务器地址和端口 Socket socket = new Socket(wifiIp,PORT);//wifiIp是扫描二维码拿到的接收方的ip Log.i(TAG, "远程主机地址:"+socket.getRemoteSocketAddress()); if (socket.isConnected()){ Log.i(TAG, "已连接:"+socket.getRemoteSocketAddress()); } //2、获取输出流,向服务器端发送消息 DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); FileInputStream fileInputStream = new FileInputStream(imageFile); //发送图片名称 dos.writeUTF(imageFile.getName()); dos.flush(); //发送图片 byte[] sendBytes = new byte[1024*8]; int length; while ((length = fileInputStream.read(sendBytes, 0, sendBytes.length)) > 0){ dos.write(sendBytes, 0, length); dos.flush(); } //发送结束 socket.shutdownInput();//关闭输入流 socket.close(); dos.close();//在发送消息完之后一定关闭,否则服务端无法继续接收信息后处理,手机卡机 Log.i(TAG, "发送完成"); } catch (IOException e) { Log.i(TAG, "连接失败:"+e); e.printStackTrace(); } }).start(); } ``` 最后编辑:2023年03月14日 ©著作权归作者所有
表评论2938