Android无网络传输文件之WifiP2P 您所在的位置:网站首页 文件传输上传失败请检查网络是否连接 Android无网络传输文件之WifiP2P

Android无网络传输文件之WifiP2P

2024-07-16 22:24| 来源: 网络整理| 查看: 265

    WifiP2P是在 Android 4.0 以上系统中加入的功能,通过WifiP2P可以在不连接网络的情况下,直接与配对的设备进行数据交换。他相比蓝牙传输速率更快,更远;相比网络传输过程中不会消耗流量。WifiP2P的传输依赖于无限WiFi,因此设备之间通信需要连接同一个WiFi网络。在WifiP2P技术中有一个核心类WifiP2pManager,他提供了所有的通信相关的广播信息,监听信息,设备信息以及初始化操作等。在信息传输过程中需要有个接收端接收信息和发送端发送信息,这里称为客户端和服务端,客户端和服务端通过WifiP2P作为传输手段,通过socket作为信息载体,进行通信。实现的效果如下所示:

客户端:

服务端:

    现在一步步来实现它,进一步揭开WifiP2P神秘的面纱。

一、权限申请

   

   

   

   

   

   

   

     需要注意的是由于传输过程中用到socket技术,所以需要添加网络权限,传输过程中是不需要消耗流量的。在清单文件中注册的权限中读写文件的权限是敏感权限,在6.0以上的系统上无效,因此需要额外动态注册权限,在这里使用easypermissions开源的权限管理框架进行管理,在gradle中引入:

compile 'pub.devrel:easypermissions:1.1.0'

 然后在需要申请权限的地方进行注册权限,这里需要动态注册两个权限Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE。具体的注册方法这里不再详细介绍。

二、WifiP2P的初始化

   WifiP2P的初始化需要用到WifiP2pManager:

WifiP2pManager wifiP2pManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE); WifiP2pManager.Channel channel = wifiP2pManager.initialize(this, getMainLooper(), this); 三、注册广播

    与 WifiP2P相关的广播有以下几个:

    WIFI_P2P_STATE_CHANGED_ACTION //用于指示WifiP2P是否可用

    WIFI_P2P_PEERS_CHANGED_ACTION //peers列表发生变化

    WIFI_P2P_CONNECTION_CHANGED_ACTION //WifiP2P的连接状态发生了改变

    WIFI_P2P_THIS_DEVICE_CHANGED_ACTION //本设备的设备信息发生了变化

    当客户端接收到广播时候许需要创建BroadcastReceiver进行广播信息,在onReceive里面进行接收广播信息,在app开启的时候我们进行动态的注册广播,这样客户端和服务端都能收到广播信息。因此创建一个基类的BaseActivity,进行注册广播:

Wifip2pReceiver mWifip2pReceiver = new Wifip2pReceiver(mWifiP2pManager, mChannel, this); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); registerReceiver(mWifip2pReceiver, intentFilter);

    广播onReceive中获取的action状态比较多,有设备是否可用、是否连接、设备列表等等,在这写一个回调接口,将信息返回到对应的Activity上,在Activity上面实现监听并且在Wifip2pReceiver构造方法中对接口进行初始化操作。

public interface Wifip2pActionListener extends WifiP2pManager.ChannelListener { void wifiP2pEnabled(boolean enabled); void onConnection(WifiP2pInfo wifiP2pInfo); void onDisconnection(); void onDeviceInfo(WifiP2pDevice wifiP2pDevice); void onPeersInfo(Collection wifiP2pDeviceList); } public Wifip2pReceiver(WifiP2pManager wifiP2pManager, WifiP2pManager.Channel channel, Wifip2pActionListener listener) { mWifiP2pManager= wifiP2pManager; mChannel= channel; mListener = listener; }

    1、WiFiP2P是否可用,通过wifiP2pEnabled进行设置

case WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION: if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) { mListener.wifiP2pEnabled(true); } else { mListener.wifiP2pEnabled(false); } break;

    2、peers列表发生变化,可以通过 requestPeers 方法得到可用的设备列表,可以对列表中的某个设备进行连接

case WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION: mWifiP2pManager.requestPeers(mChannel, new WifiP2pManager.PeerListListener() { @Override public void onPeersAvailable(WifiP2pDeviceList peers) { mListener.onPeersInfo(peers.getDeviceList()); } }); break;

    3、WiFP2P连接发生变化,可以通过WifiP2pManager.requestConnectionInfo方法获取到设备是否连接

case WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION: NetworkInfo networkInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO); if (networkInfo.isConnected()){ mWifiP2pManager.requestConnectionInfo(mChannel, new WifiP2pManager.ConnectionInfoListener() { @Override public void onConnectionInfoAvailable(WifiP2pInfo info) { mListener.onConnection(info); } }); }else { mListener.onDisconnection(); } break;

    4、WiFiP2P设备信息发生变化时候,通过intent获取到设备信息

case WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION: WifiP2pDevice device = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE); mListener.onDeviceInfo(device); break;

    以上为整个广播接收者的内容代码,在广播中监听到回调信息,我们让基类的Activity实现这个接口,以便于处理回调信息。加上上面注册广播和初始化信息,所以基类的BaseActivity代码如下:

public class BaseActivity extends AppCompatActivity implements Wifip2pActionListener { private static final String TAG = "BaseActivity"; public WifiP2pManager mWifiP2pManager; public WifiP2pManager.Channel mChannel; public Wifip2pReceiver mWifip2pReceiver; public WifiP2pInfo mWifiP2pInfo; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //注册WifiP2pManager mWifiP2pManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE); mChannel = mWifiP2pManager.initialize(this, getMainLooper(), this); //注册广播 mWifip2pReceiver = new Wifip2pReceiver(mWifiP2pManager, mChannel, this); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); registerReceiver(mWifip2pReceiver, intentFilter); } @Override protected void onDestroy() { super.onDestroy(); //注销广播 unregisterReceiver(mWifip2pReceiver); mWifip2pReceiver = null; } @Override public void wifiP2pEnabled(boolean enabled) { Log.e(TAG, "传输通道是否可用:" + enabled); } @Override public void onConnection(WifiP2pInfo wifiP2pInfo) { if (wifiP2pInfo != null) { mWifiP2pInfo = wifiP2pInfo; Log.e(TAG, "WifiP2pInfo:" + wifiP2pInfo); } } @Override public void onDisconnection() { Log.e(TAG, "连接断开"); } @Override public void onDeviceInfo(WifiP2pDevice wifiP2pDevice) { Log.e(TAG, "当前的的设备名称" + wifiP2pDevice.deviceName); } @Override public void onPeersInfo(Collection wifiP2pDeviceList) { for (WifiP2pDevice device : wifiP2pDeviceList) { Log.e(TAG, "连接的设备信息:" + device.deviceName + "--------" + device.deviceAddress); } } @Override public void onChannelDisconnected() { } } 四、客户端创建

    创建一个SendFileActivity作为客户端的Activity,继承自BaseActivity,作为客户端。客户端发送信息到服务端,服务端需要提供组群信息,供客户端连接,关于服务端组群信息之后会有处理,这里先知道如何搜索服务端设备,然后配对连接,连接成功后就可以把所需要传输的文件信息以socket的形式发送给服务端,服务端监听socket端口,获取信息流,写入文件,这就是整个传输信息中客户端和服务端的交互过程。按照这样的步骤,客户端需要实现以下几点:

搜索设备信息选择设备连接服务端组群信息选择要传输的文件路径把该文件通过socket发送到服务端

    我们就按照这四点一步步实现:

    1、搜索设备信息

    通过mWifiP2pManager的discoverPeers方法进行搜索,有搜索成功和搜索失败的回调,搜索到设备时候会触发WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION 广播,此时就可以调用 requestPeers 方法获取设备列表信息,在回调方法onPeersInfo中可以得到设备信息。

mWifiP2pManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { Log.e(TAG, "搜索设备成功"); } @Override public void onFailure(int reasonCode) { Log.e(TAG, "搜索设备失败"); } });      2、选择设备连接服务端组群信息

     搜索到的设备信息肯能不止一个,所以需要选择一个设备进行连接,当选择好设备之后,手动调用mWifiP2pManager.connect方法,进行设备连接,会触发WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION的广播,收到连接成功和失败的回调,在回到中可以得到WifiP2pInfo信息。

WifiP2pConfig config = new WifiP2pConfig(); if (wifiP2pDevice != null) { //需要将address,WpsInfo.PBC信息包装成WifiP2pConfig config.deviceAddress = wifiP2pDevice.deviceAddress; config.wps.setup = WpsInfo.PBC; mWifiP2pManager.connect(mChannel, config, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { Log.e(TAG, "连接成功"); Toast.makeText(SendFileActivity.this, "连接成功", Toast.LENGTH_SHORT).show(); } @Override public void onFailure(int reason) { Log.e(TAG, "连接失败"); Toast.makeText(SendFileActivity.this, "连接失败", Toast.LENGTH_SHORT).show(); } }); }     3、选择要传输的文件

   指的是sd卡的文件路径,如下跳转到文件管理,进行选择:

Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("*/*"); intent.addCategory(Intent.CATEGORY_OPENABLE); startActivityForResult(intent, 10);

   对于文件封装成FileBean,封装为三个参数:路径、文件大小、文件的MD5,如下:

public class FileBean implements Serializable{ public static final String serialVersionUID = "6321689524634663223356"; public String filePath; public long fileLength; //MD5码:保证文件的完整性 public String md5; public FileBean(String filePath, long fileLength, String md5) { this.filePath = filePath; this.fileLength = fileLength; this.md5 = md5; } }

    选择好之后会回调onActivityResult方法,可以在这里进行判断:

@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == 10) { if (resultCode == RESULT_OK) { Uri uri = data.getData(); if (uri != null) { String path = FileUtils.getAbsolutePath(this, uri); if (path != null) { final File file = new File(path); if (!file.exists() || mWifiP2pInfo == null) { Toast.makeText(SendFileActivity.this,"文件路径找不到",Toast.LENGTH_SHORT).show(); return; } String md5 = Md5Util.getMd5(file); FileBean fileBean = new FileBean(file.getPath(), file.length(), md5); String hostAddress = mWifiP2pInfo.groupOwnerAddress.getHostAddress(); new SendTask(SendFileActivity.this, fileBean).execute(hostAddress); } } } } }     4、把该文件通过socket发送到服务端

    获取到文件后,就可以通过socket发送文件到服务端,在创建socket的时候需要ip,ip地址可以从WifiP2pInfo获取到,WifiP2pInfo在回调方法中可以获取到,当设备连接上的时候会触发这个回调,在这里进行了初始化WifiP2pInfo。

    Socket的发送是需要操作IO流的,比较耗时的操作,这里使用AsyncTask在子线程里面进行操作。同时为了监听读取进度,读取完成等情况,来跟新UI进度条,需要定义发送回调接口如下:

public interface ProgressSendListener { //当传输进度发生变化时 void onProgressChanged(File file, int progress); //当传输结束时 void onFinished(File file); //传输失败时 void onFaliure(File file); }

    回调接口在socket的操作中进行设置回调数据,关于socket的完整代码如下:

public class SendSocket { public static final String TAG = "SendSocket"; public static final int PORT = 10000; private FileBean mFileBean; private String mAddress; private File mFile; private ProgressSendListener mlistener; private Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 10: int progress = (int) msg.obj; if (mlistener != null) { mlistener.onProgressChanged(mFile, progress); } break; case 20: if (mlistener != null) { mlistener.onFinished(mFile); } break; case 30: if (mlistener != null) { mlistener.onFaliure(mFile); } break; } } }; public SendSocket(FileBean fileBean, String address, ProgressSendListener listener) { mFileBean = fileBean; mAddress = address; mlistener = listener; } public void createSendSocket() { try { Socket socket = new Socket(); InetSocketAddress inetSocketAddress = new InetSocketAddress(mAddress, PORT); socket.connect(inetSocketAddress); OutputStream outputStream = socket.getOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); objectOutputStream.writeObject(mFileBean); mFile = new File(mFileBean.filePath); FileInputStream inputStream = new FileInputStream(mFile); long size = mFileBean.fileLength; long total = 0; byte bytes[] = new byte[1024]; int len; while ((len = inputStream.read(bytes)) != -1) { outputStream.write(bytes, 0, len); total += len; int progress = (int) ((total * 100) / size); Log.e(TAG, "文件发送进度:" + progress); Message message = Message.obtain(); message.what = 10; message.obj = progress; mHandler.sendMessage(message); } outputStream.close(); objectOutputStream.close(); inputStream.close(); socket.close(); mHandler.sendEmptyMessage(20); Log.e(TAG, "文件发送成功"); } catch (Exception e) { mHandler.sendEmptyMessage(30); Log.e(TAG, "文件发送异常"); } } }

    设置好回调信息后,让AsyncTask实现该接口,更新UI,核心代码如下:

@Override protected Void doInBackground(String... strings) { mSendSocket = new SendSocket(mFileBean, strings[0], this); mSendSocket.createSendSocket(); return null; }     就这样客户端的功能完成。 五、服务端创建

    服务端创主要用监听客户端发送过来的文件,接收传送过来的文件。建一个服务端的ReceiveFileActivity继承自BaseActivity,作为服务端的界面,需要实现以下功能:

创建组群信息,供客户端连接移除组群信息监听客户端发送过来的文件信息

    接下来分别实现以上三个功能。

    1、创建组群信息,供客户端连接

    使用WifiP2pManager的createGroup方法进行设置,有设置成功和失败的回调。

mWifiP2pManager.createGroup(mChannel, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { Log.e(TAG, "创建群组成功"); Toast.makeText(ReceiveFileActivity.this, "创建群组成功", Toast.LENGTH_SHORT).show(); } @Override public void onFailure(int reason) { Log.e(TAG, "创建群组失败: " + reason); Toast.makeText(ReceiveFileActivity.this, "创建群组失败,请移除已有的组群或者连接同一WIFI重试", Toast.LENGTH_SHORT).show(); } });    2、移除组群信息

    使用WifiP2pManager的removeGroup方法进行设置,也有设置成功和失败的回调。

mWifiP2pManager.removeGroup(mChannel, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { Log.e(TAG, "移除组群成功"); Toast.makeText(ReceiveFileActivity.this, "移除组群成功", Toast.LENGTH_SHORT).show(); } @Override public void onFailure(int reason) { Log.e(TAG, "移除组群失败"); Toast.makeText(ReceiveFileActivity.this, "移除组群失败,请创建组群重试", Toast.LENGTH_SHORT).show(); } });     3、监听客户端发送过来的文件信息

    作为服务端要不断地监听客户端socket的端口来获取客户端的IO信息, 所以需要开启一个服务在后台监听客户端socket,因此创建一个Wifip2pService继承自IntentService,在onHandleIntent中创建ServerSocket,当ReceiveFileActivity启动的时候开启服务,代码如下:

public class Wifip2pService extends IntentService { private static final String TAG = "Wifip2pService"; private ReceiveSocket mReceiveSocket; public Wifip2pService() { super("Wifip2pService"); } @Nullable @Override public IBinder onBind(Intent intent) { return new MyBinder(); } public class MyBinder extends Binder { public MyBinder() { super(); } public void initListener(ReceiveSocket.ProgressReceiveListener listener){ mReceiveSocket.setOnProgressReceiveListener(listener); } } @Override public void onCreate() { super.onCreate(); Log.e(TAG, "服务启动了"); } @Override protected void onHandleIntent(Intent intent) { mReceiveSocket = new ReceiveSocket(); mReceiveSocket.createServerSocket(); Log.e(TAG, "传输完毕"); } @Override public void onDestroy() { super.onDestroy(); mReceiveSocket.clean(); } }

        ReceiveSocket是一个ServerSocket的封装类,类似于客户端socket的封装,为了监听接收进度,同样需要创建监听接口对信息的写入进度等信息进行监听。Wifip2pService服务中的方法是对listener的一个初始化设置:

private ProgressReceiveListener mListener; public void setOnProgressReceiveListener(ProgressReceiveListener listener) { mListener = listener; } public interface ProgressReceiveListener { //开始传输 void onSatrt(); //当传输进度发生变化时 void onProgressChanged(File file, int progress); //当传输结束时 void onFinished(File file); //传输失败回调 void onFaliure(File file); }

    在ReceiveSocket中监听进度等信息,设置ProgressReceiveListener的各个监听接口信息。代码如下:

public class ReceiveSocket { public static final String TAG = "ReceiveSocket"; public static final int PORT = 10000; private ServerSocket mServerSocket; private Socket mSocket; private InputStream mInputStream; private ObjectInputStream mObjectInputStream; private FileOutputStream mFileOutputStream; private File mFile; private Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 40: if (mListener != null) { mListener.onSatrt(); } break; case 50: int progress = (int) msg.obj; if (mListener != null) { mListener.onProgressChanged(mFile, progress); } break; case 60: if (mListener != null) { mListener.onFinished(mFile); } break; case 70: if (mListener != null) { mListener.onFaliure(mFile); } break; } } }; public void createServerSocket() { try { mServerSocket = new ServerSocket(); mServerSocket.setReuseAddress(true); mServerSocket.bind(new InetSocketAddress(PORT)); mSocket = mServerSocket.accept(); Log.e(TAG, "客户端IP地址 : " + mSocket.getRemoteSocketAddress()); mInputStream = mSocket.getInputStream(); mObjectInputStream = new ObjectInputStream(mInputStream); FileBean fileBean = (FileBean) mObjectInputStream.readObject(); String name = new File(fileBean.filePath).getName(); Log.e(TAG, "客户端传递的文件名称 : " + name); Log.e(TAG, "客户端传递的MD5 : " + fileBean.md5); mFile = new File(FileUtils.SdCardPath(name)); mFileOutputStream = new FileOutputStream(mFile); //开始接收文件 mHandler.sendEmptyMessage(40); byte bytes[] = new byte[1024]; int len; long total = 0; int progress; while ((len = mInputStream.read(bytes)) != -1) { mFileOutputStream.write(bytes, 0, len); total += len; progress = (int) ((total * 100) / fileBean.fileLength); Log.e(TAG, "文件接收进度: " + progress); Message message = Message.obtain(); message.what = 50; message.obj = progress; mHandler.sendMessage(message); } //新写入文件的MD5 String md5New = Md5Util.getMd5(mFile); //发送过来的MD5 String md5Old = fileBean.md5; if (md5New != null || md5Old != null) { if (md5New.equals(md5Old)) { mHandler.sendEmptyMessage(60); Log.e(TAG, "文件接收成功"); } } else { mHandler.sendEmptyMessage(70); } mServerSocket.close(); mInputStream.close(); mObjectInputStream.close(); mFileOutputStream.close(); } catch (Exception e) { mHandler.sendEmptyMessage(70); Log.e(TAG, "文件接收异常"); } } public void clean() { if (mServerSocket != null) { try { mServerSocket.close(); } catch (IOException e) { e.printStackTrace(); } } if (mInputStream != null) { try { mInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (mObjectInputStream != null) { try { mObjectInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (mFileOutputStream != null) { try { mFileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }    让服务端的ReceiveFileActivity实现ProgressReceiveListener接口,为了初始化ProgressReceiveListener需要在开启服务之后绑定服务,监听文件的接收情况,从而更新接受进度,核心代码如下: private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { //调用服务里面的方法进行绑定 mBinder = (Wifip2pService.MyBinder) service; mBinder.initListener(ReceiveFileActivity.this); } @Override public void onServiceDisconnected(ComponentName name) { //服务断开重新绑定 bindService(mIntent, serviceConnection, Context.BIND_AUTO_CREATE); } }; Intent intent = new Intent(ReceiveFileActivity.this, Wifip2pService.class); startService(intent); bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);

    在每次接收完毕文件的时候需要释放Socket资源重新准备接收下次文件,因此需要在onFinished回调中重启一次服务,为下次的接收做好准备:

@Override public void onFinished(File file) { Log.e(TAG, "接收完成"); Toast.makeText(this, file.getName() + "接收完毕!", Toast.LENGTH_SHORT).show(); //接收完毕后再次启动服务等待下载一次连接 clear(); startService(mIntent); bindService(mIntent, serviceConnection, Context.BIND_AUTO_CREATE); }

    最后别忘记了当Activity关闭的时候释放资源,解绑服务:

@Override protected void onDestroy() { super.onDestroy(); clear(); } private void clear() { if (serviceConnection != null) { unbindService(serviceConnection); } if (mIntent != null) { stopService(mIntent); } }

    以上,就是WIFIP2P服务端的代码详解。至此关于WIFIP2P的应用已经介绍完毕,如果有什么问题欢迎大家提出。最后总结一下WIFIP2P的整体流程:

    1、声明权限。

    2、清单文件注册权限。

    3、注册Wifi P2P相关广播。

    4、创建客户端socket,把选择的文件解析成IO流,发送信息。

    5、创建服务端server,在server内创建服务端socket,监听客户端socket端口,获取信息。

    6、服务端创建连接的组群信息提供给客户端连接。

    7、客户端连接信息组群和服务端建立WiFip2p连接。

    8、客户端通过socket发送文件到服务端serversocket服务端监听到端口后就会获取信息,写入文件。

    源码地址:https://github.com/yoonerloop/WifiP2P,记得start点赞哦。吐舌头吐舌头点击打开链接



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有