android中发起网络请求主要的有get和post两种:
get:
public String get(String url) {
HttpURLConnection conn = null;
try {
URL mURL = new URL(url);
conn = (HttpURLConnection) mURL.openConnection();
conn.setRequestMethod("GET");
conn.setReadTimeout(5000);
conn.setConnectTimeout(10000);
//类似于Content-Type这种约定俗成的,还可以包括一些自定义的例如CUSTOM_OS,
//需要和服务器约定好
conn.setRequestProperty("Content-Type", "text/xml");
conn.addRequestProperty("CUSTOM_OS", "android");
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
InputStream is = conn.getInputStream();
ByteArrayOutputStream os = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
is.close();
String response = os.toString();
os.close();
return response;
} else {
throw new NetworkErrorException("response status is "+responseCode);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
}
return null;
}
post:
public String post(String url, String content) {
HttpURLConnection conn = null;
try {
URL mURL = new URL(url);
conn = (HttpURLConnection) mURL.openConnection();
conn.setRequestMethod("POST");
conn.setReadTimeout(5000);
conn.setConnectTimeout(10000);
conn.setDoOutput(true);// 设置此方法,允许向服务器输出内容,必须在connect之前调用
String data = content;
OutputStream out = conn.getOutputStream();
out.write(data.getBytes());
out.flush();
out.close();
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
InputStream is = conn.getInputStream();
ByteArrayOutputStream os = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
is.close();
String response = os.toString();
os.close();
return response;
} else {
throw new NetworkErrorException("response status is "+responseCode);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
}
return null;
}
首先URL的openConnection()会返回一个URLConnection的实例对象,这个对象可以对服务器读写,openConnection()方法是调用的URLStreamHandler实例对象的openConnection(),
在SDK中的URLConnection有坑,这个类中的方法很多都是空的实现,真正的源码的JDK里面,所以下面讨论的都是JDK中的类,首先URL的openConnection()会返回一个URLConnection的实例对象,这个对象可以对服务器读写,其中URL的openConnection()会辗转调用到URLStreamHandler实例对象的openConnection(),下图:
其中(1)(2)均在sun.net.www.protocol.http包中(Handler HttpURLConnection),这个包JDK中没有,其他均在java.net包中,到这里都比较简单,平常基本使用都可以了,但是我们除了要知其然还要知其所以然,很有必要继续深究下其中的原理以及其他的用法。
先从URL类开始,这个类主要是解析出主机名,端口号,协议等信息,
其中的协议有http,ftp,ftp适合传送大文件,http适合传送网页等数据,平时我们的网址还有接口都是http协议的。
然后调用openConnection或openStream打开链接,不同点是openStream会调用先openConnection返回一个URLConnection对象后在调用改对象的getInputStream,这里就是打开链接后直接获取输入流,如果我们直接调用openConnection可能后面还会写入一些东西然后在获取输入流,典型的就是post请求的设置header和写出参数。
get和post最大的区别就是有没有通过OutputStream 被虚拟机包装到body中向服务器传递数据,先分析下getOutputStream的过程
public synchronized OutputStream getOutputStream() throws IOException {
try {
if (!doOutput) {//通过setDoInput设置
throw new ProtocolException("cannot write to a URLConnection"
+ " if doOutput=false - call setDoOutput(true)");
}
if (method.equals("GET")) {//请求方法为get时会被置为post
method = "POST"; // Backward compatibility
}
if (!"POST".equals(method) && !"PUT".equals(method) &&
"http".equals(url.getProtocol())) {//必须为post或put,协议为http
throw new ProtocolException("HTTP method " + method +
" doesn't support output");
}
// if there's already an input stream open, throw an exception
if (inputStream != null) {
throw new ProtocolException("Cannot write output after reading input.");
}
if (!checkReuseConnection())
connect();//如果没有connect会调用connect()
boolean expectContinue = false;
/*
*是否设置了Expect: 100-Continue
*如果设置了会先询问服务器是否处理post数据
*/
String expects = requests.findValue("Expect");
if ("100-Continue".equalsIgnoreCase(expects)) {
http.setIgnoreContinue(false);
expectContinue = true;
}
if (streaming() && strOutputStream == null) {
/*
*将没有设置的header设置默认置然后调用httpclient的writeRequests
*向输出流写出数据
*/
writeRequests();
}
if (expectContinue) {
//如果设置了Expect: 100-Continue,检查服务器返回的code是否允许发送post请求
expect100Continue();
}
ps = (PrintStream) http.getOutputStream();//调用httpclient的getOutputStream方法
if (streaming()) {
if (strOutputStream == null) {
if (chunkLength != -1) { /* chunked */
strOutputStream = new StreamingOutputStream(
new ChunkedOutputStream(ps, chunkLength), -1L);
} else { /* must be fixed content length */
long length = 0L;
if (fixedContentLengthLong != -1) {
length = fixedContentLengthLong;
} else if (fixedContentLength != -1) {
length = fixedContentLength;
}
strOutputStream = new StreamingOutputStream(ps, length);
}
}
return strOutputStream;
} else {
if (poster == null) {
poster = new PosterOutputStream();
}
return poster;
}
}
//部分删减
}
HttpURLConnecion的getOutputStream会调用HttpClient的getOutputStream,看一下HttpClient的getOutputStream方法
public OutputStream getOutputStream() {
return serverOutput;
}
直接返回了serverOutput,这个变量在openServer(String server, int port)中赋值,openServer方法有两个地方会调用,一个是HttpClient初始化时(调用父类NetworkClient 的构造方法),一个是会在parseHTTP(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)中调用,而这个方法会在HttpURLConnecion中getInputStream中调用。
这样获得了输出流,这时只是把数据写入到内存中,并没有发送给服务器,发送过程在HttpURLConnection的getInputStream方法中调用strOutputStream.close ()写入到流中发送给服务器。
不过到这肯定还有很多疑问,不是先调用的getOutputStream,难道是在这里面就初始化了一个HttpClient对象?确实是,在HttpURLConnecion的getOutputStream和getInputStream中都会在没有建立连接时调用connect(),它又调用了plainConnect(),在这里面new了一个HttpClient 对象,这样就调用到了前面提到的openServer(),它里面建立了一个socket连接(对使用的是socket连接)。
现在就比较清楚了,post请求比get请求多了一步getOutputStream,但不管什么请求都是先调用connect(),建立一个socket连接,如果是post还会返回一个outputStream对象用来向服务器发送数据,然后getOutputStream和getInputStream中会调用writeRequests()方法,这个方法会设置一些默认的请求头参数,然后里面调用HttpClient的writeRequests()方法将这些参数发送给服务器,之后post会通过outputStream对象发送数据,这些数据就是接口的参数,发送完在getInputStream()方法中将缓存中数据发送到服务器,等待服务器返回,最后关闭连接一次请求就完成了。