因为客户端可能会多次发起连接请求,所以当此处文件传输完成后(不管成功或失败),都需要重新 startService
,让服务再次堵塞等待客户端的连接请求
FileTransfer 包含三个字段,MD5码值用于校验文件的完整性,fileLength
是为了用于计算文件的传输进度
public class FileTransfer implements Serializable {
//文件路径
private String
filePath;
//文件大小
private long
fileLength;
//MD5码
private String md5;
···
}
为了将文件传输进度发布到外部界面,所以除了需要启动Service外,界面还需要绑定Service,此处就需要用到一个更新文件传输状态的接口
public
interface OnProgressChangListener {
//当传输进度发生变化时
void
onProgressChanged(FileTransfer fileTransfer, int
progress);
//当传输结束时
void
onTransferFinished(File file);
}
因此,需要将 progressChangListener
作为参数传给 WifiServerService
,并在进度变化时更新进度对话框
private
WifiServerService.OnProgressChangListener progressChangListener =
new WifiServerService.OnProgressChangListener() {
@Override
public
void onProgressChanged(final FileTransfer fileTransfer, final int
progress) {
runOnUiThread(new Runnable()
{
@Override
public void run() {
progressDialog.setMessage("文件名: " + new
File(fileTransfer.getFilePath()).getName());
progressDialog.setProgress(progress);
progressDialog.show();
}
});
}
@Override
public
void onTransferFinished(final File file) {
runOnUiThread(new Runnable()
{
@Override
public void run() {
progressDialog.cancel();
if (file
!= null && file.exists()) {
openFile(file.getPath());
}
}
});
}
};
四、客户端加入群组并发起文件传输请求
文件发送界面 SendFileActivity 需要实现
DirectActionListener 接口 首先,需要先注册P2P广播,以便获取周边设备信息以及连接状态
@Override
protected
void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_send_file);
initView();
mWifiP2pManager =
(WifiP2pManager)
getSystemService(Context.WIFI_P2P_SERVICE);
mChannel =
mWifiP2pManager.initialize(this, getMainLooper(), this);
broadcastReceiver = new
DirectBroadcastReceiver(mWifiP2pManager, mChannel,
this);
registerReceiver(broadcastReceiver,
DirectBroadcastReceiver.getIntentFilter());
}
通过
discoverPeers 方法搜索周边设备,回调函数用于通知方法是否调用成功
mWifiP2pManager.discoverPeers(mChannel, new
WifiP2pManager.ActionListener() {
@Override
public
void onSuccess() {
showToast("Success");
}
@Override
public
void onFailure(int reasonCode) {
showToast("Failure");
loadingDialog.cancel();
}
});
当搜索结束后,系统就会触发
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION 广播,此时就可以调用
requestPeers 方法获取设备列表信息,此处用 RecyclerView 展示列表,在
onPeersAvailable 方法刷新列表
mWifiP2pManager.requestPeers(mChannel, new
WifiP2pManager.PeerListListener() {
@Override
public
void onPeersAvailable(WifiP2pDeviceList peers) {
mDirectActionListener.onPeersAvailable(peers.getDeviceList());
}
});
@Override
public
void onPeersAvailable(Collection wifiP2pDeviceList) {
Log.e(TAG, "onPeersAvailable
:" + wifiP2pDeviceList.size());
this.wifiP2pDeviceList.clear();
this.wifiP2pDeviceList.addAll(wifiP2pDeviceList);
deviceAdapter.notifyDataSetChanged();
loadingDialog.cancel();
}
之后,通过点击事件选中群主(服务器端)设备,通过 connect
方法请求与之进行连接
private void connect() {
WifiP2pConfig config = new WifiP2pConfig();
if
(config.deviceAddress != null && mWifiP2pDevice != null)
{
config.deviceAddress =
mWifiP2pDevice.deviceAddress;
config.wps.setup =
WpsInfo.PBC;
showLoadingDialog("正在连接 " +
mWifiP2pDevice.deviceName);
mWifiP2pManager.connect(mChannel, config, new
WifiP2pManager.ActionListener() {
@Override
public void onSuccess() {
Log.e(TAG,
"connect onSuccess");
}
@Override
public void onFailure(int reason) {
showToast("连接失败 " + reason);
dismissLoadingDialog();
}
});
}
}
此处依然无法通过函数函数来判断连接结果,需要依靠系统发出的
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION
方法来获取到连接结果,在此处可以通过 requestConnectionInfo
获取到组连接信息,信息最后通过 onConnectionInfoAvailable
方法传递出来,在此可以判断当前设备是否为群主,获取群组IP地址
@Override
public void
onConnectionInfoAvailable(WifiP2pInfo wifiP2pInfo) {
dismissLoadingDialog();
wifiP2pDeviceList.clear();
deviceAdapter.notifyDataSetChanged();
btn_disconnect.setEnabled(true);
btn_chooseFile.setEnabled(true);
Log.e(TAG,
"onConnectionInfoAvailable");
Log.e(TAG,
"onConnectionInfoAvailable groupFormed: " +
wifiP2pInfo.groupFormed);
Log.e(TAG,
"onConnectionInfoAvailable isGroupOwner: " +
wifiP2pInfo.isGroupOwner);
Log.e(TAG,
"onConnectionInfoAvailable getHostAddress: " +
wifiP2pInfo.groupOwnerAddress.getHostAddress());
StringBuilder stringBuilder = new StringBuilder();
if
(mWifiP2pDevice != null) {
stringBuilder.append("连接的设备名:");
stringBuilder.append(mWifiP2pDevice.deviceName);
stringBuilder.append("\n");
stringBuilder.append("连接的设备的地址:");
stringBuilder.append(mWifiP2pDevice.deviceAddress);
}
stringBuilder.append("\n");
stringBuilder.append("是否群主:");
stringBuilder.append(wifiP2pInfo.isGroupOwner ? "是群主" :
"非群主");
stringBuilder.append("\n");
stringBuilder.append("群主IP地址:");
stringBuilder.append(wifiP2pInfo.groupOwnerAddress.getHostAddress());
tv_status.setText(stringBuilder);
if
(wifiP2pInfo.groupFormed && !wifiP2pInfo.isGroupOwner)
{
this.wifiP2pInfo =
wifiP2pInfo;
}
}
至此服务器端和客户端已经通过 Wifi P2P
连接在了一起,客户端也获取到了服务器端的IP地址,在选取好待发送的文件后就可以主动发起对服务器端的连接请求了
发起选取文件的方法
Intent intent = new
Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*
public class WifiClientTask extends AsyncTask
{
private
ProgressDialog progressDialog;
private
FileTransfer fileTransfer;
private
static final int PORT = 4786;
private
static final String TAG = "WifiClientTask";
public
WifiClientTask(Context context, FileTransfer fileTransfer)
{
this.fileTransfer =
fileTransfer;
progressDialog = new
ProgressDialog(context);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setCancelable(false);
progressDialog.setCanceledOnTouchOutside(false);
progressDialog.setTitle("正在发送文件");
progressDialog.setMax(100);
}
@Override
protected
void onPreExecute() {
progressDialog.show();
}
@Override
protected
Boolean doInBackground(String... strings) {
fileTransfer.setMd5(Md5Util.getMd5(new
File(fileTransfer.getFilePath())));
Log.e(TAG, "文件的MD5码值是:" +
fileTransfer.getMd5());
Socket socket =
null;
OutputStream outputStream =
null;
ObjectOutputStream
objectOutputStream = null;
InputStream inputStream =
null;
try {
socket = new Socket();
socket.bind(null);
socket.connect((new
InetSocketAddress(strings[0], PORT)), 10000);
outputStream =
socket.getOutputStream();
objectOutputStream = new
ObjectOutputStream(outputStream);
objectOutputStream.writeObject(fileTransfer);
inputStream = new FileInputStream(new
File(fileTransfer.getFilePath()));
long fileSize =
fileTransfer.getFileLength();
long total = 0;
byte buf[] = new byte[512];
int len;
while ((len = inputStream.read(buf)) != -1)
{
outputStream.write(buf, 0, len);
total +=
len;
int
progress = (int) ((total * 100) / fileSize);
publishProgress(progress);
Log.e(TAG,
"文件发送进度:" + progress);
}
outputStream.close();
objectOutputStream.close();
inputStream.close();
socket.close();
outputStream = null;
objectOutputStream = null;
inputStream = null;
socket = null;
Log.e(TAG, "文件发送成功");
return true;
} catch (Exception e)
{
Log.e(TAG, "文件发送异常 Exception: " +
e.getMessage());
} finally {
if (outputStream != null) {
try
{
outputStream.close();
} catch
(IOException e) {
e.printStackTrace();
}
}
if (objectOutputStream != null) {
try
{
objectOutputStream.close();
} catch
(IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try
{
inputStream.close();
} catch
(IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try
{
socket.close();
} catch
(Exception e) {
e.printStackTrace();
}
}
}
return false;
}
@Override
protected
void onProgressUpdate(Integer... values) {
progressDialog.setProgress(values[0]);
}
@Override
protected
void onPostExecute(Boolean aBoolean) {
progressDialog.cancel();
Log.e(TAG, "onPostExecute: "
+ aBoolean);
}
}
五、校验文件完整性
传输文件的完整性主要是通过计算文件的MD5码值来保证了,在发送文件前,即在 WifiClientTask 的
doInBackground 方法中进行计算,将MD5码值赋给 FileTransfer 模型,通过如下方法计算得到
public class Md5Util {
public
static String getMd5(File file) {
InputStream inputStream =
null;
byte[] buffer = new
byte[2048];
int numRead;
MessageDigest md5;
try {
inputStream = new
FileInputStream(file);
md5 =
MessageDigest.getInstance("MD5");
while ((numRead = inputStream.read(buffer)) >
0) {
md5.update(buffer, 0, numRead);
}
inputStream.close();
inputStream = null;
return md5ToString(md5.digest());
} catch (Exception e)
{
return null;
} finally {
if (inputStream != null) {
try
{
inputStream.close();
} catch
(IOException e) {
e.printStackTrace();
}
}
}
}
private
static String md5ToString(byte[] md5Bytes) {
StringBuilder hexValue = new
StringBuilder();
for (byte b : md5Bytes)
{
int val = ((int) b) & 0xff;
if (val < 16) {
hexValue.append("0");
}
hexValue.append(Integer.toHexString(val));
}
return
hexValue.toString();
}
}
因为客户端会将 FileTransfer
传给服务器端,所以服务器端在文件传输结束后,可以重新计算文件的MD5码值,进行对比以判断文件是否完整