SQLite以及Room框架的学习:用SQLite给新闻app加上更完善的登录注册功能

发布于:2025-07-22 ⋅ 阅读:(17) ⋅ 点赞:(0)

SQLite以及Room框架的学习:用SQLite给新闻app加上更完善的登录注册功能

SQLite

SQLite 是一种轻量级的嵌入式数据库,广泛应用于移动应用、桌面软件和小型项目中。它不需要单独的服务器进程,数据存储在单个文件中,具有零配置、高效、可靠的特点。

1.SQLite的特性

  • 嵌入式数据库:直接嵌入到应用程序中,无需独立服务器。
  • 零配置:无需复杂设置,直接使用。
  • 单文件存储:所有数据存储在单个磁盘文件中。
  • 支持标准 SQL:遵循 SQL-92 标准的大部分特性。
  • 事务支持:支持 ACID 特性的事务处理。

2.数据类型

  • NULL:空值。
  • INTEGER:整型,根据值大小自动选择合适的位数。
  • REAL:浮点型。
  • TEXT:文本字符串,编码支持 UTF-8、UTF-16。
  • BLOB:二进制大对象,原样存储数据。

3.数据库文件结构

  • 数据库文件包含表、索引、触发器和视图的定义及数据。
  • 文件格式跨平台兼容,可在不同架构的设备间移植。

4.基本使用流程

1)通过继承 SQLiteOpenHelper 类管理数据库版本和表结构:
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DatabaseHelper extends SQLiteOpenHelper {
    private static final String DATABASE_NAME = "myapp.db";
    private static final int DATABASE_VERSION = 1;

    // 表名和列名
    public static final String TABLE_USERS = "users";
    public static final String COLUMN_ID = "_id";
    public static final String COLUMN_NAME = "name";
    public static final String COLUMN_AGE = "age";
    public static final String COLUMN_EMAIL = "email";

    // 创建表的SQL语句
    private static final String CREATE_TABLE_USERS =
        "CREATE TABLE " + TABLE_USERS + " (" +
        COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
        COLUMN_NAME + " TEXT NOT NULL, " +
        COLUMN_AGE + " INTEGER, " +
        COLUMN_EMAIL + " TEXT UNIQUE);";

    public DatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_TABLE_USERS);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // 数据库升级时的操作
        db.execSQL("DROP TABLE IF EXISTS " + TABLE_USERS);
        onCreate(db);
    }
}
2)数据模型类(User)
public class User {
    private long id;
    private String name;
    private int age;
    private String email;

    // 构造函数
    public User() {}

    public User(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }

    // Getters and Setters
    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", email='" + email + '\'' +
                '}';
    }
}
3)数据访问对象DAO
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import java.util.ArrayList;
import java.util.List;

public class UserDAO {
    private SQLiteDatabase database;
    private DatabaseHelper dbHelper;
    private String[] allColumns = {
        DatabaseHelper.COLUMN_ID,
        DatabaseHelper.COLUMN_NAME,
        DatabaseHelper.COLUMN_AGE,
        DatabaseHelper.COLUMN_EMAIL
    };

    public UserDAO(Context context) {
        dbHelper = new DatabaseHelper(context);
    }

    // 打开数据库连接
    public void open() {
        database = dbHelper.getWritableDatabase();
    }

    // 关闭数据库连接
    public void close() {
        dbHelper.close();
    }

    // 创建用户
    public User createUser(String name, int age, String email) {
        ContentValues values = new ContentValues();
        values.put(DatabaseHelper.COLUMN_NAME, name);
        values.put(DatabaseHelper.COLUMN_AGE, age);
        values.put(DatabaseHelper.COLUMN_EMAIL, email);

        long insertId = database.insert(DatabaseHelper.TABLE_USERS, null, values);
        Cursor cursor = database.query(
            DatabaseHelper.TABLE_USERS,
            allColumns,
            DatabaseHelper.COLUMN_ID + " = " + insertId,
            null, null, null, null
        );
        cursor.moveToFirst();
        User newUser = cursorToUser(cursor);
        cursor.close();
        return newUser;
    }

    // 删除用户
    public void deleteUser(User user) {
        long id = user.getId();
        database.delete(
            DatabaseHelper.TABLE_USERS,
            DatabaseHelper.COLUMN_ID + " = " + id,
            null
        );
    }

    // 获取所有用户
    public List<User> getAllUsers() {
        List<User> users = new ArrayList<>();
        Cursor cursor = database.query(
            DatabaseHelper.TABLE_USERS,
            allColumns, null, null, null, null, null
        );
        cursor.moveToFirst();
        while (!cursor.isAfterLast()) {
            User user = cursorToUser(cursor);
            users.add(user);
            cursor.moveToNext();
        }
        cursor.close();
        return users;
    }

    // 获取单个用户
    public User getUser(long id) {
        Cursor cursor = database.query(
            DatabaseHelper.TABLE_USERS,
            allColumns,
            DatabaseHelper.COLUMN_ID + " = " + id,
            null, null, null, null
        );
        User user = null;
        if (cursor != null && cursor.moveToFirst()) {
            user = cursorToUser(cursor);
            cursor.close();
        }
        return user;
    }

    // 更新用户信息
    public int updateUser(User user) {
        ContentValues values = new ContentValues();
        values.put(DatabaseHelper.COLUMN_NAME, user.getName());
        values.put(DatabaseHelper.COLUMN_AGE, user.getAge());
        values.put(DatabaseHelper.COLUMN_EMAIL, user.getEmail());

        return database.update(
            DatabaseHelper.TABLE_USERS,
            values,
            DatabaseHelper.COLUMN_ID + " = ?",
            new String[]{String.valueOf(user.getId())}
        );
    }

    // 将Cursor转换为User对象
    private User cursorToUser(Cursor cursor) {
        User user = new User();
        user.setId(cursor.getLong(0));
        user.setName(cursor.getString(1));
        user.setAge(cursor.getInt(2));
        user.setEmail(cursor.getString(3));
        return user;
    }
}
4)使用示例
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private UserDAO userDAO;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化DAO
        userDAO = new UserDAO(this);
        userDAO.open();

        // 示例:添加用户
        User user = userDAO.createUser("Alice", 25, "alice@example.com");
        Log.d(TAG, "New user added: " + user.toString());

        // 示例:获取所有用户
        List<User> users = userDAO.getAllUsers();
        for (User u : users) {
            Log.d(TAG, "User: " + u.toString());
        }

        // 示例:更新用户
        user.setAge(26);
        int rowsAffected = userDAO.updateUser(user);
        Log.d(TAG, "Rows affected: " + rowsAffected);

        // 示例:删除用户
        userDAO.deleteUser(user);
        Log.d(TAG, "User deleted: " + user.toString());

        // 关闭数据库连接
        userDAO.close();
    }
}
5)权限配置
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.sqliteexample">

    <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">
        
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Room框架主要知识点

Room 是 Android Jetpack 中的一个持久化库,它在 SQLite 上提供了一个抽象层,让开发者能够更轻松地使用数据库功能。以下是 Room 框架的核心概念和主要知识点:

1.核心组件

Entity(实体类)

对应数据库中的表,使用 @Entity 注解标记的类,每个实体类的属性对应表中的列。

import androidx.room.Entity;
import androidx.room.PrimaryKey;

@Entity(tableName = "users")
public class User {
    @PrimaryKey(autoGenerate = true)
    private int id;
    private String name;
    private int age;
    private String email;

    // 构造函数、Getter和Setter方法
    public User(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }

    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}
DAO(数据访问对象)

@Dao 注解的接口,定义数据库操作方法

import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;

@Dao
public interface UserDao {
    @Query("SELECT * FROM users")
    List<User> getAll();

    @Query("SELECT * FROM users WHERE id = :id")
    User getById(int id);

    @Insert
    void insert(User user);

    @Insert
    void insertAll(List<User> users);

    @Update
    void update(User user);

    @Delete
    void delete(User user);

    @Query("DELETE FROM users")
    void deleteAll();
}
Database(数据库类)

@Database 注解的抽象类,连接实体和 DAO:

import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;

@Dao
public interface UserDao {
    @Query("SELECT * FROM users")
    List<User> getAll();

    @Query("SELECT * FROM users WHERE id = :id")
    User getById(int id);

    @Insert
    void insert(User user);

    @Insert
    void insertAll(List<User> users);

    @Update
    void update(User user);

    @Delete
    void delete(User user);

    @Query("DELETE FROM users")
    void deleteAll();
}

响应式编程支持

Room 支持返回 LiveDataFlowable(RxJava)实现数据监听:

import androidx.lifecycle.LiveData;

@Dao
public interface UserDao {
    // 返回LiveData,数据变化时自动通知观察者
    @Query("SELECT * FROM users")
    LiveData<List<User>> getAllUsersLiveData();
}

关系型数据处理

一对多关系**

// User类保持不变

// Address类
@Entity(
    foreignKeys = @ForeignKey(
        entity = User.class,
        parentColumns = "id",
        childColumns = "userId",
        onDelete = ForeignKey.CASCADE
    )
)
public class Address {
    @PrimaryKey(autoGenerate = true)
    private int id;
    private String street;
    private int userId; // 外键

    // Getter和Setter方法
}

// 用户及其地址的组合类
public class UserWithAddresses {
    @Embedded
    public User user;

    @Relation(
        parentColumn = "id",
        entityColumn = "userId"
    )
    public List<Address> addresses;
}

// DAO方法
@Query("SELECT * FROM users")
List<UserWithAddresses> getUsersWithAddresses();

数据库迁移

import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;

// 从版本1到版本2的迁移
public static final Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL("ALTER TABLE users ADD COLUMN phone TEXT");
    }
};

// 构建数据库时添加迁移路径
AppDatabase db = Room.databaseBuilder(
    context.getApplicationContext(),
    AppDatabase.class, "database-name"
)
.addMigrations(MIGRATION_1_2)
.build();

与 ViewModel 结合使用

在ViewModel中获取Room数据并提供给UI

import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import android.app.Application;
import java.util.List;

public class UserViewModel extends AndroidViewModel {
    private UserDao userDao;
    private LiveData<List<User>> allUsers;

    public UserViewModel(Application application) {
        super(application);
        AppDatabase db = AppDatabase.getDatabase(application);
        userDao = db.userDao();
        allUsers = userDao.getAllUsersLiveData();
    }

    public LiveData<List<User>> getAllUsers() {
        return allUsers;
    }

    // 在后台线程执行插入操作
    public void insert(User user) {
        new InsertAsyncTask(userDao).execute(user);
    }

    private static class InsertAsyncTask extends AsyncTask<User, Void, Void> {
        private UserDao asyncTaskDao;

        InsertAsyncTask(UserDao dao) {
            this.asyncTaskDao = dao;
        }

        @Override
        protected Void doInBackground(final User... params) {
            asyncTaskDao.insert(params[0]);
            return null;
        }
    }
}

测试 Room DAO

使用内存数据库进行单元测试:

import androidx.room.Room;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
import java.util.List;
import static org.junit.Assert.assertEquals;

@RunWith(AndroidJUnit4.class)
public class UserDaoTest {
    private UserDao userDao;
    private AppDatabase db;

    @Before
    public void createDb() {
        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
        db = Room.inMemoryDatabaseBuilder(context, AppDatabase.class).build();
        userDao = db.userDao();
    }

    @After
    public void closeDb() throws IOException {
        db.close();
    }

    @Test
    public void insertAndGetUser() throws Exception {
        User user = new User("Alice", 25, "alice@example.com");
        userDao.insert(user);
        List<User> allUsers = userDao.getAll();
        assertEquals(allUsers.get(0).getName(), user.getName());
    }
}

给NewsApp中添加登录注册模块

//LoginActivity
package com.example.eznews.activity;

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Base64;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;


import com.example.eznews.R;
import com.example.newsapp.database.UserDatabaseHelper;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class RegisterActivity extends AppCompatActivity {

    private EditText etUsername, etPassword, etConfirmPassword;
    private ImageView ivAvatar;
    private Button btnSelectAvatar, btnRegister, btnBackToLogin;
    private TextView tvPasswordStrength;
    private CheckBox cbAgreeTerms;

    private UserDatabaseHelper dbHelper;
    private static final int PICK_IMAGE_REQUEST = 1;
    private String avatarBase64 = "";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_register);

        initViews();
        initDatabase();
        setupListeners();
    }

    private void updatePasswordStrength(String password) {
        if (password.isEmpty()) {
            tvPasswordStrength.setText("请输入至少6位密码,包含字母和数字");
            tvPasswordStrength.setTextColor(getResources().getColor(R.color.text_secondary));
            return;
        }

        if (password.length() < 6) {
            tvPasswordStrength.setText("密码长度至少6位");
            tvPasswordStrength.setTextColor(getResources().getColor(R.color.error_color));
            return;
        }

        boolean hasLetter = password.matches(".*[a-zA-Z].*");
        boolean hasDigit = password.matches(".*\\d.*");
        boolean hasSpecial = password.matches(".*[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?].*");

        if (hasLetter && hasDigit && hasSpecial && password.length() >= 8) {
            tvPasswordStrength.setText("强密码 ✓");
            tvPasswordStrength.setTextColor(getResources().getColor(R.color.success_color));
        } else if (hasLetter && hasDigit && password.length() >= 6) {
            tvPasswordStrength.setText("中等强度");
            tvPasswordStrength.setTextColor(getResources().getColor(R.color.primary_color));
        } else {
            tvPasswordStrength.setText("弱密码,建议包含字母和数字");
            tvPasswordStrength.setTextColor(getResources().getColor(R.color.error_color));
        }
    }

    private void showUserAgreement() {
        // 这里可以显示用户协议对话框或跳转到协议页面
        new androidx.appcompat.app.AlertDialog.Builder(this)
                .setTitle("用户协议")
                .setMessage("这里是用户协议的具体内容...\n\n1. 用户注册即表示同意本协议\n2. 请妥善保管账户信息\n3. 禁止恶意使用本应用\n4. 我们将保护您的隐私安全")
                .setPositiveButton("我已了解", null)
                .show();
    }

    private void initViews() {
        etUsername = findViewById(R.id.et_reg_username);
        etPassword = findViewById(R.id.et_reg_password);
        etConfirmPassword = findViewById(R.id.et_reg_confirm_password);
        ivAvatar = findViewById(R.id.iv_reg_avatar);
        btnSelectAvatar = findViewById(R.id.btn_select_avatar);
        btnRegister = findViewById(R.id.btn_register_submit);
        btnBackToLogin = findViewById(R.id.btn_back_to_login);
        tvPasswordStrength = findViewById(R.id.tv_password_strength);
        cbAgreeTerms = findViewById(R.id.cb_agree_terms);

        // 设置默认头像
        ivAvatar.setImageResource(R.drawable.default_avatar);
    }

    private void initDatabase() {
        dbHelper = new UserDatabaseHelper(this);
    }

    private void setupListeners() {
        // 选择头像按钮
        btnSelectAvatar.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                selectAvatar();
            }
        });

        // 头像点击也可以选择
        ivAvatar.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                selectAvatar();
            }
        });

        // 密码强度监听
        etPassword.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                updatePasswordStrength(s.toString());
            }

            @Override
            public void afterTextChanged(Editable s) {}
        });

        // 注册按钮
        btnRegister.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                attemptRegister();
            }
        });

        // 返回登录按钮
        btnBackToLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });

        // 用户协议点击
        TextView tvUserAgreement = findViewById(R.id.tv_user_agreement);
        tvUserAgreement.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 显示用户协议
                showUserAgreement();
            }
        });
    }

    private void selectAvatar() {
        Intent intent = new Intent();
        intent.setType("image/*");
        intent.setAction(Intent.ACTION_GET_CONTENT);
        startActivityForResult(Intent.createChooser(intent, "选择头像"), PICK_IMAGE_REQUEST);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK && data != null && data.getData() != null) {
            Uri imageUri = data.getData();

            try {
                Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), imageUri);

                // 压缩图片
                Bitmap compressedBitmap = compressBitmap(bitmap, 200, 200);

                // 显示头像
                ivAvatar.setImageBitmap(compressedBitmap);

                // 转换为Base64字符串存储
                avatarBase64 = bitmapToBase64(compressedBitmap);

            } catch (IOException e) {
                e.printStackTrace();
                Toast.makeText(this, "头像加载失败", Toast.LENGTH_SHORT).show();
            }
        }
    }

    private Bitmap compressBitmap(Bitmap bitmap, int maxWidth, int maxHeight) {
        float ratio = Math.min(
                (float) maxWidth / bitmap.getWidth(),
                (float) maxHeight / bitmap.getHeight()
        );

        int width = Math.round(ratio * bitmap.getWidth());
        int height = Math.round(ratio * bitmap.getHeight());

        return Bitmap.createScaledBitmap(bitmap, width, height, true);
    }

    private String bitmapToBase64(Bitmap bitmap) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 80, byteArrayOutputStream);
        byte[] byteArray = byteArrayOutputStream.toByteArray();
        return Base64.encodeToString(byteArray, Base64.DEFAULT);
    }

    private void attemptRegister() {
        String username = etUsername.getText().toString().trim();
        String password = etPassword.getText().toString().trim();
        String confirmPassword = etConfirmPassword.getText().toString().trim();

        // 验证输入
        if (username.isEmpty()) {
            etUsername.setError("请输入用户名");
            etUsername.requestFocus();
            return;
        }

        if (username.length() < 3) {
            etUsername.setError("用户名至少3个字符");
            etUsername.requestFocus();
            return;
        }

        if (password.isEmpty()) {
            etPassword.setError("请输入密码");
            etPassword.requestFocus();
            return;
        }

        if (password.length() < 6) {
            etPassword.setError("密码至少6个字符");
            etPassword.requestFocus();
            return;
        }

        if (!password.equals(confirmPassword)) {
            etConfirmPassword.setError("两次输入的密码不一致");
            etConfirmPassword.requestFocus();
            return;
        }

        // 检查是否同意用户协议
        if (!cbAgreeTerms.isChecked()) {
            Toast.makeText(this, "请先同意用户协议", Toast.LENGTH_SHORT).show();
            return;
        }

        // 检查用户名是否已存在
        if (dbHelper.isUserExists(username)) {
            etUsername.setError("用户名已存在");
            etUsername.requestFocus();
            return;
        }

        // 如果没有选择头像,使用默认头像的Base64
        if (avatarBase64.isEmpty()) {
            Bitmap defaultAvatar = ((BitmapDrawable) getResources().getDrawable(R.drawable.default_avatar)).getBitmap();
            avatarBase64 = bitmapToBase64(defaultAvatar);
        }

        // 注册用户
        if (dbHelper.registerUser(username, password, avatarBase64)) {
            Toast.makeText(this, "注册成功!", Toast.LENGTH_SHORT).show();

            // 返回登录界面
            Intent intent = new Intent();
            intent.putExtra("registered_username", username);
            setResult(RESULT_OK, intent);
            finish();

        } else {
            Toast.makeText(this, "注册失败,请稍后重试", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (dbHelper != null) {
            dbHelper.close();
        }
    }
}

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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@color/background_color"
    android:gravity="center"
    android:padding="32dp">

    <!-- 应用Logo或标题 -->

    <!-- 用户头像 -->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="48dp"
        android:text="新闻资讯"
        android:textColor="@color/primary_color"
        android:textSize="28sp"
        android:textStyle="bold" />

    <ImageView
        android:id="@+id/iv_user_avatar"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_marginBottom="24dp"
        android:background="@drawable/circle_background"
        android:scaleType="centerCrop"
        android:src="@drawable/default_avatar"
        android:contentDescription="用户头像" />

    <!-- 登录表单容器 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:background="@drawable/form_background"
        android:padding="24dp"
        android:elevation="4dp">

        <!-- 用户名输入框 -->
        <com.google.android.material.textfield.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="16dp"
            android:hint="用户名">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/et_username"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="text"
                android:maxLines="1"
                android:drawableStart="@drawable/ic_person"
                android:drawablePadding="12dp" />

        </com.google.android.material.textfield.TextInputLayout>

        <!-- 密码输入框 -->
        <com.google.android.material.textfield.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="16dp"
            android:hint="密码"
            app:passwordToggleEnabled="true">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/et_password"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="textPassword"
                android:maxLines="1"
                android:drawableStart="@drawable/ic_lock"
                android:drawablePadding="12dp" />

        </com.google.android.material.textfield.TextInputLayout>

        <!-- 记住密码复选框 -->
        <CheckBox
            android:id="@+id/cb_remember_password"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="记住密码"
            android:textColor="@color/text_secondary"
            android:layout_marginBottom="24dp" />

        <!-- 登录按钮 -->
        <Button
            android:id="@+id/btn_login"
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:text="登录"
            android:textSize="16sp"
            android:textColor="@color/white"
            android:background="@drawable/button_primary"
            android:layout_marginBottom="16dp"
            android:elevation="2dp" />

        <!-- 注册按钮 -->
        <Button
            android:id="@+id/btn_register"
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:text="注册新账户"
            android:textSize="16sp"
            android:textColor="@color/primary_color"
            android:background="@drawable/button_secondary"
            style="?android:attr/borderlessButtonStyle" />

    </LinearLayout>

    <!-- 忘记密码链接 -->
    <TextView
        android:id="@+id/tv_forgot_password"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="忘记密码?"
        android:textColor="@color/primary_color"
        android:textSize="14sp"
        android:layout_marginTop="24dp"
        android:clickable="true"
        android:focusable="true"
        android:background="?android:attr/selectableItemBackground"
        android:padding="8dp" />

</LinearLayout>

RegisterActivity

package com.example.eznews.activity;

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Base64;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;


import com.example.eznews.R;
import com.example.newsapp.database.UserDatabaseHelper;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class RegisterActivity extends AppCompatActivity {

    private EditText etUsername, etPassword, etConfirmPassword;
    private ImageView ivAvatar;
    private Button btnSelectAvatar, btnRegister, btnBackToLogin;
    private TextView tvPasswordStrength;
    private CheckBox cbAgreeTerms;

    private UserDatabaseHelper dbHelper;
    private static final int PICK_IMAGE_REQUEST = 1;
    private String avatarBase64 = "";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_register);

        initViews();
        initDatabase();
        setupListeners();
    }

    private void updatePasswordStrength(String password) {
        if (password.isEmpty()) {
            tvPasswordStrength.setText("请输入至少6位密码,包含字母和数字");
            tvPasswordStrength.setTextColor(getResources().getColor(R.color.text_secondary));
            return;
        }

        if (password.length() < 6) {
            tvPasswordStrength.setText("密码长度至少6位");
            tvPasswordStrength.setTextColor(getResources().getColor(R.color.error_color));
            return;
        }

        boolean hasLetter = password.matches(".*[a-zA-Z].*");
        boolean hasDigit = password.matches(".*\\d.*");
        boolean hasSpecial = password.matches(".*[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?].*");

        if (hasLetter && hasDigit && hasSpecial && password.length() >= 8) {
            tvPasswordStrength.setText("强密码 ✓");
            tvPasswordStrength.setTextColor(getResources().getColor(R.color.success_color));
        } else if (hasLetter && hasDigit && password.length() >= 6) {
            tvPasswordStrength.setText("中等强度");
            tvPasswordStrength.setTextColor(getResources().getColor(R.color.primary_color));
        } else {
            tvPasswordStrength.setText("弱密码,建议包含字母和数字");
            tvPasswordStrength.setTextColor(getResources().getColor(R.color.error_color));
        }
    }

    private void showUserAgreement() {
        // 这里可以显示用户协议对话框或跳转到协议页面
        new androidx.appcompat.app.AlertDialog.Builder(this)
                .setTitle("用户协议")
                .setMessage("这里是用户协议的具体内容...\n\n1. 用户注册即表示同意本协议\n2. 请妥善保管账户信息\n3. 禁止恶意使用本应用\n4. 我们将保护您的隐私安全")
                .setPositiveButton("我已了解", null)
                .show();
    }

    private void initViews() {
        etUsername = findViewById(R.id.et_reg_username);
        etPassword = findViewById(R.id.et_reg_password);
        etConfirmPassword = findViewById(R.id.et_reg_confirm_password);
        ivAvatar = findViewById(R.id.iv_reg_avatar);
        btnSelectAvatar = findViewById(R.id.btn_select_avatar);
        btnRegister = findViewById(R.id.btn_register_submit);
        btnBackToLogin = findViewById(R.id.btn_back_to_login);
        tvPasswordStrength = findViewById(R.id.tv_password_strength);
        cbAgreeTerms = findViewById(R.id.cb_agree_terms);

        // 设置默认头像
        ivAvatar.setImageResource(R.drawable.default_avatar);
    }

    private void initDatabase() {
        dbHelper = new UserDatabaseHelper(this);
    }

    private void setupListeners() {
        // 选择头像按钮
        btnSelectAvatar.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                selectAvatar();
            }
        });

        // 头像点击也可以选择
        ivAvatar.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                selectAvatar();
            }
        });

        // 密码强度监听
        etPassword.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                updatePasswordStrength(s.toString());
            }

            @Override
            public void afterTextChanged(Editable s) {}
        });

        // 注册按钮
        btnRegister.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                attemptRegister();
            }
        });

        // 返回登录按钮
        btnBackToLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });

        // 用户协议点击
        TextView tvUserAgreement = findViewById(R.id.tv_user_agreement);
        tvUserAgreement.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 显示用户协议
                showUserAgreement();
            }
        });
    }

    private void selectAvatar() {
        Intent intent = new Intent();
        intent.setType("image/*");
        intent.setAction(Intent.ACTION_GET_CONTENT);
        startActivityForResult(Intent.createChooser(intent, "选择头像"), PICK_IMAGE_REQUEST);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK && data != null && data.getData() != null) {
            Uri imageUri = data.getData();

            try {
                Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), imageUri);

                // 压缩图片
                Bitmap compressedBitmap = compressBitmap(bitmap, 200, 200);

                // 显示头像
                ivAvatar.setImageBitmap(compressedBitmap);

                // 转换为Base64字符串存储
                avatarBase64 = bitmapToBase64(compressedBitmap);

            } catch (IOException e) {
                e.printStackTrace();
                Toast.makeText(this, "头像加载失败", Toast.LENGTH_SHORT).show();
            }
        }
    }

    private Bitmap compressBitmap(Bitmap bitmap, int maxWidth, int maxHeight) {
        float ratio = Math.min(
                (float) maxWidth / bitmap.getWidth(),
                (float) maxHeight / bitmap.getHeight()
        );

        int width = Math.round(ratio * bitmap.getWidth());
        int height = Math.round(ratio * bitmap.getHeight());

        return Bitmap.createScaledBitmap(bitmap, width, height, true);
    }

    private String bitmapToBase64(Bitmap bitmap) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 80, byteArrayOutputStream);
        byte[] byteArray = byteArrayOutputStream.toByteArray();
        return Base64.encodeToString(byteArray, Base64.DEFAULT);
    }

    private void attemptRegister() {
        String username = etUsername.getText().toString().trim();
        String password = etPassword.getText().toString().trim();
        String confirmPassword = etConfirmPassword.getText().toString().trim();

        // 验证输入
        if (username.isEmpty()) {
            etUsername.setError("请输入用户名");
            etUsername.requestFocus();
            return;
        }

        if (username.length() < 3) {
            etUsername.setError("用户名至少3个字符");
            etUsername.requestFocus();
            return;
        }

        if (password.isEmpty()) {
            etPassword.setError("请输入密码");
            etPassword.requestFocus();
            return;
        }

        if (password.length() < 6) {
            etPassword.setError("密码至少6个字符");
            etPassword.requestFocus();
            return;
        }

        if (!password.equals(confirmPassword)) {
            etConfirmPassword.setError("两次输入的密码不一致");
            etConfirmPassword.requestFocus();
            return;
        }

        // 检查是否同意用户协议
        if (!cbAgreeTerms.isChecked()) {
            Toast.makeText(this, "请先同意用户协议", Toast.LENGTH_SHORT).show();
            return;
        }

        // 检查用户名是否已存在
        if (dbHelper.isUserExists(username)) {
            etUsername.setError("用户名已存在");
            etUsername.requestFocus();
            return;
        }

        // 如果没有选择头像,使用默认头像的Base64
        if (avatarBase64.isEmpty()) {
            Bitmap defaultAvatar = ((BitmapDrawable) getResources().getDrawable(R.drawable.default_avatar)).getBitmap();
            avatarBase64 = bitmapToBase64(defaultAvatar);
        }

        // 注册用户
        if (dbHelper.registerUser(username, password, avatarBase64)) {
            Toast.makeText(this, "注册成功!", Toast.LENGTH_SHORT).show();

            // 返回登录界面
            Intent intent = new Intent();
            intent.putExtra("registered_username", username);
            setResult(RESULT_OK, intent);
            finish();

        } else {
            Toast.makeText(this, "注册失败,请稍后重试", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (dbHelper != null) {
            dbHelper.close();
        }
    }
}

activity_register.xml

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/background_color"
    android:fillViewport="true">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:gravity="center"
        android:padding="32dp">

        <!-- 标题 -->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="创建新账户"
            android:textSize="28sp"
            android:textColor="@color/primary_color"
            android:textStyle="bold"
            android:layout_marginBottom="32dp" />

        <!-- 注册表单容器 -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:background="@drawable/form_background"
            android:padding="24dp"
            android:elevation="4dp">

            <!-- 头像选择区域 -->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:gravity="center"
                android:layout_marginBottom="24dp">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="选择头像"
                    android:textSize="16sp"
                    android:textColor="@color/text_primary"
                    android:layout_marginBottom="12dp" />

                <!-- 头像显示 -->
                <ImageView
                    android:id="@+id/iv_reg_avatar"
                    android:layout_width="100dp"
                    android:layout_height="100dp"
                    android:layout_marginBottom="12dp"
                    android:background="@drawable/circle_background"
                    android:scaleType="centerCrop"
                    android:src="@drawable/default_avatar"
                    android:contentDescription="用户头像"
                    android:clickable="true"
                    android:focusable="true" />

                <!-- 选择头像按钮 -->
                <Button
                    android:id="@+id/btn_select_avatar"
                    android:layout_width="wrap_content"
                    android:layout_height="40dp"
                    android:text="选择头像"
                    android:textSize="14sp"
                    android:textColor="@color/primary_color"
                    android:background="@drawable/button_secondary"
                    android:minWidth="120dp"
                    style="?android:attr/borderlessButtonStyle" />

            </LinearLayout>

            <!-- 用户名输入框 -->
            <com.google.android.material.textfield.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="16dp"
                android:hint="用户名"
                app:counterEnabled="true"
                app:counterMaxLength="20">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/et_reg_username"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:inputType="text"
                    android:maxLength="20"
                    android:maxLines="1"
                    android:drawableStart="@drawable/ic_person"
                    android:drawablePadding="12dp" />

            </com.google.android.material.textfield.TextInputLayout>

            <!-- 密码输入框 -->
            <com.google.android.material.textfield.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="16dp"
                android:hint="密码"
                app:passwordToggleEnabled="true"
                app:counterEnabled="true"
                app:counterMaxLength="50">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/et_reg_password"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:inputType="textPassword"
                    android:maxLength="50"
                    android:maxLines="1"
                    android:drawableStart="@drawable/ic_lock"
                    android:drawablePadding="12dp" />

            </com.google.android.material.textfield.TextInputLayout>

            <!-- 密码强度提示 -->
            <TextView
                android:id="@+id/tv_password_strength"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="请输入至少6位密码,包含字母和数字"
                android:textSize="12sp"
                android:textColor="@color/text_secondary"
                android:layout_marginBottom="16dp" />

            <!-- 确认密码输入框 -->
            <com.google.android.material.textfield.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="24dp"
                android:hint="确认密码"
                app:passwordToggleEnabled="true">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/et_reg_confirm_password"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:inputType="textPassword"
                    android:maxLines="1"
                    android:drawableStart="@drawable/ic_lock"
                    android:drawablePadding="12dp" />

            </com.google.android.material.textfield.TextInputLayout>

            <!-- 注册按钮 -->
            <Button
                android:id="@+id/btn_register_submit"
                android:layout_width="match_parent"
                android:layout_height="56dp"
                android:text="注册"
                android:textSize="16sp"
                android:textColor="@color/white"
                android:background="@drawable/button_primary"
                android:layout_marginBottom="16dp"
                android:elevation="2dp" />

            <!-- 返回登录按钮 -->
            <Button
                android:id="@+id/btn_back_to_login"
                android:layout_width="match_parent"
                android:layout_height="56dp"
                android:text="已有账户?立即登录"
                android:textSize="16sp"
                android:textColor="@color/primary_color"
                android:background="@drawable/button_secondary"
                style="?android:attr/borderlessButtonStyle" />

        </LinearLayout>

        <!-- 用户协议 -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:gravity="center"
            android:layout_marginTop="24dp">

            <CheckBox
                android:id="@+id/cb_agree_terms"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:checked="true" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="我已阅读并同意"
                android:textSize="12sp"
                android:textColor="@color/text_secondary" />

            <TextView
                android:id="@+id/tv_user_agreement"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="《用户协议》"
                android:textSize="12sp"
                android:textColor="@color/primary_color"
                android:clickable="true"
                android:focusable="true"
                android:background="?android:attr/selectableItemBackground"
                android:padding="4dp" />

        </LinearLayout>

    </LinearLayout>

</ScrollView>

这个登录系统提供了以下完整功能:

  • 用户注册和登录
  • 密码加密存储
  • 用户头像管理
  • 记住密码功能
  • 输入用户名时显示头像(类似QQ)
  • 自动完成用户名

1. 添加依赖

app/build.gradle 中添加Material Design依赖:

dependencies {
    implementation 'com.google.android.material:material:1.9.0'
   
}

2. 权限配置

AndroidManifest.xml 中添加必要权限:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />

3. Activity注册

AndroidManifest.xml 中注册Activity:

<activity
    android:name=".activity.LoginActivity"
    android:exported="true"
    android:theme="@style/Theme.NewsApp.NoActionBar">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<activity
    android:name=".activity.RegisterActivity"
    android:parentActivityName=".activity.LoginActivity" />

4. 资源文件

创建所需的drawable资源:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="48dp"
    android:height="48dp"
    android:viewportWidth="24"
    android:viewportHeight="24"
    android:tint="@color/text_secondary">
    <path
        android:fillColor="@android:color/white"
        android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
</vector>

功能详解

1. 数据库设计

数据库表结构:

CREATE TABLE users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    username TEXT UNIQUE NOT NULL,
    password TEXT NOT NULL,
    avatar TEXT,
    created_time DATETIME DEFAULT CURRENT_TIMESTAMP
);

2. 密码安全

  • 使用SHA-256哈希加密
  • Base64编码存储
  • 永不存储明文密码

3. 头像管理

  • 支持从相册选择图片
  • 自动压缩图片大小
  • Base64编码存储在数据库
  • 圆形头像显示

4. 记住密码功能

使用SharedPreferences存储:

PreferenceManager prefManager = new PreferenceManager(this);
prefManager.saveLoginCredentials(username, password, isRemember);

5. 实时头像显示

通过TextWatcher监听用户名输入:

etUsername.addTextChangedListener(new TextWatcher() {
    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        String username = s.toString().trim();
        if (!username.isEmpty()) {
            displayUserAvatar(username);
        }
    }
});

6.高级功能扩展

1. 自动登录
PreferenceManager prefManager = new PreferenceManager(this);
if (prefManager.isAutoLogin() && prefManager.isRememberPassword()) {
    String username = prefManager.getSavedUsername();
    String password = prefManager.getSavedPassword();
    // 自动登录逻辑
}

2. 密码强度验证
String strength = ValidationUtils.getPasswordStrengthDescription(password);
// 显示密码强度

3. 用户名自动完成
Cursor cursor = dbHelper.getAllUsernames();
String[] usernames = // 从cursor获取用户名数组
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, 
    android.R.layout.simple_dropdown_item_1line, usernames);
AutoCompleteTextView autoComplete = findViewById(R.id.et_username);
autoComplete.setAdapter(adapter);


网站公告

今日签到

点亮在社区的每一天
去签到