计划做一个在局域网下共享文件和消息的应用,android端的开发需要读取其他应用分享的文件并发送,本文描述了该功能的实现过程以及一些测试。
MainActivity
类中我们需要实现的功能只需要申请网络通信的权限即可
<uses-permission android:name="android.permission.INTERNET"/>
类名 | 描述 |
---|---|
Socket | 建立tcp连接 |
ContentResolver | 解析Uri对象获取共享文件的读取流(InputStream) |
ParcelFileDescriptor | 对FileDescriptor的封装,可以在进程间传递 |
FileDescriptor | 文件描述符,用于读取和写入文件 |
Uri | 通一资源标志符(Uniform Resource Identifier, URI), 在共享文件时表示目标文件 |
为了接收其他应用分享的文件,我们需要在用于处理接收文件的Activity
中加入以下intent-filter
(为了简便,我这里直接在MainActivity
里写)
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="*/*"/>
intent-filter>
activity>
官方关于category的描述如下:
一个包含应处理 Intent 组件类型的附加信息的字符串。您可以将任意数量的类别描述放入一个 Intent 中,但大多数 Intent 均不需要类别。
有两种方式读取文件,第一种方式比较简单,推荐使用
public read_test1() {
Uri data_uri;
Intent intent = getIntent();
if (intent == null) {
Log.d(TAG, "sendFile: no data to send");
return;
}
data_uri = intent.getParcelableExtra(intent.EXTRA_STREAM);
if (data_uri == null) {
Log.d(TAG, "sendFile: no data in intent");
return;
}
// 4k缓存区
byte[] buf = new byte[4096];
ContentResolver resolver = getContentResolver();
// 使用ContentResolver的openInputStream方法直接打开文件
InputStream read_stream = resolver.openInputStream(final_data_url);
while (true) {
int len = read_stream.read(buf);
if (len <= 0) {
break;
}
/*
打印数据的代码...
*/
}
}
public read_test1() {
Uri data_uri;
Intent intent = getIntent();
if (intent == null) {
Log.d(TAG, "sendFile: no data to send");
return;
}
data_uri = intent.getParcelableExtra(intent.EXTRA_STREAM);
if (data_uri == null) {
Log.d(TAG, "sendFile: no data in intent");
return;
}
// 4k缓存区
byte[] buf = new byte[4096];
ContentResolver resolver = getContentResolver();
// 使用ContentResolver的openFileDescriptor方法获取ParcelFileDescriptor对象
ParcelFileDescriptor fd= resolver.openFileDescriptor(data_uri, "r");
// 使用ParcelFileDescriptor的getFileDescriptor方法获取FileDescriptor对象
// 利用FileDescriptor对象建立文件输入流(FileInputStream)
FileInputStream read = new FileInputStream(fd.getFileDescriptor());
while (true) {
int len = read_stream.read(buf);
if (len <= 0) {
break;
}
/*
打印数据的代码...
*/
}
}
// 建立DataTrans类来处理建立连接和发送数据的部分
public class DataTrans {
Socket session_s;
// 连接
public void connect(String ip, int port) throws IOException {
session_s = new Socket(ip, port);
}
//检查是否连接
public boolean isConected() {
boolean connected = (session_s == null)? false:session_s.isConnected();
return connected;
}
// 发送数据
public void send(byte[] data) throws IOException {
OutputStream sending_stream = session_s.getOutputStream();
sending_stream.write(data);
}
// 断开连接
public void disconnect() {
try {
session_s.close();
} catch (Exception e) {
e.printStackTrace();
}
}
// 对象销毁时关闭连接
@Override
protected void finalize() throws Throwable {
super.finalize();
if(session_s != null) {
session_s.close();
}
}
}
注意,由于Android系统不允许在主线程上进行网络传输操作,需要新建线程来发送数据
public void sendFile() {
Uri data_uri;
Intent intent = getIntent();
if (intent == null) {
Log.d(TAG, "sendFile: no data to send");
return;
}
data_uri = intent.getParcelableExtra(intent.EXTRA_STREAM);
if (data_uri == null) {
Log.d(TAG, "sendFile: no data in intent");
return;
}
// 传入内部类的局部变量必须是final,建立新线程时使用了匿名内部类
final Uri final_data_url = data_uri;
Thread sending_thread = new Thread(new Runnable() {
// sending thread
@Override
public void run() {
try {
byte[] buf = new byte[4096];
// 建立连接
if (!trans.isconnected()) {
trans.connect("127.0.0.1", 19999);
}
InputStream read_stream = getContentResolver().openInputStream(final_data_url);
while (true) {
int len = read_stream.read(buf);
if (len <= 0) {
break;
}
// 发送数据
trans.send(buf);
}
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG, "sending: error send file");
}
Log.d(TAG, "sending: file sended");
}
});
sending_thread.start();
}
服务器端是一个python脚本,监听19999端口,并将接收的数据保存在received_file
中
需要使用adb
开启反向端口转发,把模拟器里的19999端口转发到pc的19999端口
adb reverse tcp:19999 tcp:19999
app目前太简陋了,放张图表示一下吧
红色框框的那个图片就是从手机传到电脑的。
intent
share files