1.Android与互联网交互的三种方式
2.初识Http协议
实际开发中我们和服务端打交道一般用得都是基于Http协议的通信,所以学好Http协议是非常 重要的,当然,我们不用过于考究一些细节的东西,有个大体的了解即可!都是一些概念性的东西!
1)什么是Http协议?
答:hypertext transfer protocol(超文本传输协议),TCP/IP协议的一个应用层协议,用于 定义WEB浏览器与WEB服务器之间交换数据的过程。客户端连上web服务器后,若想获得web服务器 中的某个web资源,需遵守一定的通讯格式,HTTP协议用于定义客户端与web服务器通迅的格式。
2)Http 1.0 与 Http 1.1的区别
答:1.0协议,客户端与web服务器建立连接后,只能获得一个web资源! 而1.1协议,允许客户端与web服务器建立连接后,在一个连接上获取多个web资源!
3)Http协议的底层工作流程:
答:我们先要知道两个名词:
SYN(synchronous):TCP/IP建立连接时使用的握手信号
ACK(Acknowledgement):确认字符,确认发来的数据已经接受无误
接着就到TCP/IP三次握手的概念:
1.客户端发送syn包(syn = j)到服务器,进入SYN_SEND状态,然后等待服务器确认
2.服务器收到syn包,确认客户的syn(ack = j + 1),同时在自己也发送一个SYN包(syn=k), 即SYN + ACK包,服务器进入SYN_RECV状态
3.客户端收到SYN + ACK包,向服务器发送确认包ACK(ack = k +1),发送完毕后,客户端与服务端 进入ESTABLISHED状态,完成三次握手,然后两者开始传送数据
如果还不是很清晰,我们再来看三次握手的示意图:
了解了是吧,然后我们就来看看Http操作的一个流程了:
1.用户点击浏览器上的url(超链接),Web浏览器与Web服务器建立连接
2.建立连接后,客户端发送请求给服务器,请求的格式为: 统一资源标识符(URL)+协议版本号(一般是1.1)+MIME信息(多个消息头)+一个空行
3.服务端收到请求后,给予相应的返回信息,返回格式为: 协议版本号 + 状态行(处理结果) + 多个信息头 + 空行 + 实体内容(比如返回的HTML)
4.客户端接收服务端返回信息,通过浏览器显示出来,然后与服务端断开连接;当然如果中途 某步发生错误的话,错误信息会返回到客户端,并显示,比如:经典的404错误!
对于上面的流程如果还不清晰,我们可以使用HttpWatch或者firefox抓下包: PS:测试网站是小猪的学校的教务系统,输入账号密码后请求登陆,我们可以看到下述信息:
HTTP请求包含的内容:
HTTP响应包括的内容:
4)Http协议的业务流程
5)Http的几种请求方式
实际开发中我们用得较多的方式是Get和Post,但是实际开发可能还会用到其他请求方式,比如PUT, 小猪的实际项目中就用到了,下面为了方便大家,就把所有的请求方式列出来吧:
Get:请求获取Request-URI所标识的资源
POST:在Request-URI所标识的资源后附加新的数据
HEAD 请求获取由Request-URI所标识的资源的响应信息报头
PUT:请求服务器存储一个资源,并用Request-URI作为其标识
DELETE:请求服务器删除Request-URI所标识的资源
TRACE:请求服务器回送收到的请求信息,主要用于测试或诊断
CONNECT:保留将来使用
OPTIONS:请求查询服务器的性能,或者查询与资源相关的选项
6)Get和Post的对比
用得最多的两个,当然要做下对比啦!
**GET:**在请求的URL地址后以?的形式带上交给服务器的数据,多个数据之间以&进行分隔, 但数据容量通常不能超过2K,比如:http://xxx?username=…&pawd=…这种就是GET
POST: 这个则可以在请求的实体内容中向服务器发送数据,传输没有数量限制
另外要说一点,这两个玩意都是发送数据的,只是发送机制不一样,不要相信网上说的 “GET获得服务器数据,POST向服务器发送数据”!!另外GET安全性非常低,Post安全性较高, 但是执行效率却比Post方法好,一般查询的时候我们用GET,数据增删改的时候用POST!!
7)Http状态码合集
当然,这些状态码只是要给参考,实际上决定权在服务器端(后台的)手上,一种方案是请求后, 服务器返回给我们的是状态,或者另一种,在应用不用弄多语言版本的时候最好用,直接返回 一串结果信息的Json给我们,我们直接显示就好,这样可以偷懒不少!下面列下状态码合集,参考 下就好:
100~199 : 成功接受请求,客户端需提交下一次请求才能完成整个处理过程
200: OK,客户端请求成功
300~399:请求资源已移到新的地址(302,307,304)
401:请求未授权,改状态代码需与WWW-Authenticate报头域一起使用
403:Forbidden,服务器收到请求,但是拒绝提供服务
404:Not Found,请求资源不存在,这个就不用说啦
500:Internal Server Error,服务器发生不可预期的错误
503:Server Unavailable,服务器当前不能处理客户端请求,一段时间后可能恢复正常
8)Http协议的特点
- 支持客户/服务器模式。
- 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、 HEAD、POST。每种方法规定了客户与服务器联系的类型不同。 由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
- 灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
- 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求, 并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
- 无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。 缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每 次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
3.HTTP请求之消息头:
4.HTTP响应之响应头:
第一行依次是:协议版本号, 状态码 302表示这里没有,但是另外一个地方有,通过Location页面重定向了
HTTP Responses Header 响应头信息对照表:
5.代码验证响应头的作用:
好了,看了那么多概念的东西,不动动手怎么行呢?是吧,那我们就写一些简单的代码来验证一些 常用的响应头的作用吧,以便加深我们的了解,这里的话服务端就用最简单的Servlet来实现,如果不会 Java Web的朋友只需将代码拷一拷,配置下web.xml,把Servlet的类名扣上,比如:
<servlet>
<servlet-name>FirstServlet</servlet-name>
<servlet-class>com.jay.server.FirstServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FirstServlet</servlet-name>
<url-pattern>/FirstServlet</url-pattern>
</servlet-mapping>
改成对应的类名即可!
1)通过Location实现页面重定向
实现代码:
package com.jay.http.test;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ServletOne extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//告诉浏览器响应码,以及重定向页面
resp.setStatus(302);
resp.setHeader("Location", "http://www.baidu.com");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
this.doGet(req, resp);
}
}
运行结果:
当我们去访问:http://localhost:8080/HttpTest/ServletOne的时候,我们会发现页面跳转到了百度, 接着我们用FireFox的开发者工具:可以看到我们发出的HTTP的内容:
2)通过Content-Encoding告诉浏览器数据的压缩格式
实现代码:
package com.jay.http.test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ServletTwo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String data = "Fresh air and sunshine can have an amazing effect on our feelings. "
+ "Sometimes when we are feeling down, all that we need to do is simply to go "
+ "outside and breathe. Movement and exercise is also a fantastic way to feel better. "
+ "Positive emotions can be generated by motion. So if we start to feel down,"
+ " take some deep breathes, go outside, feel the fresh air, "
+ "let the sun hit our face, go for a hike, a walk, a bike ride, "
+ "a swim, a run, whatever. We will feel better if we do this.";
System.out.println("原始数据长度:" + data.getBytes().length);
// 对数据进行压缩:
ByteArrayOutputStream bout = new ByteArrayOutputStream();
GZIPOutputStream gout = new GZIPOutputStream(bout);
gout.write(data.getBytes());
gout.close();
// 得到压缩后的数据
byte gdata[] = bout.toByteArray();
resp.setHeader("Content-Encoding", "gzip");
resp.setHeader("Content-Length", gdata.length + "");
resp.getOutputStream().write(gdata);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doGet(req, resp);
};
}
运行结果:
控制台输出:
浏览器输出:
再看看我们的HTTP内容:
这个gzip压缩字符串对于简单的字符串压缩,效率不高,比如小猪本来写的是一个一首静夜诗的字符串, 后来发现压缩过后的大小,竟然比原先的还要大=-=…
3)通过content-type,设置返回的数据类型
服务端返回的有时可能是一个text/html,有时也可能是一个image/jpeg,又或者是一段视频video/avi 浏览器可以根据这个对应的数据类型,然后以不同的方式将数据显示出来!好吧,这里我们弄一个读PDF的
实现代码:
package com.jay.http.test;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ServletThree extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setHeader("content-type", "application/pdf");
InputStream in = this.getServletContext().getResourceAsStream("/file/android编码规范.pdf");
byte buffer[] = new byte[1024];
int len = 0;
OutputStream out = resp.getOutputStream();
while((len = in.read(buffer)) > 0)
{
out.write(buffer,0,len);
}
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws
ServletException ,IOException
{
doGet(req, resp);
};
}
运行结果:
在浏览器上输入:http://localhost:8080/HttpTest/ServletThree
好哒,果然可以读到PDF了,对了,这个PDF我已经丢在webroot的file目录下,不然会报空指针哦~:
当然,你也可以试着去播放一段音乐或者视频,只需修改下content-type这个参数而已
下面顺便给出个HTTP Content-type的对照表吧: HTTP Content-type的对照表
4)通过refresh响应头,让浏览器隔几秒后跳转至别的页面
恩呢,一般我们可能有这样的需求,比如每隔几秒刷新一次页面,又或者加载某个页面几秒后 又跳转至另一个页面,那么refresh可以满足你的需要~
实现代码:
package com.jay.http.test;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ServletFour extends HttpServlet {
public int second = 0;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//1.浏览器每隔2秒定时刷新页面
// resp.setHeader("refresh", "2");
// resp.getWriter().write(++second + "");
// System.out.println("doGet方法被调用~");
//2.进入页面5s后,然页跳到百度~
resp.setHeader("refresh", "5;url='http://www.baidu.com'");
resp.getWriter().write("HE HE DA~");
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException ,IOException
{
doGet(req, resp);
};
}
运行结果:
1的话每隔2秒刷新一次页面,我们可以看到显示的数字是递增的,另外doGet方法也一直被调用, 说明页面真的是刷新的!
2的话进入页面后5s,就自己跳转到百度了~
5)通过content-dispostion响应头,让浏览器下载文件
这个很简单,我们只需把③中设置Content-type的一行去掉,然后加上: resp.setHeader(“content-disposition”, “attachment;filename=Android.pdf”);
package com.jay.http.test;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ServletFive extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setHeader("content-disposition", "attachment;filename=Android.pdf");
InputStream in = this.getServletContext().getResourceAsStream("/file/android编码规范.pdf");
byte buffer[] = new byte[1024];
int len = 0;
OutputStream out = resp.getOutputStream();
while((len = in.read(buffer)) > 0)
{
out.write(buffer,0,len);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doGet(req, resp);
}
}
6.HttpURLConnection的介绍
Http的协议以及协议头的一些东东,而本节我们 就要堆码了,而本节学习的是Android为我们提供的Http请求方式之一:HttpURLConnection, 除了这种,还有一种还有一种HttpClient,后者我们会下一节讲!不过前者一旦请求复杂起来,使用起来 非常麻烦,而后者我们Java抓包也经常会用到,是Apache的,毕竟不是谷歌亲儿子,而在4.4版本 HttpURLConnection已被替换成OkHttp了!好吧,与时俱进,决定讲完HttpClient也来会会这个 OkHttp!对了,一般我们实际开发并不会用HttpURLConnection和HttpClient,使用别人封装 好的第三方网络请求框架,诸如:Volley,android-async-http,loopj等,因为网络操作涉及到 异步以及多线程,自己动手撸的话,很麻烦,所以实际开发还是直接用第三方!!当然学习下也 无妨,毕竟第三方也是在这些基础上撸起来的,架构逼格高,各种优化!
答:一种多用途、轻量极的HTTP客户端,使用它来进行HTTP操作可以适用于大多数的应用程序。 虽然HttpURLConnection的API提供的比较简单,但是同时这也使得我们可以更加容易地去使 用和扩展它。继承至URLConnection,抽象类,无法直接实例化对象。通过调用openCollection() 方法获得对象实例,默认是带gzip压缩的;
7.HttpURLConnection的使用步骤
使用HttpURLConnection的步骤如下:
1.创建一个URL对象: URL url = new URL(https://www.baidu.com);
2.调用URL对象的openConnection( )来获取HttpURLConnection对象实例: HttpURLConnection conn = (HttpURLConnection) url.openConnection();
3.设置HTTP请求使用的方法:GET或者POST,或者其他请求方式比如:PUT conn.setRequestMethod(“GET”);
4.设置连接超时,读取超时的毫秒数,以及服务器希望得到的一些消息头 conn.setConnectTimeout(6*1000); conn.setReadTimeout(6 * 1000);
5.调用getInputStream()方法获得服务器返回的输入流,然后输入流进行读取了 InputStream in = conn.getInputStream();
6.最后调用disconnect()方法将HTTP连接关掉 conn.disconnect();
PS:除了上面这些外,有时我们还可能需要对响应码进行判断,比如200: if(conn.getResponseCode() != 200)然后一些处理 还有,可能有时我们 并不需要传递什么参数,而是直接去访问一个页面,我们可以直接用: final InputStream in = new URL(“url”).openStream(); 然后直接读流,不过这个方法适合于直接访问页面的情况,底层实现其实也是 return openConnection().getInputStream(),而且我们还不能设置一些 请求头的东东,所以要不要这样写,你自己要掂量掂量!
8.HttpURLConnection使用示例
这里我们主要针对GET和POST请求写两个不同的使用示例,我们可以conn.getInputStream() 获取到的是一个流,所以我们需要写一个类将流转化为二进制数组!工具类如下:
StreamTool.java:
/**
* Created by Jay on 2015/9/7 0007.
*/
public class StreamTool {
//从流中读取数据
public static byte[] read(InputStream inStream) throws Exception{
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while((len = inStream.read(buffer)) != -1)
{
outStream.write(buffer,0,len);
}
inStream.close();
return outStream.toByteArray();
}
}
1)HttpURLConnection发送GET请求代码示例
运行效果图:
核心部分代码:
布局:activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/txtMenu"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="#4EA9E9"
android:clickable="true"
android:gravity="center"
android:text="长按我,加载菜单"
android:textSize="20sp" />
<ImageView
android:id="@+id/imgPic"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<ScrollView
android:id="@+id/scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<TextView
android:id="@+id/txtshow"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</ScrollView>
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
获取数据类:GetData.java:
/**
* Created by Jay on 2015/9/7 0007.
*/
public class GetData {
// 定义一个获取网络图片数据的方法:
public static byte[] getImage(String path) throws Exception {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 设置连接超时为5秒
conn.setConnectTimeout(5000);
// 设置请求类型为Get类型
conn.setRequestMethod("GET");
// 判断请求Url是否成功
if (conn.getResponseCode() != 200) {
throw new RuntimeException("请求url失败");
}
InputStream inStream = conn.getInputStream();
byte[] bt = StreamTool.read(inStream);
inStream.close();
return bt;
}
// 获取网页的html源代码
public static String getHtml(String path) throws Exception {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
if (conn.getResponseCode() == 200) {
InputStream in = conn.getInputStream();
byte[] data = StreamTool.read(in);
String html = new String(data, "UTF-8");
return html;
}
return null;
}
}
MainActivity.java:
public class MainActivity extends AppCompatActivity {
private TextView txtMenu, txtshow;
private ImageView imgPic;
private WebView webView;
private ScrollView scroll;
private Bitmap bitmap;
private String detail = "";
private boolean flag = false;
private final static String PIC_URL = "https://ww2.sinaimg.cn/large/7a8aed7bgw1evshgr5z3oj20hs0qo0vq.jpg";
private final static String HTML_URL = "https://www.baidu.com";
// 用于刷新界面
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 0x001:
hideAllWidget();
imgPic.setVisibility(View.VISIBLE);
imgPic.setImageBitmap(bitmap);
Toast.makeText(MainActivity.this, "图片加载完毕", Toast.LENGTH_SHORT).show();
break;
case 0x002:
hideAllWidget();
scroll.setVisibility(View.VISIBLE);
txtshow.setText(detail);
Toast.makeText(MainActivity.this, "HTML代码加载完毕", Toast.LENGTH_SHORT).show();
break;
case 0x003:
hideAllWidget();
webView.setVisibility(View.VISIBLE);
webView.loadDataWithBaseURL("", detail, "text/html", "UTF-8", "");
Toast.makeText(MainActivity.this, "网页加载完毕", Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
;
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setViews();
}
private void setViews() {
txtMenu = (TextView) findViewById(R.id.txtMenu);
txtshow = (TextView) findViewById(R.id.txtshow);
imgPic = (ImageView) findViewById(R.id.imgPic);
webView = (WebView) findViewById(R.id.webView);
scroll = (ScrollView) findViewById(R.id.scroll);
registerForContextMenu(txtMenu);
}
// 定义一个隐藏所有控件的方法:
private void hideAllWidget() {
imgPic.setVisibility(View.GONE);
scroll.setVisibility(View.GONE);
webView.setVisibility(View.GONE);
}
@Override
// 重写上下文菜单的创建方法
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
MenuInflater inflator = new MenuInflater(this);
inflator.inflate(R.menu.menus, menu);
super.onCreateContextMenu(menu, v, menuInfo);
}
// 上下文菜单被点击是触发该方法
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.one:
new Thread() {
public void run() {
try {
byte[] data = GetData.getImage(PIC_URL);
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
}
handler.sendEmptyMessage(0x001);
}
;
}.start();
break;
case R.id.two:
new Thread() {
public void run() {
try {
detail = GetData.getHtml(HTML_URL);
} catch (Exception e) {
e.printStackTrace();
}
handler.sendEmptyMessage(0x002);
};
}.start();
break;
case R.id.three:
if (detail.equals("")) {
Toast.makeText(MainActivity.this, "先请求HTML先嘛~", Toast.LENGTH_SHORT).show();
} else {
handler.sendEmptyMessage(0x003);
}
break;
}
return true;
}
}
最后别忘了加上联网权限:
注意事项:
用handler的原因就不用讲了吧~ 另外我们加载html代码的使用的是webView的loadDataWithBaseURL而非LoadData, 如果用LoadData又要去纠结中文乱码的问题,so…用loadDataWithBaseURL就可以不用纠结那么多了 另外有些页面可能需要我们提交一些参数,比如账号密码:我们只需把对应参数拼接到url尾部即可,比如: http://192.168.191.1:8080/ComentServer/LoginServlet?passwd=123&name=Jack 然后服务端getParamater(“passwd”)这样就可以获得相应的参数了,我们请求时这些东西都会看得清清楚楚 ,所以说GET方式并不安全!另外还有一点要注意的就是Android从4.0开始就不允许在非UI线程中进行UI操作!
2)HttpURLConnection发送POST请求代码示例
有GET自然有POST,我们通过openConnection获取到的HttpURLConnection默认是进行Get请求的, 所以我们使用POST提交数据,应提前设置好相关的参数:conn.setRequestMethod(“POST”); 还有:conn.setDoOutput(true);conn.setDoInput(true);设置允许输入,输出 还有:conn.setUseCaches(false); POST方法不能缓存,要手动设置为false, 具体实现看代码:
运行效果图:
核心代码:
PostUtils.java
public class PostUtils {
public static String LOGIN_URL = "http://172.16.2.54:8080/HttpTest/ServletForPost";
public static String LoginByPost(String number,String passwd)
{
String msg = "";
try{
HttpURLConnection conn = (HttpURLConnection) new URL(LOGIN_URL).openConnection();
//设置请求方式,请求超时信息
conn.setRequestMethod("POST");
conn.setReadTimeout(5000);
conn.setConnectTimeout(5000);
//设置运行输入,输出:
conn.setDoOutput(true);
conn.setDoInput(true);
//Post方式不能缓存,需手动设置为false
conn.setUseCaches(false);
//我们请求的数据:
String data = "passwd="+ URLEncoder.encode(passwd, "UTF-8")+
"&number="+ URLEncoder.encode(number, "UTF-8");
//这里可以写一些请求头的东东...
//获取输出流
OutputStream out = conn.getOutputStream();
out.write(data.getBytes());
out.flush();
if (conn.getResponseCode() == 200) {
// 获取响应的输入流对象
InputStream is = conn.getInputStream();
// 创建字节输出流对象
ByteArrayOutputStream message = new ByteArrayOutputStream();
// 定义读取的长度
int len = 0;
// 定义缓冲区
byte buffer[] = new byte[1024];
// 按照缓冲区的大小,循环读取
while ((len = is.read(buffer)) != -1) {
// 根据读取的长度写入到os对象中
message.write(buffer, 0, len);
}
// 释放资源
is.close();
message.close();
// 返回字符串
msg = new String(message.toByteArray());
return msg;
}
}catch(Exception e){e.printStackTrace();}
return msg;
}
}
9.Cookie问题的处理
说这个之前,首先我们要理解两个概念:Session和Cookie Cookie只是Session机制的一种常用形式,我们也可以使用其他方式来作为客户端的一个唯一标识, 这个由服务器决定,唯一能够证明一个客户端标识就好!除了这种方式外,我们还可以使用URL重写! 方法来实现!所以以后别傻傻的跟别人说:Session不就是Cookie!
下面通过一个例子来帮助大家理解这个Cookie: 小猪输入账号密码后登陆下学校的教务系统,然后访问课表信息成功, 然后如果你用的是Chrome,按F12进入开发模式:来到Resources界面可以看到我们的Cookies:
点击后我们可以看到里面保存的内容,由名称;值;cookie所在的域(domain); cookie所在的目录(path)Asp.net默认为/即根目录;过期时间;Cookie大小:
我们可以看到请求头中有一个Cookie的字段:
恩呢,现在我们把Cookie清掉(或者等几分钟),然后再访问下述链接:
这时候,页面竟然自动跳回登陆页面了!当然一些其他的网站可能会弹出一个对话框说 "登陆超时"之类的东西!
小结下Http请求登陆的一个简单流程: 一般是登陆的时候:服务器通过Set-Cookie响应头,返回一个Cookie,浏览器默认保存这个Cookie, 后续访问相关页面的时候会带上这个Cookie,通过Cookie请求头来完成访问,如果没Cookie或者 Cookie过期,就提示用户没登陆,登陆超时,访问需要登陆之类的信息!
而我们使用HttpClient和HttpURLConnection其实也就是模拟这一个流程,登陆后拿到cookie 拿着它去发送请求: 关键代码如下: 获得Cookie:conn.getHeaderField(“Set-Cookie”); 请求时带上Cookie:conn.setRequestProperty(“Cookie”,cookie);
另外,除了这种设置请求头的方式外,还可以用另一种折衷的方法:URL重写: 就是在原先请求链接的基础上,加上一个…&sessionid=xxxxx这样的参数,然后由服务器来解析 判断!Get可以这么写,而Post写法如下:
这里我们用的是JSON字符串的形式,接到请求时服务端取出session里的内容,然后做下查询即可~
10.使用HttpURLConnection发送PUT请求
Put请求对于很多朋友来说可能有点陌生,毕竟我们平时接触的比较多的情况都是GET和POST, 一开始小猪也不知道,不过后来才发现和POST其实是差不多的,而且我们只需在POST的基础上改 点东西就可以使用了!而HttpClient也给我们提供了一个HttpPut的API, 下面贴下小猪自己项目中写的请求代码:
public static String LoginByPut(Context mContext, String mobile, String password, int from,
String devid,String version_name, int remember_me) {
String resp = "";
try {
HttpURLConnection conn = (HttpURLConnection) new URL(LOGIN_URL).openConnection();
conn.setRequestMethod("PUT");
conn.setReadTimeout(5000);
conn.setConnectTimeout(5000);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
String data = "mobile=" + mobile + "&password=" + password + "&from=" + from + "&devid=" + "devid"
+ "&version_name=" + "version_name" + "&remember_me=" + remember_me;
;
// 获取输出流:
OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream());
writer.write(data);
writer.flush();
writer.close();
// 获取相应流对象:
InputStream in = conn.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null)
response.append(line);
SPUtils.put(mContext, "session", conn.getHeaderField("Set-Cookie"));
// 资源释放:
in.close();
// 返回字符串
Log.e("HEHE", response.toString());
return response.toString();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
11.HttpClient使用流程
本节到第二种方式:HttpClient,尽管被Google 弃用了,但是我们我们平时也可以拿HttpClient来抓下包,配合Jsoup解析网页效果更佳!HttpClient 用于接收/发送Http请求/响应,但不缓存服务器响应,不执行HTML页面潜入的JS代码,不会对页面内容 进行任何解析,处理!
12.HttpClient使用示例
1)使用HttpClient发送GET请求
直接贴下简单的发送Get请求的代码:
public class MainActivity extends Activity implements OnClickListener {
private Button btnGet;
private WebView wView;
public static final int SHOW_DATA = 0X123;
private String detail = "";
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
if(msg.what == SHOW_DATA)
{
wView.loadDataWithBaseURL("",detail, "text/html","UTF-8","");
}
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
setView();
}
private void initView() {
btnGet = (Button) findViewById(R.id.btnGet);
wView = (WebView) findViewById(R.id.wView);
}
private void setView() {
btnGet.setOnClickListener(this);
wView.getSettings().setDomStorageEnabled(true);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btnGet) {
GetByHttpClient();
}
}
private void GetByHttpClient() {
new Thread()
{
public void run()
{
try {
HttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet("http://www.w3cschool.cc/python/python-tutorial.html");
HttpResponse httpResponse = httpClient.execute(httpGet);
if (httpResponse.getStatusLine().getStatusCode() == 200) {
HttpEntity entity = httpResponse.getEntity();
detail = EntityUtils.toString(entity, "utf-8");
handler.sendEmptyMessage(SHOW_DATA);
}
} catch (Exception e) {
e.printStackTrace();
}
};
}.start();
}
}
另外,如果是带有参数的GET请求的话,我们可以将参数放到一个List集合中,再对参数进行URL编码, 最后和URL拼接下就好了:
List<BasicNameValuePair> params = new LinkedList<BasicNameValuePair>();
params.add(new BasicNameValuePair("user", "猪小弟"));
params.add(new BasicNameValuePair("pawd", "123"));
String param = URLEncodedUtils.format(params, "UTF-8");
HttpGet httpGet = new HttpGet("http://www.baidu.com"+"?"+param);
2)使用HttpClient发送POST请求
POST请求比GET稍微复杂一点,创建完HttpPost对象后,通过NameValuePair集合来存储等待提交 的参数,并将参数传递到UrlEncodedFormEntity中,最后调用setEntity(entity)完成, HttpClient.execute(HttpPost)即可;这里就不写例子了,暂时没找到Post的网站,又不想 自己写个Servlet,So,直接贴核心代码吧~
private void PostByHttpClient(final String url)
{
new Thread()
{
public void run()
{
try{
HttpClient httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(url);
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("user", "猪大哥"));
params.add(new BasicNameValuePair("pawd", "123"));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params,"UTF-8");
httpPost.setEntity(entity);
HttpResponse httpResponse = httpClient.execute(httpPost);
if (httpResponse.getStatusLine().getStatusCode() == 200) {
HttpEntity entity2 = httpResponse.getEntity();
detail = EntityUtils.toString(entity2, "utf-8");
handler.sendEmptyMessage(SHOW_DATA);
}
}catch(Exception e){e.printStackTrace();}
};
}.start();
}
13.HttpClient抓数据示例(教务系统数据抓取)
其实关于HttpClient的例子有很多,比如笔者曾经用它来抓学校教务系统上学生的课程表: 这就涉及到Cookie,模拟登陆的东西,说到抓数据(爬虫),一般我们是搭配着JSoup来解析 抓到数据的,有兴趣可以自己查阅相关资料:
HttpClient可以通过下述代码获取与设置Cookie: HttpResponse loginResponse = new DefaultHttpClient().execute(getLogin); 获得Cookie:cookie = loginResponse.getFirstHeader(“Set-Cookie”).getValue(); 请求时带上Cookie:httpPost.setHeader(“Cookie”, cookie);
//获得链接,模拟登录的实现:
public int getConnect(String user, String key) throws Exception {
// 先发送get请求 获取cookie值和__ViewState值
HttpGet getLogin = new HttpGet(true_url);
// 第一步:主要的HTML:
String loginhtml = "";
HttpResponse loginResponse = new DefaultHttpClient().execute(getLogin);
if (loginResponse.getStatusLine().getStatusCode() == 200) {
HttpEntity entity = loginResponse.getEntity();
loginhtml = EntityUtils.toString(entity);
// 获取响应的cookie值
cookie = loginResponse.getFirstHeader("Set-Cookie").getValue();
System.out.println("cookie= " + cookie);
}
// 第二步:模拟登录
// 发送Post请求,禁止重定向
HttpPost httpPost = new HttpPost(true_url);
httpPost.getParams().setParameter(ClientPNames.HANDLE_REDIRECTS, false);
// 设置Post提交的头信息的参数
httpPost.setHeader("User-Agent",
"Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko");
httpPost.setHeader("Referer", true_url);
httpPost.setHeader("Cookie", cookie);
// 设置请求数据
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("__VIEWSTATE",
getViewState(loginhtml)));// __VIEWSTATE参数,如果变化可以动态抓取获取
params.add(new BasicNameValuePair("Button1", ""));
params.add(new BasicNameValuePair("hidPdrs", ""));
params.add(new BasicNameValuePair("hidsc", ""));
params.add(new BasicNameValuePair("lbLanguage", ""));
params.add(new BasicNameValuePair("RadioButtonList1", "%D1%A7%C9%FA"));
params.add(new BasicNameValuePair("txtUserName", user));
params.add(new BasicNameValuePair("TextBox2", key));
params.add(new BasicNameValuePair("txtSecretCode", "")); // ( ╯□╰ )逗比正方,竟然不需要验证码
// 设置编码方式,响应请求,获取响应状态码:
httpPost.setEntity(new UrlEncodedFormEntity(params, "gb2312"));
HttpResponse response = new DefaultHttpClient().execute(httpPost);
int Status = response.getStatusLine().getStatusCode();
if(Status == 200)return Status;
System.out.println("Status= " + Status);
// 重定向状态码为302
if (Status == 302 || Status == 301) {
// 获取头部信息中Location的值
location = response.getFirstHeader("Location").getValue();
System.out.println(location);
// 第三步:获取管理信息的主页面
// Get请求
HttpGet httpGet = new HttpGet(ip_url + location);// 带上location地址访问
httpGet.setHeader("Referer", true_url);
httpGet.setHeader("Cookie", cookie);
// 主页的html
mainhtml = "";
HttpResponse httpResponseget = new DefaultHttpClient()
.execute(httpGet);
if (httpResponseget.getStatusLine().getStatusCode() == 200) {
HttpEntity entity = httpResponseget.getEntity();
mainhtml = EntityUtils.toString(entity);
}
}
return Status;
}
14.使用HttpPut发送Put请求
示例代码如下:
public static int PutActCode(String actCode, String licPlate, Context mContext) {
int resp = 0;
String cookie = (String) SPUtils.get(mContext, "session", "");
HttpPut httpPut = new HttpPut(PUTACKCODE_URL);
httpPut.setHeader("Cookie", cookie);
try {
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("activation_code", actCode));
params.add(new BasicNameValuePair("license_plate", licPlate));
httpPut.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
HttpResponse course_response = new DefaultHttpClient().execute(httpPut);
if (course_response.getStatusLine().getStatusCode() == 200) {
HttpEntity entity2 = course_response.getEntity();
JSONObject jObject = new JSONObject(EntityUtils.toString(entity2));
resp = Integer.parseInt(jObject.getString("status_code"));
return resp;
}
} catch (Exception e) {
e.printStackTrace();
}
return resp;
}