前言
本文用于记录Android网络技术的使用, 包括我们如何发起一条HTTP请求、解析XML、JOSN格式的数据以及最好用的网络库Retrofit。
使用HTTP协议访问网络
关于HTTP协议的工作原理,我们只需要知道客户端向服务器发起一条HTTP请求,服务器接收到请求之后会返回一些数据给客户端,然后客户端再对这些数据进行解析和处理。接下来我们将实现一下手动发送HTTP请求。
使用HttpURLConnection
我们先来大致了解一下使用HttpURLConnection发送HTTP请求的步骤:
- 获取HttpURLConnection实例,一般都是先new出一个URL对象并将目标网站传入,然后调用openConnection()方法即可获得实例
Url url=new URL("http://www.baidu.com"); HttpURLConnection connection=(HttpURLConnection) url.openConnection();
- 设置HTTP请求所使用的方法。常用方法有GET和POST。GET表示希望从服务器那获取数据,POST表示希望提交数据给服务器。
connection.setRequestMethod("GET");
- 一些其他设置,如设置连接超时、读取超时毫秒数、服务器希望得到的消息头等
- 调用getInputStream()方法获取服务器返回的输入流,接下来我们就可以对输入流进行读取
InputStream in=connection.getInputStream();
- 最后调用disconnect()方法将这个HTTP连接关闭
connection.disconnect();
实例:
向百度首页发起一条HTTP请求,返回它的HTML代码
- activity_main,包括发送请求的按钮和展示返回数据的文本框
- MainActivity
public class MainActivity extends AppCompatActivity implements View.OnClickListener { 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 view) { if(view.getId()==R.id.send_request){ sendRequestWithHttpURLConnection(); } } private void sendRequestWithHttpURLConnection(){ //开启线程发送网络请求 new Thread(new Runnable() { @Override public void run() { HttpURLConnection connection=null; BufferedReader reader=null; try { //向百度的首页发起一条HTTP请求 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){ //关闭HTTP连接 connection.disconnect(); } } } }).start(); } private void showResponse(final String response){ //因为Android不允许在子线程更新UI,所有我们使用runOnUiThread方法将线程切换为主线程 runOnUiThread(new Runnable() { @Override public void run() { //将结果显示到界面上 responseText.setText(response); } }); } }
这里主要注意一下showResponse()方法中的runOnUiThread()方法,它可以将线程由子线程切换为主线程。
- 最后别忘了申请一下网络权限
POST请求
想要发送POST请求,只需将setRequestMethod的参数改为POST,并在获取输入流之前把要提交的数据写出即可。例如我们要向服务器提交用户名和密码,可以写成:
connection.setRequestMethod("POST"); DataOutputStream out=new DataOutputStream(connection.getOutputStream()); //数据与数据之间用&分隔开 out.writeBytes("username=admin&password=123456);
使用OkHttp
OkHttp是一个可以代替原生HttpURLConnection发送HTTP请求的网络通信库。可用于简化我们发送HTTP的步骤。下面我们来简单梳理一下其基本步骤:
- 首先使用前必须添加其依赖
implementation ("com.squareup.okhttp3:okhttp:4.9.0")
- 创建一个OkHttpClient实例
OkHttpClient client=new OkHttpClient();
- 要想发送HTTP请求,我们需要创建一个Request对象(目标网站为百度首页),注意此时请求并未发送
Request request=new Request.Builder() .url("http://www.baidu.com") .build();
- 调用OkHttpClient的newCall()方法来创建一个Call对象并调用它的execute()方法来发送请求并获取服务器返回的数据
Response response=client.newCall(request).execute();
- 获取具体的返回内容
String responseData=response.body().string();
POST请求
- 我们需要先构建出一个RequestBody对象来存放待提交的数据
RequestBody requestBody=new FormBody.Builder() .add("username","admin") .add("password","123456") .build();
- 接着在构建出一个Request对象并将待提交的数据传入
Request request=new Request.Builder() .url("http://www.baidu.com") .post(requestBody) .build();
最后就和GET请求一样调用execute()方法来发送请求并获取服务器返回的数据即可。
实例
这里我们就直接在上面的代码进行改造,将上面点击按钮的sendRequestWithHttpURLConnection()方法换为:
sendRequestWithOkHttp();
接着实现这个方法:
//使用OkHttp发送请求 private void sendRequestWithOkHttp(){ new Thread(new Runnable() { @Override public void run() { try { //构建OkHttpClient实例 OkHttpClient client=new OkHttpClient(); //构建Request对象并将目标网址传入 Request request=new Request.Builder() .url("https://www.baidu.com") .build(); //调用execute()方法发送请求并接收返回的数据 Response response=client.newCall(request).execute(); //将返回的数据解析成字符串 String responseData=response.body().string(); //将返回的信息显示在屏幕上 showResponse(responseData); }catch (Exception e){ e.printStackTrace(); } } }).start(); }
解析XML格式数据
数据在网络上传输通常有两种方式XML和JSON,这两种数据分别具有其自己的结构规格和语义,当我们获取到这种数据时,我们要对其进行解析从而取出我们想要的那部分内容,接下来我们先来介绍Pull解析XML格式数据。
Pull解析方式
首先你要确保你已经下载好Tomcat并做好相关配置并将Tomcat启动起来,接下来我们来自定义一段XML格式的数据(这段数据我是定义在D:\Apache\apache-tomcat-8.5.95\webapps\AndroidStudy这个路径下的):
1 Google Maps 1.0 2 Chrome 2.1 3 Google Play 2.3
接下来我们可以打开浏览器看看是不是没问题,我的Tomcat端口是9000(可以在conf\server.xml文件中修改端口号),所以我输入localhost:9000/AndroidStudy/get_data.xml会显示出以下内容:
接下来我们就可以去对这些定义好的数据进行解析,我们接着在上面写好的代码上进行修改
- 首先构造Request对象时url我们要指定为访问本机IP地址,你打开控制台查一下本机电脑IP,如下所示
.url("http://192.168.??.???:9000/AndroidStudy/get_data.xml")
- 接着实现一个parseXMLWithPull对返回的数据进行解析
//Pull解析数据 private void parseXMLWithPull(String xmlData){ try { XmlPullParserFactory factory=XmlPullParserFactory.newInstance(); //使用XmlPullParserFactory来创建出XmlPullParser实例 XmlPullParser xmlPullParser=factory.newPullParser(); //将服务器返回的数据设置进XmlPullParser就可以开始进行解析了 xmlPullParser.setInput(new StringReader(xmlData)); //通过getEventType()就可以得到当前的解析事件 int eventType=xmlPullParser.getEventType(); String id=""; String name=""; String version=""; //!=XmlPullParser.END_DOCUMENT表示解析工作还未完成 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 is "+id); Log.d("MainActivity","name is "+name); Log.d("MainActivity","version is "+version); } break; } default: break; } eventType=xmlPullParser.next(); } }catch (Exception e){ e.printStackTrace(); } }
注意:我们需要在AndroidManifest.xml的标签中加入以下一行代码,不然程序会出现问题
android:usesCleartextTraffic="true"
接着启动程序点击按钮解析出来的数据就打印出来了。
解析JSON数据
接下来学习如何解析JSON格式的数据,首先说一下JSON相比XML的优势——JSON的体积更小,在网络传输的时候可以更省流量。但同时它的缺点是语义性较差,不如XML看起来直观。
在解析之前,我们和前面一样,新建一个get_data.json文件,接下来访问localhost:9000/AndroidStudy/get_data.json,如下所示:
使用JSONObject
- 和上面一样,构造url时传入自己数据源的url
.url("http://192.168.??.???:9000/AndroidStudy/get_data.json")
- 接着实现一个parseJSONWithJSONObject()方法对返回的数据进行解析
//使用JSONObject解析JSON格式的数据 private void parseJSONWithJSONObject(String jsonData){ try { JSONArray jsonArray=new JSONArray(jsonData); for(int i=0;i JSONObject jsonObject=jsonArray.getJSONObject(i); String id=jsonObject.getString("id"); String version=jsonObject.getString("version"); String name=jsonObject.getString("name"); Log.d("MainActivity","id is "+id); Log.d("MainActivity","name is "+name); Log.d("MainActivity","version is "+version); } }catch (Exception e){ e.printStackTrace(); } } "name":"Tom","age":"20"} }.getType()); 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;} } Gson gson=new Gson(); List}.getType()); for (App app : appList) { Log.d("MainActivity","id is "+app.getId()); Log.d("MainActivity","name is "+app.getName()); Log.d("MainActivity","version is "+app.getVersion()); } } 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;} } @GET("get_data.json") public Call @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button getData=(Button) findViewById(R.id.getAppDataBtn); getData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Retrofit retrofit= new Retrofit.Builder() //配置根路径-你自己主机的IP .baseUrl("http://192.168.??.???:9000/AndroidStudy/") //指定解析数据时使用的转换库 .addConverterFactory(GsonConverterFactory.create()) .build(); //创建出AppService接口的动态代理对象,接下来就可以随意调用该接口中的方法了 AppService appService=retrofit.create(AppService.class); //调用getAppData方法之后会返回一个Call对象,接着调用enqueue()方法,Retrofit就会根据注解中配置的服务器接口地址去进行网络请求,服务器响应的数据也会回调到enqueue()方法中的Callback实现里 appService.getAppData().enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { //调用response.body()后我们会得到Retrofit解析后的对象 List appList = response.body(); if(appList!=null){ for (App app : appList) { Log.d("MainActivity","id is "+app.getId()); Log.d("MainActivity","name is "+app.getName()); Log.d("MainActivity","version is "+app.getVersion()); } } } @Override public void onFailure(Call call, Throwable t) { t.printStackTrace(); } }); } }); } }
注意:我们在构建Retrofit对象时上面两个方法是必须调用的。发起请求时,Retrofit会自动在内部开启子线程,当数据回调到Callback中之后又会自动切回主线程,整个过程我们都不需要考虑线程切换问题。
- 最后需要注意的是我们要在AndroidManifest.xml文件中需要加入网络权限以及在标签中加入以下配置
... android:networkSecurityConfig="@xml/network_config"
在res->xml文件下创建network_config,内容如下:
最后我们点击按钮发送网络请求返回的JSON数据就会被解析并将我们想要的数据打印在控制台了。
处理复杂的接口地址类型
- 上面我们的接口地址是静态的,但在很多场景下接口地址是动态变化的,如以下接口地址:
GET http://example.com//get_data.json
上面的表示页数,我们传入不同的页数,显示的内容也是不一样的,服务器返回的数据也是不同的,像这种动态接口地址在Retrofit中应该怎么写呢(实体类为Data,创建了一个DataService接口):
public interface DataService{ @GET("{page}/get_data.json") public Call getData(@Path("page") int page); }
在接口地址中,我们使用{page}来表示一个占位符,在getData()方法中添加了一个page参数,并使用@Path(“page”)注解来声明这个参数,这样当发送请求时,Retrofit会将page参数自动替换掉{page},这样不就实现了动态参数。
- 另外很多服务器的接口要求我们传入多个参数,如以下接口地址:
GET http://examlpe.com/get_data.json?u=&t=
带参数的GET请求格式如上,使用?来连接参数部分,每个参数都是使用=连接的键值对,多个参数之间使用&分隔,上面的示例就表示我们要传入user和token这两个参数,对于这种接口地址,我们可以使用@Path注解来解决,如下:
public interface DataService{ @GET("get_data.json?u={user}&t={token}") public Call getData(@Path("user") String user,@Path("token") String token); }
也可以使用Retrofit针对这类GET请求提供的语法支持:
public interface DataService{ @GET("get_data.json") public Call getData(@Query("u") String user,@Query("t") String token); }
这两种方式都可以将接口地址中的和用user和token替换。
其他注解
- @POST:提交数据
- @PUT、@PATCH:修改数据
- @DELETE:删除数据
DELETE http://example.com/data/
上述接口地址表示要根据id删除一条数据,要想发出这样的请求我们可以写成下面这样
public interface DataService{ @DELETE("data/{id}") public Call deleteData(@Path("id") String id); }
可以看到上面我们将Call的泛型指定为ResponseBody,这是因为@POST、@PUT、@PATCH、@DELETE注解与@GET注解不同,它们时操作服务器上的数据而不是获取数据,所以它们对服务器响应的数据并不关心。使用ResponseBody表示Retrofit能接收任何类型的响应数据,并且不会对响应数据进行解析。
提交数据
向服务器提交数据,如以下接口地址
POST http://example.com/data/create
使用POST来提交数据,需要我们将数据放到HTTP请求的body部分,Retrofit给我们提供了一个@Body注解来完成
public interface DataService{ @POST("data/create") public Call postData(@Body Data data);//Data是你自己定义的要提交的数据 }
这样当发送一个POST请求时,Retrofit会将Data对象中的数据转换成JSON格式的数据并放到HTTP请求的body部分。
关于Android网络技术的使用的分享到此结束,希望对您有帮助!
- 另外很多服务器的接口要求我们传入多个参数,如以下接口地址:
- 上面我们的接口地址是静态的,但在很多场景下接口地址是动态变化的,如以下接口地址:
- 最后需要注意的是我们要在AndroidManifest.xml文件中需要加入网络权限以及在标签中加入以下配置
- 接着实现一个parseJSONWithJSONObject()方法对返回的数据进行解析
- 和上面一样,构造url时传入自己数据源的url
- 接着实现一个parseXMLWithPull对返回的数据进行解析
- 首先构造Request对象时url我们要指定为访问本机IP地址,你打开控制台查一下本机电脑IP,如下所示
- 接着在构建出一个Request对象并将待提交的数据传入
- 我们需要先构建出一个RequestBody对象来存放待提交的数据
- 获取具体的返回内容
- 调用OkHttpClient的newCall()方法来创建一个Call对象并调用它的execute()方法来发送请求并获取服务器返回的数据
- 要想发送HTTP请求,我们需要创建一个Request对象(目标网站为百度首页),注意此时请求并未发送
- 创建一个OkHttpClient实例
- 首先使用前必须添加其依赖
- 最后别忘了申请一下网络权限
- MainActivity
- activity_main,包括发送请求的按钮和展示返回数据的文本框
- 最后调用disconnect()方法将这个HTTP连接关闭
- 设置HTTP请求所使用的方法。常用方法有GET和POST。GET表示希望从服务器那获取数据,POST表示希望提交数据给服务器。