• <td id="ui4cm"></td>
  • <bdo id="ui4cm"><legend id="ui4cm"></legend></bdo>
  • 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部分就是文件,F在實現這一功能。(后序:后面開發《網頁傳》功能的時候,可以考慮這兩個核心的功能都能用在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協議,就是"請求-回復(響應)"的這種通信模式?蛻舳税l出請求,服務器根據請求,返回一個回復(響應)給客戶端。

    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 345269374@qq.com
     */
    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.robbiejoe.com/gn-xiangmu/40514.html 源碼搜藏網所有源碼來自用戶上傳分享,版權問題及牽扯到商業糾紛均與源碼搜藏網無關
    上一篇:沒有了
    下一篇:沒有了

    項目源碼下載排行

    最新文章

    亚洲AV无码一区东京热
  • <td id="ui4cm"></td>
  • <bdo id="ui4cm"><legend id="ui4cm"></legend></bdo>