almost 4 years ago

最近Lab需要寫一個Android Ftp Upload Module,也希望有個管理介面
主要參考此篇加上Ftp的功能(使用apache commons FTP)
分享一下code囉!

功能

  • 支援多檔上傳 (用,分開)
    • 檔案
    • 1-level 資料夾:資料夾底下只有檔案,沒有資料夾
  • 完成的Job按下Clear會在畫面上清空

完整程式碼 Code

程式解說

MainActivity.java中為ftp server及手機端的設定:

  • ftpHost:要連線的ftp server
  • ftpUser:連到ftp server的帳號
  • ftpPasswd:連到ftp server的密碼
  • ftpRemoteDir:檔案上傳到ftp server的位置
  • port:對應的ftp server port
  • delete:手機端上傳檔案後是否刪除手機裡該筆資料(yes/no)
  • localDir:手機端要上傳的檔案/資料夾,可上傳多筆檔案/單層資料夾(該層資料夾底下只有檔案,沒有其他資料夾)。每一筆用,分開的檔案/資料夾,視為一筆Task()
MainActivity.java
bData.putString("ftpHost", "");
bData.putString("ftpUser", "");
bData.putString("ftpPasswd", "");
bData.putString("ftpRemoteDir", "");
bData.putString("localDir", "/sdcard/test2.txt,/sdcard/test1.txt,/sdcard/folder/");
bData.putInt("port", 21);
bData.putString("delete", "no");
Tasklist.java
Task[] initTasks() { 
        String[] fileList = LocalDirectory.split(",");
        final int count=fileList.length;
        total_task=count;
        Task[] result= new Task[count];

        for (int i=0;i<count;i++){
                result[i]=new Task(fileList[i],ftpHost,ftpUserName,ftpPassword,
                    ftpRemoteDirectory,LocalDirectory,Delete,port);  
        }

        return result;
}

每一筆Task中,用開啟ftpclient執行ftp的功能(connect,upload...)
並依照ftp upload的進度,更新progress和顯示的檔案名稱(presentName):上傳到第幾個file

Task.java
private Runnable ftpclient = new Runnable() {
        public void run() {
            ftpConnect(ftpHost,ftpUserName,ftpPassword,port);
                //上傳目錄底下的檔案

              if( desc.charAt(desc.length()-1) == '/') { 
                File dir = new File(desc);
            String[] child = dir.list();
            for(int i = 0; i < child.length; i++){
                    String fileName = child[i];
                String presentName = desc +" : "+fileName+"  ["+String.valueOf(i+1)+"/"+String.valueOf(child.length)+"]";
                setPresentDesc(presentName); //更改看到的textView:顯示目錄下有幾個檔案完成

                ftpUpload(desc+fileName,fileName,ftpRemoteDirectory+"/"+remoteDirName);
                if( Delete == "yes"){
                        Deletefile(desc+fileName);
                }
                        }
                }

          //File start 

          else{
                String fileName = desc.substring(desc.lastIndexOf("/")+1,desc.length());                                   ftpUpload(desc,fileName,ftpRemoteDirectory);
            if( Delete == "yes"){
                    Deletefile(desc+fileName);
            }
          }//File end


          ftpDisconnect();
        }
};//Runable

讓listener偵測到progress,presentName有改變時,更新對應到xml的區塊
如果Task是一個資料夾,則要所有資料夾中的檔案都上傳完成才能Clear finish=true

Task.java
public void setProgress(int progress) {
        this.progress = progress >= 100 ? 100 : progress;
        if(this.progress>=100){
                finish_file++;
                if(finish_file == num_files){
                        finish=true;
                }
        }
        if (!listenerList.isEmpty()) {
                for (Task.taskListener listener : listenerList)
                        listener.onProgressChanged(this.progress,this.presentName);
        }
}
Tasklist.java
this.l = new taskListener() {              
        @Override
        public void onProgressChanged(final int progress,final String name) {
                mHandler.post(new Runnable() {
            @Override
            public void run() {
                tvTaskDesc.setText(name);
                pbTask.setProgress(progress);
                if(now_task < total_task-1){ //最後一個task不用進判斷式

                    if(myTasks[now_task].getFinish()){
                        //next task

                        now_task++;
                        new Thread(myTasks[now_task]).start();
                    }
                }
            }
                });
    }
}

資料夾中的檔案都上傳完成(finish=true),按下Clear才會從畫面中移除

Tasklist.java
public void onClick(View v) {
        if(aTask.getFinish()){
                tasks.remove(aTask);
                notifyDataSetChanged();
        }
}

捨棄掉的功能

多線程上傳

程式的架構是每一支Task(一個檔案/資料夾)會與ftp server連線,
傳自己Task中需要上傳的檔案(一個檔案/資料夾中所有的檔案),上傳完成之後與ftp server斷線。
原本想用ismsor程式中
for(Task t:tasks)
new Thread(t).start();

照理說應該是可以多線程上傳互不影響,但在實作後測試發現:
先完成的Task在斷線時,會切到其他還在上傳檔案的Task的連線,
如果Task是一個檔案,仍能正常傳完;但如果Task是一個資料夾,資料夾其他還沒開始上傳的檔案都會因為斷線而無法上傳。
由於本程式最重要的是檔案能完整上傳功能,所以這部分就變成是單線程上傳,按照順序執行每一個Task

protected void onCreate(Bundle savedInstanceState) {
        now_task=0;
    myTasks=initTasks();
    tasksFtp = new ArrayList<Task>(Arrays.asList(myTasks));
        MyAdapter adapter = new MyAdapter(this,tasksFtp);
        getListView().setAdapter(adapter);

        //只從第一個開始跑
        new Thread(myTasks[now_task]).start();
}
.
.
.
this.l = new taskListener() {               
        @Override
        public void onProgressChanged(final int progress,final String name) {
                mHandler.post(new Runnable() {
                @Override
                public void run() {
                        tvTaskDesc.setText(name);
                        pbTask.setProgress(progress);
                        if(now_task < total_task-1){ //最後一個task不用進判斷式
                                if(myTasks[now_task].getFinish()){
                                        //next task
                                        now_task++;
                                        new Thread(myTasks[now_task]).start();
                                }
                        }
                }
        });//onProgress
}//taskListener

按鈕更新

原本想實作在Task完成後,畫面按鈕才從Stop變成Clear (幫以後功能鋪路)
但實做測試後,執行到最後一個task的時候做相關動作 ex: btn.setText()
都會造成ftp Upload失敗,照理說這兩個沒什麼關聯,應該不會影響,
由於程式以檔案成功上傳為優先,這個就先捨棄掉囉!

參考資料

The Way to Update progress bar in ListView :Android
deleting item from a ViewHolder

note

Android 開發時,模擬器要傳檔案上去常會有權限問題
開啓Terminal
adb shell
mount -o rw,remount rootfs /
chmod 777 /mnt/sdcard

Mac設置adb的方法

1.找到android sdk的位置,adb命令在platform-tool資料夾中
我的adb路徑是/Applications/eclipse/android-sdk-macosx/platform-tools
假設你的路徑是XXXX

2.打開Terminal輸入
touch .bash_profile
open -e .bash_profile

3.新增路徑
vim .bash_profile
如果.bash_profile已經有內容,我們只要在之後添加:XXXX
如果是一個空白的話,我們就輸入一下內容export PATH=${PATH}:XXXX,儲存關閉.bash_profile

export PATH=${PATH}:/Applications/eclipse/android-sdk-macosx/tools:/Applications/eclipse/android-sdk-macosx/platform-tools

export CLICOLOR='true'
export LSCOLORS="gxfxcxdxcxegedabagacad"

4.Terminal輸入source .bash_profile 就設置成功囉!


以後有時間可能會加上

  • 暫停/續傳 or 停止/重傳
  • ftpes 加密ftp協定
← [Koding] Node.js & Evernote、Teamwork、各種語言編譯 [開箱] 紅米手機(相機實測)&小米行動電源 (小米手機3) →
 
comments powered by Disqus