Android茄子快傳項目源碼

時間:2019-06-26 09:46 來源:互聯網 作者:源碼搜藏 瀏覽: 收藏 挑錯 推薦 打印

  • 源碼類別:項目源碼
  • 源碼大小:未知
  • 編碼格式:gbk
  • 授權方式:免費源碼
  • 運行環境:Android studio
  • 官方網址:暫無
  • 歡迎加入QQ群討論學習
  • Android學習交流
  • IDC/源碼/項目-廣告推薦

Android茄子快傳項目源碼

茄子快傳是一款文件傳輸應用,相信大家都很熟悉這款應用,應該很多人用過用來文件的傳輸。它有兩個核心的功能:

  1. 端到端的文件傳輸
  2. Web端的文件傳輸

這兩個核心的功能我們具體來分析一下!

端到端的文件傳輸

所謂的端到端的文件傳輸是指應用端發送到應用端(這里的應用端指Android應用端),這種文件傳輸方式是文件發送端和文件接收端必須安裝應用。

效果圖

文件發送方

 
文件發送方_1

 

 
文件發送方_2

 
文件發送方_3

文件接收方

 
文件接收方_1

簡單的文件傳輸的話,我們可以用藍牙,wifi直連,ftp這幾種方式來進行文件的傳輸。但是:

  1. 藍牙傳輸的話,速度太慢,而且要配對。相對比較麻煩。
  2. wifi直連差不多跟藍牙一樣,但是速率很快,也要配對。
  3. ftp可以實現文件的批量傳輸,但是沒有文件的縮略圖。

最初分析這個項目的時候就想著通過自定義協議的Socket的通信來實現,自定義的協議包括header + body的自定義協議, header部分包括了文件的信息(長度,大小,文件路徑,縮略圖), body部分就是文件。現在實現這一功能。(后序:后面開發《網頁傳》功能的時候,可以考慮這兩個核心的功能都能用在Android架設微型Http服務器來實現。這是后話了。)

流程圖

 
端到端的流程圖

編碼實現

兩部設備文件傳輸是需要在一個局域網的條件下的,只有文件發送方連接上文件接收方的熱點(搭建了一個局域網),這樣文件發送方和文件接收方就在一個局域網里面,我們才可以進行Socket通信。這是一個大前提!

初始化條件 -- Ap(熱點)和Wifi的管理, 文件的掃描

對Android的Ap(熱點)和Wifi的一些操作都封裝在下面兩個類:

WifiMgr.java

APMgr.java

關于熱點和Wifi的操作都是根據WifiManager來操作的。所以要像操作WifiManeger是必須要一些權限的。必須在AndroidManifest.xml清單文件里面聲明權限:

    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

文件接收端打開熱點并且配置熱點的代碼:

        //1.初始化熱點
        WifiMgr.getInstance(getContext()).disableWifi();
        if(ApMgr.isApOn(getContext())){
            ApMgr.disableAp(getContext());
        }

        //熱點相關的廣播
        mWifiAPBroadcastReceiver = new WifiAPBroadcastReceiver() {
            @Override
            public void onWifiApEnabled() {
                Log.i(TAG, "======>>>onWifiApEnabled !!!");
                if(!mIsInitialized){
                    mUdpServerRuannable = createSendMsgToFileSenderRunnable();
                    AppContext.MAIN_EXECUTOR.execute(mUdpServerRuannable);
                    mIsInitialized = true;

                    tv_desc.setText(getResources().getString(R.string.tip_now_init_is_finish));
                    tv_desc.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            tv_desc.setText(getResources().getString(R.string.tip_is_waitting_connect));
                        }
                    }, 2*1000);
                }
            }
        };
        IntentFilter filter = new IntentFilter(WifiAPBroadcastReceiver.ACTION_WIFI_AP_STATE_CHANGED);
        registerReceiver(mWifiAPBroadcastReceiver, filter);

        ApMgr.isApOn(getContext()); // check Ap state :boolean
        String ssid = TextUtils.isNullOrBlank(android.os.Build.DEVICE) ? Constant.DEFAULT_SSID : android.os.Build.DEVICE;
        ApMgr.configApState(getContext(), ssid); // change Ap state :boolean

對于類WifiAPBroadcastReceiver是熱點的一個廣播類,最后一行代碼是配置指定名稱的熱點,這里是以設備名稱作為熱點的名稱。

文件發送端發送文件,文件發送端首先要選擇要發送的文件,然后將要選擇的文件存儲起來,這里我是用了一個HashMap將發送的文件存儲起來,key是文件的路徑,value是FileInfo對象。

以下是掃描手機存儲盤上面的文件列表的代碼:

    /**
     * 存儲卡獲取 指定后綴名文件 
     * @param context
     * @param extension 
     * @return
     */
    public static List<FileInfo> getSpecificTypeFiles(Context context, String[] extension){
        List<FileInfo> fileInfoList = new ArrayList<FileInfo>();

        //內存卡文件的Uri
        Uri fileUri= MediaStore.Files.getContentUri("external");
        //篩選列,這里只篩選了:文件路徑和含后綴的文件名
        String[] projection=new String[]{
                MediaStore.Files.FileColumns.DATA, MediaStore.Files.FileColumns.TITLE
        };

        //構造篩選條件語句
        String selection="";
        for(int i=0;i<extension.length;i++)
        {
            if(i!=0)
            {
                selection=selection+" OR ";
            }
            selection=selection+ MediaStore.Files.FileColumns.DATA+" LIKE '%"+extension[i]+"'";
        }
        //按時間降序條件
        String sortOrder = MediaStore.Files.FileColumns.DATE_MODIFIED;

        Cursor cursor = context.getContentResolver().query(fileUri, projection, selection, null, sortOrder);
        if(cursor != null){
            while (cursor.moveToNext()){
                try{
                    String data = cursor.getString(0);
                    FileInfo fileInfo = new FileInfo();
                    fileInfo.setFilePath(data);

                    long size = 0;
                    try{
                        File file = new File(data);
                        size = file.length();
                        fileInfo.setSize(size);
                    }catch(Exception e){

                    }
                    fileInfoList.add(fileInfo);
                }catch (Exception e){
                    Log.i("FileUtils", "------>>>" + e.getMessage());
                }

            }
        }
        Log.i(TAG, "getSize ===>>> " + fileInfoList.size());
        return fileInfoList;
    }

注意**:這里掃描的FileInfo對象只是掃描了文件路徑filePath, 還有文件的大小size。
FileInfo的其他屬性到文件傳輸的時候再二次獲取,獲取FileInfo的其他屬性都在FileUtils這個工具類里面了。

文件發送端打開wifi掃描熱點并且連接熱點的代碼:

        if(!WifiMgr.getInstance(getContext()).isWifiEnable()) {//wifi未打開的情況,打開wifi
            WifiMgr.getInstance(getContext()).openWifi();
        }

        //開始掃描
        WifiMgr.getInstance(getContext()).startScan();
        mScanResultList = WifiMgr.getInstance(getContext()).getScanResultList();
        mScanResultList = ListUtils.filterWithNoPassword(mScanResultList);

        if(mScanResultList != null){
            mWifiScanResultAdapter = new WifiScanResultAdapter(getContext(),mScanResultList);
            lv_result.setAdapter(mWifiScanResultAdapter);
            lv_result.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    //單擊選中指定的網絡
                    ScanResult scanResult = mScanResultList.get(position);
                    Log.i(TAG, "###select the wifi info ======>>>" + scanResult.toString());

                    //1.連接網絡
                    String ssid = Constant.DEFAULT_SSID;
                    ssid = scanResult.SSID;
                    WifiMgr.getInstance(getContext()).openWifi();
                    WifiMgr.getInstance(getContext()).addNetwork(WifiMgr.createWifiCfg(ssid, null, WifiMgr.WIFICIPHER_NOPASS));

                    //2.發送UDP通知信息到 文件接收方 開啟ServerSocketRunnable
                    mUdpServerRuannable = createSendMsgToServerRunnable(WifiMgr.getInstance(getContext()).getIpAddressFromHotspot());
                    AppContext.MAIN_EXECUTOR.execute(mUdpServerRuannable);
                }
            });
        }

對于ListUtils.filterWithNoPassword是將掃描的結果進行過濾,過濾掉有密碼的掃描結果。

lv_result.setOnItemClickListener回調的方法是連接指定的熱點來形成一個局域網。文件傳輸的大前提條件就已經形成了。

到這里文件發送端和文件接收端的初始化環境也就搭建起來了。

文件傳輸模塊

文件傳輸模塊的核心代碼就只有4個類,Transferable, BaseTransfer, FileSender, FileReceiver。

Transferable是接口。

BaseTransfer, FileSender, FileReceiver是類。

對于文件發送端,每一個文件發送對應一個FileSender,而對于文件接收端,每一個文件的接收對應一個FileReceiver。
而FileSender,FileReceiver是繼承自 抽象類BaseTransfer的。 BaseTransfer是實現了Transferable接口。

下面是4個類圖的關系:

 
這里寫圖片描述

在Transferable接口中定義了4個方法,分別是初始化,解析頭部,解析主體,結束。解析頭部和解析主體分別對應上面說的自定義協議的header和body。初始化是為每一次文件傳輸做初始化工作,而結束是為每一次文件傳輸做結束工作,比如關閉一些資源流,Socket等等。

而BaseTransfer就只是實現了Transferable, 里面封裝了一些常量。沒有實現具體的方法,具體的實現是FileSender,FileReceiver。

代碼詳情:

Transferable
BaseTransfer
FileSender
FileReceiver

總結

端到端的文件傳輸就分析到這里,主要是Ap熱點的操作,Wifi的操作,Socket通信來實現文件的傳輸。但是這里的Socket用到的不是異步IO,是同步IO。所以會引起阻塞。比如在FileSender中的暫停文件傳輸pause方法調用之后,會引起FileReceiver中文件傳輸的阻塞。如果你對異步IO有興趣,你也可以去實現一下。

對于端對端的核心代碼都是在 io.github.mayubao.kuaichuan.core 包下面。
這是我在github上面的項目鏈接 https://github.com/mayubao/KuaiChuan

web端的文件傳輸

所謂的Web端的文件傳輸是指文件發送端作為一個Http服務器,提供文件接收端來下載。這種文件傳輸方式是文件發送端必須安裝應用,而文件接收端只需要有瀏覽器即可。

效果圖

文件發送端

開啟Http服務器

文件接收端

文件接收端瀏覽器訪問

在android應用端架設微型Http服務器來實現文件的傳輸。這里可以用ftp來實現,為什么不用ftp呢?因為沒有縮略圖,這是重點!

web端的文件傳輸的核心重點:

  1. 文件發送端熱點的開啟(參考端對端的熱點操作類 APMgr.java)
  2. 文件發送端架設Http服務器。

Android端的Http服務器

Android上微型Http服務器(Socket實現),結合上面的效果圖分析。主要解決三種Http url的請求形式就行了,由上面的文件接收端的效果圖可以看出來(文件接收端是去訪問文件發送端的Http服務器),大致可以分為三種鏈接:

  1. Index主頁鏈接 http://hostname:port
  2. Image鏈接 http://hostname:port/image/xxx.xxx
  3. Download鏈接 http://hostname:port/download/xxx.xxx
這里寫圖片描述

下面用Socket來實現在Android上面的微型Http服務器的。

關于Http協議,我簡單的描述一下Http協議。對于Http協議,就是"請求-回復(響應)"的這種通信模式。客戶端發出請求,服務器根據請求,返回一個回復(響應)給客戶端。

Http請求的大致分為四個部分:

  1. 請求行
  2. 請求頭
  3. 空行
  4. 請求實體

Http響應的大致分為四個部分:

  1. 狀態行
  2. 響應頭
  3. 空行
  4. 響應實體

Http請求(POST請求)的示例:

POST /image/index.html HTTP/1.1
Host: 127.0.0.1:7878
Connection: keep-alive
Content-Length: 247
Cache-Control: no-cache
Origin: chrome-extension://fdmmgilgnpjigdojojpjoooidkmcomcm
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryLIr5t1rdtuD8Ztuw
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6

------WebKitFormBoundaryLIr5t1rdtuD8Ztuw
Content-Disposition: form-data; name="username"

mayubao
------WebKitFormBoundaryLIr5t1rdtuD8Ztuw
Content-Disposition: form-data; name="username"

123456
------WebKitFormBoundaryLIr5t1rdtuD8Ztuw--

1.請求行(請求方式 + uri + http版本)

POST /image/index.html HTTP/1.1

2.請求頭

Host: 127.0.0.1:7878
Connection: keep-alive
Content-Length: 247
Cache-Control: no-cache
Origin: chrome-extension://fdmmgilgnpjigdojojpjoooidkmcomcm
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryLIr5t1rdtuD8Ztuw
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6

3.空行

4.請求實體(對于GET請求一般沒有請求實體)

------WebKitFormBoundaryLIr5t1rdtuD8Ztuw
Content-Disposition: form-data; name="username"

mayubao
------WebKitFormBoundaryLIr5t1rdtuD8Ztuw
Content-Disposition: form-data; name="username"

123456
------WebKitFormBoundaryLIr5t1rdtuD8Ztuw--

Http響應的示例:

HTTP/1.0 200 OK 
Cache-Control:public, max-age=86400
Content-Length:235
Content-Type:image/png
Date:Wed, 21 Dec 2016 08:20:54 GMT

請求實體

1.狀態行(Http版本 + 狀態 + 描述)

HTTP/1.0 200 OK 

2.響應頭

HTTP/1.0 200 OK 
Cache-Control:public, max-age=86400
Content-Length:235
Content-Type:image/png
Date:Wed, 21 Dec 2016 08:20:54 GMT

3.空行

4.響應實體

上面只是簡單的敘述了一下Http一般的請求-響應流程,還有對應請求,響應的結構。如果你想進一步了解http協議,請私下自行了解。

回到我們的重點 AndroidMicroServer:
AndroidMicroServer是Http服務器的核心類,還有關聯到其他的類,有IndexUriResHandler,ImageUriResHandler, DowloadUriResHandler。是AndroidMicroServer根據不同的Uri格式分配給指定的Handler去處理的。

UML的分析圖如下:

AndroidMicroServer分析

下面是AndroidMicroServer的源碼:

/**
 * The micro server in Android
 * Created by mayubao on 2016/12/14.
 * Contact me [email protected]
 */
public class AndroidMicroServer {

    private static final String TAG = AndroidMicroServer.class.getSimpleName();

    /**
     * the server port
     */
    private int mPort;

    /**
     * the server socket
     */
    private ServerSocket mServerSocket;

    /**
     *  the thread pool which handle the incoming request
     */
    private ExecutorService mThreadPool = Executors.newCachedThreadPool();

    /**
     * uri router handler
     */
    private List<ResUriHandler> mResUriHandlerList = new ArrayList<ResUriHandler>();

    /**
     * the flag which the micro server enable
     */
    private boolean mIsEnable = true;

    public AndroidMicroServer(int port){
        this.mPort = port;
    }

    /**
     * register the resource uri handler
     * @param resUriHandler
     */
    public void resgisterResUriHandler(ResUriHandler resUriHandler){
        this.mResUriHandlerList.add(resUriHandler);
    }

    /**
     * unresigter all the resource uri hanlders
     */
    public void unresgisterResUriHandlerList(){
        for(ResUriHandler resUriHandler : mResUriHandlerList){
            resUriHandler.destroy();
            resUriHandler = null;
        }
    }

    /**
     * start the android micro server
     */
    public void start(){
        mThreadPool.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    mServerSocket = new ServerSocket(mPort);

                    while(mIsEnable){
                        Socket socket = mServerSocket.accept();
                        hanlderSocketAsyn(socket);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * stop the android micro server
     */
    public void stop(){
        if(mIsEnable){
            mIsEnable = false;
        }

        //release resource
        unresgisterResUriHandlerList();

        if(mServerSocket != null){
            try {
//                mServerSocket.accept(); //fuck ! fix the problem, block the main thread
                mServerSocket.close();
                mServerSocket = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * handle the incoming socket
     * @param socket
     */
    private void hanlderSocketAsyn(final Socket socket) {
        mThreadPool.submit(new Runnable() {
            @Override
            public void run() {
                //1. auto create request object by the parameter socket
                Request request = createRequest(socket);

                //2. loop the mResUriHandlerList, and assign the task to the specify ResUriHandler
                for(ResUriHandler resUriHandler : mResUriHandlerList){
                    if(!resUriHandler.matches(request.getUri())){
                        continue;
                    }

                    resUriHandler.handler(request);
                }
            }
        });

    }

    /**
     * create the requset object by the specify socket
     *
     * @param socket
     * @return
     */
    private Request createRequest(Socket socket) {
        Request request = new Request();
        request.setUnderlySocket(socket);
        try {
            //Get the reqeust line
            SocketAddress socketAddress = socket.getRemoteSocketAddress();
            InputStream is = socket.getInputStream();
            String requestLine = IOStreamUtils.readLine(is);
            SLog.i(TAG, socketAddress + "requestLine------>>>" + requestLine);
            String requestType = requestLine.split(" ")[0];
            String requestUri = requestLine.split(" ")[1];

//            requestUri = URLDecoder.decode(requestUri, "UTF-8");

            request.setUri(requestUri);

            //Get the header line
            String header = "";
            while((header = IOStreamUtils.readLine(is)) != null){
                SLog.i(TAG, socketAddress + "header------>>>" + requestLine);
                String headerKey = header.split(":")[0];
                String headerVal = header.split(":")[1];
                request.addHeader(headerKey, headerVal);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return request;
    }

}

AndroidMicroServer主要有兩個方法:

  1. start (Http服務器的開啟)
  2. stop (Http服務器的關閉,主要用來關閉ServerSocket和反注冊UriResHandler)

start方法 是Http服務器的入口

對于start方法:

    /**
     * start the android micro server
     */
    public void start(){
        mThreadPool.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    mServerSocket = new ServerSocket(mPort);

                    while(mIsEnable){
                        Socket socket = mServerSocket.accept();
                        hanlderSocketAsyn(socket);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

開啟一個線程去執行ServerSocket, while循環去接收每一個進來的Socket。 而hanlderSocketAsyn(socket)是異步處理每一個進來的socket。

    /**
     * handle the incoming socket
     * @param socket
     */
    private void hanlderSocketAsyn(final Socket socket) {
        mThreadPool.submit(new Runnable() {
            @Override
            public void run() {
                //1. auto create request object by the parameter socket
                Request request = createRequest(socket);

                //2. loop the mResUriHandlerList, and assign the task to the specify ResUriHandler
                for(ResUriHandler resUriHandler : mResUriHandlerList){
                    if(!resUriHandler.matches(request.getUri())){
                        continue;
                    }

                    resUriHandler.handler(request);
                }
            }
        });
    }

    /**
     * create the requset object by the specify socket
     *
     * @param socket
     * @return
     */
    private Request createRequest(Socket socket) {
        Request request = new Request();
        request.setUnderlySocket(socket);
        try {
            //Get the reqeust line
            SocketAddress socketAddress = socket.getRemoteSocketAddress();
            InputStream is = socket.getInputStream();
            String requestLine = IOStreamUtils.readLine(is);
            SLog.i(TAG, socketAddress + "requestLine------>>>" + requestLine);
            String requestType = requestLine.split(" ")[0];
            String requestUri = requestLine.split(" ")[1];

//            //解決URL中文亂碼的問題
//            requestUri = URLDecoder.decode(requestUri, "UTF-8");

            request.setUri(requestUri);

            //Get the header line
            String header = "";
            while((header = IOStreamUtils.readLine(is)) != null){
                SLog.i(TAG, socketAddress + "header------>>>" + requestLine);
                String headerKey = header.split(":")[0];
                String headerVal = header.split(":")[1];
                request.addHeader(headerKey, headerVal);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return request;
    }

對于每一個進來的Socket:

  1. 通過createRequest(socket)來創建一個Request對象,對應一個Http Request對象。在createRequest(socket)中如何去從socket中去讀取每一行呢?對于每一個Http請求的每一行都是以'\r\n'字節結尾的。只要判斷讀取字節流的時候判斷連續的兩個字節是以'\r\n'結尾的就是一行結尾的標識。詳情請查看IOStreamUtils.java

  2. 根據請求行的path,分配給對應的Uri處理對象去處理,而所對應uri如何獲取,是從Socket的Inputsream讀取Http Request的請求行中讀取出來的。對于ResUriHandler,是一個接口。主要根據請求行的uri 分配給對應的ResUriHandler去處理。 ResUriHandler的實現類是對應給出響應的處理類。

注意:可參考上面的UML的類圖分析

ResUriHandler有三個實現類分別對應上面分析的三種Uri格式:

  1. IndexResUriHandler 處理發送文件列表的顯示
  2. ImageResUriHandler 處理文件圖片
  3. DownloadResUriHandler 處理文件下載

總結

AndroidMicroServer是架設在Android平臺上面的一個微型HttpServer, 是根據快傳項目的具體需求來實現的。巧妙的利用ResUriHandler來處理不同的uri。注意這不是一般通用的HttpServer, 之前有想過在Github上面去找一些Server端的代碼來進行開發,發現代碼關聯太多,而且不容易定制,所以才會萌生自己用ServerSocket來實現符合自己需求的HttpServer。

對于HttpServer的核心代碼都是在 io.github.mayubao.kuaichuan.micro_server包下面。


Android茄子快傳項目源碼轉載請注明出處http://www.glgbknaa.icu/gn-xiangmu/40514.html 源碼搜藏網所有源碼來自用戶上傳分享,版權問題及牽扯到商業糾紛均與源碼搜藏網無關
上一篇:沒有了
下一篇:沒有了

項目源碼下載排行

最新文章

内蒙古十一选五助手下载