Android上传图片(PHP服务器)
原理
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/"
参考文章
转载请注明出处http://www.cnblogs.com/chenyg32/