需求
把Android手机的mp3文件以TCP的传输方式传递到Win10的C#程序上,并且附带歌名等信息。
PC端的要求是传递一个序列化后的文件过去,里面包含了文件本体和文件名等信息。
该序列化(C#这里的序列化好像和Java里面相差甚大啊!起初我是一头雾水的)后的文件格式如下:
( ⊙o⊙ )? 什么鬼,没办法硬着头皮试,起初根本不知道这是什么码,因为在Android里,传递文件最底层的就是字节流,那这个又是什么编码呢。
尝试
方式有很多种,比如把所有信息转成字节传递过去,但是告知对方在哪里是什么,这种很麻烦;把文件转成byte[]后放入一个类中,该类携带歌曲信息以byte[]发送过去。
尝试了几种方法:
1、 第一次(失败)
ObjectOutputSteam object = new ObjectOutputStream(socket.getOutputStream());
通过对象流发送,发送过去后直接乱码了,不用说,肯定是不能通过这样的方式发送了,因为对象流是JVM之间的一种传递方式,把对象以这种方式传递明显行不通。
2、 第二次(失败)
直接利用文件流把文件转成byte[]然后传递过去,但是这种传递没有办法传递歌曲信息,尝试用类来包裹
byte[] musicBytes = getBytes(filePath);//歌曲文件的字节数组
SimpleMusicEntity simpleMusicEntity = new SimpleMusicEntity();
simpleMusicEntity.setMusicBytes(musicBytes);
simpleMusicEntity.setTitle(title);
/**
* 获得指定文件的byte数组
*/
public static byte[] getBytes(String filePath) {
byte[] buffer = null;
try {
File file = new File(filePath);
FileInputStream fis = new FileInputStream(file);
ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
byte[] b = new byte[1024];
int n;
while ((n = fis.read(b)) != -1) {
bos.write(b, 0, n);
}
fis.close();
bos.close();
buffer = bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return buffer;
}
so,问题来了,我得到了一个类SimpleMusicEntity,里面富含了歌曲的所有信息(名称、ext、文件字节),怎么传呢。
用Gson转对象为Json传递过去,成功了!就这么的,我测试了传递一曲“滴滴滴.mp3”成功了,对方从其中拿到byte[]数组转文件也能播放成功。继续测试,传一首歌曲,直接OOM!错误大概是消耗了几十兆的空间导致App直接crash。
为什么呢,显而易见,因为字符串太长了!你把一个歌曲转换成byte[]然后再转换成String,你知道这个String.length()有多长么,不说了高兴太早。
3、第三次
找了半天终于知道有这么一个东西“Base64”!
目前的做法是File转byte[],byte[]通过转为Base64的字符串,这样得到的文件大小比原文件大1.2倍左右(拿首歌曲测试)
byte[] musicBytes = getBytes(filePath);//歌曲文件的字节数组
String str = new String(Base64.encode(musicBytes, Base64.NO_WRAP ));
SimpleMusicEntity2 simpleMusicEntity2 = new SimpleMusicEntity2();
simpleMusicEntity2.setMusicBytes(str);
simpleMusicEntity2.setTitle(title);
String string =simpleMusicEntity2.toString;
public class SimpleMusicEntity2{
private String title;
private String musicBytes;
//构造一个json字符串
@Override
public String toString() {
return "{"
+ "\"title\":"
+ "\"" + title + '\"'
+ ","
+ "\"musicBytes\":"
+ "\"" + musicBytes + '\"' +
'}'+
"\r\n"
;
}
}
Base64简单说一下
上面第二行“Base64.NO_WRAP”
为什么要用这个,因为如果用Base64.DEFAULT的话,我们的string里会自动添加换行符“\n”
flag常量
Base64.CRLF 这个参数意思是Win风格的换行符,意思就是使用CR LF这一对作为一行的结尾而不是Unix风格的LF
Base64.DEFAULT 这个参数是默认,使用默认的方法来加密
Base64.NO_PADDING 这个参数是略去加密字符串最后的”=”
Base64.NO_WRAP 这个参数意思是略去所有的换行符(设置后CRLF就没用了)
Base64.URL_SAFE 这个参数意思是加密时不使用对URL和文件名有特殊意义的字符来作为加密字符,具体就是以-和_取代+和/
//习惯上使用Base64.NO_WRAP,使用什么方式编码就需要使用什么方式解码。
Base64Api
//将字节数组编码,返回为String
Base64.encodeToString(byte[] bs,int flag);
//将字节数组编码,返回字节数组
Base64.encode(byte[] bs,int flag);
//将字节数组按指定位置部分编码,返回字符串
Base64.encodeToString(byte[] bs,int offset,int lenth);
//将字节数组按指定位置部分编码,返回字节数组
Base64.encode(byte[] bs,int offset,int lenth);
//将编码后的字符串解码返回字节数组
Base64.decode(String str,int flag);
//将编码后的字节数组解码返回字节数组
Base64.decode(byte[],int flag);
//将编码后的字节数组按指定位置部分解码,返回字节数组
Base64.decode(byte[] bs,int offset,int len);
最后
发送方式,下面两种方式都行:
byte[] bytes = string.getBytes();
//byte[] enter = "\r\n".getBytes();//这个换行符必须写在字符串末端,不能单独发送换行符
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
dos.write(bytes);
//dos.write(enter);
dos.flush();
dos.close();
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bufferedWriter.write(string);
bufferedWriter.flush();
bufferedWriter.close();
终于PC端的C#程序能接收到文件信息,也能正确播放了,并且也有歌曲名字。
如果文件较大,可以先zip压缩。如果还是较大,可以分段传输。
缺点
这种传递文件的做法不推荐,因为传递10M以上的文件极有可能OOM,因为Java虚拟机顶不住。
改进
请看第二篇文章Java传递音频给PC端C#程序(2)
这只是功能demo而已,其实Android还要考虑很多,比如ip是变化的,我怎么获取对应设备的ip,然后建立TCP连接等等。
喝水不忘挖井人
参考文献:
Android编码解码及其原理
文件与base64二进制转换
图片与Base64相互转换,c#与java通用
Byte[]和BASE64之间的转换
对文件进行base64编码成字符串进行保存或传输