Android上傳文件到服務器中的簡單實例
最近一直在完成個任務,有關Android手機文件傳輸的,現在先做了一步,實現了手機可以上傳文件到pc端。
先簡單介紹一下吧,架設在電腦上的pc端,運行在Android手機上的客戶端,pc端用java語言編寫,客戶端這邊是結合c和
java的JNI來編寫的。為什么這么特殊呢~呵呵 ,完全是出于任務要求的需要啦!
先上代碼吧! 這邊為了思路清晰點先上客戶端的代碼~順序由上至下~
package zeng.Glogo.learn; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.app.ProgressDialog; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.Spinner; import android.widget.Toast; public class JniClient_File extends Activity { static{ System.loadLibrary("FileOperation"); }
我自己建的包,還有需要的一些包~ static{}內的代碼為用jni編寫的靜態庫~
public String IPAddress=""; public int PORT; private EditText editText1=null; private EditText editText2=null; private Spinner spinner=null; private Button send=null; private EditText editText3=null; private EditText editText4=null; private Button sure=null; private Button connect=null; //重點1 private Button disconnect=null; //重點2 private Button exit=null; FileOperation fileOperation=new FileOperation(); //對文件進行操作的類 ,重點3 private ProgressDialog progressdialog;
這些都很簡單吧~
private static final String file_Selected[]={ "選擇您需要傳輸的文件","HelloJni.c","HelloNDK.c","HelloCDT.txt","HelloJava.java","Hello.txt","hellop.txt" }; private static final String filePath[]={ " ","/mnt/sdcard/HelloJni.c","/mnt/sdcard/HelloNDK.c","/mnt/sdcard/HelloCDT.txt","/mnt/sdcard/HelloJava.java", "/mnt/sdcard/Hello.txt","/mnt/sdcard/hellop.txt" }; private ArrayAdapter<String> adapter; //聲明一個適配器 private List<String> fileNamesList; //List容器,存放選擇的文件名
有ArrayAdapter和List,大家應該才出來這些都是為Spinner做準備的吧~
/** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //根據控件的ID找到各個控件 editText1=(EditText)findViewById(R.id.file_name); editText2=(EditText)findViewById(R.id.file_seletced); spinner=(Spinner)findViewById(R.id.spinner); send=(Button)findViewById(R.id.send); editText3=(EditText)findViewById(R.id.ip); editText4=(EditText)findViewById(R.id.port); sure=(Button)findViewById(R.id.sure); //progressbar=(ProgressBar)findViewById(R.id.progressBar); connect=(Button)findViewById(R.id.connect); disconnect=(Button)findViewById(R.id.disconnect); exit=(Button)findViewById(R.id.exit); //為容器List添加內容 fileNamesList=new ArrayList<String>(); for(int i=0;i<file_Selected.length;i++){ fileNamesList.add(file_Selected[i]); } //適配器設置 adapter=new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, file_Selected); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); //為Spinner添加適配器 spinner.setAdapter(adapter); //為Spinner添加時間監聽 spinner.setOnItemSelectedListener(new OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) { // TODO Auto-generated method stub //arg2為點擊所選擇的選項 //arg0為spinner設置顯示當前的選項 if(arg2!=0){ editText1.setText(filePath[arg2]); editText2.setText(file_Selected[arg2]); arg0.setVisibility(View.VISIBLE); }else{ editText1.setText(""); editText2.setText(""); editText1.setHint(R.string.file_name_hint); editText2.setHint(R.string.file_seletced_hint); arg0.setVisibility(View.VISIBLE); } } @Override public void onNothingSelected(AdapterView<?> arg0) { // TODO Auto-generated method stub //這個方法暫時不知道有什么用處,等待google之~ } });
上面這些東東如果大家不了解的話去看一下有關Android入門的書,這些都會有的~
接下來的就是幾個按鈕的設定了~
//退出 exit.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub JniClient_File.this.finish(); } }); //確定IP和端口號 sure.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub IPAddress=editText3.getText().toString(); PORT=Integer.decode(editText4.getText().toString()); editText3.setText(""); editText4.setText(""); editText3.setHint(IPAddress); String port=String.valueOf(PORT); //EditText的類型為Editable。接收String類型,所以在這里必須轉換一下類型 editText4.setHint(port); Toast toast=Toast.makeText(JniClient_File.this, "IP地址;"+IPAddress+"\n"+"端口號:"+PORT, Toast.LENGTH_LONG); toast.show(); } }); //建立連接 connect.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub String str1=fileOperation.connect(IPAddress,PORT); if(str1.endsWith("101")){ Toast toast=Toast.makeText(JniClient_File.this, str1+" 沒有建立連接", Toast.LENGTH_LONG); toast.show(); } else{ Toast toast=Toast.makeText(JniClient_File.this, str1+" 連接已建立", Toast.LENGTH_LONG); toast.show(); } } }); //斷開連接 disconnect.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub String str2=fileOperation.disconnect(); if(str2.endsWith("102")){ Toast toast=Toast.makeText(JniClient_File.this, str2+" 斷開異常",Toast.LENGTH_LONG); toast.show(); }else{ Toast toast=Toast.makeText(JniClient_File.this, str2+" 連接已斷開", Toast.LENGTH_LONG); toast.show(); } } });
大家應該主要到了斷開disconnect和 連接connect的功能都是調用我用jni編寫的那個靜態庫(FileOperation)來實現的吧~并且還有相應的錯誤提示信息~接下來是最后一個按鈕send~
send.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub String str3=editText1.getText().toString(); //文件路徑 String str4=editText2.getText().toString()+"\r\n"; //文件名 //String str4=editText2.getText().toString(); int total=fileOperation.fileOperatin(str3,str4); if(total<=0){ Toast toast=Toast.makeText(JniClient_File.this, "上傳文件不成功"+total, Toast.LENGTH_LONG); toast.show(); } else{ Toast toast=Toast.makeText(JniClient_File.this, "the total is"+total, Toast.LENGTH_LONG); toast.show(); progressdialog=new ProgressDialog(JniClient_File.this); progressdialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progressdialog.setTitle("文件傳輸進度"); progressdialog.setMessage("~稍等一會哈~"); progressdialog.setIcon(R.drawable.android1); progressdialog.setProgress(100); progressdialog.setIndeterminate(false); progressdialog.setCancelable(false); progressdialog.show(); Log.d("DUBUG", "total is"+total); new Thread(){ int count=0; public void run() { // TODO Auto-generated method stub try{ while(count<100) { progressdialog.setProgress(count+=4); Thread.sleep(100); } progressdialog.cancel(); }catch(InterruptedException e){ e.printStackTrace(); } } }.start(); } } }); } }
這個很簡單吧~發送的東西交友jni編寫的靜待庫去做了~它返回獨到的字節數并Toast出來,這個便于我們統計嘛~還有一個progredialog。額·這個···美化一下哈~實際上沒什么用處滴~
好了客戶端java部分就到此為止了,下面是重頭戲之一,FileOperation.so啦!!
繼續上代碼,大家如果對JNI有不熟悉的話可以先去了解一下哈~
#include<sys/socket.h> #include<sys/types.h> #include<sys/stat.h> #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> #include<arpa/inet.h> #include<sys/wait.h> #include<netinet/in.h> #include "zeng_Glogo_learn_FileOperation.h" #define MAXBUF 1024 #define FILEPATH 255 #define FILENAME 255 int sockfd; unsigned char buffer[MAXBUF]; char *end; unsigned char end_buf[29]; struct sockaddr_in client_addr; jint Java_zeng_Glogo_learn_FileOperation_fileOperatin (JNIEnv *env, jobject thiz, jstring FilePath,jstring FileName) { const char *filepath_buf=(*env)->GetStringUTFChars(env,FilePath,0); char filepath[FILEPATH]; strcpy(filepath,filepath_buf); (*env)->ReleaseStringUTFChars(env,FilePath,filepath_buf); const char *filename_buf=(*env)->GetStringUTFChars(env,FileName,0); char filename[FILENAME]; memset(filename,0,FILENAME); strncpy(filename,filename_buf,strlen(filename_buf)); (*env)->ReleaseStringUTFChars(env,FileName,filename_buf); //開始讀取文件,并發送給服務端 FILE *fp; fp=fopen(filepath,"rb"); if(!fp) { return -1; } int file_name=send(sockfd,filename,strlen(filename),0); //發送文件名 if(file_name<0) { return -2; } //int file_block_length=0; int count=0; //將文件分塊傳輸 int ReadNum=0; int ReadSum=0; unsigned char LenBuffer[1]; while(!feof(fp)) //讀取文件的內容到buffer中 { ReadNum=fread(buffer,1,MAXBUF,fp); ReadSum+=ReadNum; if(ReadNum>0) { if(send(sockfd,buffer,ReadNum,0)==-1) { fclose(fp); return -3; } bzero(buffer,MAXBUF); count++; } else { fclose(fp); break; } } //bzero(buffer,MAXBUF); /*end="EndLessLimiteFromGlogoPassion"; strcmp(end_buf,end); send(sockfd,end_buf,29,0);*/ //send(sockfd,end_buf,strlen(end_buf),0); fclose(fp); return ReadSum; } jstring Java_zeng_Glogo_learn_FileOperation_connect (JNIEnv *env, jobject thiz, jstring IPAddress, jint PORT) { //轉換String類型 const char * ipaddress_buf=(*env)->GetStringUTFChars(env,IPAddress,0); char ipaddress[255]; strcpy(ipaddress,ipaddress_buf); (*env)->ReleaseStringUTFChars(env,IPAddress,ipaddress_buf); int port=PORT; bzero(&client_addr,sizeof(client_addr)); //把一段內存區的內容全部設置為0 /* AF_INET域 struct sockaddr_in { short int sin_family; //AF_INET unsigned short int sin_port; //Port number struct in_addr{ unsigned long s_addr //Internet address } }*/ sockfd=socket(AF_INET,SOCK_STREAM,0); if(sockfd<0) { return (*env)->NewStringUTF(env,"Socket Error 101"); } client_addr.sin_family=AF_INET; //internet協議族 client_addr.sin_port=htons(port); //端口號 /*也可以這么寫 client_addr.sin_addr.s_addr=inet_addr(ipaddress); //轉化IP地址 inet_addr和inet_aton的不同在于結果返回值的形式不同, //inet_addr返回值為in_addr_t, inet_aton返回值為整形,但兩者的轉換的地址仍存放在straddr中 //in_addr_t inet_addr(const char* straddr) , int inet_aton(const char* straddr,struct in_addr *addrp) //另外,sin_addr.s_addr=htonl(INADDR_ANY)表示*/ if(inet_aton(ipaddress,&client_addr.sin_addr)<0) { return (*env)->NewStringUTF(env,"inet_aton Error 101"); } if(connect(sockfd,(struct sockaddr*)&client_addr,sizeof(client_addr))<0) { return (*env)->NewStringUTF(env,"Connect Error 101"); } else { return (*env)->NewStringUTF(env,"Connec OK!"); } } jstring Java_zeng_Glogo_learn_FileOperation_disconnect (JNIEnv *env, jobject thiz) { close(sockfd); return (*env)->NewStringUTF(env,"Socket Close!"); }
大家應該看到了~這些都是Linux下C編程的一些簡單的東西,這里說明一下,在jint Java_zeng_Glogo_learn_FileOperation_fileOperatin函數中的count變量是沒什么用的,我懶得刪掉而已哈~
在發送文件這邊沒什么的,就是根據傳進來的文件路徑FilePath打開文件讀取內容,并發送文件名給服務端,然后就是在!fp的情況下一次一次的send而已。嗯~客戶端的就到此為止啦!!
下面的是服務端的啦~在這里我糾結了很久,后來終于發現問題,發送方發送的字節數是對的,但是接收方由于是java編寫的,所以傳過來的時候會涉及到基本數據類型的轉換問題,這是一個老問題了~但是嘛~基礎不夠扎實的我還是忽略了~在這里耽誤了很多時間,好了~上代碼吧~!
package learn; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.DataInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; import java.nio.ByteBuffer; public class JniServer_File implements Runnable{ int PORT=8888; /** * @param args */ @Override public void run() { // TODO Auto-generated method stub try{ System.out.println(" 服務器開啟..."); System.out.println("---- ---- ---- ----"); ServerSocket serverSocket=new ServerSocket(PORT); while(true){ Socket client=serverSocket.accept(); System.out.println(" 接收到客戶端請求..."); System.out.println("---- ---- ---- ----"); System.out.println(" 打開輸入流。。"); System.out.println("---- ---- ---- ----"); BufferedInputStream filename=new BufferedInputStream(client.getInputStream()); System.out.println(" 正在讀取內容(文件名)..."); System.out.println("---- ---- ---- ----"); byte file_name[]=new byte[255]; filename.read(file_name); String file_name_trans=new String(file_name); System.out.println(" 讀取文件名完畢,文件名是"+new String(file_name)); System.out.println("---- ---- ---- ----"); try{ if(file_name_trans!=""){ System.out.println(" 開始創建文件.. "+file_name_trans); String file="D:/Eclipse/test/HelloCDT.txt"; //文件的絕對路徑 File newFile=new File(file); //創建文件對象 if(newFile.exists()) { //檢查文件在當前路徑下是否存在 newFile.createNewFile(); } System.out.println("---- ---- ---- ----"); System.out.println("---- ---- ---- ----"); System.out.println(" 打開文件輸出流,準備將讀取內容寫入相應文件"); BufferedOutputStream file_context_in_buf=new BufferedOutputStream(new FileOutputStream(file,false)); System.out.println("---- ---- ---- ----"); System.out.println(" 正在將內容寫入文件..."); int count=0; //測試用的標志 byte[] file_context=new byte[1024]; while(filename.read(file_context)>0){ //循環讀取文件內容,并寫入到相應的文件保存起來 count++; System.out.println(" read times for "+count); String end_buf_str=new String(file_context); if(end_buf_str.contains("END")){ int len=end_buf_str.lastIndexOf("END"); String end_buf_str1=end_buf_str.substring(0, len+3); byte end_buf_byte[]=end_buf_str1.getBytes(); file_context_in_buf.write(end_buf_byte); System.out.println(" write times for "+count); System.out.println(" This times is "+count); System.out.println("---- ---- ---- ----"); break; } file_context_in_buf.write(file_context); System.out.println(" write times for "+count); System.out.println(" This times is "+count); System.out.println("---- ---- ---- ----"); } file_context_in_buf.flush(); System.out.println(" file_context_in_buf flush times for "+count); System.out.println("---- ---- ---- ----"); System.out.println(" 寫入完畢,請打開文件查看..."+count); System.out.println("---- ---- ---- ----"); System.out.println(" 關閉文件各種流..."); System.out.println("---- ---- ---- ----"); file_context_in_buf.close(); //先關閉外層的緩沖連接流 filename.close(); file_name_trans=""; } } catch(IOException e){ e.printStackTrace(); System.out.println(e.getMessage()+" ---1"); } finally{ client.close(); //關閉socket System.out.println(" 關閉連接"); } } } catch(Exception e){ e.printStackTrace(); System.out.println(e.getMessage()+" ---2"); } } public static void main(String[] args) { // TODO Auto-generated method stub Thread jniServer_File=new Thread(new JniServer_File()); jniServer_File.start(); } }
熟悉java的同學應該清楚上面的代碼吧~比較特殊的是在循環接收客戶端send()過來的東西的時候,我這邊做了一點小偷懶,就是發送是.txt文件最后都是以END結尾的,這個給了我一個方便,就是我可以根據這個來判斷什么時候終止再往文件寫入內容。還有一點是,傳輸是以字節為單位來傳輸的,所以要用Strean來接收和存入,用字符流Reader和Writer都是不靠譜的!這里面還涉及到String和byte類型的轉化問題,我在這里也糾結過很久啦~呵呵 ,大家先別噴,我坦誠是我的基礎部夠扎實啦~
好了基本就是這樣子的! 這邊的上圖比較麻煩,所以沒圖沒真相···額好吧········大家這樣想的話也么辦法啦·不過本人已經試驗過啦~一個15014KB的文件還有一個396800KB的文件傳輸都是沒問題的,放在手機上測試也OK~
上圖不方便我這里貼一下man.xml的代碼讓大家都整個布局都有些了解吧~
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" android:id="@+id/tv" /> <TextView android:layout_marginTop="15dp" android:layout_below="@id/tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/dir" android:text="@string/dir"/> <EditText android:layout_width="260dp" android:layout_height="wrap_content" android:hint="@string/file_name_hint" android:id="@+id/file_name" android:layout_below="@id/tv" android:layout_toRightOf="@id/dir"/> <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="@string/dir1" android:layout_below="@id/dir" android:id="@+id/dir1" android:layout_marginTop="25dp"/> <EditText android:layout_width="260dp" android:layout_height="wrap_content" android:hint="@string/file_seletced_hint" android:id="@+id/file_seletced" android:layout_below="@id/file_name" android:layout_toRightOf="@id/dir1"/> <Spinner android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/spinner" android:layout_below="@id/file_seletced"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/send" android:id="@+id/send" android:layout_below="@id/spinner" android:layout_alignRight="@id/spinner"/> <EditText android:layout_height="wrap_content" android:layout_width="150dp" android:layout_below="@id/send" android:layout_alignParentLeft="true" android:hint="@string/ip" android:id="@+id/ip"/> <EditText android:layout_height="wrap_content" android:layout_width="80dp" android:layout_toRightOf="@id/ip" android:hint="@string/port" android:layout_below="@id/send" android:layout_marginLeft="5dp" android:id="@+id/port"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/port" android:layout_below="@id/send" android:id="@+id/sure" android:layout_alignParentRight="true" android:text="@string/sure"/> <!-- <ProgressBar android:layout_height="wrap_content" android:layout_width="fill_parent" android:layout_below="@id/sure" android:visibility="gone" android:id="@+id/progressBar" style="?android:attr/progressBarStyleHorizontal" android:max="100" android:progress="2" android:secondaryProgress="4"/> --> <Button android:layout_width="90dp" android:layout_height="wrap_content" android:text="@string/exit" android:id="@+id/exit" android:layout_alignParentRight="true" android:layout_alignParentBottom="true"/> <Button android:layout_width="120dp" android:layout_height="wrap_content" android:text="@string/disconnect" android:id="@+id/disconnect" android:layout_toLeftOf="@id/exit" android:layout_alignParentBottom="true"/> <Button android:layout_height="wrap_content" android:layout_width="120dp" android:text="@string/connect" android:id="@+id/connect" android:layout_toLeftOf="@id/disconnect" android:layout_alignParentBottom="true"/> </RelativeLayout>