Android学习(九)
WebView 的使用方法
创建一个WebViewDemo工程 并编辑activity_main的布局文件如下。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" > <WebView android:id="@+id/web_view" android:layout_width="match_parent" android:layout_height="match_parent"></WebView> </LinearLayout>
修改MainActivity中的代码如下
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("https://www.baidu.com"); //必须加上 schema } }
首先使用findViewById()获得了WebView的实例,然后调用WebView的 getSettings()方法去设置一些浏览器的属性,。这里设置的只有setJavaScriptEnable()方法让WebView支持JavaScript脚本,
接下来调用了WebView的setWebViewClient()方法,并传了一个WebViewClient的实例,他的作用是,当需要从一个网页跳转到另一个网页的时候仍然在当前WebView中显示。而不是打开系统的浏览器。
最后一步就是调用WebView的loadUrl()方法并将网址传入就可以展示相应的网页的内容。声明权限
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
使用HttpURLConnection
首先要先获取到HTTPURLConnection的实例,一般只需要new出一个URL对象,并传入目标的网络地址,然后调用一下openConnection()方法就可以了。
URL url = new URL("http://www.baidu.com"); HttpURLConnection connection = (HttpURLConnection)url.openConnection();
在得到了HttpURLConnection实例后,我们可以设置一下HTTP请求所使用的方法。 主要由两个 get 和 post
connection.setRequestMethod("GET");
接下来就可以进项一些自由的定制了,比如说设置连接超时,读取超时,以及服务器希望得到的一些消息头。
connection.setReadTimeout(8000); connection.setConnectTimeout(8000);
之后再调用getInputStream()方法就可以获取到服务器返回的输入流了。剩下的任务就是对输入流进行读取。
InputStream in = connection.getInputStream();
- 最后调用disconnect()方法将这个http连接关闭。
例子:
创建一个HttpURLConnectionDemo项目,
编辑activity_main布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/btn_send_request" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="发送请求" /> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/tv_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!"/> </ScrollView> </LinearLayout>
修改MainActivity中的代码如下。
public class MainActivity extends AppCompatActivity { TextView tvText; Button btnSendRequest; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnSendRequest = (Button) findViewById(R.id.btn_send_request); tvText = (TextView)findViewById(R.id.tv_text); btnSendRequest.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendRequestWithHttpURLConnection(); } }); } public void sendRequestWithHttpURLConnection(){ //开启新线程来发起网络请求 new Thread(new Runnable() { @Override public void run() { HttpURLConnection connection = null; BufferedReader reader = null; try { URL url = new URL("https://www.baidu.com"); //如果访问百度的话 http 访问不到 必须是https connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setConnectTimeout(8000); connection.setReadTimeout(8000); InputStream in = connection.getInputStream(); //对获取的输入流进行读取 reader = new BufferedReader(new InputStreamReader(in)); StringBuilder response = new StringBuilder(); char [] buf = new char[1024]; int len; while((len = reader.read(buf,0,1024)) != -1){ response.append(buf); } showResponse(response.toString()); } catch (MalformedURLException e) { e.printStackTrace(); } 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() { //在这里进行UI操作,将结果显示到界面上。 tvText.setText(response); } }); } }
在按钮的点击事件里面调用了sendRequestWithHttpURLConnection()方法,我们在这个方法中开启了一个子线程,然后在子线程中使用HttpURLConnection发出一条http请求,然后用BufferedReader对服务器返回的流进行读取,并将结果传入了showResponse()方法中,而在showResponse()方法里则是调用了runOnUIThread()方法,然后在这个方法的匿名类参数中进行操作,这是因为Android中不允许在子线程中进行UI操作,我们需要通过这个方法将线程切换到主线程,然后再更新Ui元素。
声明使用网络的权限。
<uses-permission android:name="android.permission.INTERNET"/>
使用OkHttp
使用OkHttp之前需要在项目中添加OkHttp的依赖编辑app/build.gradle文件,在dependencies闭包中添加
implementation 'com.squareup.okhttp3:okhttp:3.12.0'
以上依赖会自动下载两个库 一个是OkHttp 还有一个是Okio 后者是前者的通信基础
具体用法:
创建一个OkHttpClient的实例,
OkHttpClient client = new OkHttpClient();
如果想要发起一条http请求就要创建一个Request对象:
Request request = new Request.Builder() .build();
这样就创建了一个空的Request对象,并没有什么实际的作用,我们可以再最终的build()方法之前连缀很对其他方法来丰富这个Request对象,比如可以通过url()方法来设置目标的网络地址
Request request = new Request.Builder() .url("http://www.baidu.com") .build();
然后调用OkHttpClient的newCall()方法来创建一个Call对象,并调用他的execute()方法来发送请求并获取服务器返回的数据
Response response = client.newCall(request).execute();
其中Response对象就是服务器返回的数据,我们可以使用如下写法来的到返回的具体内容,。
String responseData = response.body().string();
如果发起的是一条POST请求的化就会比get请求稍微复杂一点我们需要先构建出来一个RequestBody对象来存放待提交的参数如下
RequestBody requestBody = new FormBory.Builder() .add("username","123") .add("passwd","123") .build();
然后在RequestBuilder中调用一下post()方法,并将RequestBody对象传入
Request request = new Request.Builder() .url("http://www.baidu.com") .post(requestBody) .build();
例子:
创建一个OkHttpDemo
编辑activity_main布局文件。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/btn_send_request" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="发送请求" /> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/tv_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!"/> </ScrollView> </LinearLayout>
编辑MainActivity中的代码如下;
public class MainActivity extends AppCompatActivity { TextView tvText; Button btnSendRequest; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnSendRequest = (Button) findViewById(R.id.btn_send_request); tvText = (TextView)findViewById(R.id.tv_text); btnSendRequest.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendRequestWithOkHttp(); } }); } public void sendRequestWithOkHttp(){ new Thread(new Runnable() { @Override public void run() { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("http://www.baidu.com") .build(); try { 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() { //在这里进行UI操作,将结果显示到界面上。 tvText.setText(response); } }); } }
解析XML格式的数据
Pull解析方式
开启本机服务器并新建一个get_data.xml文件内容如下。将这个文件放入服务器根目录 使用
http://127.0.0.1:8080/get_data.xml
能访问到这个文件即可。<?xml version="1.0" encoding="utf-8"?> <apps> <app> <id>1</id> <name>google maps</name> <version>1.0</version> </app> <app> <id>2</id> <name>google Chrome</name> <version>2.1</version> </app> <app> <id>3</id> <name>google play</name> <version>2.3</version> </app> </apps>
创建PullPaserDemo 并 修改activity_main中的代码如下。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/btn_send_request" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="发送请求" /> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/tv_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!"/> </ScrollView> </LinearLayout>
修改MainActivity中的代码如下
public class MainActivity extends AppCompatActivity { TextView tvText; Button btnSendRequest; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnSendRequest = (Button) findViewById(R.id.btn_send_request); tvText = (TextView)findViewById(R.id.tv_text); btnSendRequest.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendRequestWithOkHttp(); } }); } public void sendRequestWithOkHttp(){ new Thread(new Runnable() { @Override public void run() { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("http://10.0.2.2:8080/get_data.xml")//指定访问的服务器地址是电脑本机 .build(); try { Response response = client.newCall(request).execute(); String responseData = response.body().string(); Log.i("MainActivity", "run: "+responseData); parseXMLWithPull(responseData);//使用pull解析 showResponse(responseData); } catch (IOException e) { e.printStackTrace(); } } }).start(); } public void parseXMLWithPull(String xmlData){ try { XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); XmlPullParser xmlPullParser = factory.newPullParser(); xmlPullParser.setInput(new StringReader(xmlData)); int eventType = xmlPullParser.getEventType(); String id = ""; String name = ""; String version = ""; while(eventType != 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)){ Log.d("MainActivity", "id = "+id); Log.d("MainActivity", "name = "+name); Log.d("MainActivity", "version = "+version); } } eventType = xmlPullParser.next(); } } catch (XmlPullParserException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } private void showResponse(final String response){ runOnUiThread(new Runnable() { @Override public void run() { //在这里进行UI操作,将结果显示到界 tvText.setText(response); } }); } }
这里首先将HTTP请求的地址改成了http://10.0.2.2/get_data.xml 因为10.0.2.2这个地址对于模拟器来说就是电脑本机的ip地址,在得到了服务器返回的数据之后,我们调用了parserXMLWithPull()方法来解析服务器返回的数据
首先要获取到一个XMLPullParserFactory的实例, 并借助这个实例得到一个XmlPullParser对象,然后调用这个对象的setInput()方法将服务器返回的XML数据设置进去,就可以开始解析了,。解析的过程很简单通过getEventTypoe()可以得到当前的解析事件,然后一个while循环,不断地进行解析,如果当前解析事件不等与XmlPullParser.END_COCUMENT
,说名解析工作还没有完成,就调用next()方法获取下一个解析事件
在while循环中,通过调用getName()方法得到当前节点的名字, 如果发现节点名是id、name、version就调用nextText()方法来获取节点内具体的内容, 每当解析玩一个app节点后,就将获取到的内容打印出来,
SAX解析
Pull解析方式虽然非常好用 ,但这并不是我们的唯一选择, SAX解析也是一种特别常用得XML解析方式,虽然他的用法比Pull解析要复杂一点,但在语义方面会更加侵袭。
如果要使用SAX解析方式通常我们都会新建一个类继承自DefaultHandler,并重写他的五个方法
public class MyHandler extends DefaultHandler {
@Override
public void startDocument() throws SAXException {
// 该方法会在开始XML解析的时候调用。
super.startDocument();
}
@Override
public void endDocument() throws SAXException {
// 会在结束解析文档的时候调用
super.endDocument();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
// 会在开始解析某个节点的时候调用
super.startElement(uri, localName, qName, attributes);
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
// 会在结束解析某个节点的时候调用
super.endElement(uri, localName, qName);
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
// 会在获取节点内用的时候调用。
super.characters(ch, start, length);
}
}
其中startElement() 、 characters() 、 endElement()这三个方法是有参数的,从xml解析出来的数据就会以参数的形式传入到这些方法中,需要注意的是,在获取节点中的内容时候 ,characters()方法可能会被调用多次, 一些换行符也被当做内容被解析出来,我们需要针对这种情况在代码中做好控制。
例子:
尝试使用SAX解析的方式实现和Pull解析一样的功能。
新建一个ContentHandler类继承自DefaultHandler,并重写父类的5个方法。如下。
public class ContentHandler extends DefaultHandler { public static final String TAG = "ContentHandler"; private String nodeName; private StringBuilder id; private StringBuilder name; private StringBuilder version; @Override public void startDocument() throws SAXException { // 该方法会在开始XML解析的时候调用。 super.startDocument(); id = new StringBuilder(); name = new StringBuilder(); version = new StringBuilder(); } @Override public void endDocument() throws SAXException { // 会在结束解析文档的时候调用 super.endDocument(); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { // 会在开始解析某个节点的时候调用 super.startElement(uri, localName, qName, attributes); nodeName = localName; } @Override public void endElement(String uri, String localName, String qName) throws SAXException { // 会在结束解析某个节点的时候调用 super.endElement(uri, localName, qName); if("app".equals(localName)){ Log.d(TAG, "id = "+ id ); Log.d(TAG, "name = "+ name ); Log.d(TAG, "version = "+ version ); // 最后将StringBuilder中的内容清空掉 id.setLength(0); name.setLength(0); version.setLength(0); } } @Override public void characters(char[] ch, int start, int length) throws SAXException { // 会在获取节点内容的时候调用。 super.characters(ch, start, length); 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); } } }
这里我们先给三个节点分别定义了一个StringBuilder对象, 然后在startDocument()方法中对他们进行了初始化, 每当开始解析某个节点的时候, startElement()方法就会得到调用, 其中localName参数记录这当前节点的名字。 这里我们把他记录下来, 接着在解析节点中具体内容的时候就会调用characters()方法。我们会根据当前的节点名进行判断, 将解析出来的内容添加到哪一个StringBuilder对象中,最后在endElement()方法中进行判断如果App节点已经解析完成就淡印出来其中三个节点的内容,需要注意的是目前id name version中可能都是包括回车和换行的 因此在打印之前我们还需要调用一下trim方法并打印完成后还要将StringBuilder中的内容清空掉。不然会影响到下一次打印的结果。
接下来就是修改MainActivity中的代码如下
public class MainActivity extends AppCompatActivity { public static final String TAG = "MainActivity"; private Button btnSendRequest; private String url = "http://10.0.2.2:8080/get_data.xml"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnSendRequest = (Button)findViewById(R.id.btn_send_request); btnSendRequest.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendRequestWithOkHttp(url); } }); } public void sendRequestWithOkHttp(final String url){ new Thread(new Runnable() { @Override public void run() { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url(url) .build(); try { Response response = client.newCall(request).execute(); String responseData = response.body().string(); parseXMLWithSAX(responseData); } catch (IOException e) { e.printStackTrace(); } } }).start(); } public void parseXMLWithSAX(String xmlData){ Log.d(TAG, "parseXMLWithSAX: "); SAXParserFactory factory = SAXParserFactory.newInstance(); try { XMLReader xmlReader = factory.newSAXParser().getXMLReader(); ContentHandler handler = new ContentHandler(); //将handler的实例设置到XMLReader中 xmlReader.setContentHandler(handler); xmlReader.parse(new InputSource(new StringReader(xmlData))); } catch (SAXException e) { e.printStackTrace(); } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
在得到了服务器返回的数据之后, 我们去调用parseXMLWithSAX()方法来解析XML数据,这个方法中创建了一个SAXParserFactory对象,然后在获取到XMLReader对象,接着将我们编写的ContentHandler的实例设置到XMLReader中,最后调用parse()方法开始执行解析就好嘞。
最后不要忘了在ANdroidManifest中申请网络的权限。
<uses-permission android:name="android.permission.INTERNET" />
解析JSON格式的数据,
在已经掌握了xml解析数据的解析方式, 那么接下来我们要学习如何解析json格式的数据, 比起xml json的主要优势在于他的体积更小,在网络上传输可以节省流量,但缺点是,他的语义较差,不如xml直观
在开始之前 我们在服务器的根目录中创建一个get_data.json文件,然后编辑内容如下
[{"id":"5","version":"5.5","name":"Clash of Clans"},
{"id":"6","version":"7.0","name":"Boom Beach"},
{"id":"7","version":"3.5","name":"Clash Royale"}]
在浏览器中访问 http://127.0.0.1:8080/get_data.json
这个地址能访问到内容即可。
使用JSONObject方式解析json数据
类似的解析Json数据也有很多方法,可以使用官方提供的JSONObject,也可使用谷歌开源库GSON。另外一些第三方的开源库Jackson、FastJSON也非常不错。
修改MainActivity中的代码如下
public class MainActivity extends AppCompatActivity { public static final String TAG = "MainActivity"; private static String url = "http://10.0.2.2:8080/get_data.json"; private Button btnSendRequest; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnSendRequest = (Button) findViewById(R.id.btn_send_request); btnSendRequest.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendRequestWithOkHttp(url); } }); } public void sendRequestWithOkHttp(final String url){ new Thread(new Runnable() { @Override public void run() { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url(url) .build(); try { Response response = client.newCall(request).execute(); String responseData = response.body().string(); Log.d(TAG, "run: "+responseData); parseJSONWithJSONObject(responseData); } catch (IOException e) { e.printStackTrace(); } } }).start(); } public void parseJSONWithJSONObject(String jsonData){ try { JSONArray jsonArray = new JSONArray(jsonData); 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"); Log.d(TAG, "id = "+id); Log.d(TAG, "name = "+name); Log.d(TAG, "version = "+version); } } catch (JSONException e) { e.printStackTrace(); } } }
这里在我们得到了服务器返回的数据后调用了parseJSONWithJSONObject()方法来解析数据,我们将服务器返回的数据传入到了一个JSONArray对象中,然后循环遍历这个JSONArray从中去除的每一个元素都是一个JSONObject对象,每个JSONObject对象又会包含id name version 这些数据,接下来只需要调用getString()方法将这些数据取出来就可以了。
对了 别忘了声明使用网络的权限
使用GSON解析Json数据
如果想要使用GSON方法解析Json数据需要添加GSON库的依赖,编辑app/build.gradle文件,在dependencies闭包中添加如下依赖。
implementation 'com.google.code.gson:gson:2.8.5'
GSON可以将一段json格式的字符串自动映射成一个对象。而不需要手动编写代码去解析了。
比如说有一段json数据如下
{"name":"TOM", "age":"20"}
那么 我们就要可以定义一个Person类, 并加入name和age这两个字段,然后只需要简单的调用
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)
我们首先来新增一个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中的代码如下
public class MainActivity extends AppCompatActivity { public static final String TAG = "MainActivity"; public static String url = "http://10.0.2.2:8080/get_data.json"; private Button btnSendRequest; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnSendRequest = (Button)findViewById(R.id.btn_send_request); btnSendRequest.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendRequestWithOkHttp(url); } }); } public void sendRequestWithOkHttp(final String url){ new Thread(new Runnable() { @Override public void run() { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url(url) .build(); try { Response response = client.newCall(request).execute(); String responseData = response.body().string(); parseJSONWithGSON(responseData); } catch (IOException e) { e.printStackTrace(); } } }).start();; } public void parseJSONWithGSON(String jsonData){ Gson gson = new Gson(); List<App> appList = gson.fromJson(jsonData, new TypeToken<List<App>>(){}.getType()); for(App app : appList){ Log.d(TAG, "id: " + app.getId()); Log.d(TAG, "version: " + app.getVersion()); Log.d(TAG, "name: " + app.getName()); } } }
网络变成的最佳实践。
通常情况下我们都应该将这些通用的网络操作提取到一个公共的类中, 并提供一个静态方法, 当想要发起网络请求的时候,我们只需要简答的调用一下这个方法即可比如使用如下写法。
public class HttpUtil {
public static String sendHttpRequest(String address){
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);
}
return response.toString();
} catch (MalformedURLException e) {
e.printStackTrace();
return e.getMessage();
} catch (IOException e) {
e.printStackTrace();
return e.getMessage();
}finally {
if(connection != null){
connection.disconnect();
}
}
}
}
但是,需要注意的是网络请求通常是属于耗时操作,而sendHttpRequest()方法并没有内部开启线程,这样可能在调用这个方法时使得主线程阻塞。
如果在子线程中发起http请求那么服务器相应的数据是无法返回的,所有的耗时任务都是在子线程中进行的,sendHttpRequest()方法会在服务器还没来得及相应的时候就执行结束了,当然也就无法返回数据了。
遇到这种清空的时候我们应该利用Java的回调机制,
首先我们要定义一个接口, 命名成HttpCallbackListener 代码如下。
public interface HttpCallbackListener {
void onFinish(String response);
void onError(Exception e);
}
我们在这个接口中定义了两个方法, 这两个方法分别在服务器成功相应的时候调用和相应错误的时候调用, 然后我们修该HttpUtil中的代码如下。
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 (MalformedURLException e) {
e.printStackTrace();
if(listener != null){
// 回调用onError方法
listener.onError(e);
}
} catch (IOException e) {
e.printStackTrace();
if(listener != null){
// 回调用onError方法
listener.onError(e);
}
}finally {
if(connection != null){
connection.disconnect();
}
}
}
}).start();
}
}
这样的话 当服务器相应的时候就可以在onFinish()方法里对数据进行处理,如果出现了异常就可以在onError中进行处理。
HttpUtil.sendHttpRequest("http://www.baidu.com", new HttpCallbackListener() {
@Override
public void onFinish(String response) {
// 成功返回的结果
}
@Override
public void onError(Exception e) {
// 发生异常执行
}
});
不过在OkHttp中会简单一点,
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);
}
在sendOkHttpRequest方法中有一个okhttp3.Callback 参数,这个是Okhttp自带的一个回调接口,类似于刚才自己些的那个回调接口一样。然后在newCall()方法之后调用了enqueue()方法 而不是 execute()方法。
在enqueue()方法中内部已经帮我们开好子线程了。然后会在子线程中去执行http请求,并将最后的请求结果回调到okhttp3.Callback参数中,
在调用sendOkHttpRequest()方法时候可以这样写。
HttpUtil.sendOkHttpRequest("http://www.baidu.com", new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 在这里处理异常情况。
}
@Override
public void onResponse(Call call, Response response) throws IOException {
// 得到服务器返回的具体内容
String responseData =response.body().string();
}
});
需要注意的是不管使用HttpURLConnection还是OkHttp,最终的回调接口都还是在子线程中运行的,因此我们还不可以在这里执行任何UI操作,除非借助runOnUoThread()方法来进行线程转换。