效果图
思路
实现简单的注册登录(账号密码登录)功能,除了注册登录界面的设计,需要数据库和加密功能的配合。思路就是
- 注册界面注册账号,进行用户数据初始化。
- 对密码进行加密,并存入数据库。
- 登录界面登录账号,进行账号密码验证,并设置是否记住密码
- 密码正确则进入主界面。
特殊情况: - 如果退出登录,那么记住密码选项应该保存,账号密码也保存。
- 如果是第一次登录,则判断数据库中是否有记住密码的账户,有的话则直接登录该用户。
为了解决第一次登录和退出登录的不同,我的解决方法是:新建一个MyApp活动作为入口活动,然后在这个活动中启动Login活动(并发送"first_in"==true),Login活动对此进行判断是否是第一次进入APP。
//MyAPP
public class FoodShareTest extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(FoodShareTest.this, Login.class);
intent.putExtra("first_in", true);
startActivity(intent);
}
}
//Login
//...
//如果是第一次打开并且不是退出登录,则直接登录
if(getIntent().getBooleanExtra("first_in",false)) {
Log.d("food","first_in");
List<User> users = LitePal.findAll(User.class);
for (User u : users) {
if (u.getRemember().equals(1)) {
//登入并存入LoginUser
LoginUser.getInstance().login(u);
//启动主界面
Intent intent1 = new Intent(Login.this, ButtomTab.class);
startActivity(intent1);
toastUtils.showShort(Login.this, "账户" + u.getName() + " 登录成功");
break;
}
}
}
//...
界面设计
登录和注册界面的较为简单,就不多做阐述,代码在
登录界面,注册界面
然后是主界面的退出登录,上一节我们使用了bottomTab,我在设置界面放了退出登录,为了简便测试,你可以直接在MainActicity里面放个button代替。
其他准备
1. 数据库
使用的是郭神的LitePal数据库,操作起来比较方便,可以参考郭神的第一行代码第二版或者参考项目地址。
接着再项目java的com.***.***文件夹内new directory,命名为db,在里面新建一个model文件夹和一个LoginUser类,如下(忽略DBManager):
其中User是model,LoginUser是用来临时存放登录的用户数据,而且用来管理登陆账户较为方便,LoginUser使用单例模式实现
(User类中只有get,set等,而LoginUser有login,logout,update等方法)
User类(此处代码缺少get和set方法,在Android Studio中右键Generate Getter and Setter,然后全选,在ok即可)
package com.foodsharetest.android.db.model;
import android.util.Log;
import androidx.annotation.NonNull;
import org.litepal.crud.LitePalSupport;
/**
LitePal巨坑之一!下面的属性remember本可以设计为boolean值,但是LitePal的boolean值update不了!int尝试也不行!所以只能用Integer代替。
可能如int和boolean这种基本类型可能都是设为不可更改(但是第一次set还是成功了呀。。。后面就失败了)
**/
public class User extends LitePalSupport implements Comparable<User> {
private int id;
private String name;
private String password;
private Integer remember;
private byte[] portrait;
private String region;
private String gender;
private String brithday;
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
", remember=" + remember +
", portrait='" + portrait + '\'' +
", region='" + region + '\'' +
", gender='" + gender + '\'' +
", brithday='" + brithday + '\'' +
'}';
}
//check是传入未MD5加密的
public boolean checkPassword(String str){
// if (remember.equals(0)) str = com.foodsharetest.android.util.MD5.md5(str);
if (password.equals(str)) return true;
else return false;
}
@Override
public boolean equals(Object o) {
if (o != null) {
User UserInfo = (User) o;
return (getId()==UserInfo.getId());
} else {
return false;
}
}
@Override
public int compareTo(@NonNull User User) {
return this.getName().compareTo(User.getName());
}
//缺少getter和setter
}
LoginUser类(同上,需要Generate getter and setter)
package com.foodsharetest.android.db;
import android.app.Application;
import android.util.Log;
import androidx.annotation.Nullable;
import com.foodsharetest.android.db.model.User;
import org.litepal.LitePal;
import java.util.List;
//使用饿汉模式实现单例的登录用户信息记录
//但是此处是存储用户信息的副本,这点是否合适有待考虑,可以考虑使用User类
//LoginUser相对于模拟登陆,并且作为存储数据库的一个缓冲区
public class LoginUser extends Application {
private static LoginUser login_user = new LoginUser();
private static User _user;
private int id;
private String name;
private byte[] portrait;
private String region;
private String gender;
private String brithday;
public static LoginUser getInstance(){
return login_user;
}
public User getUser(){
return _user;
}
//保存至数据库
public void update(){
if(_user.getId()==this.id){
_user.setName(this.name);
_user.setPortrait(this.portrait);
_user.setGender(this.gender);
_user.setRegion(this.region);
_user.setBrithday(this.brithday);
_user.update(_user.getId());
}
}
//重新init
public void reinit(){
login_user.id = _user.getId();
login_user.name = _user.getName();
login_user.portrait = _user.getPortrait();
login_user.region = _user.getRegion();
login_user.gender = _user.getGender();
login_user.brithday = _user.getBrithday();
}
public boolean login(User user) {
_user = user;
login_user.id = user.getId();
login_user.name = user.getName();
login_user.portrait = user.getPortrait();
login_user.region = user.getRegion();
login_user.gender = user.getGender();
login_user.brithday = user.getBrithday();
return true;
}
public static boolean logout(){
if(login_user.id == -1) return false;
login_user.id = -1;
login_user.name = null;
login_user.portrait = null;
login_user.region = null;
login_user.gender = null;
login_user.brithday = null;
return true;
}
@Override
public String toString() {
return "LoginUser{" +
"id=" + id +
", name='" + name + '\'' +
", portrait ='" + portrait + '\'' +
", region='" + region + '\'' +
", gender='" + gender + '\'' +
", brithday='" + brithday + '\'' +
'}';
}
2. 加密
只实现简单的注册登录功能,此处也可以略过,在这里我使用的是MD5加密。网上有特别多的解密代码,这里提供一个:
在java的包文件夹下新建一个utils文件夹,然后在里面新建一个MD5类
代码如下:
package com.foodsharetest.android.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
//MD5加密算法
public class MD5 {
public static String md5(String content) {
byte[] hash;
try {
hash = MessageDigest.getInstance("MD5").digest(content.getBytes());
StringBuilder hex = new StringBuilder(hash.length * 2);
for (byte b : hash) {
if ((b & 0xFF) < 0x10){
hex.append("0");
}
hex.append(Integer.toHexString(b & 0xFF));
}
return hex.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("NoSuchAlgorithmException",e);
}
}
}
活动设计
这也是全部最关键的地方(此处自建了一个ActivityCollector类来方便管理活动,可以直接删掉与这个相关的代码,不影响运行)
1. 注册
直接放代码,注册逻辑较简单。
package com.foodsharetest.android.ui.activity;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.foodsharetest.android.R;
import com.foodsharetest.android.db.model.User;
import com.foodsharetest.android.util.ActivityCollector;
import com.foodsharetest.android.util.MD5;
import com.foodsharetest.android.util.PhotoUtils;
import org.litepal.LitePal;
import org.litepal.tablemanager.Connector;
import java.util.List;
public class Register extends AppCompatActivity implements View.OnClickListener {
private EditText et_account_name,et_password;
private Button register;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCollector.addActivity(this);
setContentView(R.layout.activity_register);
et_account_name = (EditText) findViewById(R.id.et_account_name);
et_password = (EditText) findViewById(R.id.et_password);
register = (Button)findViewById(R.id.register);
Connector.getDatabase();
//注册逻辑实现
register.setOnClickListener(this);
}
@Override
protected void onDestroy(){
super.onDestroy();
ActivityCollector.removeActivity(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.register:
String name = et_account_name.getText().toString();
String password = et_password.getText().toString();
password = MD5.md5(password); //MD5加密
List<User> users = LitePal.where("name==?", name).find(User.class);
Toast mToast = Toast.makeText(this, null, Toast.LENGTH_SHORT); //下面用setText不用makeText,为了取消小米手机自带的Toast应用名
//判断用户名是否存在
if (!users.isEmpty()) {
mToast.setText("该用户名已存在");
mToast.show();
}
//如果用户名不存在,则新建用户
else {
User user = new User();
user.setName(name);
user.setPassword(password);
//默认不记住密码,并设置默认头像
user.setPortrait((new PhotoUtils()).file2byte(this ,"default_portrait.jpg"));
user.setRemember(0);
user.save();
mToast.setText("注册成功");
mToast.show();
Intent intent = new Intent(Register.this, Login.class);
startActivity(intent);
}
}
}
}
2. 登录
登录界面较为复杂,为了防止代码冗杂,我把import部分略去了
代码如下:
public class Login extends AppCompatActivity implements View.OnClickListener {
private EditText et_name,et_password;
private Button login,register;
private ImageView iv_eye,iv_more_account;
private CheckBox cb_remember;
private boolean passwordVisible = false;
private ToastUtils toastUtils = new ToastUtils();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCollector.addActivity(this);
setContentView(R.layout.activity_login);
login = (Button) findViewById(R.id.login);
register = (Button) findViewById(R.id.register);
et_name = (EditText) findViewById(R.id.et_account_name);
et_password = (EditText) findViewById(R.id.et_password);
iv_eye = (ImageView) findViewById(R.id.iv_eye);
iv_more_account = (ImageView) findViewById(R.id.iv_more_accout);
cb_remember = (CheckBox) findViewById(R.id.cb_remember);
//设置监听器
login.setOnClickListener(this);
register.setOnClickListener(this);
iv_eye.setOnClickListener(this);
iv_more_account.setOnClickListener(this);
//如果是第一次打开并且不是退出登录,则直接登录
if(getIntent().getBooleanExtra("first_in",false)) {
Log.d("food","first_in");
List<User> users = LitePal.findAll(User.class);
for (User u : users) {
if (u.getRemember().equals(1)) {
//登入并存入LoginUser
LoginUser.getInstance().login(u);
//启动主界面
Intent intent1 = new Intent(Login.this, ButtomTab.class);
startActivity(intent1);
toastUtils.showShort(Login.this, "账户" + u.getName() + " 登录成功");
break;
}
}
}
}
@Override
protected void onDestroy(){
super.onDestroy();
ActivityCollector.removeActivity(this);
}
@Override
protected void onStart(){
super.onStart();
//从本地数据库判断是否记住密码
List<User> users = LitePal.findAll(User.class);
for (User u:users){
if(u.getRemember().equals(1)){
et_name.setText(u.getName());
et_password.setText("12345678"); //因为限制密码不能是全数字,利用12345678为通用密码简化验证,但会降低安全性
cb_remember.setChecked(true);
break;
}
}
}
@Override
public void onClick(View v){
String name = et_name.getText().toString();
String password=et_password.getText().toString();
switch (v.getId()){
//注册按钮的逻辑
case R.id.register:
Intent intent = new Intent(Login.this, Register.class);
startActivity(intent);
break;
//登录按钮的逻辑
case R.id.login:
boolean login_flag = false; //是否登录成功的标志,
User user = LitePal.where("name=?",name).findFirst(User.class);
//根据user的remember状态,判断是否需要MD5加密
if (password.equals("12345678")) password = user.getPassword();
else password = MD5.md5(password);
//密码正确则登录成功
if (user.checkPassword(password)){
//更新remember状态
if(cb_remember.isChecked()) {
user.setRemember(1);
}else{
user.setRemember(0);
}
user.update(user.getId());
//用户登入,存入LoginUser
LoginUser.getInstance().login(user);
//启动主界面
Intent intent1 = new Intent(Login.this, ButtomTab.class);
startActivity(intent1);
login_flag = true;
toastUtils.showShort(Login.this,"账户"+user.getName()+" 登录成功");
break;
}else {
user.setRemember(0);
}
if(login_flag == false){
toastUtils.showShort(Login.this,"登录失败");
}
break;
//隐藏密码功能
case R.id.iv_eye:
if(passwordVisible){ //如果可见,则转为不可见
iv_eye.setSelected(false);
et_password.setTransformationMethod(PasswordTransformationMethod.getInstance());
passwordVisible = false;
}else { //如果不可见,则转为可见
iv_eye.setSelected(true);
et_password.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
passwordVisible = true;
}
break;
//显示本地所有登录信息
case R.id.iv_more_accout:
List<User> users1 = LitePal.findAll(User.class);
for(User u:users1) Log.d("food",""+u.toString());
break;
}
}
}
参考链接:
如何优雅的实现登录注册
Litepal实现登录注册
inputtype取值
EditText的可见不可见
浅谈android的数据加密
MD5加密
Android设计模式之单例模式的七种写法
android使用全局变量的两种方法