CH9-网络编程

2023-11-04

目标

  • 了解HTTP协议通信简介,能够说出什么是HTTP协议
  • 掌握HttpURLConnection的使用方法,能够使用HttpURLConnection访问网络
  • 掌握WebView控件的使用方式,能够使用WebView控件加载不同的网页
  • 掌握JSON数据的解析,能够通过不同的方式解析JSON数据
  • 熟悉Handler消息机制的概述,能够归纳Handler消息机制的原理

​ 在移动互联网时代,手机联网实现信息互通是最基本的功能体验。例如,在上下班的途中或旅行时,只要有时间人们就会拿出手机上网,通过手机接收新资讯、搜索网络资源。Android作为智能手机市场中主流的操作系统,它的强大离不开其对网络功能的支持。Android系统提供了多种实现网络通信的方式。接下来,我们从最基础的HTTP协议开始,到Android中原生的HttpURLConnection、WebView控件的使用以及网络数据的解析进行详细讲解。

一、通过HTTP访问网络

  • 了解HTTP协议通信简介,能够说出什么是HTTP协议
  • 掌握HttpURLConnection的使用方法,能够使用HttpURLConnection访问网络

1.1 HTTP协议通信简介

HTTP(Hyper Text Transfer Protocol)即超文本传输协议,它规定了浏览器服务器之间相互通信的规则。

HTTP协议是一种请求/响应式的协议

  • 当客户端与服务器端建立连接后,向服务器端发送的请求,称作HTTP请求

  • 服务器端接收到请求后会做出响应,称为HTTP响应

​ 使用手机客户端访问百度时,会发送一个HTTP请求,当服务器端接收到请求后,会做出响应并将百度页面(数据)返回给客户端浏览器,这个请求响应的过程就是HTTP通信的过程。

image-20220306143836237

1.2 使用HttpURLConnection访问网络

GET与POST请求

(1)GET方式

GET方式是以实体的方式得到由请求URL所指向的资源信息,它向服务器提交的参数跟在请求URL后面。使用GET方式访问网络URL的长度一般要小于1KB

(2)POST方式

POST方式向服务器发出请求时需要在请求后附加实体。它向服务器提交的参数在请求后的实体中,POST方式对URL的长度是没有限制的。

​ 采用POST方式提交数据时,用户在浏览器中看不到向服务器提交的请求参数,因此POST方式要比GET方式相对安全。

GET方式提交数据

//将用户名和密码拼在指定资源路径后面,并对用户名和密码进行编码
String path = "http://192.168.1.100:8080/web/LoginServlet?username="
                  + URLEncoder.encode("zhangsan")
                  +"&password="+ URLEncoder.encode("123");  
URL url =  new  URL(path);                      
HttpURLConnection  conn  =  (HttpURLConnection)url.openConnection();  
conn.setRequestMethod("GET");                  
conn.setConnectTimeout(5000);                  
int responseCode = conn.getResponseCode();  //获取到状态码
if(responseCode == 200){          //状态码为200,表示访问成功获取返回内容的输入流         
        InputStream is = conn.getInputStream(); 
}

POST方式提交数据

URL url = new URL("http://192.168.1.100:8080/web/LoginServlet");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);                
conn.setRequestMethod("POST");               
//准备数据并给参数进行编码
String data = "username=" + URLEncoder.encode("zhangsan")
                  + "&password=" + URLEncoder.encode("123");
//设置请求头数据提交方式以及提交数据的长度,这里是以form表单的方式提交
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); 
conn.setRequestProperty("Content-Length", data.length() + ""); 
//以流的形式将数据写到服务器上
conn.setDoOutput(true); 
OutputStream os = conn.getOutputStream(); 
os.write(data.getBytes()); 
int code = conn.getResponseCode(); 
if (code == 200) {
    InputStream is = conn.getInputStream(); 
}

注意

​ 在实际开发中,手机端与服务器端进行交互的过程中避免不了要提交中文到服务器,这时就会出现中文乱码的情况。无论是GET方式还是POST方式提交参数时都要给参数进行编码,编码方式必须与服务器解码方式一致。同样在获取服务器返回的中文字符时,也需要用指定格式进行解码。

二、使用WebView进行网络开发

目标

  • 掌握WebView的使用方式,能够使用WebView浏览不同网页、执行HTML代码和支持JavaScript

2.1 使用WebView浏览网页

​ 在Android程序中,WebView控件可以在XML布局文件中使用标签来添加,也可以在Java文件中通过new关键字来创建。

​ 通常会采用在XML布局文件中添加标签的形式,具体代码如下

<!--WebView控件的id-->
<WebView
    android:id="@+id/webView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

​ 控件的常用方法如下表所示。

属性名称 功能描述
loadUrl(String url) 用于加载指定URL对应的网页
loadData(String data, String mimeType, String encoding) 用于将指定的字符串数据加载到浏览器中
loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding,String historyUrl) 基于URL加载指定的数据
capturePicture() 用于创建当前屏幕的快照
goBack() 用于执行后退操作,相当于浏览器上后退按钮的功能
goForward() 用于执行前进操作,相当于浏览器上前进按钮的功能
stopLoading() 用于停止加载当前页面
reload() 用于刷新当前页面
emulator.exe -list-avds
emulator.exe -avd Nexus_4_API_28 -dns-server 192.168.1.1
setprop net.dns1 192.168.1.1
setprop net.eth0.dns1 192.168.1.1
setprop net.eth0.gw 192.168.1.1

接下来通过一个案例来演示如何使用WebView控件加载网页,本案例的界面效果如下图所示。

image-20220306145625620

放置界面控件 res\layout\activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

编写界面交互代码 webview\MainActivity.java

package cn.itcast.webview;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.webkit.WebView;
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.webView);
        webview.loadUrl("http://m.itheima.com/"); // 指定要加载的网页
    }
}

​ 在MainActivity中实现WebView控件浏览网页的功能,通过WebView控件的loadUrl()方法来加载指定的网页,主要代码如下。

WebView webview=(WebView)findViewById(R.id.webView); 
webview.loadUrl("http://www.itheima.com/"); 	//通过加载网页地址加载网页
	 需要在清单文件(AndroidManifest.xml)的< manifest>标签中<font color='cornflowerblue'>添加允许访问网络资源的权限</font>。

注 意

​ 如果想让上述WebView控件具备放大和缩小网页的功能,则需要对该控件进行如下设置:

//设置WebView控件支持使用屏幕控件或手势进行缩放
webview.getSettings().setSupportZoom(true);
//设置WebView控件使用其内置的变焦机制,该机制集合屏幕缩放控件使用
webview.getSettings().setBuiltInZoomControls(true);

2.2 使用WebView执行HTML代码

​ WebView类提供了loadData()和 loadDataWithBaseURL()方法加载HTML代码。当使用loadData()方法来加载带中文的HTML内容时会产生乱码,但是使用loadDataWithBaseURL()方法就不会出现这种情况。loadDataWithBaseURL()方法的定义方式如下:

image-20220306150031612

​ 接下来通过一个案例来演示如何使用WebView控件加载HTML代码,本案例的界面效果如下图所示。

image-20220306150639340

放置界面控件 res\layout\activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

实现加载HTML的功能 webviewhtml\MainActivity.java

package cn.itcast.webviewhtml;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.webkit.WebView;
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.webView);
        // 创建一个字符串构建器,将要显示的HTML内容放置在该构建器中
        StringBuilder sb = new StringBuilder();
        sb.append("<div>请选择您要学习的课程:</div>");
        sb.append("<ul>");
        sb.append("<li>新媒体课程</li>");
        sb.append("<li>大数据课程</li>");
        sb.append("<li>人工智能课程</li>");
        sb.append("</ul>");
        // 加载数据
//        null      表示默认的空白页面
//        sb.toString()     sb中的数据
//        "text/html"       指定要显示内容的MIME类型
        webview.loadDataWithBaseURL(null, sb.toString(), "text/html", "utf-8",
                null);
    }
}

2.3 设置WebView支持JavaScript

​ 为了解决WebView控件在默认情况下不支持JavaScript代码的问题,我们需要通过setJavaScriptEnabled()方法来设置WebView控件,使其可以支持JavaScript代码

WebSettings settings= webview.getSettings(); // 获取WebSettings对象
settings.setJavaScriptEnabled(true); 		//设置JavaScript可用
//使WebView控件显示带有JavaScript代码的提示框
webview.setWebChromeClient(new WebChromeClient());	

​ 接下来通过一个案例来演示如何使用WebView控件支持一个带有JavaScript代码的网页,本案例的界面效果如下图所示。

image-20220306151029436

image-20220306152215525

导入JS文件 src\main\assets\alert.html和alert.js

<!DOCTYPE html>
<html>
<head>
<title>alert.html</title>
<meta charset="UTF-8">
<meta name="content-type" content="text/html; charset=UTF-8">
</head>
<body>
	This is my HTML page.
	<br>
	<script type="text/javascript" src="alert.js"></script>
	<script type="text/javascript">
alert("我是一个消息提示框");
</script>
</body>
</html>
window.alert = function(msg, callback) {
	var div = document.createElement("div");
	div.innerHTML = "<style type=\"text/css\">"
			+ ".nbaMask { position: fixed; z-index: 1000; top: 0; right: 0; left: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); }                                                                                                                                                                       "
			+ ".nbaMaskTransparent { position: fixed; z-index: 1000; top: 0; right: 0; left: 0; bottom: 0; }                                                                                                                                                                                            "
			+ ".nbaDialog { position: fixed; z-index: 5000; width: 80%; max-width: 300px; top: 50%; left: 50%; -webkit-transform: translate(-50%, -50%); transform: translate(-50%, -50%); background-color: #fff; text-align: center; border-radius: 8px; overflow: hidden; opacity: 1; color: white; }"
			+ ".nbaDialog .nbaDialogHd { padding: .2rem .27rem .08rem .27rem; }                                                                                                                                                                                                                         "
			+ ".nbaDialog .nbaDialogHd .nbaDialogTitle { font-size: 17px; font-weight: 400; }                                                                                                                                                                                                           "
			+ ".nbaDialog .nbaDialogBd { padding: 0 .27rem; font-size: 15px; line-height: 1.3; word-wrap: break-word; word-break: break-all; color: #000000; }                                                                                                                                          "
			+ ".nbaDialog .nbaDialogFt { position: relative; line-height: 48px; font-size: 17px; display: -webkit-box; display: -webkit-flex; display: flex; }                                                                                                                                          "
			+ ".nbaDialog .nbaDialogFt:after { content: \" \"; position: absolute; left: 0; top: 0; right: 0; height: 1px; border-top: 1px solid #e6e6e6; color: #e6e6e6; -webkit-transform-origin: 0 0; transform-origin: 0 0; -webkit-transform: scaleY(0.5); transform: scaleY(0.5); }               "
			+ ".nbaDialog .nbaDialogBtn { display: block; -webkit-box-flex: 1; -webkit-flex: 1; flex: 1; color: #09BB07; text-decoration: none; -webkit-tap-highlight-color: transparent; position: relative; margin-bottom: 0; }                                                                       "
			+ ".nbaDialog .nbaDialogBtn:after { content: \" \"; position: absolute; left: 0; top: 0; width: 1px; bottom: 0; border-left: 1px solid #e6e6e6; color: #e6e6e6; -webkit-transform-origin: 0 0; transform-origin: 0 0; -webkit-transform: scaleX(0.5); transform: scaleX(0.5); }             "
			+ ".nbaDialog a { text-decoration: none; -webkit-tap-highlight-color: transparent; }"
			+ "</style>"
			+ "<div id=\"dialogs2\" style=\"display: none\">"
			+ "<div class=\"nbaMask\"></div>"
			+ "<div class=\"nbaDialog\">"
			+ "	<div class=\"nbaDialogHd\">"
			+ "		<strong class=\"nbaDialogTitle\"></strong>"
			+ "	</div>"
			+ "	<div class=\"nbaDialogBd\" id=\"dialog_msg2\">弹窗内容,告知当前状态、信息和解决方法,描述文字尽量控制在三行内</div>"
			+ "	<div class=\"nbaDialogHd\">"
			+ "		<strong class=\"nbaDialogTitle\"></strong>"
			+ "	</div>"
			+ "	<div class=\"nbaDialogFt\">"
			+ "		<a href=\"javascript:;\" class=\"nbaDialogBtn nbaDialogBtnPrimary\" id=\"dialog_ok2\">确定</a>"
			+ "	</div></div></div>";
	document.body.appendChild(div);
 
	var dialogs2 = document.getElementById("dialogs2");
	dialogs2.style.display = 'block';
 
	var dialog_msg2 = document.getElementById("dialog_msg2");
	dialog_msg2.innerHTML = msg;
 
	// var dialog_cancel = document.getElementById("dialog_cancel");
	// dialog_cancel.onclick = function() {
	// dialogs2.style.display = 'none';
	// };
	var dialog_ok2 = document.getElementById("dialog_ok2");
	dialog_ok2.onclick = function() {
		dialogs2.style.display = 'none';
		callback();
	};
};

放置界面控件 res\layout\activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<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:id="@+id/btn_dialog"
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="执行JAVASCRIPT代码并弹出提示框"
        android:layout_margin="8dp"
        android:textColor="@android:color/white"
        android:background="@drawable/btn_dialog_selector"/>
    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

创建背景选择器 res\drawable\btn_dialog_selector.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/btn_dialog_selected"
        android:state_pressed="true" />
    <item android:drawable="@drawable/btn_dialog_normal" />
</selector>

实现加载JS代码功能 webviewjs\MainActivity.java

package cn.itcast.webviewjs;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final WebView webview = findViewById(R.id.webView);
        Button btn = findViewById(R.id.btn_dialog);
        webview.loadUrl("file:///android_asset/alert.html"); //指定要加载的网页
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 设置webview控件支持JavaScript代码
                webview.getSettings().setJavaScriptEnabled(true);
                // 显示网页中通过JavaScript代码弹出的提示框
                webview.setWebChromeClient(new WebChromeClient());
                webview.loadUrl("file:///android_asset/alert.html");
            };
        });
    }
}

三、JSON数据解析

目标

  • 掌握JSON数据的解析,能够通过不同的方式解析JSON数据

3.1 JSON数据

JSON数据的特点

(1)JSON即JavaScript Object Notation(对象表示法),是一种轻量级的数据交换格式

(2)JSON是基于纯文本的数据格式,它可以传输String、Number、Boolean类型的数据,也可以传输数组或者Object对象。

(3)JSON文件的扩展名为.json

(4)JSON分为JSON对象和JSON数组两种数据结构。

对象结构的JSON数据

​ 以“{”开始,以“}”结束。中间部分由0个或多个以“,”分隔的key:value对构成,注意关键字和值之间以“:”分隔。

​ 关键字key必须为String类型,值value可以是String、Number、Object、Array等数据类型。

image-20220306152538881

image-20220306152618944

数组结构的JSON数据

​ 以“[”开始,以“]”结束。中间部分由0个或多个以“,”分隔的值的列表组成。

值value可以是String、Number、Boolean、null等数据类型。

image-20220306152723534

image-20220306152727806

注意:

​ 使用JSON存储单个数据(如“abc”),一定使用数组结构,因为对象结构必须是由“key:value”的形式构成。

image-20220306152919626

3.2 JSON解析

两种解析方式

  • org.json
    • Android SDK中为开发者提供的,通过使用JSONObject和JSONArray两个类完成对JSON数据的解析。
  • Gson
    • 由Google公司提供的,在使用Gson库之前,首先需要将gson.jar添加到项目中,然后才能调用其提供的方法。

解析JSON对象

例如,要解析的JSON数据如下:

{ "name": "zhangsan", "age": 27, "married":true }         //json1 一个json对象
[{"name": "lisi","age": 25},{"name": "Jason","age": 20}]  //json2 一个json数组

使用JSONObject解析JSON对象

optXXX()方法在解析数据时比getXXX()方法更安全,如果对应字段不存在,optXXX()方法会返回空值或者0,而getXXX()方法会抛出异常。

JSONObject   jsonObj  =  new JSONObject(json1); 
String name = jsonObj.optString("name"); 
int age = jsonObj.optInt("age"); 
boolean married = jsonObj.optBoolean("married");

解析JSON数组

​ 使用JSONArray解析JSON数组:

数组的解析方法和对象类似,只是将key值替换为数组中的下标。

JSONArray jsonArray = new JSONArray(json2); 
for(int i = 0; i < jsonArray.length(); i++) {
     JSONObject jsonObj = jsonArray.getJSONObject(i);
     String name = jsonObj.optString("name"); 
     int age = jsonObj.optInt("age");
}

Gson库解析JSON数据

​ 例如,要解析的JSON数据如下(与org.json解析数据相同):

使用Gson库前,首先需要将gson.jar添加到项目中,并且创建JSON数据对应的实体类Person1与Person2,需要注意的是,实体类中的成员名称要与JSON数据中的key值一致

{ "name": "zhangsan", "age": 27, "married":true }         //json1 一个json对象
[{"name": "lisi","age": 25},{"name": "Jason","age": 20}]  //json2 一个json数组

使用Gson解析JSON对象

 Gson gson = new Gson(); 
 Person person1 = gson.fromJson(json1, Person1.class);	//将JSON数据转换成对象

使用Gson解析JSON数组

 Gson gson = new Gson(); 
//TypeToken是Google提供的一个解析JSON数据的类
 Type listType = new TypeToken<List<Person2>>(){}.getType();
 List<Person2> person2 = gson.fromJson(json2, listType);

Android Studio添加库文件

​ 在Android程序中添加库文件进行讲解,具体操作步骤如下:

  1. 在Android Studio中,选择【File】à【Project Structure…】选项,此时会弹出一个Project Structure窗口,如下图所示。

image-20220306160153179

  1. 选中Project Structure窗口中的【Dependencies】选项卡,接着单击该窗口右上角的“ ”,选择Library dependency选项,此时会弹出一个Choose Library Dependency窗口,在该窗口中找到Gson库com.google.code.gson:gson:2.8.5并选中,如下图所示。

image-20220306160301489

3.3 实战演练—仿拼多多砍价界面

​ 本节我们将通过仿拼多多砍价界面的案例来演示如何解析JSON数据并将数据显示到界面上。本案例的界面效果如下图所示。

image-20220306160346630

放置界面控件 res\layout\activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black"
    android:orientation="vertical">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="20dp"
        android:layout_marginRight="20dp"
        android:background="@drawable/title_bg"
        android:gravity="center_vertical"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="25dp"
            android:text="一刀砍成卡"
            android:textColor="#ce4032"
            android:textSize="24sp"
            android:textStyle="bold" />
        <View
            android:layout_width="1dp"
            android:layout_height="match_parent"
            android:layout_margin="20dp"
            android:background="#af560e" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="商品直接带回家"
            android:textColor="#875a1e"
            android:textSize="18sp"
            android:textStyle="bold" />
    </LinearLayout>
    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

搭建商品的条目布局 res\layout\goods_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="230dp"
    android:layout_marginLeft="30dp"
    android:layout_marginTop="20dp"
    android:background="@android:color/black"
    android:orientation="vertical">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/goods_bg"
        android:gravity="center_horizontal"
        android:orientation="vertical">
        <TextView
            android:id="@+id/tv_count"
            android:layout_width="wrap_content"
            android:layout_height="27dp"
            android:layout_gravity="center_horizontal"
            android:gravity="center"
            android:padding="5dp"
            android:textColor="#573516"
            android:textSize="12sp" />
        <TextView
            android:id="@+id/tv_goods_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:textColor="#573516"
            android:textSize="16sp"
            android:textStyle="bold" />
        <ImageView
            android:id="@+id/iv_img"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginTop="10dp" />
        <Button
            android:id="@+id/btn_free"
            android:layout_width="110dp"
            android:layout_height="35dp"
            android:layout_margin="10dp"
            android:background="@drawable/btn_free_bg"
            android:text="点击免费拿"
            android:textColor="@android:color/white"
            android:textSize="14sp"
            android:textStyle="bold" />
    </LinearLayout>
</LinearLayout>

封装商品信息的实体类 pinduoduo\GoodsInfo.java

package cn.itcast.pinduoduo;
public class GoodsInfo {
    private int id;             // 商品id
    private String count;      // 已砍商品的数量
    private String goodsName; // 商品名称
    private String goodsPic;  // 商品图片
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getCount() {
        return count;
    }
    public void setCount(String count) {
        this.count = count;
    }
    public String getGoodsName() {
        return goodsName;
    }
    public void setGoodsName(String goodsName) {
        this.goodsName = goodsName;
    }
    public String getGoodsPic() {
        return goodsPic;
    }
    public void setGoodsPic(String goodsPic) {
        this.goodsPic = goodsPic;
    }
}

编写商品列表的适配器 pinduoduo\GoodsAdapter.java

package cn.itcast.pinduoduo;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;

import java.util.ArrayList;
import java.util.List;

public class GoodsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private Context mContext;
    private List<GoodsInfo> GoodsList = new ArrayList<>();

    public GoodsAdapter(Context context) {
        this.mContext = context;
    }

    /**
     * 获取数据更新界面
     */
    public void setData(List<GoodsInfo> GoodsList) {
        this.GoodsList = GoodsList;     //获取从Activity界面传递过来的数据GoodsList
        notifyDataSetChanged();
    }

//    inflate()方法加载布局文件
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = null;
        RecyclerView.ViewHolder holder = null;
        itemView = LayoutInflater.from(mContext).inflate(R.layout.goods_item, parent, false);
        holder = new MyViewHolder(itemView);
        return holder;
    }

//    数据绑定
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        GoodsInfo bean = GoodsList.get(position);
//        将已砍的商品数量和商品名称设置到界面控件上
        ((MyViewHolder) holder).tv_count.setText("已砍" + bean.getCount() + "件");
        ((MyViewHolder) holder).tv_goods_name.setText(bean.getGoodsName());
//        将商品图片数据设置到图片控件iv_img上
        Glide.with(mContext)
                .load(bean.getGoodsPic())
                .error(R.mipmap.ic_launcher)
                .into(((MyViewHolder) holder).iv_img);
    }

//    获取条目总数
    @Override
    public int getItemCount() {
        return GoodsList.size();
    }

    class MyViewHolder extends RecyclerView.ViewHolder {
        TextView tv_count, tv_goods_name;
        ImageView iv_img;
        Button btn_free;

        public MyViewHolder(View view) {
            super(view);
            tv_count = view.findViewById(R.id.tv_count);
            tv_goods_name = view.findViewById(R.id.tv_goods_name);
            iv_img = view.findViewById(R.id.iv_img);
            btn_free = view.findViewById(R.id.btn_free);
        }
    }
}

实现商品显示功能 pinduoduo\MainActivity.java

package cn.itcast.pinduoduo;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class MainActivity extends AppCompatActivity {
    private GoodsAdapter adapter;             // 列表的适配器
    public static final int MSG_GOODS_OK = 1; // 获取数据
    private MHandler mHandler;
    // 内网接口
    public static final String WEB_SITE = "http://172.16.43.20:8080/goods";
    // 商品列表接口
    public static final String REQUEST_GOODS_URL = "/goods_list_data.json";
    private RecyclerView rv_list;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler = new MHandler();
        init();
        initData();
    }

    private void init() {
        rv_list = findViewById(R.id.rv_list);
//        this 表示上下文
//        2     表示商品列表的每个条目中显示两条商品信息
        GridLayoutManager manager = new GridLayoutManager(this, 2);
//        将manager对象设置到控件rv_list上
        rv_list.setLayoutManager(manager);
        adapter = new GoodsAdapter(MainActivity.this);
//        将数据适配器的对象adapter设置到控件rv_list上
        rv_list.setAdapter(adapter);
    }

    private void initData() {
        OkHttpClient okHttpClient = new OkHttpClient();
        Request request = new Request.Builder().url(WEB_SITE +
                REQUEST_GOODS_URL).build();
        Call call = okHttpClient.newCall(request);
        // 开启异步线程访问网络,从服务器上获取商品列表的数据
        call.enqueue(new Callback() {
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String res = response.body().string(); // 获取商品数据
                Message msg = new Message();
                msg.what = MSG_GOODS_OK;
                msg.obj = res;
                mHandler.sendMessage(msg);
            }

            @Override
            public void onFailure(Call call, IOException e) {
            }
        });
    }

    /**
     * 事件捕获
     */
    class MHandler extends Handler {
        @Override
        public void dispatchMessage(Message msg) {
            super.dispatchMessage(msg);
            switch (msg.what) {
                case MSG_GOODS_OK:
                    if (msg.obj != null) {
//                        获取传递过来的JSON数据vlResult
                        String vlResult = (String) msg.obj;
                        // 解析获取的JSON数据vlResult,并将解析后的数据存放在集合goodsInfos中
                        List<GoodsInfo> goodsInfos = getGoodsList(vlResult);
//                        将集合goodsInfos设置到数据适配器的对象adapter中
                        adapter.setData(goodsInfos);
                    }
                    break;
            }
        }
    }

    public List<GoodsInfo> getGoodsList(String json) {
        Gson gson = new Gson(); // 使用gson库解析JSON数据
        // 创建一个TypeToken的匿名子类对象,并调用对象的getType()方法
        Type listType = new TypeToken<List<GoodsInfo>>() {
        }.getType();
        // 把获取到的集合数据存放到goodsInfos中
        List<GoodsInfo> goodsInfos = gson.fromJson(json, listType);
        return goodsInfos;
    }
}

3.4 安装配置Tomcat服务器

​ Tomcat运行稳定、可靠、效率高,不仅可以和目前大部分主流的Web服务器(如Apache、IIS服务器)一起工作,还可以作为独立的Web服务器软件。

1.下载Tomcat

​ 在Tomcat官网上下载apache-tomcat-8.5.59-windows-x64.zip文件,解压该文件可以看到Tomcat的目录结构,如下图所示。

image-20220306160429366

2. 启动Tomcat

在Tomcat安装目录的bin目录下,存放了许多脚本文件,其中startup.bat就是启动Tomcat的脚本文件,如下图所示。

image-20220306160508044

双击startup.bat文件,便会启动Tomcat服务器,Tomcat启动信息窗口如下图所示。

image-20220306160518981

Tomcat服务器启动后,在浏览器的地址栏中输入http://localhost:8080访问Tomcat服务器,如果浏览器中的显示Tomcat页面如下图所示,则说明Tomcat 服务器安装部署成功了。

image-20220306160544300

3. 关闭Tomcat

在Tomcat根目录下的bin文件夹中,运行shutdown.bat脚本文件即可关闭Tomcat或者直接关闭Tomcat启动信息窗口。

四、Handler消息机制

目标

  • 熟悉Handler消息机制的概述,能够归纳Handler消息机制的原理

Handler是一种异步回调机制,主要负责与子线程进行通信

Handler机制主要包括四个关键对象:

  • Message:是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。

  • Handler:是处理者的意思,它主要用于发送消息和处理消息。

  • MessageQueue:是消息队列的意思,它主要用来存放通过Handler发送的消息。通过Handler发送的消息会存在MessageQueue中等待处理,每个线程中只会有一个MessageQueue对象。

  • Looper:是每个线程中的MessageQueue的管家。调用Looper的loop()方法后,就会进入到一个无限循环中。每当发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的handleMessage()方法中。

image-20220306160757333

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CH9-网络编程 的相关文章

  • Android 主机意图过滤器通配符

    是否可以在 android host 属性上使用通配符 就像是 android host site com android pathPattern android pathPrefix m android scheme http gt Or
  • 按回键隐藏软键盘

    我有一个EditText in an Activity我希望当我打开它时它处于活动状态并且软键盘处于打开状态Activity 这是我的xml for EditText
  • 我可以在 firebase android 中加载另一个用户个人资料图像吗?

    如果我有其他用户的电子邮件但我以其他用户身份登录 我是否可以加载其他用户的个人资料图像 如果您使用 Firebase Storage 那么从技术上讲是的 它只是一个您可以从中检索任何文件的文件系统 如果不伪造您的应用程序 获取 api 密钥
  • Android Studio:lambda 不起作用[重复]

    这个问题在这里已经有答案了 当尝试使用 lambda 表达式时 我遇到了一些 Gradle 构建错误 错误 41 100 错误 source 1 7 不支持 lambda 表达式 使用 source 8 或更高版本来启用 lambda 表达
  • 如何将 EditText 传递给另一个活动?

    Intent intent new Intent this Name class intent putExtra key et getText toString startActivity intent Intent intent getI
  • 连接到不可发现的蓝牙设备

    我正在开发一个安卓应用程序 只是一个一般性问题 是否可以连接到公开不可发现的设备 提前致谢 如果您之前已与该设备配对 则即使该设备未处于可发现模式 也可以再次连接到该设备 参见这篇文章 以编程方式连接到配对的蓝牙设备 https stack
  • 访问角落里的存储

    我能找到的与文件存储有关的最接近文档的是这个帖子 http nookdeveloper zendesk com entries 20257971 updated what are the size constraints on my app
  • Android-工具栏中的SearchView

    我只想在我的应用程序中添加 searchview 但我不想搜索任何东西 只是我想要用户输入的查询 到目前为止 我尝试了这段代码 但是当我运行我的应用程序时它崩溃了 Update 我尝试了这个 但即使我的应用程序崩溃了 main menu x
  • 使用 Android 播放任意音调

    有没有办法让Android发出任意频率的声音 意思是 我不想预先录制声音文件 我环顾四周 音调发生器 http developer android com reference android media ToneGenerator html
  • android 确定设备是否采用从右到左的语言/布局

    有没有办法确定设备是否使用从右到左的语言 例如阿拉伯语 而不是从左到右的语言 英语 与较旧的 API 级别 低至 10 兼容的东西是必要的 SOLUTION 我最终在接受的答案中使用了 xml 方法 接下来 我还添加了此处指示的代码 以应对
  • AnalyticsService 未在应用程序清单中注册 - 错误

    我正在尝试使用 sdk 中提供的以下文档向 Android 应用程序实施谷歌分析服务 https developers google com analytics devguides collection android v4 https d
  • Android ListView setSelection() 似乎不起作用

    我有一个ListActivity实现onListItemClick 并调用doSomething 类的功能 后者包含l setSelection position where l is the ListView object 现在有一个on
  • Android Web Intent 问题

    G day 免责声明 我不是 Android 开发人员 我正在对我所描述的问题进行质量检查 我用来描述这个问题的技术术语可能是错误的 我正在测试一个 Android 应用程序 该应用程序在其清单中描述它可以使用 type 的地址处理 Web
  • 在 AppAuth-Android 中注销

    我有一个用JAVA开发的Android应用程序 对于这个应用程序 我使用的是身份服务器4 https github com IdentityServer IdentityServer4作为我的 STS 一切正常 但我找不到任何注销的实现Ap
  • 如何从 Facebook 邀请好友到 Android 应用程序? - 给出错误

    我正在开发一个 Android 应用程序 我正在努力将 邀请朋友 功能添加到我的应用程序中 它转到我的AppLinkUrl成功但显示错误 我的清单代码如下
  • 删除Android所有语言中的字符串

    我有一个包含多个翻译的应用程序 我想删除一些字符串 我怎样才能重构并删除它们一次 例如在默认情况下strings xml文件并自动将删除传播到其他翻译的其他 strings xml 文件 您可以通过 Android Studio 中的 翻译
  • 检测 ListView(或 ScrollView)内的滚动位置

    我正在构建一个聊天室应用程序 其中每 X 秒就会轮询一次新事件 每次发生这种情况时 此代码都会使用新数据更新 RoomAdapter ArrayAdapter 的自定义子类 并将其滚动到底部 RoomAdapter adapter Room
  • 在 Android 中上传文件出现内存不足错误

    我的上传代码如下 String end r n String twoHyphens String boundary try URL url new URL ActionUrl HttpURLConnection con HttpURLCon
  • Android:确定 2.2 及更高版本上的摄像头数量

    我的应用程序需要在 Android 2 2 及更高版本上运行 我需要一种方法来确定可用摄像机的数量 有很多帖子解决了这个问题 但我找不到一个有效的 一种解决方案是简单地检测操作系统版本 任何 2 2 版本的设备都仅限于 1 个摄像头 即使该
  • 在状态栏下方显示DialogFragment内容

    我试图显示高度和宽度均具有 match parent 的 DialogFragment 但碰巧在顶部 DialogFragment 显示在 StatusBar 下方 DialogFragment 正在应用一些默认值来填充底部 右侧 左侧和顶

随机推荐

  • 秋招-算法-查分与前缀和数组篇

    秋招 算法 查分与前缀和数组篇 差分数组 差分数组的主要适用场景是频繁对原始数组的某个区间的元素进行增减 1109 航班预订统计 class Solution public int corpFlightBookings int bookin
  • 为什么我选择了springcloud而不是dubbo?

    写好的代码越来越满足不了需求 因为需求总是在不断的变化 在技术选型时 实在是心有余而力不足 思来想去 就考虑了使用微服务架构来实现 功能模块化 今天主要讲讲为什么需要微服务架构 还是以故事的形式呈现 一 认识微服务 阶段一 单体服务 话说小
  • 重新理解百度智能云:写在大模型开放后的24小时

    在这些回答背后共同折射出的一个现实是 大模型不再是一个单选题 而更是一个综合题 在这个新的时代帆船上 产品 服务 安全 开放等全部都需要成为必需品 甚至是从企业的落地层面来看 这些更是刚需品 作者 皮爷 出品 产业家 过去的5个月 李亮很忙
  • 哇,ElasticSearch多字段权重排序居然可以这么玩

    背景 读者提问 ES 的权重排序有没有示列 参考参考 刚好之前也稍微接触过 于是写了这篇文章 可以简单参考下 在很多复杂的业务场景下 排序的规则会比较复杂 单一的降序 升序无法满足日常需求 不过 ES 中提供了给文档加权重的方式来排序 还是
  • python中的tkinter包的使用-Label & Button

    首先我们先建一个简单的窗口 代码 import tkinter as tk window tk Tk window title my window window geometry 200x100 窗口尺寸 l tk Label window
  • 算法—打印回形数

    题目 题目描述 回形数是一个矩阵输出 从矩阵的左上角往右开始打印数字0 遇到矩阵边界时 顺时针90方向继续打印 并数字增长1 如此类推直到把矩阵填满 输入一个整形宽和高单位 每输出一个数字 占用1单位宽高空间 根据入参打印出对应的回形数 输
  • 【Linux】root和子用户都能执行的命令,sudo无法执行(已解决)

    全流程帖子 https ask oceanbase com t topic 35604437 7 1 问题 如题 在编译miniob的时候遇到如下错误 mu vm cnt8 code miniob sudo bash build sh in
  • 华为OD机试 - 座位调整(Python)

    题目描述 疫情期间课堂的座位进行了特殊的调整 不能出现两个同学紧挨着 必须隔至少一个空位 给你一个整数数组 desk 表示当前座位的占座情况 由若干 0 和 1 组成 其中 0 表示没有占位 1 表示占位 在不改变原有座位秩序情况下 还能安
  • 如何申请@MSN.Com后缀的邮箱?

    最近辞职在家无事 想申请个 MSN Com后缀的信箱 在网上搜索了一下 原来只要从下面的地址进入注册即可 注册抵制 https accountservices passport net reg srf ns msn com sl 1 lc
  • Pytorch深度学习(六):Softmax函数实现多分类

    Pytorch深度学习 六 Softmax函数实现多分类 参考B站课程 PyTorch深度学习实践 完结合集 传送门 PyTorch深度学习实践 完结合集 一 预备知识 多分类 与之前的二分类不同 这个例子要识别手写数字的多分类 需要求出各
  • surface go 快乐装Ubuntu

    咳咳 首先我只是想体验一下 surface go 装Ubuntu 滑稽 每次开机看到田字格 然后显示出Ubuntu 有一种莫名的喜感 安装前准备 一个u盘 至少能装下你的映像文件 一台笔记本 拿来看博客 百度 你的surface go 我拿
  • (2)Mysql的安装及配置

    一 下载mysql 安装mysql有两种方式 一种是下载安装包安装 另一种是下载压缩包解压配置 这里使用安装包方式 进去后点击下面的Download 点击No thinks进入下载 二 安装mysql 双击下载好的安装包 选择Server
  • IntelliJ IDEA+SpringBoot+Tomcat部署404问题

    因为并行开发 后端一直拿不出接口 我不得不自己做Mock 为了完成网络请求逻辑 我使用Idea做模拟接口 后来发现 使用Idea创建的SpringBoot项目在软件内部的虚拟Tomcat上面可以正常运行 但是因为要做一些WebSocket和
  • 苹果笔记本计算机管理员删除,如何删除一个管理员?

    注销要删除的管理员帐户 再以管理员帐户登入 然后跟下面方法 如果您是管理员 则可以删除您不再想让其可访问电脑的用户 删除用户时 您可以存储该用户的个人文件夹 包含用户的文件和设置 或删除该个人文件夹 选取苹果菜单 gt 系统偏好设置 然后点
  • Python反反爬之JS混淆---动态Cookie(持续更新详细教程)

    写在前面 第一题JS混淆 源码乱码 经过上一题的练习JS混淆 源码乱码 我们已经对JS混淆有了大致的了解 这次我们再来练习一道同类型的题目 只不过这次是动态Cookie 首先 让我们了解一下什么是Cookie Cookie 并不是它的原意
  • lua中的常用字符串处理方法

    对lua中string类库中常用方法总结一二 1 数字和字符串相加减lua自动转换 2 得到字符串的长度 3 string byte 返回字符的内部数字编码 ASCII码 4 string char 返回和参数数量相同长度的字符串 其中每个
  • LeetCode202.快乐数(Python)

    题目 解题思路 运用哈希表 将各个位数进行平方 将得到的数进行判断 如果为1输出True 如果不为1 判断是否重复了 如果重复输出False 如果没有重复 进行各个位数平方 反复以上操作 class Solution def isHappy
  • [转]Smartgit :Remove Discard Delete

    Smartgit Remove Discard Delete https blog csdn net u010272085 article details 85039801 Remove 从仓库中移除选中的文件或目录 可通过Discard还
  • C# this.Invoke()的作用与用法、不阻塞UI界面线程的延时函数

    一 this Invoke 的作用与用法 不阻塞UI界面线程的延时函数 Invoke 的作用是 在应用程序的主线程上执行指定的委托 一般应用 在辅助线程中修改UI线程 主线程 中对象的属性时 调用this Invoke 在多线程编程中 我们
  • CH9-网络编程

    目标 了解HTTP协议通信简介 能够说出什么是HTTP协议 掌握HttpURLConnection的使用方法 能够使用HttpURLConnection访问网络 掌握WebView控件的使用方式 能够使用WebView控件加载不同的网页 掌