部分案例需要一定的XML和JSON解析知识
https://blog.csdn.net/nishigesb123/article/details/89190973
Socket、TCP、HTTP等等概念搞不清区别的可以先走下面的链接(好像也是转的,不过贴的几张图很好)
https://www.cnblogs.com/merray/p/7918977.html
暂时不涉及Socket部分的内容,网上找了篇文章,可以参考下
https://www.jianshu.com/p/fb4dfab4eec1
安卓网络编程
引言
从2010年移动互联网的概念提出到如今,不管下一个风口是物联网还是大数据亦或是人工智能,未来的事不好预测,移动互联网的全面覆盖已经是板上钉钉。而网络编程技术自然是移动互联网绕不开的话题,因此网络编程在安卓开发中具有相当的地位。
Android主要有三种形式实现网络编程:
- Socket(基于JAVA JDK本身的TCP、UDP网络通信的API)
- HttpURLConnection(Android支持基于HTTP协议的网络编程方式,这种是最常用的,也是这篇文章的重点)
- Apache HttpClient (本意在提供API来简化HTTP的操作,但是已经过时了,还是老实用👆)
文本及后续文章将会涉及的一些技术和内容,包括但不仅仅是
- WebView控件(显示网页)
- HttpURLConnection(见第一部分)
- Apache HttpClient(见第一部分)
- WebService:https://blog.csdn.net/nishigesb123/article/details/89396081
- Volley:https://blog.csdn.net/nishigesb123/article/details/89356776
- Android-async-http:https://blog.csdn.net/nishigesb123/article/details/89376890
HTTP协议
不管是HttpURLConnection还是Apache HttpClient都涉及HTTP协议,那么什么是HTTP协议呢?
要点
- HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写。
- HTTP协议是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。
- HTTP协议基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。
- HTTP协议是万维网协会(World Wide Web Consortium)和Internet工作小组IETF(Internet Engineering Task Force)合作的结果,他们最终发布了一系列的RFC,RFC1945定义了HTTP/1.0版本。其中最著名的就是RFC 2616(定义了今天最普遍使用的一个版本——HTTP 1.1)。
关于HTTP1.0、1.1、2.0
HTTP协议在TCP/IP协议栈中的位置
- HTTP协议通常承载于TCP协议之上
- 有时也承载于TLS或SSL协议层之上(此时候,就成了我们常说的HTTPS)
TCP/IP协议栈(网络博文)
如下图:默认HTTP端口号为80,HTTPS的端口号为443。
HTTP的请求响应
HTTP协议永远都是客户端发起请求,服务器回送响应。见下图:
这样就限制了使用HTTP协议,无法实现在客户端没有发起请求的时候,服务器将消息推送给客户端。
HTTP协议是一个无状态协议,同一个客户端的这次请求和上次请求是没有对应关系的。
工作流程
一次HTTP操作称为一个事务,其工作过程可分为四步:
- 首先客户机与服务器需要建立连接。只要单击某个超级链接,HTTP的工作开始
- 建立连接后,客户机发送一个请求给服务器,请求方式的格式为:统一资源标识符(URL)、协议版本号,后边是MIME信息包括请求修饰符、客户机信息和可能的内容。
- 服务器接到请求后,给予相应的响应信息,其格式为一个状态行,包括信息的协议版本号、一个成功或错误的代码,后边是MIME信息包栝服务器信息、实体信息和可能的内容。
- 客户端接收服务器所返回的信息通过浏览器显示在用户的显示屏上,然后客户机与服务器断开连接。
如果在以上过程中的某一步出现错误,那么产生错误的信息将返回到客户端,由显示屏输出。对于用户来说,这些过程是由HTTP自己完成的,用户只要用鼠标点击,等待信息显示就可以了。
请求包示例
HTTP请求包(GET、POST等请求方式)由三个部分构成,分别是:
- 方法-URL-协议/版本
- 请求头
- 请求正文
应答包示例
和HTTP请求包相似,也由三个部分构成,分别是:
- 协议-状态代码-描述
- 应答头
- 应答正文
这部分就不贴了,太长orz
HTTP的常见状态响应码
检查网络连接状态
要在安卓中涉及网络的App,第一步自然是要检查网络连接状态,如果没有连上网,网络应用也无从说起。
权限
既然要访问手机设备的网络状态,显然是需要权限的,配置清单添加
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
判断是否有网络连接
public boolean isNetworkConnected(Context context){
if (context!=null){
ConnectivityManager connectivityManager= (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo=connectivityManager.getActiveNetworkInfo();
if (networkInfo!=null){
return networkInfo.isAvailable();
}
}
return false;
}
如需测试还需要一个按钮及对应点击事件(下面的几个案例也类似的需要一个按钮,差不多,就不展示按钮这部分代码了)
public void CheckNet(View view){
boolean bool = isNetworkConnected(this);
if(bool){
Toast.makeText(this,"网络可用",Toast.LENGTH_LONG).show();
}else{
Toast.makeText(this,"网络不可用",Toast.LENGTH_LONG).show();
}
}
效果如下:
判断WIFI网络是否可用
public boolean isWifiConnected(Context context){
if (context!=null){
ConnectivityManager connectivityManager= (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo wiFiNetworkInfo=connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if (wiFiNetworkInfo!=null){
return wiFiNetworkInfo.isAvailable();
}
}
return false;
}
判断MOBILE网络是否可用
public boolean isMobileConnected(Context context){
if (context!=null){
ConnectivityManager mConnectivityManager= (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo mMobileNetworkInfo=mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
if(mMobileNetworkInfo!=null){
return mMobileNetworkInfo.isAvailable();
}
}
return false;
}
获取当前网络类型
注意返回值发生了变化,回返回一个INT类型的数字常量,常量的话找了些资料贴在下面,可以参考一下(不过感觉哪里不对劲?)
public static int getConnectedType(Context context){
if (context!=null){
ConnectivityManager connectivityManager= (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo=connectivityManager.getActiveNetworkInfo();
if (networkInfo!=null&&networkInfo.isAvailable()){
return networkInfo.getType();
}
}
return -1;
}
相关类型列表:
- NETWORK_TYPE_1xRTT: 常量值:7 网络类型:1xRTT
- NETWORK_TYPE_CDMA : 常量值:4 网络类型: CDMA (电信2g)
- NETWORK_TYPE_EDGE: 常量值:2 网络类型:EDGE(移动2g)
- NETWORK_TYPE_EHRPD: 常量值:14 网络类型:eHRPD
- NETWORK_TYPE_EVDO_0: 常量值:5 网络类型:EVDO 版本0.(电信3g)
- NETWORK_TYPE_EVDO_A: 常量值:6 网络类型:EVDO 版本A (电信3g)
- NETWORK_TYPE_EVDO_B: 常量值:12 网络类型:EVDO 版本B(电信3g)
- NETWORK_TYPE_GPRS: 常量值:1 网络类型:GPRS (联通2g)
- NETWORK_TYPE_HSDPA: 常量值:8 网络类型:HSDPA(联通3g)
- NETWORK_TYPE_HSPA: 常量值:10 网络类型:HSPA
- NETWORK_TYPE_HSPAP: 常量值:15 网络类型:HSPA+
- NETWORK_TYPE_HSUPA: 常量值:9 网络类型:HSUPA
- NETWORK_TYPE_IDEN: 常量值:11 网络类型:iDen
- NETWORK_TYPE_LTE: 常量值:13 网络类型:LTE(3g到4g的一个过渡,称为准4g)
- NETWORK_TYPE_UMTS: 常量值:3 网络类型:UMTS(联通3g)
- NETWORK_TYPE_UNKNOWN:常量值:0 网络类型:未知
看完HTTP,再看一下,HttpURLConnection,中间还夹着一个URL,所以我们也先了解一下。
URL
概述
URL其实就是Uniform Resource Locators的缩写,翻译过来为 统一的资源定位符。
其实抬头不见低头见,浏览器中我们就需要输入域名访问网页,其实这就算URL的一部分。
Web上的每个资源都有唯一的地址,而这个地址采用的就是URL格式。
出处:http://www.runoob.com/html/html-url.html
URL格式例如:scheme://host.domain:port/path/filename
- scheme - 定义因特网服务的类型。最常见的类型是 http
- host - 定义域主机(http 的默认主机是 www)
- domain - 定义因特网域名,比如 runoob.com
- :port - 定义主机上的端口号(http 的默认端口号是 80)
- path - 定义服务器上的路径(如果省略,则文档必须位于网站的根目录中)。
- filename - 定义文档/资源的名称
显示网络图片
实际上这个和之前的某篇文章里的下载网络图片的案例异曲同工之妙。
涉及网络,所以需要必要的网络权限。
<uses-permission android:name="android.permission.INTERNET"/>
准备一个按钮,及显示图片用的imageView
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/Button"
android:onClick="showImage"
android:text="显示网络图片"
/>
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/Button"
android:scaleType="centerCrop"
android:id="@+id/ImageView" />
</android.support.constraint.ConstraintLayout>
接下来利用线程去通过URL路径访问网络资源
并通过MyHandler将URL路径对应的网络资源的图片设置在ImageView上。
完整代码如👇
package com.example.a4_15netimage;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URL;
public class MainActivity extends AppCompatActivity {
private ImageView iv ;
private final MyHandler handler=new MyHandler(this);
//加载成功的常量...
private static final int LOAD_SUCCESS =0x1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = findViewById(R.id.ImageView);
}
public void showImage(View view){
//访问网络的操作必须在工作线程中操作,否则可能造成阻塞
new Thread(new Runnable() {
@Override
public void run() {
try {
URL url = new URL("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1556009786&di=269c23e25f31d20e6b2bafe767b3ac86&imgtype=jpg&er=1&src=http%3A%2F%2Fpic.qiantucdn.com%2F58pic%2F11%2F01%2F09%2F17V58PICsEi.jpg");
InputStream inputStream=url.openStream();
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
Message msg = handler.obtainMessage(LOAD_SUCCESS,bitmap);
handler.sendMessage(msg);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
private static class MyHandler extends Handler {
//软引用
private final WeakReference<MainActivity> weakReference;
public MyHandler(MainActivity mainActivity){
weakReference=new WeakReference<MainActivity>(mainActivity);
}
@Override
public void handleMessage(Message msg) {
MainActivity mainActivity=weakReference.get();
if (mainActivity!=null){
switch (msg.what){
case LOAD_SUCCESS:
mainActivity.iv.setImageBitmap((Bitmap) msg.obj);
break;
}
}
}
}
}
效果如👇 图片尺寸没搞好 将就一下 orz
HttpURLConnection
终于进入正题,o(* ̄▽ ̄*)ブ
概述
- HttpURLConnection是Java的标准类,它继承自URLConnection类(HttpURLConnection与URLConnection一样二者都是抽象类)
- HttpURLConnection主要用于向指定网站发送GET请求、POST请求
然后这里引出了一个问题,什么是GET请求和POST请求..JAVA WEB没学过吗?Po个传送门好了
HTTP 方法:GET 对比 POST
需要注意的一个点是:GET把参数放在URL字符串的后面,传递给服务器。而POST方法的参数是放在HTTP请求中。所以在编程之前,应当首先明确使用的请求方法,然后再根据所使用的方法选择相应的编程方式。
建立
需要注意!同样需要权限的配置,所以第一步应该是在配置清单加入
<uses-permission android:name=“android.permission.INTERNET”/>
HttpURLConnection对象不能直接构造,需要通过URL类中的openConnection()方法来获得。
String url = "https://www.baidu.com/";
URL url = new URL(url);
//创建connection对象。
HttpURLConnection urlConn= (HttpURLConnection) url.openConnection();
必要的设置
需要对一些参数进行设置才可用正常使用
urlConn.setDoOutput(true);//post情况下需要设置DoOutput为true,默认为false
urlConn.setDoInput(true);//设置是否从HttpURLConnection读入,默认情况下是true
urlConn.setRequestMethod("POST");//设置请求方式
urlConn.setUseCache(false);//设置是否使用缓存,注意post请求方式是不能使用缓存的
还有content-type相关的设置
urlConn.setRequestPropery("content-type","application/x-www-form-urlencoded");
urlConn.setConnectTimeout(30000);//设置连接主机超时(单位毫秒)
urlConn.setReadTimeout(30000);//设置从主机读取数据超时(单位毫秒)
处理输入输出
实际上核心的处理都在在这部分,最后类似连接数据库结束的时候需要关闭,别忘记关闭连接。
//获得输出流,便于向服务器发送信息
DataOutputStream dos = new DataOutputStream(urIConn.getOutputStream();
//往流里面写请求参数dos.writeBytes('name="+URLEncoder.encode("hello","gb2312";
dos.flush();
dos.close();//发送完后马上关闭
//获得输入流,取数据
BufferReader reader = new BufferedReader(new InputStreamReader(urlConn.getInputStream()));
reader.readLinel();//用!-null来判断是否结束
reader.close();
//关闭connection
urlConn.disconnect();
综合案例——模拟登陆
首先需要了解的:
Android模拟器访问本地服务器IP:10.0.2.2(或直接输入电脑的真实IP)
所以如果在模拟器上测试该案例,可以在本机启动服务器,然后安卓项目中的URL以10.0.2.2:8080打头
首先是布局,准备一个提示文本(TextView),一个按钮,两个输入框(EditText)
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="登录信息将会显示在这里!"
android:id="@+id/info"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/info"
android:hint="请输入用户名"
android:id="@+id/username"
/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码"
app:layout_constraintTop_toBottomOf="@+id/username"
app:layout_constraintStart_toStartOf="parent"
android:id="@+id/password"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="登录"
android:id="@+id/button"
android:onClick="login"
app:layout_constraintTop_toBottomOf="@+id/password"
app:layout_constraintStart_toStartOf="parent"
/>
</android.support.constraint.ConstraintLayout>
完整代码:
package com.example.a4_15httpurlconnection;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.net.URLEncoder;
public class MainActivity extends AppCompatActivity {
private static final int LOAD_SECCESS = 200;
private static final int LOAD_ERROR = -1;
private final MainActivity.MyHandler handler = new MainActivity.MyHandler(this);
private TextView tv_info;
private EditText et_username, et_password;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_info = findViewById(R.id.info);
et_username = findViewById(R.id.username);
et_password = findViewById(R.id.password);
}
private static class MyHandler extends Handler {
private final WeakReference<MainActivity> weakReference;
public MyHandler(MainActivity mainActivity) {
weakReference = new WeakReference<MainActivity>(mainActivity);
}
@Override
public void handleMessage(Message msg) {
MainActivity mainActivity = weakReference.get();
if (mainActivity != null) {
switch (msg.what) {
case LOAD_SECCESS:
String json = (String) msg.obj;
mainActivity.jsonToObject(json);
break;
case LOAD_ERROR:
mainActivity.tv_info.setText("登录失败,请检查用户名或者密码是否正确");
break;
}
}
}
}
//解析JSON为对象
public void jsonToObject(String json) {
Gson gson = new Gson();
JsonObject object = gson.fromJson(json, JsonObject.class);
tv_info.setText(object.toString());
}
/**
*登录按钮点击事件
* @param view
*/
public void login(View view) {
final String username = et_username.getText().toString();
//如果username长度为0或者为null
if (TextUtils.isEmpty(username)) {
Toast.makeText(this, "用户名不能为空", Toast.LENGTH_SHORT).show();
return;
}
//密码同理
final String password = et_password.getText().toString();
if (TextUtils.isEmpty(password)) {
Toast.makeText(this, "密码不能为空", Toast.LENGTH_SHORT).show();
return;
}
//启动登录工作线程
new Thread(new Runnable() {
@Override
public void run() {
//path换成你自己的服务器项目登录模块的url地址,
String path = "http://10.0.2.2:8080/contact/login";
try {
System.out.println("访问的path:"+path);
URL url = new URL(path);
System.out.println("URL:"+url);
//打开HTTP连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//带密码的一般用post比较安全
//设置部分
conn.setRequestMethod("POST");
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setConnectTimeout(1000 * 30);
conn.setReadTimeout(1000 * 30);
conn.setUseCaches(false);
conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
//使用输入输出流对服务器端读取/写入数据
//获取连接的输出流
DataOutputStream outputStream = new DataOutputStream(conn.getOutputStream());
//GBK?
outputStream.writeBytes("username=" + URLEncoder.encode("vince", "GBK"));
outputStream.writeBytes("&password=" + URLEncoder.encode("123" , "GBK"));
outputStream.flush();
outputStream.close();
//从服务器获取响应数据
//字节流转字符流
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String result = br.readLine();
System.out.println("result=" + result);
br.close();//关闭输入流
conn.disconnect();//关闭连接
Message msg = handler.obtainMessage(LOAD_SECCESS, result);
handler.sendMessage(msg);
} catch (IOException e) {
e.printStackTrace();
handler.sendEmptyMessage(LOAD_ERROR);
}
}
}).start();
}
}
涉及到GSON
之前GSON是直接下载好JAR包添加,这里提供另一个方法,在项目上右键,出现如下菜单
进入设置界面后切换到Dependencies,点加号
找到GSON 加入,点OK即可
效果如下:
客户端控制台
服务器端控制台