写在前面的话:好一阵子没写博客了,心里有点惭愧。在这期间我主要做了两件事:一、之前在研究Android的过程中比较吃力,主要是Java的一些基础知识掌握得还不太好,于是近半个月恶补了一下Java基础,我发现自己连多态!多态!多态!(重要的事说三遍)这个面向对象的核心知识点都没掌握,说起来真是太惭愧了,我都不知道自己在不懂多态这个知识点的情况下,是怎么把Android的核心知识学下来的,要知道在Java以及Android的API中到处都是多态,现在打死我也能把多态发生的必要条件说出来了:1、要有类的继承(或接口的实现);2、要有方法的重写;3、要有父类引用指向子类对象。除了多态,托马士兵老师的福(学了马士兵Java视频教程),我把Java代码在堆栈中的运行机制学了下,较好的理解了Java代码的内存运行机制,理解了这些再看Android代码,感觉比以前轻松多了!套用马士兵老师的一句话,就是“掌握了Java代码在内存中的运行机制,就掌握了一切!” 二、除了夯实Java基础,我还把郭霖前辈(《第一行代码》的作者)的博客仔细研究了一下,不得不说郭大神除了代码写的好,文笔也很棒,当初选择《第一行代码》作为Android学习的入门书籍,绝对是正确决定。在博客中,郭神通过源码的解读,详细剖析了Android中初学者不容易掌握的知识点,对于我这种菜鸟,绝对值得反复研究,当然了,目前涉及到Android源码的内容,我还暂时无法理解,不过以后我会慢慢把它搞明白!:)
之前写过一篇Android网络编程《浅谈android网络编程》,随着了解了更多Android知识,我意识到HttpClient已经不推荐使用了,更是在Android 6.0中被废弃了,原因之一就是比起HttpURLConnection,HttpClient的封装性更好,导致其可扩展性较差,当然现在网络编程更多的是使用框架了,HttpURLConnection被封装了,socket也被封装了,而像OKHttp和Volley这种框架,使用起来极为方便,开发者甚至不需要知道http、TCP/IP协议的相关知识就能高效地进行网络通信,但在有些时候,当开发者遇到bug或是修改些涉及到基于原理的功能时,可能会因为没能理解这些框架的原理而无法实现,所以本文就通过案例,着重分析基于Http协议的Android网络的原理实现,而不使用框架实现,当然更多的还是基础知识。
Http是Internet中广泛使用的协议,几乎所有的计算机语言和SDK都会不同程度地支持HTTP,而以网络著称的Google公司自然也会使Android SDK拥有强大的HTTP访问能力。在Android SDK中可以采用多种方式使用HTTP,例如HttpURLConnection、HttpClient ( HttpGet、HttpPost )等。
Http是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展。
Http协议的主要特点如下:
HttpURLConnection类是jdk中的标准网络接口,该类的全限定名是java.net.HttpURLConnection,下面将介绍利用HttpURLConnection实现get请求和post请求。
为了实现GET请求,需在后端搭建服务器,该服务器已搭好,访问地址为:http://cloud.bmob.cn/0906a62b462a3082/getMemberBySex?sex=boy。使用浏览器访问该地址,将返回如下JSON格式的数据:
HttpURLConnection实现get请求步骤如下:
示例如下:
首先在界面中添加一个按钮,点击该按钮,程序将尝试请求上述server的相应数据并返回给程序,界面如下:
代码示例:
public class MainActivity extends AppCompatActivity {
private Button mButtonHttpUrlConnctionGet;
private String mUrl = "http://cloud.bmob.cn/0906a62b462a3082/";
private String mMethod = "getMemberBySex";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButtonHttpUrlConnctionGet = (Button) findViewById(R.id.button_httpurlconnection_get);
mButtonHttpUrlConnctionGet.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doGet("boy");
}
});
}
//doGet请求
private void doGet(String s) {
//1、获得需要访问的server地址、方法、及参数键值
final String serverAddress = mUrl + mMethod + "?" + "sex=" + s;
//访问网络,开启一个线程
new Thread(new Runnable() {
@Override
public void run() {
try {
//2、创建URL对象,将上述访问地址传入;
URL url = new URL(serverAddress);
//3、调用URL.openConnection()方法返回HttpURLConnection对象
HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection();
//4、调用HttpURLConnection.connect()方法连接server
httpUrlConnection.connect();
//5、调用HttpURLConnection.respondCode()方法,根据返回码判断server返回是否正确
if (httpUrlConnection.getResponseCode() == 200) {
//6、调用HttpURLConnection.getInputStream()方法读取server返回的内容
InputStream is = httpUrlConnection.getInputStream();
//包装流:字节流->转换字符流(处理流)->缓冲字符流(处理流)
BufferedReader br = new BufferedReader(new InputStreamReader(is));
StringBuffer sb = new StringBuffer();
String readLine = "";
while ((readLine = br.readLine()) != null) {
sb.append(readLine);
}
//7、关闭流、调用HttpURLConnection.disconnect()方法断开连接
is.close();
br.close();
httpUrlConnection.disconnect();
//Log日志中显示返回结果
Log.i("TAG", sb.toString());
} else {
Log.e("TAG", "failed to connect server!");
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
使用HttpURLConnection的get请求需要注意的地方:
<uses-permission android:name="android.permission.INTERNET">uses-permission>
new Thread(new Runnable
{
//access the Internet
}).start();
当需要向server写数据时,就需要使用post请求。Http请求由三部分组成,分别是:请求头、消息报头(可选)、请求正文。
Http响应也是由三个部分组成,分别是:响应头、消息报头(可选)、响应正文。
其中请求头允许客户端传递关于自身的信息和希望的响应形式,它负责通知服务器有关客户端请求的信息,下表列出了http的请求头,其中用黄色标记的请求头较为常用:
与请求头对应,响应头域允许服务器传递不能放在状态行的附加信息,下表列出了http的响应头,其中用黄色标记的响应头较为常用:
HttpURLConnection实现post请求步骤如下:
由此看出,相比于get方式,post方式不允许将参数值出入URL对象,而是需要调用HttpURLConnection.getOutputStream()方法专门向server发送参数信息;另外,需要调用HttpURLConnection的方法设置输入输出流、响应头等操作。
下面是post请求的代码示例:
//doPost请求
private void doPost(final String s) {
//post请求的URL没有请求参数
final String postAddress = mUrl + mMethod;
new Thread(new Runnable() {
@Override
public void run() {
try {
URL url = new URL(postAddress);
HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection();
//打开HttpURLConnection的输入输出
httpUrlConnection.setDoInput(true);
httpUrlConnection.setDoOutput(true);
//设置请求为post请求
httpUrlConnection.setRequestMethod("POST");
//不使用缓存
httpUrlConnection.setUseCaches(false);
//设置请求头
//设置编码集
httpUrlConnection.setRequestProperty("Accept-Charset", "UTF-8");
//设置content-type
httpUrlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
//连接server
httpUrlConnection.connect();
//将需要发送的参数信息写给server
OutputStream os = httpUrlConnection.getOutputStream();
//包装流:数据输出流(字符处理流)
DataOutputStream dos = new DataOutputStream(os);
String content = "sex" + s;
dos.writeBytes(content);
os.flush();
os.close();
dos.flush();
dos.close();
//获得server返回数据
if (httpUrlConnection.getResponseCode() == 200) {
InputStream is = httpUrlConnection.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
StringBuffer sb = new StringBuffer();
String readLine = "";
while ((readLine = br.readLine()) != null) {
sb.append(readLine);
}
is.close();
br.close();
httpUrlConnection.disconnect();
Log.i("POST_TAG", sb.toString());
} else {
Log.i("POST_TAG", "failed!");
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
点击在LogCat中的显示如下:
上面介绍了使用HttpURLConnection类实现Get请求和Post请求,下面简要介绍它们的区别:
在较低的Android API版本中,通过Org.apache.HttpClient,Android也能访问网络,但在4.x及后续版本已不被推荐使用,更是在Android 6.0 (API 23)中被废弃,很大一部分原因就是HttpClient类的封装性很高,导致其扩展性不如HttpURLConnection。当然,如今HttpURLConnection类也被封装的很好,OkHttp就是将其封装得很好且扩展性更高的一个框架,下面将对OkHttp做简单介绍。
HttpURLConnection和HttpClient,虽然两者都能满足HTTPS流的上传和下载,配置超时,IPv6和连接池等各种HTTP请求的需求,但更高效的使用HTTP可以让我们的应用运行更快、更节省流量。而OkHttp库就是为此而生。
OkHttp是一个高效的HTTP库:
1、支持 SPDY ,共享同一个Socket来处理同一个服务器的所有请求,如果SPDY不可用,则通过连接池来减少请求延时。
2、无缝的支持GZIP来减少数据流量
3、缓存响应数据来减少重复的网络请求:会从很多常用的连接问题中自动恢复。如果服务器配置了多个IP地址,当第一个IP连接失败的时候,OkHttp会自动尝试下一个IP。OkHttp还处理了代理服务器问题和SSL握手失败问题。
4、使用 OkHttp 无需重写程序中的网络代码。OkHttp实现了几乎和HttpURLConnection一样的API。如果用了HttpClient,则OkHttp也提供了一个对应的okhttp-apache 模块。
5、OkHttp是一个相对成熟的解决方案,Android4.4的源码中已经将HttpURLConnection替换成了OkHttp。
为了在程序中使用OkHttp框架,需要在gradle中添加依赖:
compile 'com.squareup.okhttp3:okhttp:3.2.0'
在布局中添加两个按钮,用于发送Get请求和Post请求。
以下是Get请求的代码示例:
private void doGet(String s) {
//拼接访问server地址
final String address = new StringBuilder().append(mUrl).append(mMethod).append("?sex=").append(s).toString();
new Thread(new Runnable() {
@Override
public void run() {
//创建okhttp3.Request对象,传入访问地址
Request request = new Request.Builder().url(address).build();
try {
//使用同步方式获得返回对象okhttp3.Response
Response response = mOkHttpCLient.newCall(request).execute();
//访问成功
if (response.isSuccessful()) {
//调用Response.body().string()方法获得server返回的结果
Log.i("TAG", response.body().string());
} else {
Log.i("TAG", "error!");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
/**
* @param s post的请求参数
*/
private void doPost(String s) {
//创建FormBody对象,用于封装post的请求参数
FormBody formBody = new FormBody.Builder().add("sex", s).build();
//创建Request对象,用于封装URL请求地址及请求参数
Request request = new Request.Builder().url(new StringBuilder().append(mUrl).append(mMethod).toString()).post(formBody).build();
//采用异步方式回调server返回的结果
mOkHttpCLient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.i("TAG", "failed");
}
//该回调方法的返回参数Response就是server的返回结果
@Override
public void onResponse(Call call, Response response) throws IOException {
//输出结果
Log.i("TAG", response.body().string());
}
});
}
有关OkHttp的更多学习资料,请您访问:
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它基于ECMAScript的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C、C++、C#、Java、JavaScript、Perl、Python等)。这些特性使JSON成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成(一般用于提升网络传输速率)。
JSON 语法规则:
1、数据在键值对中
2、数据由逗号分隔
3、花括号保存对象
4、方括号保存数组
JSON 值可以是:
1、数字(整数或浮点数)
2、字符串(在双引号中)
3、逻辑值(true 或 false)
4、数组(在方括号中)
5、对象(在花括号中)
6、null
优点:
1、数据格式比较简单,易于读写,格式都是压缩的,占用带宽小;
2、易于解析,客户端JavaScript可以简单的通过eval()进行JSON数据的读取;
3、支持多种语言,包括ActionScript, C, C#, ColdFusion, Java, JavaScript, Perl, PHP, Python, Ruby等服务器端语言,便于服务器端的解析;
4、在PHP世界,已经有PHP-JSON和JSON-PHP出现了,偏于PHP序列化后的程序直接调用,PHP服务器端的对象、数组等能直接生成JSON格式,便于客户端的访问提取;
5、因为JSON格式能直接为服务器端代码使用,大大简化了服务器端和客户端的代码开发量,且完成任务不变,并且易于维护。
缺点:
1、没有XML格式这么推广的深入人心和喜用广泛,没有XML那么通用性;
2、JSON格式目前在Web Service中推广还属于初级阶段。
下面代码将解析上述JSON数据:
/**
* @param json
*/
private void parseData(String json) {
JSONObject objectOutside = null;
try {
objectOutside = new JSONObject(json);
} catch (JSONException e) {
e.printStackTrace();
}
try {
String country = objectOutside.getString("name");
Log.i("TAG", "Country: " + country);
JSONArray provinces = objectOutside.getJSONArray("provinces");
for (int i = 0; i < provinces.length(); ++i) {
JSONObject objectInside = provinces.getJSONObject(i);
String province = objectInside.getString("name");
Log.i("TAG", "Province " + (i + 1) + ": " + province);
JSONObject objectCities = objectInside.getJSONObject("citys");
JSONArray arrayCity = objectCities.getJSONArray("city");
StringBuffer sb = new StringBuffer();
for (int j = 0; j < arrayCity.length(); ++j) {
String city = arrayCity.getString(j);
sb.append("City " + (j + 1) + ": " + city + " ");
}
Log.i("TAG", sb.toString());
}
} catch (JSONException e) {
e.printStackTrace();
}
}
XML是一种可扩展标记语言:
1、可扩展标记语言是一种很像超文本标记语言的标记语言。
2、它的设计宗旨是传输数据,而不是显示数据。
3、它的标签没有被预定义。您需要自行定义标签。
4、它被设计为具有自我描述性。
5、它是W3C的推荐标准。
可扩展标记语言(XML)和超文本标记语言(HTML)之间的差异:
1、它不是超文本标记语言的替代。
2、它是对超文本标记语言的补充。
3、它和超文本标记语言为不同的目的而设计:
4、它被设计用来传输和存储数据,其焦点是数据的内容。
5、超文本标记语言被设计用来显示数据,其焦点是数据的外观。
超文本标记语言(XML)存在的问题:
1、某些起始标签可以选择性出现结束标签或者隐含了结束标签。
2、标签可以以任何顺序嵌套,即使结束标签不按照起始标签的逆序出现也是允许的。
3、某些特性不要求一定有值。
4、定义特性的两边有没有加上双引号都是可以的,所以都是允许的。
可扩展标记语言如何解决问题:
1、任何的起始标签都必须有一个结束标签。
2、可以采用另一种简化语法,可以在一个标签中同时表示起始和结束标签。
3、标签必须按合适的顺序进行嵌套,所以结束标签必须按镜像顺序匹配起始标签。
4、所有的特性都必须有值。
5、所有的特性都必须在值的周围加上双引号。
XML的优点:
1、格式统一,符合标准;
2、容易与其他系统进行远程交互,数据共享比较方便。
XML的缺点:
1、XML文件庞大,文件格式复杂,传输占带宽;
2、服务器端和客户端都需要花费大量代码来解析XML,导致服务器端和客户端代码变得异常复杂且不易维护;
3、客户端不同浏览器之间解析XML的方式不一致,需要重复编写很多代码;
4、服务器端和客户端解析XML花费较多的资源和时间。
下限是一段XML格式的数据:
<root>
<item>
<name>刘亦菲name>
<url>MingXing/LiuYiFei.htmurl>
<color>redcolor>
item>
<item>
<name>蔡依林name>
<url>MingXing/CaiYiLin.htmurl>
<color>bluecolor>
item>
<item>
<name>张娜拉name>
<url>MingXing/ZhangNaLa.htmurl>
<color>greencolor>
item>
<item>
<name>张韶涵name>
<url>MingXiang/ZhangShaoHan.htmurl>
<color>greycolor>
item>
<item>
<name>张靓颖name>
<url>MingXing/ZhangLiangYin.htmurl>
<color>blackcolor>
item>
<item>
<name>李宇春name>
<url>MingXing/LiYuChun.htmurl>
<color>yellowcolor>
item>
<item>
<name>徐若瑄name>
<url>MingXing/XuLuXuan.htmurl>
<color>pinkcolor>
item>
root>
下面使用PULL解析方式对上述XML格式数据进行解析:
/**
* @param mXML2
*/
private void parseXML2(String mXML2) {
List list = null;
Star star = null;
//XMLPullParser.setInput()方法接收InputStream类型,故首先将String类型的XML数据转换成InputStream类型
InputStream is = new ByteArrayInputStream(mXML2.getBytes());
XmlPullParser xpp = Xml.newPullParser();
try {
//XMLPullParser.setInput()方法接收InputStream类型
xpp.setInput(is, "UTF-8");
//获取标签类型
int type = xpp.getEventType();
//若未读到最后的结束标签,则不停遍历每个标签及其内容
while (type != XmlPullParser.END_DOCUMENT) {
switch (type) {
//若为XML文件起始标签,则实例化List对象
case XmlPullParser.START_DOCUMENT:
list = new ArrayList<>();
break;
//若读到每项的起始标签
case XmlPullParser.START_TAG:
//起始标签为"item"
if ("item".equals(xpp.getName())) {
//实例化Star对象
star = new Star();
}
// 起始标签为"name"
else if ("name".equals(xpp.getName())) {
//后移一项
xpp.next();
//此时指向"name"标签的内容,将该内容设置到创建的Star对象中
star.setName(xpp.getText());
}
//起始标签为"url"
else if ("url".equals(xpp.getName())) {
//后移一项
xpp.next();
//此时指向"url"标签的内容,将该内容设置到创建的Star对象中
star.setUrl(xpp.getText());
}
//起始标签为"color"
else if ("color".equals(xpp.getName())) {
//后移一项
xpp.next();
//此时指向"color"标签的内容,将该内容设置到创建的Star对象中
star.setColor(xpp.getText());
}
break;
//若指向结束标签
case XmlPullParser.END_TAG:
//结束标签为"item"时,说明已读完一个完整的Star对象,该Star的所有字段已被赋值对象
if ("item".equals(xpp.getName())) {
//将该Star对象加入List集合中
list.add(star);
}
break;
default:
break;
}
//后移一项
type = xpp.next();
}
//在LogCat中打印结果
for (Star s : list) {
Log.i("XML2", s.toString());
}
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}