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-网络编程 的相关文章

  • 如何对这个字符串进行子串化

    我想得到这个字符串的 4 个部分 String string 10 trillion 896 billion 45 million 56873 我需要的4个部分是 10万亿 8960亿 4500万 和 56873 我所做的是删除所有空格 然
  • 如何在android中获取Camera2 API的当前曝光

    In android hardware Camera旧的 我使用下面的代码获取当前曝光并获取它Camera Camera Parameters param mCamera getParameters currentExposure para
  • Android 中 Kotlin 协程的正确使用方式

    我正在尝试使用异步更新适配器内的列表 我可以看到有太多的样板 这是使用 Kotlin 协程的正确方法吗 这个可以进一步优化吗 fun loadListOfMediaInAsync async CommonPool try Long runn
  • 在 java 类和 android 活动之间传输时音频不清晰

    我有一个android活动 它连接到一个java类并以套接字的形式向它发送数据包 该类接收声音数据包并将它们扔到 PC 扬声器 该代码运行良好 但在 PC 扬声器中播放声音时会出现持续的抖动 中断 安卓活动 public class Sen
  • 使用 Android 发送 HTTP Post 请求

    我一直在尝试从 SO 和其他网站上的大量示例中学习 但我无法弄清楚为什么我编写的示例不起作用 我正在构建一个小型概念验证应用程序 它可以识别语音并将其 文本 作为 POST 请求发送到 node js 服务器 我已确认语音识别有效 并且服务
  • 无法访问 com.google.android.gms.internal.zzbfm 的 zzbfm 类文件未找到

    我正在将我的 Android 应用程序项目从GCM to FCM 为此 我使用 Android Studio 中的 Firebase 助手工具 并遵循 Google 开发人员指南中的说明 一切都很顺利 并将我的应用程序代码更改为FCM根据助
  • 如何使用 Cordova 获取当前安装的应用程序的版本?

    我已经找到了应用程序可用性插件 https github com ohh2ahh AppAvailability它主要检查用户是否在其设备上安装了某个应用程序 是否有可能获得应用程序的当前版本 开发者名称 重要 以及所有可能的信息 一般来说
  • Android MediaExtractor seek() 对 MP3 音频文件的准确性

    我在使用 Android 时无法在eek 上获得合理的准确度MediaExtractor 对于某些文件 例如this one http www archive org download emma solo librivox emma 01
  • JavaMail 只获取新邮件

    我想知道是否有一种方法可以在javamail中只获取新消息 例如 在初始加载时 获取收件箱中的所有消息并存储它们 然后 每当应用程序再次加载时 仅获取新消息 而不是再次重新加载它们 javamail 可以做到这一点吗 它是如何工作的 一些背
  • 你的CPU不支持NX

    我刚刚下载了 android studio 但是我遇到了一个问题 当我运行它时 它说你的 cpu 不支持 NX 我应该怎么办 NX 或实际上是 NX 处理器位 是处理器的一项功能 有助于保护您的 PC 免受恶意软件的攻击 当此功能未启用并且
  • 在 SQLite 中搜索时排除 HTML 标签和一些 UNICODE 字符

    更新 4 我已经成功运行了firstchar例如 但现在的问题是使用regex 即使包含头文件 它也无法识别regex操作员 有什么线索可以解决这个问题吗 更新 2 我已经编译了sqlite3我的项目中的库 我现在正在寻找任何人帮助我为我的
  • 如何默认在 ActionOpenDocument 意图中显示“内部存储”选项

    我需要用户选择一个自定义文件类型的文件 并将其从 Windows 文件资源管理器拖到 Android 设备上 但默认情况下内部存储选项不可用 当我使用以下命令启动意图时 var libraryIntent new Intent Intent
  • 在 android DatePickerDialog 中将语言设置为法语

    有什么办法可以让日期显示在DatePickerDialog用法语 我已经搜索过这个但没有找到结果 这是我的代码 Calendar c Calendar getInstance picker new DatePickerDialog Paym
  • Android访问远程SQL数据库

    我可以直接从 Android 程序访问远程 SQL 数据库 在网络服务器上 吗 即简单地打开包含所有必需参数的连接 然后执行 SQL 查询 这是一个私人程序 不对公众开放 仅在指定的手机上可用 因此我不担心第三方获得数据库访问权限 如果是这
  • Android向menuItem添加子菜单,addSubMenu()在哪里?

    我想根据我的参数以编程方式将 OptionsMenu 内的子菜单添加到 menuItem 中 我检查了android sdk中的 MenuItem 没有addSubMenu 方法 尽管你可以找到 hasSubMenu 和 getSubMen
  • 一次显示两条Toast消息?

    我希望在一个位置显示一条 Toast 消息 并在另一位置同时显示另一条 Toast 消息 多个 Toast 消息似乎总是按顺序排队和显示 是否可以同时显示两条消息 是否有一种解决方法至少可以提供这种外观并且不涉及扰乱活动布局 Edit 看来
  • Firebase 添加新节点

    如何将这些节点放入用户节点中 并创建另一个节点来存储帖子 我的数据库参考 databaseReference child user getUid setValue userInformations 您需要使用以下代码 databaseRef
  • 捕获的图像分辨率太大

    我在做什么 我允许用户捕获图像 将其存储到 SD 卡中并上传到服务器 但捕获图像的分辨率为宽度 4608 像素和高度 2592 像素 现在我想要什么 如何在不影响质量的情况下获得小分辨率图像 例如我可以获取或设置捕获的图像分辨率为原始图像分
  • Crashlytics 出现 Android Studio 构建错误

    我正在尝试将 CrashLytics 与 Android Studio 和 gradle 一起使用 但出现一个令人困惑的错误 java lang NoSuchMethodError 我的 build gradle 是 buildscript
  • 按日期对 RecyclerView 进行排序

    我正在尝试按日期对 RecyclerView 进行排序 但我尝试了太多的事情 我不知道现在该尝试什么 问题就出在这条线上适配器 notifyDataSetChanged 因为如果我不放 不会显示错误 但也不会更新 recyclerview

随机推荐

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

    秋招 算法 查分与前缀和数组篇 差分数组 差分数组的主要适用场景是频繁对原始数组的某个区间的元素进行增减 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控件加载不同的网页 掌