在Android中上传文件可以采用HTTP方式,也可以采用Socket方式,但是HTTP方式不能上传大文件,这里介绍一种通过Socket方式来进行断点续传的方式,服务端会记录下文件的上传进度,当某一次上传过程意外终止后,下一次可以继续上传,这里用到的其实还是J2SE里的知识。
这个上传程序的原理是:客户端第一次上传时向服务端发送“Content-Length=35;filename=WinRAR_3.90_SC.exe;sourceid=“这种格式的字符串,服务端收到后会查找该文件是否有上传记录,如果有就返回已经上传的位置,否则返回新生成的sourceid以及position为0,类似”sourceid=2324838389;position=0“这样的字符串,客户端收到返回后的字符串后再从指定的位置开始上传文件。
首先是服务端代码:
SocketServer.java
1.package com.android.socket.server;
2.
3.import java.io.File; 4.import java.io.FileInputStream; 5.import java.io.FileOutputStream; 6.import java.io.IOException; 7.import java.io.OutputStream; 8.import java.io.PushbackInputStream; 9.import java.io.RandomAccessFile; 10.import java.net.ServerSocket; 11.import java.net.Socket; 12.import java.text.SimpleDateFormat; 13.import java.util.Date; 14.import java.util.HashMap; 15.import java.util.Map; 16.import java.util.Properties; 17.import java.util.concurrent.ExecutorService; 18.import java.util.concurrent.Executors; 19.
20.import com.android.socket.utils.StreamTool; 21.
22.public class SocketServer { 23. private ExecutorService executorService;// 线程池 24. private ServerSocket ss = null; 25. private int port;// 监听端口 26. private boolean quit;// 是否退出 27. private Map<Long, FileLog> datas = new HashMap<Long, FileLog>();// 存放断点数据,最好改为数据库存放 28.
29. public SocketServer(int port) { 30. this.port = port; 31. // 初始化线程池 32. executorService = Executors.newFixedThreadPool(Runtime.getRuntime()
33. .availableProcessors() * 50); 34. }
35.
36. // 启动服务 37. public void start() throws Exception { 38. ss = new ServerSocket(port); 39. while (!quit) { 40. Socket socket = ss.accept();// www.linuxidc.com接受客户端的请求 41. // 为支持多用户并发访问,采用线程池管理每一个用户的连接请求 42. executorService.execute(new SocketTask(socket));// 启动一个线程来处理请求 43. }
44. }
45.
46. // 退出 47. public void quit() { 48. this.quit = true; 49. try { 50. ss.close();
51. } catch (IOException e) { 52. e.printStackTrace();
53. }
54. }
55.
56. public static void main(String[] args) throws Exception { 57. SocketServer server = new SocketServer(8787); 58. server.start();
59. }
60.
61. private class SocketTask implements Runnable { 62. private Socket socket; 63.
64. public SocketTask(Socket socket) { 65. this.socket = socket; 66. }
67.
68. @Override 69. public void run() { 70. try { 71. System.out.println("accepted connenction from " 72. + socket.getInetAddress() + " @ " + socket.getPort()); 73. PushbackInputStream inStream = new PushbackInputStream( 74. socket.getInputStream());
75. // 得到客户端发来的第一行协议数据:Content-Length=143253434;filename=xxx.3gp;sourceid= 76. // 如果用户初次上传文件,sourceid的值为空。 77. String head = StreamTool.readLine(inStream);
78. System.out.println(head);
79. if (head != null) { 80. // 下面从协议数据中读取各种参数值 81. String[] items = head.split(";"); 82. String filelength = items[0].substring(items[0].indexOf("=") + 1); 83. String filename = items[1].substring(items[1].indexOf("=") + 1); 84. String sourceid = items[2].substring(items[2].indexOf("=") + 1); 85. Long id = System.currentTimeMillis();
86. FileLog log = null; 87. if (null != sourceid && !"".equals(sourceid)) { 88. id = Long.valueOf(sourceid);
89. log = find(id);//查找上传的文件是否存在上传记录 90. }
91. File file = null; 92. int position = 0; 93. if(log==null){//如果上传的文件不存在上传记录,为文件添加跟踪记录 94. String path = new SimpleDateFormat("yyyy/MM/dd/HH/mm").format(new Date()); 95. File dir = new File("file/"+ path); 96. if(!dir.exists()) dir.mkdirs(); 97. file = new File(dir, filename); 98. if(file.exists()){//如果上传的文件发生重名,然后进行改名 99. filename = filename.substring(0, filename.indexOf(".")-1)+ dir.listFiles().length+ filename.substring(filename.indexOf(".")); 100. file = new File(dir, filename); 101. }
102. save(id, file);
103. }else{// 如果上传的文件存在上传记录,读取上次的断点位置 104. file = new File(log.getPath());//从上传记录中得到文件的路径 105. if(file.exists()){ 106. File logFile = new File(file.getParentFile(), file.getName()+".log"); 107. if(logFile.exists()){ 108. Properties properties = new Properties(); 109. properties.load(new FileInputStream(logFile)); 110. position = Integer.valueOf(properties.getProperty("length"));//读取断点位置 111. }
112. }
113. }
114.
115. OutputStream outStream = socket.getOutputStream();
116. String response = "sourceid="+ id+ ";position="+ position+ "\r\n"; 117. //服务器收到客户端的请求信息后,给客户端返回响应信息:sourceid=1274773833264;position=0 118. //sourceid由服务生成,唯一标识上传的文件,position指示客户端从文件的什么位置开始上传 119. outStream.write(response.getBytes());
120.
121. RandomAccessFile fileOutStream = new RandomAccessFile(file, "rwd"); 122. if(position==0) fileOutStream.setLength(Integer.valueOf(filelength));//设置文件长度 123. fileOutStream.seek(position);//移动文件指定的位置开始写入数据 124. byte[] buffer = new byte[1024]; 125. int len = -1; 126. int length = position; 127. while( (len=inStream.read(buffer)) != -1){//从输入流中读取数据写入到文件中 128. fileOutStream.write(buffer, 0, len); 129. length += len;
130. Properties properties = new Properties(); 131. properties.put("length", String.valueOf(length)); 132. FileOutputStream logFile = new FileOutputStream(new File(file.getParentFile(), file.getName()+".log")); 133. properties.store(logFile, null);//实时记录文件的最后保存位置 134. logFile.close();
135. }
136. if(length==fileOutStream.length()) delete(id); 137. fileOutStream.close();
138. inStream.close();
139. outStream.close();
140. file = null; 141. }
142. } catch (Exception e) { 143. e.printStackTrace();
144. } finally { 145. try { 146. if(socket != null && !socket.isClosed()) socket.close(); 147. } catch (IOException e) {} 148. }
149. }
150.
151. }
152.
153. public FileLog find(Long sourceid) { 154. return datas.get(sourceid); 155. }
156.
157. // 保存上传记录 158. public void save(Long id, File saveFile) { 159. // 日后可以改成通过数据库存放 160. datas.put(id, new FileLog(id, saveFile.getAbsolutePath())); 161. }
162.
163. // 当文件上传完毕,删除记录 164. public void delete(long sourceid) { 165. if (datas.containsKey(sourceid)) 166. datas.remove(sourceid);
167. }
168.
169. private class FileLog { 170. private Long id; 171. private String path; 172.
173. public FileLog(Long id, String path) { 174. super(); 175. this.id = id; 176. this.path = path; 177. }
178.
179. public Long getId() { 180. return id; 181. }
182.
183. public void setId(Long id) { 184. this.id = id; 185. }
186.
187. public String getPath() { 188. return path; 189. }
190.
191. public void setPath(String path) { 192. this.path = path; 193. }
194.
195. }
196.}
ServerWindow.java
1.package com.android.socket.server;
2.
3.import java.awt.BorderLayout; 4.import java.awt.Frame; 5.import java.awt.Label; 6.import java.awt.event.WindowEvent; 7.import java.awt.event.WindowListener; 8.
9.public class ServerWindow extends Frame{ 10. private SocketServer server; 11. private Label label; 12.
13. public ServerWindow(String title){ 14. super(title); 15. server = new SocketServer(8787); 16. label = new Label(); 17. add(label, BorderLayout.PAGE_START);
18. label.setText("服务器已经启动www.linuxidc.com"); 19. this.addWindowListener(new WindowListener() { 20. @Override 21. public void windowOpened(WindowEvent e) { 22. new Thread(new Runnable() { 23. @Override 24. public void run() { 25. try { 26. server.start();
27. } catch (Exception e) { 28. e.printStackTrace();
29. }
30. }
31. }).start();
32. }
33.
34. @Override 35. public void windowIconified(WindowEvent e) { 36. }
37.
38. @Override 39. public void windowDeiconified(WindowEvent e) { 40. }
41.
42. @Override 43. public void windowDeactivated(WindowEvent e) { 44. }
45.
46. @Override 47. public void windowClosing(WindowEvent e) { 48. server.quit();
49. System.exit(0); 50. }
51.
52. @Override 53. public void windowClosed(WindowEvent e) { 54. }
55.
56. @Override 57. public void windowActivated(WindowEvent e) { 58. }
59. });
60. }
61. /** 62. * @param args
63. */
64. public static void main(String[] args) { 65. ServerWindow window = new ServerWindow("文件上传服务端"); 66. window.setSize(300, 300); 67. window.setVisible(true); 68. }
69.
70.}
工具类StreamTool.java
1.package com.android.socket.utils;
2.
3.import java.io.ByteArrayOutputStream; 4.import java.io.File; 5.import java.io.FileOutputStream; 6.import java.io.IOException; 7.import java.io.InputStream; 8.import java.io.PushbackInputStream; 9.
10.public class StreamTool { 11.
12. public static void save(File file, byte[] data) throws Exception { 13. FileOutputStream outStream = new FileOutputStream(file); 14. outStream.write(data);
15. outStream.close();
16. }
17.
18. public static String readLine(PushbackInputStream in) throws IOException { 19. char buf[] = new char[128]; 20. int room = buf.length; 21. int offset = 0; 22. int c; 23.loop: while (true) { 24. switch (c = in.read()) { 25. case -1: 26. case '\n': 27. break loop; 28. case '\r': 29. int c2 = in.read(); 30. if ((c2 != '\n') && (c2 != -1)) in.unread(c2); 31. break loop; 32. default: 33. if (--room < 0) { 34. char[] lineBuffer = buf; 35. buf = new char[offset + 128]; 36. room = buf.length - offset - 1; 37. System.arraycopy(lineBuffer, 0, buf, 0, offset); 38.
39. }
40. buf[offset++] = (char) c; 41. break; 42. }
43. }
44. if ((c == -1) && (offset == 0)) return null; 45. return String.copyValueOf(buf, 0, offset); 46. }
47.
48. /** 49. * 读取流
50. * @param inStream
51. * @return 字节数组
52. * @throws Exception
53. */
54. public static byte[] readStream(InputStream inStream) throws Exception{ 55. ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); 56. byte[] buffer = new byte[1024]; 57. int len = -1; 58. while( (len=inStream.read(buffer)) != -1){ 59. outSteam.write(buffer, 0, len); 60. }
61. outSteam.close();
62. inStream.close();
63. return outSteam.toByteArray(); 64. }
65.}
Android客户端代码:
本篇文章来源于 Linux公社网站(www.linuxidc.com) 原文链接:http://www.linuxidc.com/Linux/2012-03/55567.htm
布局文件layout/main.xml
1.<?xml version="1.0" encoding="utf-8"?>
2.<LinearLayout xmlns:Android="http://schemas.android.com/apk/res/android" 3. android:orientation="vertical" 4. android:layout_width="fill_parent" 5. android:layout_height="fill_parent" 6. > 7.<TextView 8. android:layout_width="fill_parent" 9. android:layout_height="wrap_content" 10. android:text="@string/filename" 11. /> 12.
13. <EditText 14. android:layout_width="fill_parent" 15. android:layout_height="wrap_content" 16. android:text="WinRAR_3.90_SC.exe" 17. android:id="@+id/filename" 18. /> 19.
20. <Button 21. android:layout_width="wrap_content" 22. android:layout_height="wrap_content" 23. android:text="@string/button" 24. android:id="@+id/button" 25. /> 26. <ProgressBar 27. android:layout_width="fill_parent" 28. android:layout_height="20px" 29. style="?android:attr/progressBarStyleHorizontal" 30. android:id="@+id/uploadbar" 31. /> 32. <TextView 33. android:layout_width="fill_parent" 34. android:layout_height="wrap_content" 35. android:gravity="center" 36. android:id="@+id/result" 37. /> 38.</LinearLayout> 数据文件values/strings.xml
1.<?xml version="1.0" encoding="utf-8"?>
2.<resources> 3. <string name="hello">Hello World, UploadActivity!</string> 4. <string name="app_name">大视频文件断点上传</string> 5. <string name="filename">文件名称</string> 6. <string name="button">上传</string> 7. <string name="sdcarderror">SDCard不存在或者写保护</string> 8. <string name="success">上传完成</string> 9. <string name="error">上传失败</string> 10. <string name="filenotexsit">文件不存在</string> 11.</resources> AndroidManifest.xml
1.<?xml version="1.0" encoding="utf-8"?>
2.<manifest xmlns:android="http://schemas.android.com/apk/res/android" 3. package="com.android.upload" 4. android:versionCode="1" 5. android:versionName="1.0" > 6.
7. <uses-sdk android:minSdkVersion="8" /> 8.
9. <application 10. android:icon="@drawable/ic_launcher" 11. android:label="@string/app_name" > 12. <activity 13. android:name=".UploadActivity" 14. android:label="@string/app_name" > 15. <intent-filter> 16. <action android:name="android.intent.action.MAIN" /> 17.
18. <category android:name="android.intent.category.LAUNCHER" /> 19. </intent-filter> 20. </activity> 21. </application> 22. <!-- 访问网络的权限 --> 23. <uses-permission android:name="android.permission.INTERNET"/> 24. <!-- 在SDCard中创建与删除文件权限 --> 25. <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> 26. <!-- 往SDCard写入数据权限 --> 27. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 28.</manifest> UploadActivity.java
1.package com.android.upload;
2.
3.import java.io.File; 4.import java.io.OutputStream; 5.import java.io.PushbackInputStream; 6.import java.io.RandomAccessFile; 7.import java.net.Socket; 8.
9.import android.app.Activity; 10.import android.os.Bundle; 11.import android.os.Environment; 12.import android.os.Handler; 13.import android.os.Message; 14.import android.view.View; 15.import android.widget.Button; 16.import android.widget.EditText; 17.import android.widget.ProgressBar; 18.import android.widget.TextView; 19.import android.widget.Toast; 20.
21.import com.android.service.UploadLogService; 22.import com.android.utils.StreamTool; 23.
24.public class UploadActivity extends Activity { 25. private EditText filenameText; 26. private TextView resulView; 27. private ProgressBar uploadbar; 28. private UploadLogService logService; 29. private Handler handler = new Handler(){ 30. @Override 31. public void handleMessage(Message msg) { 32. int length = msg.getData().getInt("size"); 33. uploadbar.setProgress(length);
34. float num = (float)uploadbar.getProgress()/(float)uploadbar.getMax(); 35. int result = (int)(num * 100); 36. resulView.setText(result+ "%"); 37. if(uploadbar.getProgress()==uploadbar.getMax()){ 38. Toast.makeText(UploadActivity.this, R.string.success, 1).show(); 39. }
40. }
41. };
42.
43. @Override 44. public void onCreate(Bundle savedInstanceState) { 45. super.onCreate(savedInstanceState); 46. setContentView(R.layout.main);
47.
48. logService = new UploadLogService(this); 49. filenameText = (EditText)this.findViewById(R.id.filename); 50. uploadbar = (ProgressBar) this.findViewById(R.id.uploadbar); 51. resulView = (TextView)this.findViewById(R.id.result); 52. Button button =(Button)this.findViewById(R.id.button); 53. button.setOnClickListener(new View.OnClickListener() { 54. @Override 55. public void onClick(View v) { 56. String filename = filenameText.getText().toString();
57. if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ 58. File uploadFile = new File(Environment.getExternalStorageDirectory(), filename); 59. if(uploadFile.exists()){ 60. uploadFile(uploadFile);
61. }else{ 62. Toast.makeText(UploadActivity.this, R.string.filenotexsit, 1).show(); 63. }
64. }else{ 65. Toast.makeText(UploadActivity.this, R.string.sdcarderror, 1).show(); 66. }
67. }
68. });
69. }
70. /** 71. * 上传文件
72. * @param uploadFile
73. */
74. private void uploadFile(final File uploadFile) { 75. new Thread(new Runnable() { 76. @Override 77. public void run() { 78. try { 79. uploadbar.setMax((int)uploadFile.length()); 80. String souceid = logService.getBindId(uploadFile);
81. String head = "Content-Length="+ uploadFile.length() + ";filename="+ uploadFile.getName() + ";sourceid="+ 82. (souceid==null? "" : souceid)+"\r\n"; 83. Socket socket = new Socket("192.168.1.123", 8787); 84. OutputStream outStream = socket.getOutputStream();
85. outStream.write(head.getBytes());
86.
87. PushbackInputStream inStream = new PushbackInputStream(socket.getInputStream()); 88. String response = StreamTool.readLine(inStream);
89. String[] items = response.split(";"); 90. String responseid = items[0].substring(items[0].indexOf("=")+1); 91. String position = items[1].substring(items[1].indexOf("=")+1); 92. if(souceid==null){//代表原来没有上传过此文件,往数据库添加一条绑定记录 93. logService.save(responseid, uploadFile);
94. }
95. RandomAccessFile fileOutStream = new RandomAccessFile(uploadFile, "r"); 96. fileOutStream.seek(Integer.valueOf(position));
97. byte[] buffer = new byte[1024]; 98. int len = -1; 99. int length = Integer.valueOf(position); 100. while( (len = fileOutStream.read(buffer)) != -1){ 101. outStream.write(buffer, 0, len); 102. length += len;
103. Message msg = new Message(); 104. msg.getData().putInt("size", length); 105. handler.sendMessage(msg);
106. }
107. fileOutStream.close();
108. outStream.close();
109. inStream.close();
110. socket.close();
111. if(length==uploadFile.length()) logService.delete(uploadFile); 112. } catch (Exception e) { 113. e.printStackTrace();
114. }
115. }
116. }).start();
117. }
118.}
UploadLogService.java
1.package com.android.service;
2.
3.import java.io.File; 4.
5.import android.content.Context; 6.import android.database.Cursor; 7.import android.database.sqlite.SQLiteDatabase; 8.
9.public class UploadLogService { 10. private DBOpenHelper dbOpenHelper; 11.
12. public UploadLogService(Context context){ 13. this.dbOpenHelper = new DBOpenHelper(context); 14. }
15.
16. public void save(String sourceid, File uploadFile){ 17. SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
18. db.execSQL("insert into uploadlog(uploadfilepath, sourceid) values(?,?)", 19. new Object[]{uploadFile.getAbsolutePath(),sourceid}); 20. }
21.
22. public void delete(File uploadFile){ 23. SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
24. db.execSQL("delete from uploadlog where uploadfilepath=?", new Object[]{uploadFile.getAbsolutePath()}); 25. }
26.
27. public String getBindId(File uploadFile){ 28. SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
29. Cursor cursor = db.rawQuery("select sourceid from uploadlog where uploadfilepath=?", 30. new String[]{uploadFile.getAbsolutePath()}); 31. if(cursor.moveToFirst()){ 32. return cursor.getString(0); 33. }
34. return null; 35. }
36.}
DBOpenHelper.java
1.package com.android.service;
2.
3.import android.content.Context; 4.import android.database.sqlite.SQLiteDatabase; 5.import android.database.sqlite.SQLiteOpenHelper; 6.
7.public class DBOpenHelper extends SQLiteOpenHelper { 8.
9. public DBOpenHelper(Context context) { 10. super(context, "upload.db", null, 1); 11. }
12.
13. @Override 14. public void onCreate(SQLiteDatabase db) { 15. db.execSQL("CREATE TABLE uploadlog (_id integer primary key autoincrement, uploadfilepath varchar(100), sourceid varchar(10))"); 16. }
17.
18. @Override 19. public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 20. db.execSQL("DROP TABLE IF EXISTS uploadlog"); 21. onCreate(db);
22. }
23.
24.}
StreamTool.java上面已经给出过了
本篇文章来源于 Linux公社网站(www.linuxidc.com) 原文链接:http://www.linuxidc.com/Linux/2012-03/55567p2.htm