最近一直在完成个任务,有关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>
当然整个程序的bug还是很明显的~不过基本功能以及可以实现~不足之处亟待完善~希望大家多多指教~
第一次写博客哦~哈哈!
来源:oschina
链接:https://my.oschina.net/u/265349/blog/60197