网络技术
WebView的用法
WebView的用法也是相当简单的,新建一个WebViewTest项目,然后修改activity_main.xml中的代码,如下
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/web_view" />
</LinearLayout>
这里就可以看出,我们在布局文件中使用到了一个新的控件:WebView.这个控件当然也就是用来显示网页的,并给他设置了一个id,让他充满整个屏幕.
然后修改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中显示,而不是打开系统浏览器
webView.loadUrl("http://www.baidu.com");//将网址传入,展示内容
}
}
//详解
首先使用findViewById()方法获取到了WebView的实例,然后调用WebView的getSettings()方法去设置一些浏览器的属性,这里只是调用了setJavaScriptEnabled()方法来让WebView支持JavaScript脚本
另外需要注意,本程序使用到了网络功能,而访问网络是需要声明权限的,因此修改AndroidMainfest.xml文件,并加入权限声明,如下:
<uses-permission android:name="android.permission.INTERNET"/>
使用HTTP协议访问网络
工作原理:就是客户端向服务器发出一条HTTP协议,服务器收到请求之后会返回一些数据给客户端,然后客户端在对这些数据进行解析和处理就可以了.接下来我们就可以通过手动发送HTTP请求的方式,来更加深入地理解过程
使用HTTPURLConneciton
首先需要获取到HttpURLConnection的实例,一般只需要new出一个URL对象,并传入目标的网络地址,然后调用openConnection()方法即可,如下:
URL url = new URL("http://www.baidu.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
在得到了HttpURLConnection的实例之后,可以设置一下HTTP请求所使用的方法,常用的方法主要有两个:GET和POST.GET表示希望从服务器那里获取数据,而POST则表示希望提交数据给服务器,写法如下:
connection.setRequestMethod("GET");
接下来就可以进行一些自由的定制了,比如设置连接超时,读取超时的毫秒数,以及服务器希望得到的一些消息头等,示例写法如下:
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
之后在调用getInputStream()方法就可以获取到服务器返回的输入流了,剩下的任务就是对输入流进行读取,如下:
InputStream in = connection.getInputStream();
最后可以调用disconnnect()方法将这个HTTP连接关闭掉,如下:
connection.disconnect();
通过一个例子,来体会用法:
首先修改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">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/send_request"
android:text="Send Request"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/response_text" />
</ScrollView>
</LinearLayout>
//这里的ScrollView控件,就是可以以滚动的形式查看屏幕外的那部分内容,其中Button用于发送HTTP请求,TextView用于将服务器返回的数据显示出来
接着修改MainActivity中的代码,如下
public class MainActivity extends AppCompatActivity {
TextView responseText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
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){
sendRequestWithHttpURLConneciton();
}
}
private void sendRequestWithHttpURLConneciton() {
//开启线程发起网络请求
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
BufferedReader reader = null;
try{
URL url = new URL("https://www.baidu.com");
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();
String line;
while((line = reader.readLine()) != null){
response.append(line);
}
showResponse(response.toString());
}catch (Exception 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操作,将结果显示到界面上
responseText.setText(response);
}
});
}
}
//注解:在Send Request按钮中的点击事件里调用了sendRequestWithHttpURLConnection()方法,在这个方法中先是开启了一个子线程,然后再子线程里使用HttpURLConnection发出一条HTTP请求,请求的目标是百度的首页.接着利用BufferedReader对服务器返回的流进行读取,并将结果传入到了showResponse()方法中.而在showResponse()方法里则是调用了一个runOnUiThread()方法,然后在这个方法的匿名类参数中进行操作,将返回的数据显示到界面上,这里为什么要使用runOnUiThread()方法呢?这是因为Android是不允许在子线程进行UI操作的,我们需要通过这个方法将线程切换到主线程,然后更新UI元素
怎样将数据提交给服务器呢?只需要将HTTP请求的方法改成POST,并在获取输入流之前把要提交的数据写出即可.注意每条数据都要以键值对的形式存在,数据与数据之间用"&"隔开,比如我们向服务器提交用户名和密码,就可以写成:
connection.setRequestMethod("POST");
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.writeBytes("username=admin&password=123456");
使用OkHttp
在使用OkHttp之前,我们需要先在项目中添加OkHttp库的依赖
OkHttp的具体用法,首先需要创建一个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 FormBody.Builder().add("username","admin")
.add("password","123456")
.build();
然后在Request.Builder中调用一下post()方法,并将RequestBody对象传入:
Request request = new Request.Builder().url("http://www.baidu.com")
.post(requestBody)
.build();
接下来操作就和GET请求一样,调用execute()方法来发送请求并获取服务端返回的数据即可.
布局部分不用改,现在直接修改MainActivity中的代码
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
public void onClick(View view) {
if(view.getId() == R.id.send_request) {
sendRequestWithOkHttp();
}
}
private void sendRequestWithOkHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try{
Object OkHttpClient;
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("http://www.baidu.com")
.build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
showResponse(responseData);
}catch (Exception e){
e.printStackTrace();
}
}
}).start();
}
private void showResponse(final String response){
runOnUiThread(new Runnable() {
@Override
public void run() {
responseText.setText(response);
}
});
}
}
解析JSON格式数据
使用JSONObject解析
private void sendRequestWithOkHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try{
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("http://10.0.2.2/get_data.json").build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
parseJSONWithJSONObject(responseData);
}catch (Exception e){
e.printStackTrace();
}
}
}).start();
}
private 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("name");
String name = jsonObject.getString("name");
String verson = jsonObject.getString("verson");
Log.d("MainActivity","id is" + id);
Log.d("MainActivity","name is" + name);
Log.d("MainAcitivity","verson is" + verson);
}
}catch(Exception e){
e.printStackTrace();
}
}
//解析:在得到服务器返回的数据后调用 parseJSONWithJSONObject()方法来解析数据.可以看到,解析JSON的代码非常简单,由于我们在服务器定义的是一个JSON数组,因此这里首先是将服务器返回的数据传入到了一个JSONArray对象中,然后遍历这个JSONArray,从中取出的每一个元素都是一个JSONObject对象,每个JSONObject对象中又会包含id,name,和verson这些数据.接下来只需调用getString()方法将这些数据取出,并打印出来
使用GSON
首先要想使用GSON,要在项目里面添加GSON库的依赖.
GSON库的功能,就是主要将一段JSON格式的字符串自动映射成一个对象,从而不需要在手动进行编码了
例如:一段JSON格式的数据如下:
{"name":"Tom","age":20}
我们就可以定义一个Person类,并加入name和age这两个字段,然后就只需简单的调用如下代码就可以自动的将JSON数据自动解析成一个Person对象:
Gson gson = new Gson();
Person person = gson.fromJson(jsonData,Person.calss);
如果需要解析的是一段JSON数组较为麻烦的,我们需要借助TypeToken将期望解析成的数据传入到fromJson()方法中,例如:
List<Person> people = gson.fromJson(jsonData, new TypeToken<List<Person>>(){}.getType());
真正实现:首先新增一个App类,并加入id,name和verson这3个字段
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;
}
}
然后修改MainAcitivity中的代码
private void sendRequestWithOkHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try{
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("http://10.0.2.2/get_data.json").build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
parseJSONWithJSONObject(responseData);
}catch (Exception e){
e.printStackTrace();
}
}
}).start();
}
private void parseJSONWithJSONObject(String jsonData) {
Gson gson = new Gson();
List<App> appList = gson.fromJson(jsonData,new TypeToken<List<App>>(){}.getType());
for(App app: appList){
Log.d("MainActivity","id is"+ app.getId());
Log.d("MainActivity","name is"+ app.getName());
Log.d("MainActivity","verson is"+app.getVersion());
}
}
网络编程的最佳实践
结合实际情况,通常情况下我们都应该将这些通用的网络请求提取到一个公共的类里,并提供一个静态方法,当想要发起网络请求时,只需简单调用一下这个方法即可
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 (Exception e){
e.printStackTrace();
return e.getMessage();
}finally {
if(connection != null){
connection.disconnect();
}
}
}
}
以后每当发起一条HTTP请求的时候就可以这样写:
String address = "http://www.baidu.com";
String response = HttpUtil.sendHttpRequest(address);
但是这样写的问题是:sendHttpRequest()方法的内部并没有开启线程,这样就有可能导致在调用sendHttpRequest()方法的时候使得主线程被阻塞.
但是如果我们在sendHttpRequest()方法中开启一个线程来发起HTTP请求时,服务器响应的数据是无法进行返回的,所有的耗时逻辑都是在子线程里进行的,sendHttpRequest()方法会在服务器还没来得及相应的时候就执行结束了,这时就需要使用Java的回调机制了
回调机制
首先需要定义一个接口,命名成HttpCallbackListener,代码如下:
public interface HttpCallbackListener{
void onFinish(String response);
void onError(Exception e);
}
onFinish()方法表示当服务器成功响应时,我们请求成功的时候调用的,onError()表示当进行网络操作出现错误的时候调用.这两个方法都带有参数,onFinish()方法中的参数代表着服务器返回的数据,而onError()方法中的参数记录着错误的详细信息.
接着修改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 (Exception e) {
if (listener != null) {
//回调onError()方法
listener.onError(e);
}
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}).start();
}
}
注意,子线程中是无法通过return语句来返回数据的,因此这里我们将服务器响应的数据传入了HttpCallbackListener的onFinish()方法中,如果出现了异常就将异常原因传入到了onError()方法中.
现在sendHttpRequest()方法中接受了两个参数,因此在调用它的时候还需要将 HttpCallbackListener 的实例传入,如下:
HttpUtil.sendHttpRequest(address,new HttpCallbackListener(){
@Override
public void onFinish(String response){
//根据返回内容执行具体的逻辑
}
@Override
public void onError(Exception e){
//对异常情况进行处理
}
});
但是,使用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库中自带的一个回调接口;OkHttp在enqueue()方法的内部已经帮我们开好线程了,会在子线程中去执行HTTP请求,并将最终的请求结果回调到okhttp3.Callback当中.
我们在调用sendOkHttpRequest()方法的时候就可以这样写:
HttpUtil.sendOkHttpRequest("http://www.baidu.com",new okhttp3.Callback(){
@Override
public void onResponse(Call call,Response response) throw IOException{
//根据返回内容执行具体的逻辑
String responseData = response.body().string();
}
@Override
public void onFailure(Call call,IOException e){
//对异常情况进行处理
}
});