1.WebView的用法
- 使用WebView控件,
借其在自己的应用程序中
嵌入一个浏览器
, 以轻松展示各种网页
;
新建一个WebViewTest项目,
修改activity_main.xml:
代码语言:javascript复制<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<WebView
android:id="@ id/web_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
接下来修改MainActivity.java:
代码语言:javascript复制public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WebView webView = (WebView) findViewById(R.id.web_view);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient());
webView.loadUrl("http://www.baidu.com");
}
}
- 首先是findViewById实例化对象;
- getSettings()用来设置浏览器属性; setJavaScriptEnabled(true)让WebView支持JavaScript脚本;
- 调用WebView 的setWebViewClient()方法, 传入一个WebViewClient实例, 作用是:使当需要从一个网页跳转到另外一个网页时, 目标网页仍然在当前WebView中显示,而不是打开系统浏览器;
- loadUrl()传入网址,显示网页内容;
接下来,还需在AndroidManifest.xml中添加访问网络的权限
:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.webviewtest">
<uses-permission android:name="android.permission.INTERNET"/>
...
</manifest>
至此,即可运行程序了,其效果如下:
当然还要注意一点,如果你的模拟器和SDK是Android 9.0(API级别28),那运行如上代码会出现下面这个问题:
原因是从Android 9.0(API级别28)开始,默认情况下禁用明文支持。
因此http的url均无法在webview中加载。
解决方法是在AndroidManifest.xml对应的地方加入一句代码即可:
代码语言:javascript复制android:usesCleartextTraffic="true"
解决之后便可以运行成功了:
当然,小伙伴们,生活往往没那么简单,
百度搜索引擎框下面有很多吸引我们眼球的文章对吧,
你会发现当你随便点击一篇文章,想要跳转过去的时候,会出现下方这种报错:
亦或者:
莫慌,其实都是一个道理,仔细看一下报错,我们可以发现url的前缀都被替换了:
- 这是因为其自定义了
scheme
, 类似的还有alipays://
,weixin://
等等。 而webView
只能识别http://
或https://
开头的url
,因此才会报此错。 处理方法,对于这种自定义scheme的url
单独处理
即可。
修改代码如下:
我们刚刚写了这段代码对吧:
webView.setWebViewClient(new WebViewClient())
现在,
把传入的WebViewClient实例
变成一个以WebViewClient
为父类
的匿名内部类
,
并重写shouldOverrideUrlLoading()
方法,
在里面对方才报错中的自定义scheme
进行单独处理即可:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
...
final WebView webView = (WebView) findViewById(R.id.web_view);
...
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
try{
if(url.startsWith("baiduboxapp://") || url.startsWith("baiduboxlite://" )){
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
return true;
}
}catch (Exception e){
return false;
}
webView.loadUrl(url);
return true;
}
});
webView.loadUrl("http://www.baidu.com");
}
}
ok,这时候再运行程序,便可以成功打开各种推文了:
参考文章
- https://www.jianshu.com/p/119823e5cfb5
- https://blog.csdn.net/qq_32623363/article/details/79173485
2. 使用HTTP协议访问网络
- HTTP基于android的工作原理简述
客户端
向服务器
发出一条HTTP
请求,服务器
收到请求之后会返回一些数据给客户端
, 然后客户端
再对这些数据进行解析
和处理
就可以。 - 一个浏览器的基本工作原理也就是如上了。
上面使用的
WebView
控件, 其实也就是app向百度服务器
发起一条HTTP
请求, 接着服务器
分析出我们想要访问的是百度
的首页, 于是会把该网页的HTML代码进行返回
, 然后WebView
再调用手机浏览器的内核
对返回的HTML代码
进行解析
, 最终将页面展示
出来。 - 也即WebView封装了
发送HTTP请求
、接受服务响应
、解析返回数据
,以及最终页面的展示
这几步工作。
- 下面暂时摆脱WebView,
手动发送HTTP请求
,直观地理解一下HTTP
协议的工作过程。
2.1 使用HttpURLConnection
- 首先, 获取HttpURLconnection的实例:
a. 以参数为目标的网络地址,new 出一个URL对象;
b. url对象调用openConnection();
返回的结果转型付给HttpURLConnection对象;
代码语言:javascript复制URL url = new URL("http://www.baidu.com");
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
- 得到HttpURLConnection实例之后,设置HTTP请求所使用的方法;
常使用的方法主要有两个:GET和POST。
GET表示希望从服务器获取数据,
POST希望提交数据给服务器:
代码语言:javascript复制connection.setRequestMethod("GET");
- 接下来进行一些自由的定制, 如设置连接超时、读取超时的毫秒数、服务器希望得到的一些消息头等。 这些都是自己根据实际情况进行编写:
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
- 之后再调用getInputStream()方法, 就可以获取到服务器返回的输入流了, 后续的对输入流进行读取即可:
InputStream in = connection.getInputStream();
- 最后调用disconnect()关闭HTTP连接:
connection.disconnect();
下面新建一个名为NetworkTest的空活动,调试一下上面的知识点,
修改对应的xml:
代码语言:javascript复制<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".NetworkTest">
<Button
android:id="@ id/send_request"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Send Request"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@ id/response_text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</ScrollView>
</LinearLayout>
以上,
- ScrollView用于滚动查看内容;
- Button用来发送HTTP请求;
- TextView用来显示服务器返回的数据;
接着修改NetworkTest.java:
代码语言:javascript复制public class NetworkTest extends AppCompatActivity implements View.OnClickListener{
TextView responseText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_network_test);
Button sendRequest = (Button) findViewById(R.id.send_request);
responseText = (TextView) findViewById(R.id.response_text);
sendRequest.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if(v.getId() == R.id.send_request){
sendRequestWithHttpURLConnection();
}
}
private void sendRequestWithHttpURLConnection() {
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
URL url = new URL("https://hao.360.cn/");
connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
InputStream in = connection.getInputStream();
//下面对获取的输入流进行读取
//InputStreamReader赋值给BufferedReader让其来读
reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
//一行一行地读取并加进stringbuilder
while((line = reader.readLine()) != null){
response.append(line);
}
showResponse(response.toString());
Log.d("NetworkTest: ", response.toString());
} catch (IOException e) {
e.printStackTrace();
}finally{
if(reader !=null){
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(connection !=null){
connection.disconnect();
}
}
}
}).start();
}
private void showResponse(final String response) {
runOnUiThread(new Runnable() {
@Override
public void run() {
responseText.setText(response);
}
});
}
}
- sendRequestWithHttpURLConnection()中: 开启一个子线程, 在子线程里使用HttpURLConnection发出一条HTTP请求, 请求的目标地址就是百度的首页; 接着用BufferedReader读取返回的输入流, 转成string传给showResponse()
- showResponse()中通过runOnUiThread()将返回的数据显示到界面上;
关于runOnUiThread()方法, 因为Android不允许在子线程中进行UI操作, 我们需要通过这个方法在子线程中将线程切换到主线程, 然后再更新UI元素。
运行效果如下:
注: 如网址不正确, 会出现 NetworkSecurityConfig: No Network Security Config specified, using platform default的提示。 另外,用手机热点也是如此!!! 只有稳定PC端接线网络或其热点才可顺利使用!!
- 服务器返回的就是这些HTML代码, 只是通常浏览器都会将这些代码解析成漂亮的网页再展示出来;
- 如果想
提交数据
给服务器, 只需将HTTP请求方法改成POST
, 并在获取输入流之前
把要提交的数据
写出即可。 每条数据都要以键值对
的形式存在, 数据与数据之间用“&”符号
隔开,如提交用户名和密码
:
connection.setRequestMethod("POST");
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.writeBytes("username=admin&password=123456");
2. 使用OkHttp
- OkHttp由Square公司开发,其不仅在接口封装上面做的简单易用, 就连在底层实现上也是自成一派, 比起原生的HttpURLConnection,可以说是有过之而无不及, 现在已经成了广大Android开发者的首选网络通信库。
- OkHttp项目主页地址:https://github.com/square/okhttp
- 使用之前,需添加OkHttp库依赖, 打开app/buid.gradle,在dependencies闭包中添加如下内容:
implementation("com.squareup.okhttp3:okhttp:3.14.0")
添加此依赖,会自动下载两个库:OkHttp库、Okio库(是前者的通信基础)。
- 注意,添加前最好是访问一下OkHttp项目主页查看当前最新的版本是多少,再在gradle处添加依赖;
下面是OkHttp具体用法
- 首先,需要创建OkHttpClient实例,如下:
OkHttpClient client = new OkHttpClient();
- 接下来,如想发起一条HTTP请求,需创建
Request对象
:
Request request = new Request.Builder().build();
- 当然上述代码只是创建一个空的Request对象, 需要在build()方法之前可连缀很多其他方法丰富此Request对象。
Request request = new Request.Builder()
.url("https://www.baidu.com")
.build();
- 之后调用OkHttpCilent的newCall()方法创建一个
Call对象
, 并调用它的execute()
方法发送请求
, 并获取服务器
返回的数据:
Response response = client.newCall(request).execute();
- request存请求;
- newCall接收request
- execute执行request
- Response对象接收服务器返回的数据;
- 下面得到返回的具体内容
String responseData = response.body().string();
如果发起一条POST请求,会比GET复杂些;
- 需先构建
RequestBody
对象存放待提交的参数
:
RequestBody requestBody = new FormBody.Builder()
.add("username", "admin")
.add("password", "123456")
.build();
- 然后在
Request.Builder
中以RequestBody
对象为传入的参数调用post()
方法,:
Request request = new Request.Builder()
.url("http://www.baidu.com")
.post(requestBody)
.build();
- 接下来的操作就和
GET
请求一样了, 调用execute()
方法发送请求并获取服务器返回的数据即可。
现在改用OkHttp的方式把刚刚NewworkTest的内容再实现一遍:
代码语言:javascript复制 @Override
public void onClick(View v) {
if(v.getId() == R.id.send_request){
// sendRequestWithHttpURLConnection();
sendRequestWithOkHttp();
}
}
private void sendRequestWithOkHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://hao.360.cn/")
.build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
showResponse(responseData);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
private void showResponse(final String response) {
runOnUiThread(new Runnable() {
@Override
public void run() {
responseText.setText(response);
}
});
}
运行时有可能出现下面的问题,解决方法是把gradle中minsdk改成26以上即可:
运行效果图:
3.解析XML格式数据
- 通常,每个需要访问网络的应用程序都会有一个自己的服务器, 我们可以向服务器提交数据或者从服务器上获取数据;
- 为了双方能够快速知道文本的用途,一般在网络传输的数据都是格式化后的; 这种数据会有一定的结构规格和语义; 当另一方收到数据消息之后就可以按照相同的结构规格进行解析,从而去除他想要的那部分内容;
搭建一个本地服务器
- 在网络上传输数据最常用的格式有两种:
XML
和JSON
在开始学习这两种数据格式之前,
我们还需要搭建一个本地服务器
,
进度
大概进行到 可以在本地服务器文件夹下放置文件, 然后在本地浏览器可以访问即可;
这里提供两种方法:
- 可以使用单模块原生的本地服务器Apache, 具体的操作我之前已经写过一篇详细的博文:
- 本地模拟服务器开发与交互——Apache服务器填坑之路(下载、安装、使用demo、卸载)
博文剪影1
博文剪影2
- 或者学过PHP的朋友也可以使用PhpStudy集成环境(中的Apache模块)来做服务器,具体的相关我也写过相关的博文哈:
- PhpStudy集成环境下载、安装以及配置启动检测
- phpStudy启动界面的功能简介
- Apache服务启动失败解决方法
- PHPstudy | 使用站点管理器来创建虚拟主机
PHPStudy的话,在如下文件途径放下文件即可:
文件内容:
代码语言:javascript复制<apps>
<app>
<id>1</id>
<name>Google Maps</name>
<version>1.0</version>
</app>
<app>
<id>2</id>
<name>Chrome</name>
<version>2.1</version>
</app>
<app>
<id>3</id>
<name>Google Play</name>
<version>2.3</version>
</app>
</apps>
接着浏览器键入localhost/get_data.xml
即可访问:
- 当然,键入
127.0.0.1/get_data.xml
也是可以的:
- 解析XML格式数据有很多方式,
Pull
和SAX
解析是常用的两种。
3.1 Pull解析方式
- 这里我们依旧在NetworkTest 这个活动上面做开发,重用方才网络通信的代码,把重心放在XML数据解析上;
- 以上,我们已经准备好XML格式的数据, 现在编写代码从中解析出我们想要得到的那部分内容;
修改NetworkTest.java:
代码语言:javascript复制@Override
public void onClick(View v) {
if(v.getId() == R.id.send_request){
// sendRequestWithHttpURLConnection();
sendRequestWithOkHttp();
}
}
private void sendRequestWithOkHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient();
//http://10.0.2.2/对于模拟器来说是电脑本机IP地址**
Request request = new Request.Builder()
.url("http://47.100.78.251/testPackage/get_data.xml")
.build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
parseXMLWithPull(responseData);//解析服务器返回的数据**
// showResponse(responseData);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
private void parseXMLWithPull(String xmlData) {
try{
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();//获取一个XmlPullParserFactory 实例
XmlPullParser xmlPullParser = factory.newPullParser();//借助XmlPullParserFactory 实例得到 XmlPullParser对象
xmlPullParser.setInput(new StringReader(xmlData));//XmlPullParser对象 setInput 把服务器返回的数据传入即可开始解析
int eventType = xmlPullParser.getEventType();//通过getEventType()得到当前的解析事件
String id = "";
String name = "";
String version = "";
//如果不到文末
//解析事件不为XmlPullParser.END_DOCUMENT,说明解析工作没完成
responseText.setText("");
while(eventType != XmlPullParser.END_DOCUMENT){
//解析事件不为XmlPullParser.END_DOCUMENT,说明解析工作没完成
String nodeName = xmlPullParser.getName();//获取当前结点名字
switch (eventType) {
//开始解析某个节点
case XmlPullParser.START_TAG:
if ("id".equals(nodeName)) {
id = xmlPullParser.nextText();//获取节点具体内容
} else if ("name".equals(nodeName)) {
name = xmlPullParser.nextText();
} else if ("version".equals(nodeName)) {
version = xmlPullParser.nextText();
}
break;
//完成解析某个节点
case XmlPullParser.END_TAG:
if ("app".equals(nodeName)) {
//解析完一个app节点后打印获取到的内容
String finalId = id;
String finalName = name;
String finalVersion = version;
runOnUiThread(new Runnable() {
@Override
public void run() {
responseText.append("MainActivity: id is " finalId "n");
responseText.append("MainActivity: name is " finalName "n");
responseText.append("MainActivity: version is " finalVersion "n");
}
});
}
break;
default:
break;
}
eventType = xmlPullParser.next();//获取下一个解析事件
}
} catch (Exception e) {
e.printStackTrace();
}
}
Pull解析的使用思路是:
- 通过XmlPullParserFactory等一系列API,
得到一个XmlPullParser实例,
再把数据
传给XmlPullParser实例
xmlPullParser.setInput(new StringReader(xmlData));
接着就可以开始解析了; - 把XML的文末标志、起始标签以及结束标签,
约定为一个事件
int eventType = xmlPullParser.getEventType();
- 文末标志事件用于判断文件是否解析完,
- 起始标签事件用于 判断 以及 获取标签节点中的内容,
- 结束标签事件则用于 判断 以及 去实现一个解析阶段结束后的业务逻辑;
3.2 SAX解析方式
- 除了Pull解析,SAX解析也是一种常用的解析方式, 其用法比Pull解析复杂一些, 但语义上会更清楚;
用法:
- 新建一个类继承自DefaultHandler,并重写父类5个方法。
public class MyHandler extends DefaultHandler {
@Override
public void startDocument() throws SAXException {
super.startDocument();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
}
@Override
public void endDocument() throws SAXException {
super.endDocument();
}
}
- startDocument()在开始XML解析时候调用;
- startElement()在开始解析某个节点时调用;
- characters()在获取节点中的内容时候调用;
- endElement()在完成解析某个节点的时候调用;
- endDocument()在完成整个XML解析时调用;
- startElement()、characters()、endElement()三个方法是有参数的, 从XML中解析的数据会以参数的形式传入到这些方法中;
- 在获取节点中的内容时, characters()方法可能会被调用多次, 一些换行符也被当做内容解析出来, 我们需要针对这种情况在代码中做好控制;
实践
- 新建一个类继承自DefaultHandler,并重写父类5个方法: (注意代码中的注释)
public class ContentHandler extends DefaultHandler {
public String nodeName;
//面向XML文件配置全局属性
public StringBuilder id;
public StringBuilder name;
public StringBuilder version;
@Override
public void startDocument() throws SAXException {
id = new StringBuilder();
name = new StringBuilder();
version = new StringBuilder();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
//记录当前节点
nodeName = localName;
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
//根据当前的节点名判断将内容添加到哪一个StringBuilder对象中
if ("id".equals(nodeName)){
id.append(ch, start, length);
}else if ("name".equals(nodeName)){
name.append(ch, start, length);
}else if ("version".equals(nodeName)){
version.append(ch, start, length);
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if ("app".equals(localName)){
// !!!!!!!!!!!
//idnameversion中可能包含回车或换行符,需调用trim()方法除去
// !!!!!!!!!!!
Log.d("ContentHandler", "id is " id.toString().trim());
Log.d("ContentHandler", "name is " name.toString().trim());
Log.d("ContentHandler", "version is " version.toString().trim());
//最后要将StringBuilder清空,避免影响下一次内容读取
id.setLength(0);
name.setLength(0);
version.setLength(0);
}
}
@Override
public void endDocument() throws SAXException {
super.endDocument();
}
}
修改MainActivity:
代码语言:javascript复制@Override
public void onClick(View v) {
if(v.getId() == R.id.send_request){
// sendRequestWithHttpURLConnection();
sendRequestWithOkHttp();
}
}
private void sendRequestWithOkHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient();
//http://10.0.2.2/对于模拟器来说是电脑本机IP地址**
Request request = new Request.Builder()
.url("http://47.100.78.251/testPackage/get_data.xml")
.build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
// parseXMLWithPull(responseData);//解析服务器返回的数据**
parseXMLWithSAX(responseData);//解析服务器返回的数据**
// showResponse(responseData);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
private void parseXMLWithSAX(String xmlData) {
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
XMLReader xmlReader = factory.newSAXParser().getXMLReader();
ContentHandler handler = new ContentHandler();
//将ContentHandler的实例设置到XMLReader中
xmlReader.setContentHandler(handler);
//开始执行解析
xmlReader.parse(new InputSource(new StringReader(xmlData)));
} catch (Exception e) {
e.printStackTrace();
}
}
运行程序,点击按钮,查看日志:
除了Pull和SAX,还有DOM解析方式可用;
4.解析JSON数据
- JSON的体积比XML更小,网络传输更省流量, 但语义性差,不如XML直观。
- 在对应的服务器目录下,新建一个get_data.json文件,内容如下:
[{"id":"5","name":"Clash of Clans","version":"5.5"},
{"id":"6","name":"Boom Beach","version":"7.0"},
{"id":"7","name":"Clash Royale","version":"3.5"}]
4.1使用JSONObject
- 解析JSON数据也有很多方法,可使用官方的JSONObject, 谷歌的开源库GSON, 或第三方的开源库如Jackson、FastJSON等.
- 我们在服务器中定义的json文件get_data.json的内容是一个JSON数组, 因此这里获取到服务器的数据之后, 直接将数据传入到一个JSONArray对象中;
- 然后循环遍历这个JSONArray, 从中取出的每一个元素都是一个JSONObject对象;
- 这个JSONObject对象又会包含id、name和version这些数据, 即我们定义的json文件中的键值;
- 接着只要调用getString()将这些数据取出来即可;
使用JSONObject,修改MainActivity:
代码语言:javascript复制@Override
public void onClick(View v) {
if(v.getId() == R.id.send_request){
// sendRequestWithHttpURLConnection();
sendRequestWithOkHttp();
}
}
//子线程中进行!!!!!!!!!
private void sendRequestWithOkHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient();
//http://10.0.2.2/对于模拟器来说是电脑本机IP地址**
Request request = new Request.Builder()
.url("http://47.100.78.251/testPackage/get_data.json")
.build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
parseJSONWithJSONObject(responseData);
// parseXMLWithPull(responseData);//解析服务器返回的数据**
// parseXMLWithSAX(responseData);//解析服务器返回的数据**
// showResponse(responseData);
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
}
}).start();
}
private void parseJSONWithJSONObject(String responseData) throws JSONException {
//我们在服务器中定义的json文件get_data.json的内容是一个JSON数组
JSONArray jsonArray = new JSONArray(responseData);
responseText.setText("");
for (int i = 0; i < jsonArray.length(); i ) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
String id = jsonObject.getString("id");
String name = jsonObject.getString("name");
String version = jsonObject.getString("version");
runOnUiThread(new Runnable() {
@Override
public void run() {
responseText.append("MainActivity: id is " id "n");
responseText.append("MainActivity: name is " name "n");
responseText.append("MainActivity: version is " version "n");
}
});
}
}
运行程序:
4.2 使用GSON
- 添加依赖:
implementation 'com.google.code.gson:gson:2.8.5'
- 它主要可以将一段JSON格式的字符串自动映射成一个对象(定义一个类对应), 不需手动编写代码解析。
- 比如说一段json格式的数据如下所示:
{"name":"Tom","age":20}
则定义一个Person类, 加入name和age这两个字段;
面向**
需要解析的json数据
**定义**JavaBean类
**, 如果一个**json数据
**有十几几十个**键值对
**, 而我们的**业务只需要
**取其中的**5个键值
**, 那这个**JavaBean类
**,就定义需要的**5个字段
**即可,Gson
**会将**json数据字符串
**,**根据
**我们**定义
**的**JavaBean类
**,**提取
**出相应的数据并**映射
**成**对应的List
**;**json字符串
**中有多少套**JavaBean类字段
**对应的**键值
**,** 映射得到的**List
**的**size
**就有多少;
- 接着简单调用如下代码即可将JSON数据
自动解析成
一个Person对象
了:
Gson gson = new Gson();
Person person = gson.fromJson(jsonData, Person.class);
- 如果需要解析的是
一段JSON数组
会稍微麻烦一点, 需要借助TypeToken
将期望解析成的数据类型传入到fromJson()
方法中,如:List<Person> people = gson.fromJson(jsonData, new TypeToken<List<Person>>(){}.getType());
基本用法就如上所述了,
接下来用GSON解析一下一开始的数据;
- 首先新建一个App类:
public class App {
private String id;
private String name;
private String version;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
修改MainActivity:
代码语言:javascript复制 @Override
public void onClick(View v) {
if(v.getId() == R.id.send_request){
// sendRequestWithHttpURLConnection();
sendRequestWithOkHttp();
}
}
//!!!!!!!!
//注意这里在子线程中进行!!
// UI更新需要切到主线程
// !!!!!!!
private void sendRequestWithOkHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient();
//http://10.0.2.2/对于模拟器来说是电脑本机IP地址**
Request request = new Request.Builder()
.url("http://47.100.78.251/testPackage/get_data.json")
.build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
// parseJSONWithJSONObject(responseData);
parsJSONWithGSON(responseData);
// parseXMLWithPull(responseData);//解析服务器返回的数据**
// parseXMLWithSAX(responseData);//解析服务器返回的数据**
// showResponse(responseData);
} catch (IOException e) {
e.printStackTrace();
}
// catch (JSONException e) {
// e.printStackTrace();
// }
}
}).start();
}
private void parsJSONWithGSON(String jsonData) {
Gson gson = new Gson();
List<App> appList = gson.fromJson(jsonData, new TypeToken<List<App>>(){}.getType());
for (App app : appList) {
runOnUiThread(new Runnable() {
@Override
public void run() {
responseText.append("MainActivity: id is " app.getId() "n");
responseText.append("MainActivity: name is " app.getName() "n");
responseText.append("MainActivity: version is " app.getVersion() "n");
}
});
}
}
运行程序,效果同上。
5.网络编程的最佳实践
- (**
方法提取
**) 应用程序很可能会在许多地方都使用网络功能, 而发送HTTP请求的代码基本相同, 所以我们不能每次都去编写一遍发送HTTP请求的代码, 通常应该把通用的网络操作提取到一个公共类里, 并提供一个静态方法, 当想要发起网络请求的时候, 只需要简单地调用一下这个方法即可。
(**耗时操作
**)
另外,
网络请求通常都是属于**耗时操作
**,
我们提取的发送HTTP请求的方法内部
如果没有开启子线程,
则有可能导致在调用的时候使得**主线程阻塞
**,
这里则需**开启子线程
**来发起HTTP请求,
(**数据返回
**)
另外还要考虑到,
如果我们在一个**请求方法
**内部的
开启了一个子线程来**发送HTTP请求,
**
那**服务器响应的数据
**是无法进行返回的,
所有的**耗时逻辑
**都是在**子线程
**里进行的,
这个**请求方法
**会在服务器**还没来得及响应
**的时候就**执行结束
**了,
当然也就**无法返回响应的数据
**了;
- 遇到这种既需要**
子线程
**来处理**耗时操作
**, 又要求能**实时接收到服务器响应到的数据
**的情况, 可以考虑使用**Java的回调机制
**来实现:
- 实现一个接口就是写一个插座, 把封装的东西写进实现接口的类中, 把这个(匿名内部)类赋给回调方法(如setOnClickListener())
- 内部**
抽象调用
**,外部**具体实现
**(的方法);内部
**只管**调用
**,**外部
**只管**实现
**!** - 连接处:
外部实现
**的方法,** 封装在一个匿名内部类**接口实现类实例
**中, 将**实例
**传给**抽象调用的工具类
**的**设置方法
**或者**构造方法
**, 实现**内外连接
**; - 首先需要定义一个接口,这里取名HttpCallbackListener:
onFinish(String response)
当服务器成功响应请求时调用,参数为服务器返回的数据;onError(Exception e)
当进行网络操作出现错误时调用,参数记录错误的详细信息;
public interface HttpCallbackListener {
void onFinish(String response);
void onError(Exception e);
}
- 接着新建一个刚刚说的放着提取了
通用网络操作
的公共类
:listener.onFinish(response.toString());
回调**外部传进来
**的**写好
**的匿名内部接口类
**的**具体实现好
**的方法,** 这里**公共类
**中**抽象调用
**,调用公共类方法的地方
**则**具体实现接口类
**(常用**匿名内部类
**方式实现),** 实现好了**赋到这里来
**;
public class HttpUtil {
public static void sendHttpRequest( final String address, final HttpCallbackListener listener){
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
try {
URL url = new URL(address);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
connection.setDoInput(true);
connection.setDoOutput(true);
InputStream in = connection.getInputStream();
//对获取到的输入流进行读取
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null){
response.append(line);
}
if (listener != null){
//回调onFinish方法
listener.onFinish(response.toString());
}
} catch (Exception e) {
if (listener != null){
//回调onError方法
listener.onError(e);
}
}finally {
if (connection != null){
connection.disconnect();
}
}
}
}).start();
}
}
sendHttpRequest()
注意点:
- `HttpCallbackListener`参数;
- 内部开启子线程,子线程中进行具体的网络操作;
代码语言:javascript复制String address = "http://www/baidu.com";
HttpUtil.sendHttpRequest(address, new HttpCallbackListener(){
@Override
public void onFinish(String response){
//在这里根据返回内容执行具体的逻辑
}
@Override
public void onError(Exception e){
//在这里对异常情况进行处理
}
});
- 使用HttpURLConnection的写法总体比较复杂; 使用OkHttp会简单一些;
- 在HttpUtil中加入一个
sendOkHttpRequest()
方法:
public static void sendOkHttpRequest(String address, okhttp3.Callback callback) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(address)
.build();
client.newCall(request).enqueue(callback);
}
- 注意:
方法中有一个**
okhttp3.Callback
**参数, 这个是**OkHttp
**库中自带的一个回调接口, 类似于我们刚刚自己编写的**HttpCallbackListener
**;
然后在**client.newCall()
**之后,
没有像之前那样直接调用**execute()
**,
而是调用了**enqueue()
**,
并把**okhttp3.Callback
**参数传入,
OkHttp
**在**enqueue()
**中已经帮我们开好了**子线程
**,**
在子线程中去执行**HTTP请求
**,
并将最后的请求结果回调到**okhttp3.Callback
**,
(也就是说,
我们刚刚在**sendHttpRequest()
**做的事情,
子线程、请求、数据返回
**,**
OkHttp都帮我们做好了)
最后,
我们在外部实例化一个**接口对象
**并具体实现方法,
再把接口实例传进来**sendOkHttpRequest()
**,
赋值给对应的**enqueue()
**方法,
完成任务!
参考自《第一行代码》