最近写的项目里要用到文件传输的功能,因此我查找了很多相关的博文,查找到的文章都大同小异。
他们的demo能运行吗?也能运行,但是仅限于一些小文件,比如一个几百K到的txt文档,但是我们实际需要传输的数据量远大于这个数,就我们平时正常录个小视频,都要几十M的大小,因此要真应用那些方法,解决不了实际问题。
那接下来,这篇博文将用readfully方法实现较大文件的传输,当然这个也有一定的局限性,只适用一些较大的文件,大约在100M以内,对于更大的文件,涉及断点续传的问题,本篇文章没有展现。
由于我也是初学android,做一个app也是在一边学更多的知识,一边写这个项目,但是这个功能折磨了我很多天,如果这个功能实现不了,后面的工作都无法进行。
我之前应用其他人的方法放在我的项目里,要传输一个几十M的视频文件,一直报一个叫"Broken pipe"的错误,我觉得是因为传输的数据量过大导致信道终止。但是一次偶然查东西我发现了问题在哪,是接收信息端发生了问题,导致传输出现异常情况,我查阅的大量博文或资料里,通通都用read()方法来读取发送端传来的数据,但是read()方法只是将字节流中的数据读完,用read()去读,可能还没有读完就返回了,导致一些数据不能被正常读取,这个时候应用readFuly就能很好的解决这个问题。
(如果不用readFully的话用readByte方法也可以,但是在实测中,我传了一个不到一分钟的视频,大约30M,发过去用了7分钟,效率特别低。)
关于read和readFully区别可以看这个:https://blog.csdn.net/yangzhihello/article/details/8078684.
做成的效果是这样的:
server:
client:
下面放一下demo的代码,为了更直接的展现这个功能,demo中只写了sever端到client端传输数据这个的功能,至于优化等代码可以自行添加。
MainActivity:
public class MainActivity extends AppCompatActivity {
private Button client;
private Button server;
static TextView filename;
static TextView progress;
static int vis;
File file;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
client=findViewById(R.id.btn_client);
server=findViewById(R.id.btn_server);
progress=findViewById(R.id.text_progress);
filename=findViewById(R.id.text_file);
/*在实测时发现文件读取时android 8.1版本的手机好像不能用Environment.getExternalStorageDirectory()
读取路径下的文件,因此我们提前要把手机android版本号获取到,然后对此进行不同的操作*/
char Vision[] = null;
Vision = Build.VERSION.RELEASE.toCharArray();
//获取android版本号
for (int i = 0, j = 1; (i < Vision.length) && (Vision[i] != '.'); i++, j = j * 10) {
vis = vis * j + ((int) Vision[i] - 48);
}
//动态获取权限
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},1);
}
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
}
//根据版本进行不同操作来获取文件
//文件名称和路径一定要设对,不然后面的操作都无法进行
if(vis>9){
file=new File(Environment.getExternalStorageDirectory().getPath()+"/test123/1.mp4");
}
else{
file=new File("sdcard//storage/emulated/0/test123/"+"1.mp4");
}
client.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//网络等耗时操作不能在主线程中进行,因此开辟一个新的线程,进行网络传输数据的操作
new Thread(){
@Override
public void run() {
new Client();
filename.setText("文件("+Client.name+")接收完毕");
}
}.start();
}
});
server.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Server(file).start();
}
});
}
}
Server:
public class Server extends Thread{
private Socket socket;
private ServerSocket server;
private FileInputStream fis;
private DataOutputStream dos;
private File file;
private byte[] bytes;
Server(File file){
this.file=file;
}
public void run() {
try {
server = new ServerSocket(9999);
while (true) {
socket = server.accept();
MainActivity.progress.setText("发送中");
dos = new DataOutputStream(socket.getOutputStream());
fis = new FileInputStream(file);
bytes = new byte[(int)file.length()];
//传输文件名称
dos.writeUTF(file.getName());
dos.flush();
//传输文件长度
dos.writeLong((int)file.length());
dos.flush();
int len=-1;
// 将文件读取到字节数组
while ((len = fis.read(bytes)) != -1) {
// 把字节数组输出
dos.write(bytes);
}
dos.flush();
fis.close();
dos.close();
socket.close();
MainActivity.progress.setText(100 + "%");
MainActivity.filename.setText("文件("+file.getName()+")发送完毕");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Client:
public class Client{
private DataOutputStream dos;
private DataInputStream dis;
private Socket socket;
static String name;
Client(){
try {
//server端手机的ip地址
socket = new Socket("192.168.2.170", 9999);
dos = new DataOutputStream(socket.getOutputStream());
dis = new DataInputStream(socket.getInputStream());
name = dis.readUTF();// 文件名读取
long lengths = dis.readLong();
byte[] bt = new byte[(int) lengths];
long p = 0;
for (int i = 0; i < bt.length; i = i + 2 * 1024 * 1024, p = p + 2 * 1024 * 1024) {
//每次接收2*1024*1024字节的数据,接收到最后不足2*1024*1024字节时,将剩余数据全部接收
if (i + 2 * 1024 * 1024 > bt.length) {
dis.readFully(bt, i, bt.length - i);
//将传输的完成情况显示出来
MainActivity.progress.setText(String.valueOf(p * 100 / bt.length) + "%");
System.out.println(p * 100 / bt.length + "%");
} else {
dis.readFully(bt, i, 2 * 1024 * 1024);
//将传输的完成情况显示出来
MainActivity.progress.setText(String.valueOf(p * 100 / bt.length) + "%");
System.out.println(p * 100 / bt.length + "%");
}
}
//接收发送过来的文件
File file;
if (MainActivity.vis > 9) {
file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/MySocket/" + name);
} else {
file = new File("sdcard//" + "MySocket/" + name);
}
//文件夹或文件不存在就创建
if (!file.exists()) {
file.getParentFile().mkdirs();
file.createNewFile();
}
//将字节数组中的数据写入本地已创建好的文件里
FileOutputStream fops = new FileOutputStream(file.getAbsoluteFile());
fops.write(bt);
fops.flush();
fops.close();
dis.close();
dos.close();
socket.close();
System.out.println("文件接收完毕。");
MainActivity.progress.setText( "100%");
} catch(IOException e){
e.printStackTrace();
}
}
}
xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_file"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_above="@+id/btn_server"/>
<TextView
android:id="@+id/text_progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="30sp"
android:text="0%"
android:gravity="center"
android:layout_centerInParent="true"/>
<Button
android:id="@+id/btn_server"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:text="发送文件"
android:layout_centerHorizontal="true"
android:layout_above="@+id/btn_client"/>
<Button
android:id="@+id/btn_client"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:text="接收文件"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:layout_marginBottom="100dp"/>
</RelativeLayout>
需要注意的几点:
1.socket通信只能在同一子网下,也就是说两个手机必须连接同一WIFI,不能说一个手机连着WIFI,一个手机用流量。
2.涉及到文件读取和联网的相关权限一定要加好,除了权限尤其不要忘了在mainifests文件里加
android:requestLegacyExternalStorage="true"
这里是源码
Android Socket文件传输源码下载