一、原理
Andorid app通过服务器上的web工程操作数据库,所以我们大概需要写两部分的代码:服务器上的web服务端、Android上的客户端。
本例是连接数据库实现最简单的注册、登录功能。
二、数据库设计
三、Web工程
1.工程目录
2.web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>GOAService</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<description></description>
<display-name>LogLet</display-name>
<servlet-name>LogLet</servlet-name>
<servlet-class>com.servlet.LogLet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LogLet</servlet-name>
<url-pattern>/LogLet</url-pattern>
</servlet-mapping>
<servlet>
<description></description>
<display-name>RegLet</display-name>
<servlet-name>RegLet</servlet-name>
<servlet-class>com.servlet.RegLet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RegLet</servlet-name>
<url-pattern>/RegLet</url-pattern>
</servlet-mapping>
</web-app>
3.需要的jar包
mysql-connector-java-8.0.20.jar
4.工程代码
① DBManager.java
package com.db;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class DBManager {
//数据库连接常量
public static final String DRIVER = "com.mysql.cj.jdbc.Driver";
public static final String USER = "root";
public static final String PASS = "123456";
public static final String URL = "jdbc:mysql://localhost:3306/goa?serverTimezone=UTC";
//静态成员,支持单态模式
private static DBManager per = null;
private Connection conn = null;
private Statement stmt = null;
//单态模式-懒汉模式
private DBManager() {
}
public static DBManager createInstance() {
if (per == null) {
per = new DBManager();
per.initDB();
}
return per;
}
//加载驱动
public void initDB() {
try {
getClass();
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (Exception e) {
e.printStackTrace();
}
}
//连接数据库,获取句柄+对象
public void connectDB() {
System.out.println("Connecting to database...");
try {
conn = DriverManager.getConnection(URL,USER,PASS);
stmt = conn.createStatement();
} catch (SQLException e) {
e.printStackTrace();
}
System.out.println("SqlManager:Connect to database successful.");
}
//关闭数据库 关闭对象,释放句柄
public void closeDB() {
System.out.println("Close connection to database..");
try {
stmt.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
System.out.println("Close connection successful");
}
// 查询
public ResultSet executeQuery(String sql) {
ResultSet rs = null;
try {
rs = stmt.executeQuery(sql);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return rs;
}
// 增添/删除/修改
public int executeUpdate(String sql) {
int ret = 0;
try {
ret = stmt.executeUpdate(sql);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return ret;
}
}
② Service.java
package com.service;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.db.DBManager;
public class Service {
public Boolean login(String username, String password) {
// 获取Sql查询语句
String logSql = "select * from users where username ='" + username
+ "' and password ='" + password + "'";
// 获取DB对象
DBManager sql = DBManager.createInstance();
sql.connectDB();
// 操作DB对象
try {
ResultSet rs = sql.executeQuery(logSql);
if (rs.next()) {
sql.closeDB();
return true;
}
} catch (SQLException e) {
e.printStackTrace();
}
sql.closeDB();
return false;
}
public Boolean register(String username, String password) {
// 获取Sql查询语句
String regSql = "insert into users(username,password)" + " values("+username+","+password+")";
// 获取DB对象
DBManager sql = DBManager.createInstance();
sql.connectDB();
int ret = sql.executeUpdate(regSql);
if (ret != 0) {
sql.closeDB();
return true;
}
sql.closeDB();
return false;
}
}
③ LogLet.java (servlet文件)
package com.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.service.Service;
/**
* Servlet implementation class LogLet
*/
public class LogLet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//接受客户端信息
String username = request.getParameter("username");
username = new String(username.getBytes("ISO-8859-1"),"UTF-8");
String password = request.getParameter("password");
password = new String(password.getBytes("ISO-8859-1"),"UTF-8");
System.out.println(username + ":" + password);
//新建服务对象
Service service = new Service();
//验证处理
boolean log = service.login(username, password);
if (log) {
System.out.println("log success");
}else {
System.out.println("log fail");
}
//返回信息到客户端
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html");
PrintWriter out = response.getWriter();
if (log) {
out.print("用户名:" + username);
out.print("密码:" + password);
}else {
out.print("false");
}
out.flush();
out.close();
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
④ RegLet.java (Servlet文件)
package com.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.service.Service;
/**
* Servlet implementation class RegLet
*/
public class RegLet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
username = new String(username.getBytes("ISO-8859-1"),"UTF-8");
String password = request.getParameter("password");
password = new String(password.getBytes("ISO-8859-1"),"UTF-8");
System.out.println(username + ":" + password);
//新建服务对象
Service service = new Service();
//验证处理
boolean reg = service.register(username, password);
if( reg ){
System.out.println("reg success");
//request.getSession().setAttribute("username", username);
}else{
System.out.println("reg fail");
}
//返回信息到客户端
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html");
PrintWriter out = response.getWriter();
if(reg){
out.print("true");
}else{
out.print("false");
}
out.flush();
out.close();
}
}
四、Android Studio工程
1.工程目录
2.AndoridManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.goa">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:networkSecurityConfig="@xml/network_security_config">
<activity android:name=".main.RegisterActivity"></activity>
<activity android:name=".main.LoginActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".MainActivity">
</activity>
</application>
</manifest>
注意:获取联网权限:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
3.xml布局文件代码
① activity_login.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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=".main.LoginActivity"
android:orientation="vertical"
android:layout_margin="20dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:id="@+id/text_login"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="账号:"
android:textSize="20dp"
android:gravity="center_vertical"/>
<EditText
android:id="@+id/et_username"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toRightOf="@+id/text_login"/>
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:id="@+id/text_password"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="密码:"
android:textSize="20dp"
android:gravity="center_vertical"/>
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toRightOf="@+id/text_password"/>
</RelativeLayout>
<Button
android:id="@+id/btn_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="登录"
android:layout_gravity="center"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:id="@+id/text_return"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="返回信息:"
android:textSize="20dp"
android:gravity="center_vertical"/>
<TextView
android:id="@+id/return_text"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:layout_toRightOf="@id/text_return"
android:gravity="center_vertical"
android:textSize="20dp"/>
</RelativeLayout>
<TextView
android:id="@+id/text_to_register"
android:layout_marginTop="30dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="没有账号?点击注册"
android:layout_gravity="center"/>
</LinearLayout>
② activity_register.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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=".main.RegisterActivity"
android:orientation="vertical"
android:layout_margin="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="注册页面"
android:textSize="30dp"
android:layout_gravity="center"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:id="@+id/text_username"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="用户名:"
android:textSize="20dp"
android:gravity="center_vertical"/>
<EditText
android:id="@+id/re_et_username"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toRightOf="@+id/text_username"/>
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:id="@+id/text_register"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="密码:"
android:textSize="20dp"
android:gravity="center_vertical"/>
<EditText
android:id="@+id/re_et_password"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toRightOf="@+id/text_register"/>
</RelativeLayout>
<Button
android:id="@+id/btn_register"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="注册"
android:layout_gravity="center"/>
</LinearLayout>
4.java文件代码
① WebServiceGet.java
package com.example.goa.web;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
/**
* 使用get方法获取Http服务器数据
*/
public class WebServiceGet {
public static String executeHttpGet(String username,String password,String address){
HttpURLConnection connection = null;
InputStream in = null;
try{
String Url = "http://119.3.108.194/GOAService/" + address;
String path = Url + "?username=" + username + "&password=" + password;
try {
URL url = new URL(path);
connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);//建立连接超时
connection.setReadTimeout(8000);//传递数据超时
in = connection.getInputStream();
return parseInfo(in);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}catch (Exception e){
e.printStackTrace();
}finally {
//意外退出时,连接关闭保护
if(connection != null){
connection.disconnect();
}
if(in != null){
try{
in.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
return null;
}
//得到字节输入流,将字节输入流转化为String类型
public static String parseInfo(InputStream inputStream){
BufferedReader reader = null;
String line = "";
StringBuilder response = new StringBuilder();
try {
reader = new BufferedReader(new InputStreamReader(inputStream));
while((line = reader.readLine()) != null){
response.append(line);
}
return response.toString();
}catch (Exception e){
e.printStackTrace();
}finally {
if(reader != null){
try{
reader.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
return null;
}
}
② WebServicePost.java
package com.example.goa.web;
import android.util.Log;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
public class WebServicePost {
public static String executeHttpPost(String username,String password,String address){
HttpURLConnection connection = null;
InputStream in = null;
try{
String Url = "http:119.3.108.194/GOAService/" + address;
try {
URL url = new URL(Url);
connection = (HttpURLConnection)url.openConnection();
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setReadTimeout(8000);//传递数据超时
connection.setUseCaches(false);
connection.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
connection.connect();
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
String data = "username=" + URLEncoder.encode(username,"UTF-8") + "&password=" + URLEncoder.encode(password,"UTF-8");
out.writeBytes(data);
out.flush();
out.close();
int resultCode = connection.getResponseCode();
if(HttpURLConnection.HTTP_OK == resultCode) {
in = connection.getInputStream();
return parseInfo(in);
}
return null;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}catch (Exception e){
e.printStackTrace();
}finally {
//意外退出时,连接关闭保护
if(connection != null){
connection.disconnect();
}
if(in != null){
try{
in.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
return null;
}
//得到字节输入流,将字节输入流转化为String类型
public static String parseInfo(InputStream inputStream){
BufferedReader reader = null;
String line = "";
StringBuilder response = new StringBuilder();
try {
reader = new BufferedReader(new InputStreamReader(inputStream));
while((line = reader.readLine()) != null){
Log.d("RegisterActivity",line);
response.append(line);
}
Log.d("RegisterActivity","response.toString():"+response.toString());
return response.toString();
}catch (Exception e){
e.printStackTrace();
}finally {
if(reader != null){
try{
reader.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
return null;
}
}
5.Activity代码
① LoginActivity
package com.example.goa.main;
import androidx.appcompat.app.AppCompatActivity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.example.goa.R;
import com.example.goa.web.WebServiceGet;
public class LoginActivity extends AppCompatActivity implements View.OnClickListener{
private EditText username;
private EditText password;
private Button login;
private TextView info;
private TextView register;
//提示框
private ProgressDialog dialog;
//服务器返回的数据
private String infoString;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
//初始化信息
username = findViewById(R.id.et_username);
password = findViewById(R.id.et_password);
login = findViewById(R.id.btn_login);
info = findViewById(R.id.return_text);
register = findViewById(R.id.text_to_register);
//设置按钮监听器
login.setOnClickListener(this);
register.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_login:
//设置提示框
dialog = new ProgressDialog(LoginActivity.this);
dialog.setTitle("正在登录");
dialog.setMessage("请稍后");
dialog.setCancelable(false);//设置可以通过back键取消
dialog.show();
//设置子线程,分别进行Get和Post传输数据
new Thread(new MyThread()).start();
break;
case R.id.text_to_register:
//跳转注册页面
Intent intent = new Intent(LoginActivity.this,RegisterActivity.class);
startActivity(intent);
break;
}
}
public class MyThread implements Runnable{
@Override
public void run() {
infoString = WebServiceGet.executeHttpGet(username.getText().toString(),password.getText().toString(),"LogLet");//获取服务器返回的数据
//更新UI,使用runOnUiThread()方法
showResponse(infoString);
}
}
private void showResponse(final String response){
runOnUiThread(new Runnable() {
//更新UI
@Override
public void run() {
if(response.equals("false")){
Toast.makeText(LoginActivity.this,"登陆失败!", Toast.LENGTH_SHORT).show();
}else {
info.setText(response);
}
dialog.dismiss();
}
});
}
}
② RegisterActivity
package com.example.goa.main;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import com.example.goa.R;
import com.example.goa.web.WebServicePost;
public class RegisterActivity extends AppCompatActivity implements View.OnClickListener {
private EditText regUseName;
private EditText regPassWord;
private Button btn_reg;
ProgressDialog dialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
ActionBar ac = getSupportActionBar();
ac.setTitle("注册");
regUseName = (EditText)findViewById(R.id.re_et_username);
regPassWord = findViewById(R.id.re_et_password);
btn_reg = findViewById(R.id.btn_register);
btn_reg.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_register:
dialog = new ProgressDialog(RegisterActivity.this);
dialog.setTitle("正在注册");
dialog.setMessage("请稍后");
dialog.show();
new Thread(new RegThread()).start();
break;
}
}
public class RegThread implements Runnable{
@Override
public void run() {
//获取服务器返回数据
//String RegRet = WebServiceGet.executeHttpGet(regUserName.getText().toString(),regPassWord.getText().toString(),"RegLet");
String RegRet = WebServicePost.executeHttpPost(regUseName.getText().toString(),regPassWord.getText().toString(),"RegLet");
//更新UI,界面处理
showReq(RegRet);
}
}
private void showReq(final String RegRet){
runOnUiThread(new Runnable() {
@Override
public void run() {
if(RegRet.equals("true")){
dialog.dismiss();
AlertDialog.Builder builder = new AlertDialog.Builder(RegisterActivity.this);
builder.setTitle("注册信息");
builder.setMessage("注册成功");
builder.setCancelable(false);
builder.setPositiveButton("OK",new DialogInterface.OnClickListener(){
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Intent intent = new Intent(RegisterActivity.this,LoginActivity.class);
startActivity(intent);
}
});
builder.show();
}else{
dialog.dismiss();
AlertDialog.Builder builder = new AlertDialog.Builder(RegisterActivity.this);
builder.setTitle("注册信息");
builder.setMessage("注册失败");
builder.setCancelable(false);
builder.setPositiveButton("OK",new DialogInterface.OnClickListener(){
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Intent intent = new Intent(RegisterActivity.this,LoginActivity.class);
startActivity(intent);
}
});
builder.show();
}
}
});
}
}
6. 因为我的Tomcat没有配置Https,在WebServiceGet以及WebServicePost中不能用Https来访问服务器,只能用http,在Android P系统的设备上,如果应用使用的是非加密的明文流量的http网络请求,则会导致该应用无法进行网络请求,https则不会受影响,同样地,如果应用嵌套了webview,webview也只能使用https请求。
针对这个问题,有以下三种解决方法:
(1)APP改用https请求
(2)targetSdkVersion 降到27或以下
(3)更改网络安全配置
但是有些情况下,我们可能无法实现前两种方法。
比如第一种,可能api是第三方提供的,我们暂时无法让他配置为https方式。
第二种,受限于应用商店的要求,虽然他的公告中写的是2019年8月不在接受新应用低于28,2019年11月不再接受更新应用的targetSdkVersion小于28。google play store 已经无法上传targetSdkVersion<28的应用。按这个趋势,国内的商店基本都会在google最后期限的半年后也会限制targetSdkVersion。所以还是赶紧适配吧。
下面具体说说第三种方法,更改网络安全配置。
① 在res文件夹下创建一个xml文件夹,然后创建一个network_security_config.xml文件,文件内容如下:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
② 在AndroidManifest.xml文件下的application标签增加以下属性:
<application
...
android:networkSecurityConfig="@xml/network_security_config"
...
/>
在服务器上导入sql文件,部署web工程之后就能成功的让Android应用通过Http连接MySql数据库了。