原理
Android客戶端模擬一個HTTP的Post請求到服務器端,服務器端接收相應的Post請求后,返回響應信息給給客戶端。
PHP服務器
<?php
move_uploaded_file($_FILES['file']['tmp_name'], "./upload/".$_FILES["file"]["name"]);
?>
Android客戶端

package com.example.uploadfile.app; import android.app.Activity; import android.os.Bundle; import android.os.Environment; import android.os.StrictMode; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import java.io.DataOutputStream; import java.io.FileInputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; public class MainActivity extends Activity { private String fileName = "image.jpg"; //報文中的文件名參數 private String path = Environment.getExternalStorageDirectory().getPath(); //Don't use "/sdcard/" here private String uploadFile = path + "/" + fileName; //待上傳的文件路徑 private String postUrl = "http://mycloudnote.sinaapp.com/upload.php"; //處理POST請求的頁面 private TextView mText1; private TextView mText2; private Button mButton; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //"文件路徑:\n"+ mText1 = (TextView) findViewById(R.id.myText1); mText1.setText(uploadFile); //"上傳網址:\n"+ mText2 = (TextView) findViewById(R.id.myText2); mText2.setText(postUrl); /* 設置mButton的onClick事件處理 */ mButton = (Button) findViewById(R.id.myButton); mButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { uploadFile(); } }); } /* 上傳文件至Server的方法 */ private void uploadFile() { String end = "\r\n"; String twoHyphens = "--"; String boundary = "*****"; try { URL url = new URL(postUrl); HttpURLConnection con = (HttpURLConnection) url.openConnection(); /* Output to the connection. Default is false, set to true because post method must write something to the connection */ con.setDoOutput(true); /* Read from the connection. Default is true.*/ con.setDoInput(true); /* Post cannot use caches */ con.setUseCaches(false); /* Set the post method. Default is GET*/ con.setRequestMethod("POST"); /* 設置請求屬性 */ con.setRequestProperty("Connection", "Keep-Alive"); con.setRequestProperty("Charset", "UTF-8"); con.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary); /*設置StrictMode 否則HTTPURLConnection連接失敗,因為這是在主進程中進行網絡連接*/ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectDiskReads().detectDiskWrites().detectNetwork().penaltyLog().build()); /* 設置DataOutputStream,getOutputStream中默認調用connect()*/ DataOutputStream ds = new DataOutputStream(con.getOutputStream()); //output to the connection ds.writeBytes(twoHyphens + boundary + end); ds.writeBytes("Content-Disposition: form-data; " + "name=\"file\";filename=\"" + fileName + "\"" + end); ds.writeBytes(end); /* 取得文件的FileInputStream */ FileInputStream fStream = new FileInputStream(uploadFile); /* 設置每次寫入8192bytes */ int bufferSize = 8192; byte[] buffer = new byte[bufferSize]; //8k int length = -1; /* 從文件讀取數據至緩沖區 */ while ((length = fStream.read(buffer)) != -1) { /* 將資料寫入DataOutputStream中 */ ds.write(buffer, 0, length); } ds.writeBytes(end); ds.writeBytes(twoHyphens + boundary + twoHyphens + end); /* 關閉流,寫入的東西自動生成Http正文*/ fStream.close(); /* 關閉DataOutputStream */ ds.close(); /* 從返回的輸入流讀取響應信息 */ InputStream is = con.getInputStream(); //input from the connection 正式建立HTTP連接 int ch; StringBuffer b = new StringBuffer(); while ((ch = is.read()) != -1) { b.append((char) ch); } /* 顯示網頁響應內容 */ Toast.makeText(MainActivity.this, b.toString().trim(), Toast.LENGTH_SHORT).show();//Post成功 } catch (Exception e) { /* 顯示異常信息 */ Toast.makeText(MainActivity.this, "Fail:" + e, Toast.LENGTH_SHORT).show();//Post失敗 } } }
設置連接(HTTP頭) -> 建立TCP連接 -> 設置HTTP正文 -> 建立HTTP連接(正式Post)-> 從返回的輸入流讀取響應信息
1.設置連接(HTTP頭)
URL url = new URL(postUrl); HttpURLConnection con = (HttpURLConnection) url.openConnection(); /* Output to the connection. Default is false, set to true because post method must write something to the connection */ con.setDoOutput(true); /* Read from the connection. Default is true.*/ con.setDoInput(true); /* Post cannot use caches */ con.setUseCaches(false); /* Set the post method. Default is GET*/ con.setRequestMethod("POST"); /* 設置請求屬性 */ con.setRequestProperty("Connection", "Keep-Alive"); con.setRequestProperty("Charset", "UTF-8"); con.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
2.建立TCP連接
DataOutputStream ds = new DataOutputStream(con.getOutputStream()); //output to the connection
con.getOutputStream()中會默認調用con.connect(),此時客戶端與服務器建立的只是1個TCP連接而非HTTP。
HTTP請求=HTTP頭+HTTP正文。
在connect()里面,會根據HttpURLConnection對象的配置值生成HTTP頭,所以對con的一切配置都必須在connect()方法之前完成。
3.設置HTTP正文
正文通過DataOutputStream寫入,只是寫入內存而不會發送到網絡中去,而是在流關閉時,根據寫入的內容生成HTTP正文。
ds.writeBytes(twoHyphens + boundary + end); ds.writeBytes("Content-Disposition: form-data; " + "name=\"file\";filename=\"" + fileName + "\"" + end); ds.writeBytes(end); /* 取得文件的FileInputStream */ FileInputStream fStream = new FileInputStream(uploadFile); /* 設置每次寫入8192bytes */ int bufferSize = 8192; byte[] buffer = new byte[bufferSize]; //8k int length = -1; /* 從文件讀取數據至緩沖區 */ while ((length = fStream.read(buffer)) != -1) { /* 將資料寫入DataOutputStream中 */ ds.write(buffer, 0, length); } ds.writeBytes(end); ds.writeBytes(twoHyphens + boundary + twoHyphens + end); /* 關閉流,寫入的東西自動生成Http正文*/ fStream.close(); /* 關閉DataOutputStream */ ds.close();
4.建立HTTP連接(正式Post)
至此,HTTP請求設置完畢,con.getInputStream()中會將請求(HTTP頭+HTTP正文)發送到服務器,並返回一個輸入流。所以在getInputStream()之前,HTTP正文部分一定要先設置好。
InputStream is = con.getInputStream(); //input from the connection 正式建立HTTP連接
5.從返回的輸入流讀取響應信息
int ch; StringBuffer b = new StringBuffer(); while ((ch = is.read()) != -1) { b.append((char) ch); } /* 顯示網頁響應內容 */ Toast.makeText(MainActivity.this, b.toString().trim(), Toast.LENGTH_SHORT).show();//Post成功
布局XML
兩個Text和一個Button

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="${packageName}.${activityClass}"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="New Text" android:id="@+id/myText1" android:layout_above="@+id/myText2" android:layout_centerHorizontal="true" android:layout_marginBottom="80dp"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="New Text" android:id="@+id/myText2" android:layout_centerVertical="true" android:layout_centerHorizontal="true"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Upload" android:id="@+id/myButton" android:layout_marginTop="80dp" android:layout_below="@+id/myText2" android:layout_centerHorizontal="true"/> </RelativeLayout>
AndroidManifest
添加網絡權限、SD卡讀寫權限、掛載文件系統權限。

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.uploadfile.app" > <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.example.uploadfile.app.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="15" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> </manifest>
注意事項
1.由於Android不建議在主進程中進行網絡訪問,所以使用HttpURLConnection連接到服務端時拋出異常,加入以下語句即可。
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectDiskReads().detectDiskWrites().detectNetwork().penaltyLog().build());
2.獲取SD卡路徑時,請使用Environment.getExternalStorageDirectory().getPath();而不是"/sdcard/"