計劃做一個在局域網下共享文件和消息的應用,android端的開發需要讀取其他應用分享的文件併發送,本文描述了該功能的實現過程以及一些測試。
約定
- 未聲明的變量爲類變量
- 未指明所屬類的方法在
MainActivity
類中
權限
我們需要實現的功能只需要申請網絡通信的權限即可
<uses-permission android:name="android.permission.INTERNET"/>
需要用到的主要Java類
類名 | 描述 |
---|---|
Socket | 建立tcp連接 |
ContentResolver | 解析Uri對象獲取共享文件的讀取流(InputStream) |
ParcelFileDescriptor | 對FileDescriptor的封裝,可以在進程間傳遞 |
FileDescriptor | 文件描述符,用於讀取和寫入文件 |
Uri | 通一資源標誌符(Uniform Resource Identifier, URI), 在共享文件時表示目標文件 |
接收其他應用分享的文件(intent-filter配置)
爲了接收其他應用分享的文件,我們需要在用於處理接收文件的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>
<!--新建一個intent-filter標籤,注意一個intent-filter標籤裏不能寫兩個action,否則會衝突,導致前面action的失效>
<intent-filter>
<!-- 響應分享(SEND)操作-->
<action android:name="android.intent.action.SEND" />
<!-- 額外的類別信息,不是必要的一般設置爲DEFAULT-->
<category android:name="android.intent.category.DEFAULT"/>
<!-- 接收分享的文件類型,這裏表示接收所有類型文件-->
<data android:mimeType="*/*"/>
</intent-filter>
</activity>
官方關於category的描述如下:
一個包含應處理 Intent 組件類型的附加信息的字符串。您可以將任意數量的類別描述放入一個 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;
}
/*
打印數據的代碼...
*/
}
}
建立tcp連接
// 建立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目前太簡陋了,放張圖表示一下吧
紅色框框的那個圖片就是從手機傳到電腦的。