安卓间、安卓PC间通过局域网传输文件
声明:本文参考自网络资料,绝非首创,感谢以下,但不止于以下几篇博客:
https://www.cnblogs.com/yangfengwu/p/12543923.html
https://blog.youkuaiyun.com/cai13160674275/article/details/50152579
https://blog.youkuaiyun.com/feibafeibafeiba/article/details/53516574
1. A2ATransferActivity.Java
A2ATransferActivity.Java
public class A2ATransferActivity extends AppCompatActivity implements View.OnClickListener {
private static final int FILE_CODE = 0;
private static final int REQUEST_CODE = 1;
private TextView tvMsg;
private EditText txtIP, txtPort, txtEt;
private Button btnSend;
private Handler handler;
private SocketManager socketManager;
private final String[] PERMISSIONS_STORAGE = {
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE" };
private final String TAG = A2ATransferActivity.this.getClass().getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_a2a_transfer);
tvMsg = (TextView) findViewById(R.id.tvMsg);
txtIP = (EditText) findViewById(R.id.txtIP);
txtPort = (EditText) findViewById(R.id.txtPort);
txtEt = (EditText) findViewById(R.id.et);
btnSend = (Button) findViewById(R.id.btnSend);
ActivityCompat.requestPermissions(A2ATransferActivity.this, PERMISSIONS_STORAGE, REQUEST_CODE);
btnSend.setOnClickListener(this);
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
SimpleDateFormat format = new SimpleDateFormat("hh:mm:ss");
txtEt.append("\n[" + format.format(new Date()) + "]" + msg.obj.toString());
break;
case 1:
tvMsg.setText("本机IP:" + GetIpAddress() + " 监听端口:" + msg.obj.toString());
break;
case 2:
Toast.makeText(getApplicationContext(), msg.obj.toString(), Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
};
socketManager = new SocketManager(handler);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnSend:
Intent i = new Intent(A2ATransferActivity.this, FilePickerActivity.class);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, true);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, false);
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_FILE);
i.putExtra(FilePickerActivity.EXTRA_START_PATH, Environment.getExternalStorageDirectory().getPath());
Log.i(TAG, "onClick: Environment.getExternalStorageDirectory().getPath()" + Environment.getExternalStorageDirectory().getPath());
startActivityForResult(i, FILE_CODE);
break;
default:
break;
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.i(TAG, "onActivityResult: + resultCode" + resultCode);
if (requestCode == FILE_CODE && resultCode == Activity.RESULT_OK) {
final String ipAddress = txtIP.getText().toString();
final int port = Integer.parseInt(txtPort.getText().toString());
if (data.getBooleanExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, true)) {
// For JellyBean and above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
ClipData clip = data.getClipData();
final ArrayList<String> fileNames = new ArrayList<>();
final ArrayList<String> paths = new ArrayList<>();
if (clip != null) {
for (int i = 0; i < clip.getItemCount(); i++) {
Uri uri = clip.getItemAt(i).getUri();
paths.add(uri.getPath());
fileNames.add(uri.getLastPathSegment());
}
Message.obtain(handler, 0, "正在发送至" + ipAddress + ":" + port).sendToTarget();
Thread sendThread = new Thread(new Runnable() {
@Override
public void run() {
Log.i(TAG, "run:fileNames.size() = " + fileNames.size());
socketManager.SendFile(fileNames, paths, ipAddress, port);
}
});
sendThread.start();
}
} else {
final ArrayList<String> paths = data.getStringArrayListExtra
(FilePickerActivity.EXTRA_PATHS);
final ArrayList<String> fileNames = new ArrayList<>();
if (paths != null) {
for (String path : paths) {
Uri uri = Uri.parse(path);
paths.add(uri.getPath());
fileNames.add(uri.getLastPathSegment());
socketManager.SendFile(fileNames, paths, ipAddress, port);
}
Message.obtain(handler, 0, "正在发送至" + ipAddress + ":" + port).sendToTarget();
Thread sendThread = new Thread(new Runnable() {
@Override
public void run() {
socketManager.SendFile(fileNames, paths, ipAddress, port);
}
});
sendThread.start();
}
}
}
}
super.onActivityResult(requestCode, resultCode, data);
}
@Override
protected void onDestroy() {
super.onDestroy();
//System.exit(0);
}
public String GetIpAddress() {
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
int i = wifiInfo.getIpAddress();
return (i & 0xFF) + "." +
((i >> 8) & 0xFF) + "." +
((i >> 16) & 0xFF) + "." +
((i >> 24) & 0xFF);
}
}
2. SocketManager
SocketManager
public class SocketManager {
private ServerSocket ss;
private Handler handler = null;
private final String TAG = SocketManager.this.getClass().getSimpleName();
private final String regEx = "/root/";
public SocketManager(Handler handler) {
this.handler = handler;
int port = 9999;
while (port > 9000) {
try {
//1. 建立连接监听窗口;
ss = new ServerSocket(port);
Log.i(TAG, "server.getReuseAddress() = " + ss.getReuseAddress());
if (!ss.getReuseAddress()) {
ss.setReuseAddress(true);
}
break;
} catch (Exception e) {
port--;
Log.i(TAG, "SocketManager: e = " + e);
}
}
SendMessage(1, port);
Thread receiveFileThread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
Log.i(TAG, "ReceiveFile run: 被调用了! ");
ReceiveFile();
}
}
});
receiveFileThread.start();
}
void SendMessage(int what, Object obj) {
if (handler != null) {
Message.obtain(handler, what, obj).sendToTarget();
}
}
public void SendFile(ArrayList<String> fileName, ArrayList<String> path, String ipAddress, int port) {
Log.i(TAG, "SendFile:fileName(0) = " + fileName.get(0).toString());
Log.i(TAG, "SendFile:path(0) = " + path.get(0).toString());
try {
Socket name = new Socket(ipAddress, port);
Log.i(TAG, "SendFile: Socketname = " + name);
for (int i = 0; i < fileName.size(); i++) {
OutputStream outputName = name.getOutputStream();
OutputStreamWriter outputWriter = new OutputStreamWriter(outputName);
BufferedWriter bwName = new BufferedWriter(outputWriter);
bwName.write(fileName.get(i));
bwName.close();
outputWriter.close();
outputName.close();
name.close();
SendMessage(0, "正在发送" + fileName.get(i));
Socket data = new Socket(ipAddress, port);
Log.i(TAG, "SendFile: Socketdata = " + data);
OutputStream outputData = data.getOutputStream();
Log.i(TAG, "outputData = " + outputData);
Log.i(TAG, "path.get(i) = " + path.get(i));
//File file = new File(path.get(i));
//以下这行爆出:文件不存在;原因在于默认得到的目录是 带有 /root/前缀的;故输入流无法创建成功;
String tempPath = path.get(i).replaceAll(regEx, "");
FileInputStream fileInput = new FileInputStream(tempPath);
Log.i(TAG, "fileInput = " + fileInput);
//判断是否读到文件末尾
int size = -1;
byte[] buffer = new byte[1024];
Log.i(TAG, "SendFile: size = " + size);
while ((size = fileInput.read(buffer)) > 0) {
Log.i(TAG, "SendFile: While : size = " + size);
outputData.write(buffer, 0, size);
outputData.flush();
}
//告诉服务端,文件已传输完毕
//data.shutdownOutput();
Log.i(TAG, "SendFile: While : 完成 " + size);
outputData.close();
fileInput.close();
data.close();
SendMessage(0, fileName.get(i) + " 发送完成");
SendMessage(0, "所有文件发送完成");
}
} catch (Exception e) {
Log.i(TAG, "发送错误 + " + e.getMessage());
SendMessage(0, "发送错误:\n" + e.getMessage());
}
}
void ReceiveFile() {
Log.i(TAG, "ReceiveFile: 被用调了! ");
try {
//2. 连接客户端对象
//阻塞式方法,只有客户端连接了才会继续往下运行
while (true) {
Socket name = ss.accept();
Log.i(TAG, "ReceiveFile Socket name = " + name);
//3.获取客户端发来的数据
InputStream nameIPS = name.getInputStream();
Log.i(TAG, "ReceiveFile :nameIPS = " + nameIPS);
InputStreamReader inputStreamReader = new InputStreamReader(nameIPS);
Log.i(TAG, "ReceiveFile inputStreamReader = " + inputStreamReader);
//4.开始读取,获取输入信息
BufferedReader br = new BufferedReader(inputStreamReader);
String fileName = br.readLine();
Log.i(TAG, "ReceiveFile br = " + br + " fileName = br.readLine() = " + br.readLine());
br.close();
inputStreamReader.close();
nameIPS.close();
name.close();
SendMessage(0, "正在接收:" + fileName);
Socket data = ss.accept();
Log.i(TAG, "ReceiveFile Socket data = " + data);
//3. 获取客户端发送过来的数据
InputStream dataIPS = data.getInputStream();
Log.i(TAG, "ReceiveFile: dataIPS = " + dataIPS);
//BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(dataIPS));
String savePath = Environment.getExternalStorageDirectory().getPath() + "/DCIM/Screenshots/" + fileName;
savePath = savePath.replaceAll(regEx, "");
Log.i(TAG, "ReceiveFile savePath = " + savePath);
FileOutputStream fos = new FileOutputStream(savePath, false);
Log.i(TAG, "ReceiveFile FileOutputStream fos = " + fos);
//装载文件名的数组
byte[] buffer = new byte[1024];
int size;
while ((size = dataIPS.read(buffer)) != -1) {
Log.i(TAG, "ReceiveFile size == " + size);
fos.write(buffer, 0, size);
fos.flush();
}
fos.flush();
fos.close();
//告诉发送端我已经接收完毕
//OutputStream outputStream = data.getOutputStream();
fos.close();
dataIPS.close();
data.close();
SendMessage(0, fileName + "接收完成");
}
} catch (Exception e) {
SendMessage(0, "接收错误:\n" + e.getMessage());
}
}
}
3. activity_a2a_transfer.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="16dp"
android:paddingTop="16dp"
android:paddingRight="16dp"
android:paddingBottom="16dp">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tvMsg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:focusable="false"
android:focusableInTouchMode="false"
android:text="TextView"
android:textColor="#AAA" />
<EditText
android:id="@+id/txtIP"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/tvMsg"
android:layout_centerVertical="true"
android:contentDescription="目标IP地址"
android:ems="10"
android:text="192.168.1.10" />
<EditText
android:id="@+id/txtPort"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/txtIP"
android:layout_alignLeft="@+id/txtIP"
android:ems="10"
android:text="9999" />
<EditText
android:id="@+id/et"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_below="@+id/txtPort"
android:layout_alignLeft="@+id/txtIP"
android:clickable="false"
android:editable="false"
android:ems="10"
android:focusable="false"
android:focusableInTouchMode="false"
android:gravity="center_vertical|left|top"
android:inputType="textMultiLine"
android:longClickable="false"
android:scrollbarAlwaysDrawVerticalTrack="true"
android:scrollbarStyle="insideInset"
android:scrollbars="vertical"
android:textSize="15dp">
<requestFocus />
</EditText>
<Button
android:id="@+id/btnSend"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/et"
android:layout_alignLeft="@+id/txtPort"
android:text="选择文件并发送" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#090909"
android:textSize="16sp"
android:text="注意:\n本机IP应在WIFI设置中设置为静态!\n文件默认保存在手机截图的默认保存位置\n输入框输入接收对象的IP;"
android:layout_below="@+id/btnSend"/>
</RelativeLayout>
</ScrollView>
</RelativeLayout>
4. 依赖
//使用指南:https://github.com/spacecowboy/NoNonsense-FilePicker
implementation 'com.nononsenseapps:filepicker:4.1.0'
5. manifest
-
权限
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
-
在 application 中添加如下:
<activity android:name="com.nononsenseapps.filepicker.FilePickerActivity" android:label="选择文件" android:theme="@style/FilePickerTheme"> <intent-filter> <action android:name="android.intent.action.GET_CONTENT" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/nnf_provider_paths" /> </provider>
6. 注意
- 本文没有进行权限处理,打开应用,默认请求存储权限,如果选择禁止并没有做处理;
- 本文接收文件默认保存的目录在:安卓系统截图的保存位置:
DCIM/Scrennshots
,可以自行调试; - 使用时将安卓端 IP 设置为静态 IP;
- 推荐不错的项目:https://www.imooc.com/article/296288;这不是我的文章,不过文中两个项目十分不错,可以借鉴。
- 如果可行,请给我赞把。笔芯
声明:本文整理自网络,如有侵权请联系我。