Android開發--獲取其他應用分享的文件並通過網絡發送

  計劃做一個在局域網下共享文件和消息的應用,android端的開發需要讀取其他應用分享的文件併發送,本文描述了該功能的實現過程以及一些測試。

約定

  1. 未聲明的變量爲類變量
  2. 未指明所屬類的方法在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目前太簡陋了,放張圖表示一下吧
在這裏插入圖片描述
紅色框框的那個圖片就是從手機傳到電腦的。

參考資料

intent

share files

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章