不通过httpinputfile如何上传文件到服务器
你是想用不想用http协议实现?还是只是不用 inputfile,如果不用http协议,可以用第三方插件,activeMq,或者其他的如果http,可以用异步query的fileUpload,或者,通过java建立httpclient上传
分析一:
在研究Volley框架的源码中,发现它在HTTP请求的使用上比较有意思,在Android
23及以上版本,使用的是HttpURLConnection,而在Android
22及以下版本,使用的是HttpClient。我也比较好奇这么使用的原因,于是专门找到了一位Google的工程师写的一篇博客,文中对
HttpURLConnection和HttpClient进行了对比,下面我就给大家简要地翻译一下。
原文地址:http://android-developersblogspotcom/2011/09/androids-http-clientshtml
大多数的Android应用程序都会使用HTTP协议来发送和接收网络数据,而Android中主要提供了两种方式来进行HTTP操
作,HttpURLConnection和HttpClient。这两种方式都支持HTTPS协议、以流的形式进行上传和下载、配置超时时间、IPv6、
以及连接池等功能。
HttpClient:
DefaultHttpClient和它的兄弟AndroidHttpClient都是HttpClient具体的实现类,它们都拥有众多的API,而且实现比较稳定,bug数量也很少。
但同时也由于HttpClient的API数量过多,使得我们很难在不破坏兼容性的情况下对它进行升级和扩展,所以目前Android团队在提升和优化HttpClient方面的工作态度并不积极。
HttpURLConnection:
HttpURLConnection是一种多用途、轻量极的HTTP客户端,使用它来进行HTTP操作可以适用于大多数的应用程序。虽然HttpURLConnection的API提供的比较简单,但是同时这也使得我们可以更加容易地去使用和扩展它。
不过在Android 22版本之前,HttpURLConnection一直存在着一些令人厌烦的bug。比如说对一个可读的InputStream调用close()方法时,就有可能会导致连接池失效了。那么我们通常的解决办法就是直接禁用掉连接池的功能:
[java] view plaincopy
private void disableConnectionReuseIfNecessary() {
// 这是一个22版本之前的bug
if (IntegerparseInt(BuildVERSIONSDK) < BuildVERSION_CODESFROYO) {
SystemsetProperty("httpkeepAlive", "false");
}
}
配置你的Web服务器来支持对客户端的响应进行压缩的功能,从而可以在这一改进上获取到最大的好处。如果在压缩响应的时候出现了问题,这篇文档会告诉你如何禁用掉这个功能。
但
是如果启动了响应压缩的功能,HTTP响应头里的Content-Length就会代表着压缩后的长度,这时再使用getContentLength()
方法来取出解压后的数据就是错误的了。正确的做法应该是一直调用InputStreamread()方法来读取响应数据,一直到出现-1为止。
我
们在Android 23版本中还增加了一些HTTPS方面的改进,现在HttpsURLConnection会使用SNI(Server Name
Indication)的方式进行连接,使得多个HTTPS主机可以共享同一个IP地址。除此之外,还增加了一些压缩和会话的机制。如果连接失败,它会自
动去尝试重新进行连接。这使得HttpsURLConnection可以在不破坏老版本兼容性的前提下,更加高效地连接最新的服务器。
在Android 40版本中,我们又添加了一些响应的缓存机制。当缓存被安装后(调用HttpResponseCache的install()方法),所有的HTTP请求都会满足以下三种情况:
所有的缓存响应都由本地存储来提供。因为没有必要去发起任务的网络连接请求,所有的响应都可以立刻获取到。
视
情况而定的缓存响应必须要有服务器来进行更新检查。比如说客户端发起了一条类似于 “如果/foopng这张发生了改变,就将它发送给我”
这样的请求,服务器需要将更新后的数据进行返回,或者返回一个304 Not
Modified状态。如果请求的内容没有发生,客户端就不会下载任何数据。
没有缓存的响应都是由服务器直接提供的。这部分响应会在稍后存储到响应缓存中。
由于这个功能是在40之后的版本才有的,通常我们就可以使用反射的方式来启动响应缓存功能。下面的示例代码展示了如何在Android 40及以后的版本中去启用响应缓存的功能,同时还不会影响到之前的版本:
[java] view plaincopy
private void enableHttpResponseCache() {
try {
long httpCacheSize = 10 1024 1024; // 10 MiB
File httpCacheDir = new File(getCacheDir(), "http");
ClassforName("androidnethttpHttpResponseCache")
getMethod("install", Fileclass, longclass)
invoke(null, httpCacheDir, httpCacheSize);
} catch (Exception httpResponseCacheNotAvailable) {
}
}
你也应该同时配置一下你的Web服务器,在HTTP响应上加入缓存的消息头。哪一种才是最好的?在Android
22版本之前,HttpClient拥有较少的bug,因此使用它是最好的选择。
而在Android
23版本及以后,HttpURLConnection则是最佳的选择。它的API简单,体积较小,因而非常适用于Android项目。压缩和缓存机制可
以有效地减少网络访问的流量,在提升速度和省电方面也起到了较大的作用。对于新的应用程序应该更加偏向于使用HttpURLConnection,因为在
以后的工作当中我们也会将更多的时间放在优化HttpURLConnection上面。
分析二:
HTTP 协议可能是现在 Internet 上使用得最多、最重要的协议了,越来越多的 Java 应用程序需要直接通过 HTTP
协议来访问网络资源。在 JDK 的 javanet 包中已经提供了访问 HTTP 协议的基本功能:HttpURLConnection。
HttpURLConnection是java的标准类,HttpURLConnection继承自URLConnection,可用于向指定网站发送GET请求、POST请求。它在URLConnection的基础上提供了如下便捷的方法:
int getResponseCode():获取服务器的响应代码。
String getResponseMessage():获取服务器的响应消息。
String getResponseMethod():获取发送请求的方法。
void setRequestMethod(String method):设置发送请求的方法。
在一般情况下,如果只是需要Web站点的某个简单页面提交请求并获取服务器响应,HttpURLConnection完全可以胜任。但在绝大部分情
况下,Web站点的网页可能没这么简单,这些页面并不是通过一个简单的URL就可访问的,可能需要用户登录而且具有相应的权限才可访问该页面。在这种情况
下,就需要涉及Session、Cookie的处理了,如果打算使用HttpURLConnection来处理这些细节,当然也是可能实现的,只是处理起
来难度就大了。
为了更好地处理向Web站点请求,包括处理Session、Cookie等细节问题,Apache开源组织提供了一个HttpClient项目,看它的
名称就知道,它是一个简单的HTTP客户端(并不是浏览器),可以用于发送HTTP请求,接收HTTP响应。但不会缓存服务器的响应,不能执行HTML页
面中嵌入的Javascript代码;也不会对页面内容进行任何解析、处理。
简单来说,HttpClient就是一个增强版的HttpURLConnection,HttpURLConnection可以做的事情
HttpClient全部可以做;HttpURLConnection没有提供的有些功能,HttpClient也提供了,但它只是关注于如何发送请求、
接收
响应,以及管理HTTP连接。
使用HttpClient发送请求、接收响应很简单,只要如下几步即可。
创建HttpClient对象。
如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象。
如果需要发送请求参数,可调用HttpGet、HttpPost共同的setParams(HetpParams params)方法来添加请求参数;对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数。
调用HttpClient对象的execute(HttpUriRequest request)发送请求,执行该方法返回一个HttpResponse。
调
用HttpResponse的getAllHeaders()、getHeaders(String
name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器
的响应内容。程序可通过该对象获取服务器的响应内容。
另外,Android已经成功地集成了HttpClient,这意味着开发人员可以直接在Android应用中使用Httpclient来访问提交请求、接收响应。
比如一个Android应用需要向指定页面发送请求,但该页面并不是一个简单的页面,只有当用户已经登录,而且登录用户的用户名有效时才可访问该页面。如果使用HttpURLConnection来访问这个被保护的页面,那么需要处理的细节就太复杂了。
其实访问Web应用中被保护的页面,使用浏览器则十分简单,用户通过系统提供的登录页面登录系统,浏览器会负责维护与服务器之间的Sesion,如果用户登录的用户名、密码符合要求,就可以访问被保护资源了。
在Android应用程序中,则可使用HttpClient来登录系统,只要应用程序使用同一个HttpClient发送请求,HttpClient会
自动维护与服务器之间的Session状态,也就是说程序第一次使用HttpClient登录系统后,接下来使用HttpClient即可访问被保护页而
了。
首先,需要明确一下http通信流程,Android目前提供两种http通信方式,HttpURLConnection和HttpClient,HttpURLConnection多用于发送或接收流式数据,因此比较适合上传/下载文件,HttpClient相对来讲更大更全能,但是速度相对也要慢一点。在此只介绍HttpClient的通信流程:
1创建HttpClient对象,改对象可以用来多次发送不同的http请求
2创建HttpPost或HttpGet对象,设置参数,每发送一次http请求,都需要这样一个对象
3利用HttpClient的execute方法发送请求并等待结果,该方法会一直阻塞当前线程,直到返回结果或抛出异常。
4针对结果和异常做相应处理
根据上述流程,发现在设计类的时候,有几点需要考虑到:
1HttpClient对象可以重复使用,因此可以作为类的静态变量
2HttpPost/HttpGet对象一般无法重复使用(如果你每次请求的参数都差不多,也可以重复使用),因此可以创建一个方法用来初始化,同时设置一些需要上传到服务器的资源
3目前Android不再支持在UI线程中发起Http请求,实际上也不该这么做,因为这样会阻塞UI线程。因此还需要一个子线程,用来发起Http请求,即执行execute方法
4不同的请求对应不同的返回结果,对于如何处理返回结果(一般来说都是解析json&更新UI),需要有一定的自由度。
5最简单的方法是,每次需要发送http请求时,开一个子线程用于发送请求,子线程中接收到结果或抛出异常时,根据情况给UI线程发送message,最后在UI线程的handler的handleMessage方法中做结果解析和UI更新。这么写虽然简单,但是UI线程和Http请求的耦合度很高,而且代码比较散乱、丑陋。
import javaioBufferedReader;
import javaioIOException;
import javaioInputStream;
import javaioInputStreamReader;
import javaioUnsupportedEncodingException;
import javanetHttpURLConnection;
import javanetMalformedURLException;
import javanetURL;
import javautilArrayList;
import javautilList;
import orgapachehttpHttpEntity;
import orgapachehttpHttpResponse;
import orgapachehttpHttpStatus;
import orgapachehttpNameValuePair;
import orgapachehttpclientClientProtocolException;
import orgapachehttpclientHttpClient;
import orgapachehttpcliententityUrlEncodedFormEntity;
import orgapachehttpclientmethodsHttpGet;
import orgapachehttpclientmethodsHttpPost;
import orgapachehttpimplclientDefaultHttpClient;
import orgapachehttpmessageBasicNameValuePair;
import orgapachehttputilEntityUtils;
import androidappActivity;
import androidosBundle;
import androidosHandler;
import androidosMessage;
import androidviewView;
import androidwidgetButton;
import androidwidgetTextView;
public class Test extends Activity implements Runnable{
/ Called when the activity is first created /
private Button btn_get = null;
private Button btn_post = null;
private TextView tv_rp = null;
@Override
public void onCreate(Bundle savedInstanceState) {
superonCreate(savedInstanceState);
setContentView(Rlayoutmain);
btn_get = (Button) thisfindViewById(RidButton01);
btn_post = (Button) thisfindViewById(RidButton02);
tv_rp = (TextView) thisfindViewById(RidTextView);
btn_getsetOnClickListener(new ButtonOnClickListener(){
public void onClick(View v) {
// TODO Auto-generated method stub
String httpUrl = "http://1921680132:8080/Android/httpreqjsppar=request-get";
HttpGet request = new HttpGet(httpUrl);
HttpClient httpClient = new DefaultHttpClient();
try {
HttpResponse response = httpClientexecute(request);
if(responsegetStatusLine()getStatusCode()==HttpStatusSC_OK){
String str = EntityUtilstoString(responsegetEntity());
tv_rpsetText(str);
}else{
tv_rpsetText("请求错误");
}
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
eprintStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
eprintStackTrace();
}
}
});
btn_postsetOnClickListener(new ButtonOnClickListener(){
public void onClick(View v) {
// TODO Auto-generated method stub
String httpUrl = "http://1921680132:8080/Android/httpreqjsp";
HttpPost request = new HttpPost(httpUrl);
List<namevaluepair> params = new ArrayList<namevaluepair>();
paramsadd(new BasicNameValuePair("par","request-post"));
try {
HttpEntity entity = new UrlEncodedFormEntity(params, "UTF-8");
requestsetEntity(entity);
HttpClient client = new DefaultHttpClient();
HttpResponse response = clientexecute(request);
if(responsegetStatusLine()getStatusCode()==HttpStatusSC_OK){
String str = EntityUtilstoString(responsegetEntity());
tv_rpsetText(str);
}else{
tv_rpsetText("请求错误");
}
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
eprintStackTrace();
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
eprintStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
eprintStackTrace();
}
}
});
new Thread(this)start();
}
public void refresh(){
String httpUrl = "http://1921680132:8080/Android/httpreqjsp";
try {
URL url = new URL(httpUrl);
HttpURLConnection urlConn = (HttpURLConnection) urlopenConnection();
urlConnconnect();
InputStream input = urlConngetInputStream();
InputStreamReader inputreader = new InputStreamReader(input);
BufferedReader reader = new BufferedReader(inputreader);
String str = null;
StringBuffer sb = new StringBuffer();
while((str = readerreadLine())!= null){
sbappend(str)append("\n");
}
if(sb != null){
tv_rpsetText(sbtoString());
}else{
tv_rpsetText("NULL");
}
readerclose();
inputreaderclose();
inputclose();
reader = null;
inputreader = null;
input = null;
} catch (MalformedURLException e) {
eprintStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
eprintStackTrace();
}
}
public Handler handler = new Handler(){
public void handleMessage(Message msg){
superhandleMessage(msg);
refresh();
}
};
public void run() {
// TODO Auto-generated method stub
while(true){
try {
Threadsleep(1000);
handlersendMessage(handlerobtainMessage());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
eprintStackTrace();
}
}
}
}
相信很多开发者会把存放到七牛上,我的web站点也是吧存储到七牛上,对于以为主的站点,这样可以节省很大带宽。
将上传到七牛服务器的重点就是获得上传凭证uploadToken,直接把AccessKey和Secret放到客户端太不安全,容易被反编译。所以需要在服务器端根据AccessKey和Secret动态生成一个uploadToken,然后传回到客户端,客户端通过这个uploadToken将上传到七牛服务器。
第一、在服务器端生成uploadToken
//将上传到七牛 start
$bucket='七牛空间名称';
$expires = 3600;
$accessKey='去七牛查看';
$secretKey='去七牛查看';
$client = new QiniuClient($accessKey,$secretKey);
$flags = array();
$scope = $bucket;
$deadline = time() + $expires;
$flags['scope'] = $scope;
$flags['deadline'] = $deadline;
$flags['returnBody'] = null;
echo $client->uploadToken($flags);
这里注意一下bucket:七牛空间名称和deadline:uploadToken失效时间,具体可查看一下官网上传凭证介绍
uploadToken($flags)是自己封装的用于生成上传凭证的函数
public function uploadToken($flags) { if(!isset($flags['deadline'])) $flags['deadline'] = 3600 + time(); $encodedFlags = self::urlsafe_base64_encode(json_encode($flags)); $sign = hash_hmac('sha1', $encodedFlags, $this->secretKey, true); $encodedSign = self::urlsafe_base64_encode($sign); $token = $this->accessKey':'$encodedSign ':' $encodedFlags; return $token; }
public static function urlsafe_base64_encode($str){
$find = array("+","/");
$replace = array("-", "_");
return str_replace($find, $replace, base64_encode($str));
}
第二、下载qiniu-android-sdk-700jar和android-async-http-146并导入项目
第三、android上传
由于Android40 以后不允许在主线程进行网络连接,所以需要新开个线程来获取上传凭证。
/
上传到七牛
/
private void uploadImg(){
new Thread(new Runnable(){
@Override
public void run() {
//获得七牛上传凭证uploadToken
String token=getUploadToken();
//手机SD卡存放路径
String imgPath="";
try {
imgPath=FileUtilgetBasePath()+ "/testjpg";
} catch (IOException e) {
eprintStackTrace();
}
if(token!=null){
String data = imgPath;
//名称为当前日期+随机数生成
String key = getRandomFileName();
UploadManager uploadManager = new UploadManager();
uploadManagerput(data, key, token,
new UpCompletionHandler() {
@Override
public void complete(String arg0, ResponseInfo info, JSONObject response) {
// TODO Auto-generated method stub
Logi("qiniu", infotoString());
}
}, null);
}
else{
Logi("fail", "上传失败");
}
}
})start();
}
FileUtilgetBasePath()使用来获取SD卡基本路径,getRandomFileName()生成一个随机数来命名上传,具体方法我在这就不写了。
获得上传凭证的方法也很简单,直接使用httpget和服务器通信,获得第一步中生成的数据即可。(注意10022是模拟器提供的特殊IP,等同于在电脑端的环回测试IP127001)
/
获得七牛上传凭证uploadtoken
/
private String getUploadToken()
{
HttpClient client = new DefaultHttpClient();
StringBuilder builder = new StringBuilder();
HttpGet myget = new HttpGet("");
try {
HttpResponse response = clientexecute(myget);
BufferedReader reader = new BufferedReader(new InputStreamReader(
responsegetEntity()getContent()));
for (String s = readerreadLine(); s != null; s = readerreadLine()) {
builderappend(s);
}
return buildertoString();
} catch (Exception e) {
Logi("url response", "false");
eprintStackTrace();
return null;
}
}
通过LOG日志可以看到Qiniu--success,说明上传成功。
Android应用开发中,会经常要提交数据到服务器和从服务器得到数据,本文主要是给出了利用http协议采用HttpClient方式向服务器提交数据的方法。
代码比较简单,这里不去过多的阐述,直接看代码。
/
@author Dylan 本类封装了Android中向web服务器提交数据的两种方式四种方法
/
public class SubmitDataByHttpClientAndOrdinaryWay {
使用get请求以普通方式提交数据
@param map
传递进来的数据,以map的形式进行了封装
@param path
要求服务器servlet的地址
@return 返回的boolean类型的参数
@throws Exception
/
public Boolean submitDataByDoGet(Map<String, String> map, String path)
throws Exception {
// 拼凑出请求地址
StringBuilder sb = new StringBuilder(path);
sbappend("");
for (MapEntry<String, String> entry : mapentrySet()) {
sbappend(entrygetKey())append("=")append(entrygetValue());
sbappend("&");
}
sbdeleteCharAt(sblength() - 1);
String str = sbtoString();
Systemoutprintln(str);
URL Url = new URL(str);
HttpURLConnection HttpConn = (HttpURLConnection) UrlopenConnection();
HttpConnsetRequestMethod("GET");
HttpConnsetReadTimeout(5000);
// GET方式的请求不用设置什么DoOutPut()之类的吗?
if (HttpConngetResponseCode() == HttpURLConnectionHTTP_OK) {
return true;
}
return false;
}
/
普通方式的DoPost请求提交数据
@param map
传递进来的数据,以map的形式进行了封装
@param path
要求服务器servlet的地址
@return 返回的boolean类型的参数
@throws Exception
/
public Boolean submitDataByDoPost(Map<String, String> map, String path) throws Exception {
// 注意Post地址中是不带参数的,所以newURL的时候要注意不能加上后面的参数
URL Url = new URL(path);
// Post方式提交的时候参数和URL是分开提交的,参数形式是这样子的:name=y&age=6
StringBuilder sb = new StringBuilder();
// sbappend("");
for (MapEntry<String, String> entry : mapentrySet()) {
sbappend(entrygetKey())append("=")append(entrygetValue());
sbappend("&");
}
sbdeleteCharAt(sblength() - 1);
String str = sbtoString();[/font][/size]
HttpURLConnection HttpConn = (HttpURLConnection) UrlopenConnection();
HttpConnsetRequestMethod("POST");
HttpConnsetReadTimeout(5000);
HttpConnsetDoOutput(true);
HttpConnsetRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
HttpConnsetRequestProperty("Content-Length",
StringvalueOf(strgetBytes()length));
OutputStream os = HttpConngetOutputStream();
oswrite(strgetBytes());
if (HttpConngetResponseCode() == HttpURLConnectionHTTP_OK) {
return true;
}
return false;
}
/
以HttpClient的DoGet方式向服务器发送请数据
@param map
传递进来的数据,以map的形式进行了封装
@param path
要求服务器servlet的地址
@return 返回的boolean类型的参数
@throws Exception
/
public Boolean submitDataByHttpClientDoGet(Map<String, String> map,
String path) throws Exception {
HttpClient hc = new DefaultHttpClient();
// 请求路径
StringBuilder sb = new StringBuilder(path);
sbappend("");
for (MapEntry<String, String> entry : mapentrySet()) {
sbappend(entrygetKey())append("=")append(entrygetValue());
sbappend("&");
}
sbdeleteCharAt(sblength() - 1);
String str = sbtoString();
Systemoutprintln(str);
HttpGet request = new HttpGet(sbtoString());[/font][/size]
HttpResponse response = hcexecute(request);
if (responsegetStatusLine()getStatusCode() == HttpURLConnectionHTTP_OK) {
return true;
}
return false;
}
复制代码
/
以HttpClient的DoPost方式提交数据到服务器
@param map
传递进来的数据,以map的形式进行了封装
@param path
要求服务器servlet的地址
@return 返回的boolean类型的参数
@throws Exception
/
public Boolean submintDataByHttpClientDoPost(Map<String, String> map, String path) throws Exception {
// 1 获得一个相当于浏览器对象HttpClient,使用这个接口的实现类来创建对象,DefaultHttpClient
HttpClient hc = new DefaultHttpClient();
// DoPost方式请求的时候设置请求,关键是路径
HttpPost request = new HttpPost(path);
// 2 为请求设置请求参数,也即是将要上传到web服务器上的参数
List<NameValuePair> parameters = new ArrayList<NameValuePair>();
for (MapEntry<String, String> entry : mapentrySet()) {
NameValuePair nameValuePairs = new BasicNameValuePair(
entrygetKey(), entrygetValue());
parametersadd(nameValuePairs);
}
// 请求实体HttpEntity也是一个接口,我们用它的实现类UrlEncodedFormEntity来创建对象,注意后面一个String类型的参数是用来指定编码的
HttpEntity entity = new UrlEncodedFormEntity(parameters, "UTF-8");
requestsetEntity(entity);
// 3 执行请求
HttpResponse response = hcexecute(request);
// 4 通过返回码来判断请求成功与否
if (responsegetStatusLine()getStatusCode() == HttpURLConnectionHTTP_OK) {
return true;
}
return false;
}
}
0条评论