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.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之~ } });</i> 上面这些东东如果大家不了解的话去看一下有关Android入门的书,这些都会有的~ 接下来的就是几个按钮的设定了~ 查看源码打印? 01 //退出 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> |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架