【Android】 网络技术

慈云数据 2024-04-27 技术支持 27 0

前言

本文用于记录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网络技术的使用的分享到此结束,希望对您有帮助!

微信扫一扫加客服

微信扫一扫加客服

点击启动AI问答
Draggable Icon