安卓开发之图片分享应用2:注册功能的实现(已更新数据库的表结构)
一、开发过程中踩中的坑以及解决方案
1. 出现java.lang.NullPointerException: Attempt to invoke interface method 'java.sql.PreparedStatement java.sql.Connection.prepareStatement(java.lang.String)' on a null object reference
2022-08-21 15:57:45.809 6176-6176/com.example.pictureapp I/System.out: -----------创建连接失败
2022-08-21 15:57:45.809 6176-6176/com.example.pictureapp I/System.out: ------null
2022-08-21 15:57:45.809 6176-6176/com.example.pictureapp W/System.err: java.lang.NullPointerException: Attempt to invoke interface method 'java.sql.PreparedStatement java.sql.Connection.prepareStatement(java.lang.String)' on a null object reference
2022-08-21 15:57:45.809 6176-6176/com.example.pictureapp W/System.err: at com.example.pictureapp.dao.UserDao.findUser(UserDao.java:25)
2022-08-21 15:57:45.809 6176-6176/com.example.pictureapp W/System.err: at com.example.pictureapp.RegisterActivity$1.onClick(RegisterActivity.java:35)
2022-08-21 15:57:45.809 6176-6176/com.example.pictureapp W/System.err: at android.view.View.performClick(View.java:6597)
2022-08-21 15:57:45.809 6176-6176/com.example.pictureapp W/System.err: at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1119)
2022-08-21 15:57:45.809 6176-6176/com.example.pictureapp W/System.err: at android.view.View.performClickInternal(View.java:6574)
2022-08-21 15:57:45.809 6176-6176/com.example.pictureapp W/System.err: at android.view.View.access$3100(View.java:778)
2022-08-21 15:57:45.809 6176-6176/com.example.pictureapp W/System.err: at android.view.View$PerformClick.run(View.java:25885)
2022-08-21 15:57:45.809 6176-6176/com.example.pictureapp W/System.err: at android.os.Handler.handleCallback(Handler.java:873)
2022-08-21 15:57:45.809 6176-6176/com.example.pictureapp W/System.err: at android.os.Handler.dispatchMessage(Handler.java:99)
2022-08-21 15:57:45.809 6176-6176/com.example.pictureapp W/System.err: at android.os.Looper.loop(Looper.java:193)
2022-08-21 15:57:45.809 6176-6176/com.example.pictureapp W/System.err: at android.app.ActivityThread.main(ActivityThread.java:6669)
2022-08-21 15:57:45.809 6176-6176/com.example.pictureapp W/System.err: at java.lang.reflect.Method.invoke(Native Method)
2022-08-21 15:57:45.809 6176-6176/com.example.pictureapp W/System.err: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:533)
2022-08-21 15:57:45.809 6176-6176/com.example.pictureapp W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:861)
问题分析: 按道理说根据异常提示应该是connection
为空,我输出了一下确实为空,但是上一篇博客我也是使用该connection
连接数据库来进行登录,而没有报错,为什么到了这里就不行了呢,忽然想到忘记将这次连接数据库放在子线程里执行,android4.0以上连接数据库不能在主线程执行,要另外来一个子线程去执行,在主线程上执行就会报以上的异常。
解决方案: 将连接数据库的代码放在一个子线程里执行即可
举个栗子:
//在线程中调用数据库
Thread t2 = new Thread(new Runnable() {
public void run() {
//这个就是连接数据库的代码,需要放在子线程里执行
UserDao.UserRegister(inputNickname,inputUsername,inputPassword,1);
}
});
t2.start();
//在数据库连接完成之前暂停其他活动
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
二、建立数据库以及用户信息表
1. 建立数据库
2. 建立用户信息表语句(已更新表结构)
建完数据库之后,在刚刚建立的数据库中建立用户信息表
create table users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
nickname varchar(14) not null,
username varchar(100) not null,
password varchar(100) not null,
sex int not null
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
3. 往用户信息表插入信息
我们往该用户信息表中插入一条用户信息,该用户用来测试重复注册
功能。
insert into users(nickname,username,password,sex)
VALUES ('熏悟空','admin','666666',1);
三、注册功能的实现
效果演示
1. 创建RegisterActivity
基于上篇博客的app项目安卓开发之图片分享应用1:登录功能, 我们接着鼠标右键点击app->New->Activity->Empty Activity创建一个空Activity,取名为RegisterActivity, 如下图所示
创建好之后就会发现layout布局里面也多了一个activity_register.xml
注册页面布局
2. 对注册页面进行布局
activity_register.xml:
其中@drawable/register_color
是一个渐变色背景颜色,在后面有其代码。
<?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"
android:background="@drawable/register_color">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginLeft="40dp"
android:layout_marginRight="40dp"
android:background="@color/white">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="40dp"
android:layout_marginLeft="40dp"
android:layout_marginRight="40dp"
android:layout_marginBottom="40dp">
<EditText
android:id="@+id/re_nickname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="5dp"
android:hint="请输入昵称"
android:maxLines="1"/>
<EditText
android:id="@+id/re_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="5dp"
android:hint="请输入账号"
android:maxLines="1"/>
<EditText
android:id="@+id/re_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:drawablePadding="5dp"
android:hint="请输入密码"
android:maxLines="1"
android:inputType="textPassword"/>
<EditText
android:id="@+id/re_affirm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:drawablePadding="5dp"
android:hint="请确认密码"
android:maxLines="1"
android:inputType="textPassword"/>
<Button
android:id="@+id/re_register"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:text="注册"
android:textSize="18sp"
android:textColor="@color/white"
android:background="@color/克莱因蓝"/>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
register_color.xml:
在drawable
文件夹里创建一个xml
文件,里面填写下面代码即可。
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 登录背景渐变 -->
<gradient
android:startColor="@color/teal_200"
android:endColor="@color/white"
android:angle="270"
/>
</shape>
color.xml:
颜色代码,在value文件夹里面
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="colorDivider">#4DAAA9A9</color>
<color name="colorPrimary">#FF000000</color>
<color name="red">#FFFF0000</color>
<color name="克莱因蓝">#002FA5</color>
<color name="柠檬黄">#F7FC84</color>
<color name="流光白">#F3F0E7</color>
<color name="暮光紫">#A28FA3</color>
<color name="草莓红">#EA6B48</color>
<color name="草朱红">#F2E4E3</color>
<color name="水晶灰">#DDD0C8</color>
<color name="淡紫">#6F6695</color>
<color name="朝白">#CAD8D8</color>
<color name="暗紫">#361F3B</color>
</resources>
3. 对数据库连接池进行改写
因为现在有两个功能了,一个是登录,一个是注册,所以我们需要将登录的SQL语句与连接数据库代码分开。将登录的SQL与注册的SQL放在一起。
文件具体结构如下:
连接数据库代码 DBUtils:
package com.example.pictureapp.dbutils;
import android.util.Log;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DBUtils {
private static final String driver = "com.mysql.jdbc.Driver";
private static final String url = "jdbc:mysql://你的ip地址:3306/picture_app?useUnicode=true&characterEncoding=UTF-8";
private static final String user = "root";
private static final String pwd = "你的密码";
private static Connection conn=null;
/**
* 注册驱动只需执行一次,因此我们放在帮助类的静态初始化块中完成
*/
static{
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
Log.e("驱动","-----------注册驱动失败");
}
}
/**
* 创建数据库连接对象
*/
public static Connection getConnection(){
Connection connection = null;
try {
connection = DriverManager.getConnection(url, user, pwd);
} catch (SQLException e) {
System.out.println("-----------创建连接失败");
}
return connection;
}
/**
* 关闭连接
* 多态的应用:使用Statement接口做参数,既可以传递Statement接口对象,也可以传递PreparedStatement接口对象
*/
public static void close(PreparedStatement statement, Connection connection){
close(null,statement,connection);
}
/**
* 关闭连接
*/
public static void close(ResultSet resultSet, PreparedStatement statement, Connection connection){
try {
if(resultSet!=null && !resultSet.isClosed()){
resultSet.close();
}
if(statement!=null && !statement.isClosed()){
statement.close();
}
if(connection!=null && !connection.isClosed()){
connection.close();
}
}catch (Exception e){
System.out.println("~~~~~关闭数据库连接失败");
}
}
}
UserDao类:
package com.example.pictureapp.dao;
import com.example.pictureapp.dbutils.DBUtils;
import com.example.pictureapp.dto.UserInfo;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class UserDao {
private static Connection conn;
private static PreparedStatement preparedStatement;
private static ResultSet rs;
private static int isLogin = 0;//登录是否成功的判断标志: 0是未登录,1是登录成功,2是登录失败
private static UserInfo user = null;
/**
* 查询某用户是否存在, 根据用户名查找
* @param username 用户名
* @return true表示存在, false表示不存在
*/
public static boolean findUser(String username){
try {
String logSql = "Select * from users where username= '"+username+"' ";
conn = DBUtils.getConnection();
System.out.println("------"+conn);//测试
preparedStatement = conn.prepareStatement(logSql);
rs = preparedStatement.executeQuery(logSql);
// 获取跳转判断
if(rs.next()){
return true;//存在该用户
}else{
return false;//不存在该用户
}
}
catch (Exception e){
e.printStackTrace();
} finally {
//关闭数据库连接
DBUtils.close(rs,preparedStatement,conn);
}
return false;
}
/**
* 用户登录SQL代码
* @param username 用户名
* @param password 密码
*/
public static void UserLogin(String username,String password){
try {
String logSql = "Select * from users where username= '"+username+"' and password= '"+password+"' ";
conn = DBUtils.getConnection();
preparedStatement = conn.prepareStatement(logSql);
rs = preparedStatement.executeQuery(logSql);
// 获取跳转判断
if(rs.next()){
isLogin = 1;
user = new UserInfo();
user.setNickname(rs.getString("nickname"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
user.setSex(rs.getInt("sex"));
}else{
isLogin=2;
}
if(isLogin == 1)
DBUtils.close(rs,preparedStatement,conn);
}
catch (Exception e){
e.printStackTrace();
}
}
/**
* 用户注册SQL代码
* @param nickname 昵称
* @param username 用户名
* @param password 密码
* @param sex 性别: 0表示女生, 1表示男生
*/
public static void UserRegister(String nickname,String username,String password,int sex){
boolean flag = false;
try {
String logSql = "insert into users(nickname,username,password,sex) VALUES ('"+nickname+"','"+username+"','"+password+"',"+sex+")";
conn = DBUtils.getConnection();
preparedStatement = conn.prepareStatement(logSql);
int i = preparedStatement.executeUpdate(); // 如果i>0,表示DML操作是成功的;如果i=0表示DML操作对数据表中的数据没有影响
flag = i>0;
System.out.println("-----flag: "+flag);
}
catch (Exception e){
e.printStackTrace();
} finally {
DBUtils.close(rs,preparedStatement,conn);
}
}
/**
* 获取登录状态
* @return 返回一个登录状态
*/
public static int getIsLogin() {
return isLogin;
}
/**
* 获取登录成功用户的昵称,用于登录成功之后显示
* @return 用户昵称
*/
public static String getNickname(){
return user.getNickname();
}
}
dto
里面的用户信息类
UserInfo:
package com.example.pictureapp.dto;
public class UserInfo {
private int user_id;
private String nickname;//昵称
private String username;//用于登录的用户名
private String password;//用户名密码
private int sex;//性别,0:女,1:男
public UserInfo() {
}
public UserInfo( String nickname, String username, String password, int sex) {
this.nickname = nickname;
this.username = username;
this.password = password;
this.sex = sex;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
}
4. 编写RegisterActivity
package com.example.pictureapp;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.example.pictureapp.dao.UserDao;
public class RegisterActivity extends AppCompatActivity {
private boolean findUser;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
//注册按钮
Button re_register = findViewById(R.id.re_register);
//当点击注册按钮,开始执行注册操作
re_register.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//绑定组件
EditText nickname = findViewById(R.id.re_nickname);//昵称
EditText username = findViewById(R.id.re_username);//用户名
EditText password = findViewById(R.id.re_password);//密码
EditText passwordAffirm = findViewById(R.id.re_affirm);//二次确认密码
//将用户输入的信息取出并转成String类型
String inputNickname = nickname.getText().toString();
String inputUsername = username.getText().toString();
String inputPassword = password.getText().toString();
String inputAffirm = passwordAffirm.getText().toString();
//在线程中调用数据库,根据用户名查询,查看是否已存在该用户,将查询结果赋值给findUser
Thread t1 = new Thread(new Runnable() {
public void run() {
findUser = UserDao.findUser(inputUsername);
}
});
t1.start();
//在数据库连接完成之前暂停其他活动
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果为true则说明已存在该用户,不能进行注册,否则开始注册
if (findUser){
Toast.makeText(RegisterActivity.this,"该账号已存在!!", Toast.LENGTH_SHORT).show();
} else if (inputAffirm.equals(inputPassword)) {//判断两次输入的密码是否一致
//创建账号密码
Thread t2 = new Thread(new Runnable() {
public void run() {
UserDao.UserRegister(inputNickname,inputUsername,inputPassword,1);
}
});
t2.start();
//在数据库连接完成之前暂停其他活动
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//查看注册后数据有没有成功插入数据库,赋值给findUser
t1 = new Thread(new Runnable() {
public void run() {
findUser = UserDao.findUser(inputUsername);
}
});
t1.start();
//在数据库连接完成之前暂停其他活动
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果存在该用户,则说明注册成功,否则注册失败
if (findUser){
Toast.makeText(RegisterActivity.this,"注册成功!!", Toast.LENGTH_SHORT).show();
finish();
} else {
Toast.makeText(RegisterActivity.this,"注册失败,请重试!!", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(RegisterActivity.this,"两次密码不一致", Toast.LENGTH_SHORT).show();
}
}
});
}
}
四、在登录界面放置注册按钮
既然注册功能准备好了,那么就差将登录界面和注册界面联系起来了,联系起来就很容易了,添加少许代码即可。
效果如图所示
1. 修改登陆界面布局
activity_login.xml:
在原来的布局里面添加即可
<TextView
android:id="@+id/register"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="注册"
android:textColor="@color/克莱因蓝"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/password"
app:layout_constraintRight_toRightOf="@id/password"/>
2. 在登录界面点击注册按钮后跳转到注册界面事件
在LoginActivity
里面的OnCreate
方法里面添加
这里显示注册用的是TextView
,而点击后执行跳转的是SpannableString
final TextView register = findViewById(R.id.register);
String text1="注册";
SpannableString spannableString1=new SpannableString(text1);
spannableString1.setSpan(new ClickableSpan() {
@Override
public void onClick(View view) {
Intent intent=new Intent(LoginActivity.this, RegisterActivity.class);
startActivity(intent);
}
}, 0, text1.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
register.setText(spannableString1);
register.setMovementMethod(LinkMovementMethod.getInstance());
因为登录功能与连接数据库代码分开了,所以LoginActivity
这里还需要改写一个地方,改成如图所示即可。
那么登录成功后显示昵称的代码也得改改,在data
里面的LoginDataSource
,改成如图所示即可。
五、修改登录功能部分代码(填坑)
我们重新改写了DBUtils工具类,因为DBUtils中只存放与数据库相关的代码比较好管理,废话不多说,开整!
我们需要在LoginViewModel
里面修改,将之前的改成如下图所示即可,
最后大功告成,启动App即可。
这个注册检验这里就不弄了,就是空账号空密码注册等等这些。
想对点击注册即可跳转到注册页面进一步了解的可以查看相关的资料,谢谢大家的观看。