最近一直在完成個任務,有關Android手機文件傳輸的,如今先作了一步,實現了手機能夠上傳文件到pc端。java
先簡單介紹一下吧,架設在電腦上的pc端,運行在Android手機上的客戶端,pc端用java語言編寫,客戶端這邊是結合c和android
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;
這些都很簡單吧~app
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作準備的吧~socket
/** 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入門的書,這些都會有的~ide
接下來的就是幾個按鈕的設定了~函數
//退出 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>
固然整個程序的bug仍是很明顯的~不過基本功能以及能夠實現~不足之處亟待完善~但願你們多多指教~
第一次寫博客哦~哈哈!