利用共享参数实现记住密码,只能记住一个用户的登录信息,并且手机号码跟密码不存在从属关系,如果换个手机号码登录,前一个用户的登录信息就被覆盖了。
真正的记住密码功能是先输入手机号码,然后根据手机号匹配保存的密码,一个密码对应一个手机号码,从而实现具体手机号码的密码记忆功能。
运用SQLite技术分条存储不同用户的登录信息,并提供根据手机号码查找登录信息的方法,这样可以同时记住多个手机号码的密码。
利用SQLite记住密码有以下三处改造:
(1)声明一个用户数据库的帮助器对象,然后在活动页面的onResume方法中打开数据库连接,在onPasue方法中关闭数据库连接
(2)登录成功时,如果用户勾选了“记住密码”,就使用数据库保存手机号码与密码在内的登录信息。
(3)再次打开登录页面,用户输入手机号完毕后点击密码输入框时,App到数据库中根据手机号查找登录记录,并将记录结果中的密码填入密码框。
布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp" >
<RadioGroup
android:id="@+id/rg_login"
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal" >
<RadioButton
android:id="@+id/rb_password"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:checked="true"
android:gravity="left|center"
android:text="密码登录"
android:textColor="@color/black"
android:textSize="17sp" />
<RadioButton
android:id="@+id/rb_verifycode"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:checked="false"
android:gravity="left|center"
android:text="验证码登录"
android:textColor="@color/black"
android:textSize="17sp" />
</RadioGroup>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp" >
<TextView
android:id="@+id/tv_phone"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:gravity="center"
android:text="手机号码:"
android:textColor="@color/black"
android:textSize="17sp" />
<EditText
android:id="@+id/et_phone"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:layout_toRightOf="@+id/tv_phone"
android:background="@drawable/editext_selector"
android:gravity="left|center"
android:hint="请输入手机号码"
android:inputType="number"
android:maxLength="11"
android:textColor="@color/black"
android:textSize="17sp" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp" >
<TextView
android:id="@+id/tv_password"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:gravity="center"
android:text="登录密码:"
android:textColor="@color/black"
android:textSize="17sp" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toRightOf="@+id/tv_password" >
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:background="@drawable/editext_selector"
android:gravity="left|center"
android:hint="请输入密码"
android:inputType="numberPassword"
android:maxLength="6"
android:textColor="@color/black"
android:textSize="17sp" />
<Button
android:id="@+id/btn_forget"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:gravity="center"
android:text="忘记密码"
android:textColor="@color/black"
android:textSize="17sp" />
</RelativeLayout>
</RelativeLayout>
<CheckBox
android:id="@+id/ck_remember"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"
android:padding="10dp"
android:text="记住密码"
android:textColor="@color/black"
android:textSize="17sp" />
<Button
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="登 录"
android:textColor="@color/black"
android:textSize="20sp" />
</LinearLayout>
LoginSQLiteActivity
package com.example.myapplication;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.view.View.OnFocusChangeListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.example.myapplication.bean.UserInfo;
import com.example.myapplication.database.UserDBHelper;
import java.util.Random;
@SuppressLint("DefaultLocale")
public class LoginSQLiteActivity extends AppCompatActivity implements View.OnClickListener, OnFocusChangeListener
{
private RadioGroup rg_login; // 声明一个单选组对象
private RadioButton rb_password; // 声明一个单选按钮对象
private RadioButton rb_verifycode; // 声明一个单选按钮对象
private EditText et_phone; // 声明一个编辑框对象
private TextView tv_password; // 声明一个文本视图对象
private EditText et_password; // 声明一个编辑框对象
private Button btn_forget; // 声明一个按钮控件对象
private CheckBox ck_remember; // 声明一个复选框对象
private int mRequestCode = 0; // 跳转页面时的请求代码
private boolean isRemember = false; // 是否记住密码
private String mPassword = "111111"; // 默认密码
private String mVerifyCode; // 验证码
private UserDBHelper mHelper; // 声明一个用户数据库的帮助器对象
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login_sqlite);
rg_login = findViewById(R.id.rg_login);
rb_password = findViewById(R.id.rb_password);
rb_verifycode = findViewById(R.id.rb_verifycode);
et_phone = findViewById(R.id.et_phone);
tv_password = findViewById(R.id.tv_password);
et_password = findViewById(R.id.et_password);
btn_forget = findViewById(R.id.btn_forget);
ck_remember = findViewById(R.id.ck_remember);
// 给rg_login设置单选监听器
rg_login.setOnCheckedChangeListener(new RadioListener());
// 给ck_remember设置勾选监听器
ck_remember.setOnCheckedChangeListener(new CheckListener());
// 给et_phone添加文本变更监听器
et_phone.addTextChangedListener(new HideTextWatcher(et_phone, 11));
// 给et_password添加文本变更监听器
et_password.addTextChangedListener(new HideTextWatcher(et_password, 6));
btn_forget.setOnClickListener(this);
findViewById(R.id.btn_login).setOnClickListener(this);
// 给密码编辑框注册一个焦点变化监听器,一旦焦点发生变化,就触发监听器的onFocusChange方法
et_password.setOnFocusChangeListener(this);
}
// 定义登录方式的单选监听器
private class RadioListener implements RadioGroup.OnCheckedChangeListener
{
@Override
public void onCheckedChanged(RadioGroup group, int checkedId)
{
if (checkedId == R.id.rb_password) { // 选择了密码登录
tv_password.setText("登录密码:");
et_password.setHint("请输入密码");
btn_forget.setText("忘记密码");
ck_remember.setVisibility(View.VISIBLE);
}
else if (checkedId == R.id.rb_verifycode) { // 选择了验证码登录
tv_password.setText(" 验证码:");
et_password.setHint("请输入验证码");
btn_forget.setText("获取验证码");
ck_remember.setVisibility(View.GONE);
}
}
}
// 定义是否记住密码的勾选监听器
private class CheckListener implements CompoundButton.OnCheckedChangeListener
{
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
{
if (buttonView.getId() == R.id.ck_remember)
{
isRemember = isChecked;
}
}
}
// 定义一个编辑框监听器,在输入文本达到指定长度时自动隐藏输入法
private class HideTextWatcher implements TextWatcher
{
private EditText mView; // 声明一个编辑框对象
private int mMaxLength; // 声明一个最大长度变量
public HideTextWatcher(EditText v, int maxLength)
{
super();
mView = v;
mMaxLength = maxLength;
}
// 在编辑框的输入文本变化前触发
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
// 在编辑框的输入文本变化时触发
public void onTextChanged(CharSequence s, int start, int before, int count) {}
// 在编辑框的输入文本变化后触发
public void afterTextChanged(Editable s)
{
String str = s.toString(); // 获得已输入的文本字符串
// 输入文本达到11位(如手机号码),或者达到6位(如登录密码)时关闭输入法
if ((str.length() == 11 && mMaxLength == 11) || (str.length() == 6 && mMaxLength == 6))
{
ViewUtil.hideOneInputMethod(LoginSQLiteActivity.this, mView); // 隐藏输入法软键盘
}
}
}
@Override
public void onClick(View v)
{
String phone = et_phone.getText().toString();
if (v.getId() == R.id.btn_forget) { // 点击了“忘记密码”按钮
if (phone.length() < 11) { // 手机号码不足11位
Toast.makeText(this, "请输入正确的手机号", Toast.LENGTH_SHORT).show();
return;
}
if (rb_password.isChecked()) { // 选择了密码方式校验,此时要跳到找回密码页面
// 以下携带手机号码跳转到找回密码页面
Intent intent = new Intent(this, LoginForgetActivity.class);
intent.putExtra("phone", phone);
startActivityForResult(intent, mRequestCode); // 携带意图返回上一个页面
}
else if (rb_verifycode.isChecked()) { // 选择了验证码方式校验,此时要生成六位随机数字验证码
// 生成六位随机数字的验证码
mVerifyCode = String.format("%06d", new Random().nextInt(999999));
// 以下弹出提醒对话框,提示用户记住六位验证码数字
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("请记住验证码");
builder.setMessage("手机号" + phone + ",本次验证码是" + mVerifyCode + ",请输入验证码");
builder.setPositiveButton("好的", null);
AlertDialog alert = builder.create();
alert.show(); // 显示提醒对话框
}
}
else if (v.getId() == R.id.btn_login) { // 点击了“登录”按钮
if (phone.length() < 11) { // 手机号码不足11位
Toast.makeText(this, "请输入正确的手机号", Toast.LENGTH_SHORT).show();
return;
}
if (rb_password.isChecked()) { // 密码方式校验
if (!et_password.getText().toString().equals(mPassword)) {
Toast.makeText(this, "请输入正确的密码", Toast.LENGTH_SHORT).show();
} else { // 密码校验通过
loginSuccess(); // 提示用户登录成功
}
} else if (rb_verifycode.isChecked()) { // 验证码方式校验
if (!et_password.getText().toString().equals(mVerifyCode)) {
Toast.makeText(this, "请输入正确的验证码", Toast.LENGTH_SHORT).show();
} else { // 验证码校验通过
loginSuccess(); // 提示用户登录成功
}
}
}
}
// 从下一个页面携带参数返回当前页面时触发
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == mRequestCode && data != null)
{
// 用户密码已改为新密码,故更新密码变量
mPassword = data.getStringExtra("new_password");
}
}
// 从修改密码页面返回登录页面,要清空密码的输入框
@Override
protected void onRestart() {
super.onRestart();
et_password.setText("");
}
@Override
protected void onResume() {
super.onResume();
mHelper = UserDBHelper.getInstance(this, 1); // 获得用户数据库帮助器的实例
mHelper.openWriteLink(); // 恢复页面,则打开数据库连接
}
@Override
protected void onPause() {
super.onPause();
mHelper.closeLink(); // 暂停页面,则关闭数据库连接
}
// 校验通过,登录成功
private void loginSuccess() {
String desc = String.format("您的手机号码是%s,恭喜你通过登录验证,点击“确定”按钮返回上个页面",
et_phone.getText().toString());
// 以下弹出提醒对话框,提示用户登录成功
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("登录成功");
builder.setMessage(desc);
builder.setPositiveButton("确定返回", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish(); // 结束当前的活动页面
}
});
builder.setNegativeButton("我再看看", null);
AlertDialog alert = builder.create();
alert.show();
// 如果勾选了“记住密码”,则把手机号码和密码保存为数据库的用户表记录
if (isRemember)
{
UserInfo info = new UserInfo(); // 创建一个用户信息对象
info.phone = et_phone.getText().toString();
info.password = et_password.getText().toString();
info.update_time = DateUtil.getNowDateTime("yyyy-MM-dd HH:mm:ss");
mHelper.insert(info); // 往用户数据库添加登录成功的用户信息
}
}
// 焦点变更事件的处理方法,hasFocus表示当前控件是否获得焦点。
// 为什么光标进入密码框事件不选onClick?因为要点两下才会触发onClick动作(第一下是切换焦点动作)
@Override
public void onFocusChange(View v, boolean hasFocus) {
String phone = et_phone.getText().toString();
// 判断是否是密码编辑框发生焦点变化
if (v.getId() == R.id.et_password) {
// 用户已输入手机号码,且密码框获得焦点
if (phone.length() > 0 && hasFocus) {
// 根据手机号码到数据库中查询用户记录
UserInfo info = mHelper.queryByPhone(phone);
if (info != null) {
// 找到用户记录,则自动在密码框中填写该用户的密码
et_password.setText(info.password);
}
}
}
}
}
UserInfo
package com.example.myapplication.bean;
//用户信息
public class UserInfo
{
public long rowid; // 行号
public int xuhao; // 序号
public String name; // 姓名
public int age; // 年龄
public long height; // 身高
public float weight; // 体重
public boolean married; // 婚否
public String update_time; // 更新时间
public String phone; // 手机号
public String password; // 密码
public UserInfo()
{
rowid = 0L;
xuhao = 0;
name = "";
age = 0;
height = 0L;
weight = 0.0f;
married = false;
update_time = "";
phone = "";
password = "";
}
}
UserDBHelper
package com.example.myapplication.database;
import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import com.example.myapplication.bean.UserInfo;
import java.util.ArrayList;
import java.util.List;
@SuppressLint("DefaultLocale")
public class UserDBHelper extends SQLiteOpenHelper
{
private static final String TAG = "UserDBHelper";
private static final String DB_NAME = "user.db"; // 数据库的名称
private static final int DB_VERSION = 1; // 数据库的版本号
private static UserDBHelper mHelper = null; // 数据库帮助器的实例
private SQLiteDatabase mDB = null; // 数据库的实例
public static final String TABLE_NAME = "user_info"; // 表的名称
private UserDBHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
private UserDBHelper(Context context, int version) {
super(context, DB_NAME, null, version);
}
// 利用单例模式获取数据库帮助器的唯一实例
public static UserDBHelper getInstance(Context context, int version)
{
if (version > 0 && mHelper == null)
{
mHelper = new UserDBHelper(context, version);
}
else if (mHelper == null)
{
mHelper = new UserDBHelper(context);
}
return mHelper;
}
// 打开数据库的读连接
public SQLiteDatabase openReadLink()
{
if (mDB == null || !mDB.isOpen())
{
mDB = mHelper.getReadableDatabase();
}
return mDB;
}
// 打开数据库的写连接
public SQLiteDatabase openWriteLink()
{
if (mDB == null || !mDB.isOpen())
{
mDB = mHelper.getWritableDatabase();
}
return mDB;
}
// 关闭数据库连接
public void closeLink()
{
if (mDB != null && mDB.isOpen())
{
mDB.close();
mDB = null;
}
}
// 创建数据库,执行建表语句
public void onCreate(SQLiteDatabase db)
{
Log.d(TAG, "onCreate");
String drop_sql = "DROP TABLE IF EXISTS " + TABLE_NAME + ";";
Log.d(TAG, "drop_sql:" + drop_sql);
db.execSQL(drop_sql);
String create_sql = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
+ "name VARCHAR NOT NULL," + "age INTEGER NOT NULL,"
+ "height INTEGER NOT NULL," + "weight FLOAT NOT NULL,"
+ "married INTEGER NOT NULL," + "update_time VARCHAR NOT NULL"
//演示数据库升级时要先把下面这行注释
+ ",phone VARCHAR" + ",password VARCHAR"
+ ");";
Log.d(TAG, "create_sql:" + create_sql);
db.execSQL(create_sql); // 执行完整的SQL语句
}
// 升级数据库,执行表结构变更语句
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.d(TAG, "onUpgrade oldVersion=" + oldVersion + ", newVersion=" + newVersion);
if (newVersion > 1) {
//Android的ALTER命令不支持一次添加多列,只能分多次添加
String alter_sql = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN " + "phone VARCHAR;";
Log.d(TAG, "alter_sql:" + alter_sql);
db.execSQL(alter_sql);
alter_sql = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN " + "password VARCHAR;";
Log.d(TAG, "alter_sql:" + alter_sql);
db.execSQL(alter_sql); // 执行完整的SQL语句
}
}
// 根据指定条件删除表记录
public int delete(String condition)
{
// 执行删除记录动作,该语句返回删除记录的数目
return mDB.delete(TABLE_NAME, condition, null);
}
// 删除该表的所有记录
public int deleteAll()
{
// 执行删除记录动作,该语句返回删除记录的数目
return mDB.delete(TABLE_NAME, "1=1", null);
}
// 往该表添加一条记录
public long insert(UserInfo info)
{
List<UserInfo> infoList = new ArrayList<UserInfo>();
infoList.add(info);
return insert(infoList);
}
// 往该表添加多条记录
public long insert(List<UserInfo> infoList)
{
long result = -1;
for (int i = 0; i < infoList.size(); i++)
{
UserInfo info = infoList.get(i);
List<UserInfo> tempList = new ArrayList<UserInfo>();
// 如果存在同名记录,则更新记录
// 注意条件语句的等号后面要用单引号括起来
if (info.name != null && info.name.length() > 0)
{
String condition = String.format("name='%s'", info.name);
tempList = query(condition);
if (tempList.size() > 0)
{
update(info, condition);
result = tempList.get(0).rowid;
continue;
}
}
// 如果存在同样的手机号码,则更新记录
if (info.phone != null && info.phone.length() > 0)
{
String condition = String.format("phone='%s'", info.phone);
tempList = query(condition);
if (tempList.size() > 0)
{
update(info, condition);
result = tempList.get(0).rowid;
continue;
}
}
// 不存在唯一性重复的记录,则插入新记录
ContentValues cv = new ContentValues();
cv.put("name", info.name);
cv.put("age", info.age);
cv.put("height", info.height);
cv.put("weight", info.weight);
cv.put("married", info.married);
cv.put("update_time", info.update_time);
cv.put("phone", info.phone);
cv.put("password", info.password);
// 执行插入记录动作,该语句返回插入记录的行号
result = mDB.insert(TABLE_NAME, "", cv);
if (result == -1) { // 添加成功则返回行号,添加失败则返回-1
return result;
}
}
return result;
}
// 根据条件更新指定的表记录
public int update(UserInfo info, String condition)
{
ContentValues cv = new ContentValues();
cv.put("name", info.name);
cv.put("age", info.age);
cv.put("height", info.height);
cv.put("weight", info.weight);
cv.put("married", info.married);
cv.put("update_time", info.update_time);
cv.put("phone", info.phone);
cv.put("password", info.password);
// 执行更新记录动作,该语句返回更新的记录数量
return mDB.update(TABLE_NAME, cv, condition, null);
}
public int update(UserInfo info)
{
// 执行更新记录动作,该语句返回更新的记录数量
return update(info, "rowid=" + info.rowid);
}
// 根据指定条件查询记录,并返回结果数据列表
public List<UserInfo> query(String condition)
{
String sql = String.format("select rowid,_id,name,age,height,weight,married,update_time," +
"phone,password from %s where %s;", TABLE_NAME, condition);
Log.d(TAG, "query sql: " + sql);
List<UserInfo> infoList = new ArrayList<UserInfo>();
// 执行记录查询动作,该语句返回结果集的游标
Cursor cursor = mDB.rawQuery(sql, null);
// 循环取出游标指向的每条记录
while (cursor.moveToNext()) {
UserInfo info = new UserInfo();
info.rowid = cursor.getLong(0); // 取出长整型数
info.xuhao = cursor.getInt(1); // 取出整型数
info.name = cursor.getString(2); // 取出字符串
info.age = cursor.getInt(3); // 取出整型数
info.height = cursor.getLong(4); // 取出长整型数
info.weight = cursor.getFloat(5); // 取出浮点数
//SQLite没有布尔型,用0表示false,用1表示true
info.married = (cursor.getInt(6) == 0) ? false : true;
info.update_time = cursor.getString(7); // 取出字符串
info.phone = cursor.getString(8); // 取出字符串
info.password = cursor.getString(9); // 取出字符串
infoList.add(info);
}
cursor.close(); // 查询完毕,关闭数据库游标
return infoList;
}
// 根据手机号码查询指定记录
public UserInfo queryByPhone(String phone)
{
UserInfo info = null;
List<UserInfo> infoList = query(String.format("phone='%s'", phone));
if (infoList.size() > 0) { // 存在该号码的登录信息
info = infoList.get(0);
}
return info;
}
}