Android Studio 中使用 SQLite 数据库开发完整指南 (Java 版本)

在这里插入图片描述


在这里插入图片描述

本文将详细介绍在 Android Studio 中使用 Java 语言开发 SQLite 数据库应用的完整方案,包括每一个步骤、每一个文件和每一段代码。

1. 项目准备

1.1 创建新项目

  1. 打开 Android Studio
  2. 选择 “Start a new Android Studio project”
  3. 选择 “Empty Activity” 模板
  4. 设置项目名称(例如 “SQLiteDemoJava”)
  5. 选择语言(选择 Java)
  6. 设置最低 API 级别(建议 API 21 或更高)
  7. 点击 “Finish” 完成项目创建

1.2 添加必要依赖

确保 build.gradle (Module: app) 中包含以下依赖:

dependencies {
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    
    // Room 数据库(SQLite 的抽象层)
    implementation "androidx.room:room-runtime:2.4.2"
    annotationProcessor "androidx.room:room-compiler:2.4.2"
    
    // ViewModel 和 LiveData
    implementation "androidx.lifecycle:lifecycle-viewmodel:2.4.1"
    implementation "androidx.lifecycle:lifecycle-livedata:2.4.1"
    
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

点击 “Sync Now” 同步项目。

2. 数据库设计

假设我们要创建一个简单的笔记应用,包含以下数据表:

  • notes 表:
    • id: 主键,自增
    • title: 笔记标题
    • content: 笔记内容
    • created_at: 创建时间
    • updated_at: 更新时间

3. 实现数据库

3.1 创建实体类 (Entity)

com.yourpackage.model 包下创建 Note.java 文件:

import androidx.room.Entity;
import androidx.room.PrimaryKey;
import androidx.room.TypeConverters;
import java.util.Date;

@Entity(tableName = "notes")
public class Note {
    @PrimaryKey(autoGenerate = true)
    private long id;
    
    private String title;
    private String content;
    
    @TypeConverters(DateConverter.class)
    private Date created_at;
    
    @TypeConverters(DateConverter.class)
    private Date updated_at;
    
    public Note(String title, String content) {
        this.title = title;
        this.content = content;
        this.created_at = new Date();
        this.updated_at = new Date();
    }

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

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

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Date getCreated_at() {
        return created_at;
    }

    public void setCreated_at(Date created_at) {
        this.created_at = created_at;
    }

    public Date getUpdated_at() {
        return updated_at;
    }

    public void setUpdated_at(Date updated_at) {
        this.updated_at = updated_at;
    }
}

3.2 创建日期转换器

由于 Room 不能直接存储 Date 对象,我们需要创建一个转换器:

com.yourpackage.database 包下创建 DateConverter.java

import androidx.room.TypeConverter;
import java.util.Date;

public class DateConverter {
    @TypeConverter
    public static Date toDate(Long timestamp) {
        return timestamp == null ? null : new Date(timestamp);
    }

    @TypeConverter
    public static Long toTimestamp(Date date) {
        return date == null ? null : date.getTime();
    }
}

3.3 创建数据访问对象 (DAO)

com.yourpackage.dao 包下创建 NoteDao.java 文件:

import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Update;
import com.yourpackage.model.Note;
import java.util.List;

@Dao
public interface NoteDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertNote(Note note);
    
    @Update
    void updateNote(Note note);
    
    @Delete
    void deleteNote(Note note);
    
    @Query("SELECT * FROM notes ORDER BY updated_at DESC")
    LiveData<List<Note>> getAllNotes();
    
    @Query("SELECT * FROM notes WHERE id = :noteId")
    Note getNoteById(long noteId);
    
    @Query("SELECT * FROM notes WHERE title LIKE :query OR content LIKE :query ORDER BY updated_at DESC")
    LiveData<List<Note>> searchNotes(String query);
    
    @Query("DELETE FROM notes")
    void deleteAllNotes();
}

3.4 创建数据库类

com.yourpackage.database 包下创建 AppDatabase.java 文件:

import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.TypeConverters;
import com.yourpackage.dao.NoteDao;
import com.yourpackage.model.Note;

@Database(entities = {Note.class}, version = 1, exportSchema = false)
@TypeConverters(DateConverter.class)
public abstract class AppDatabase extends RoomDatabase {
    public abstract NoteDao noteDao();
    
    private static volatile AppDatabase INSTANCE;
    
    public static AppDatabase getDatabase(final Context context) {
        if (INSTANCE == null) {
            synchronized (AppDatabase.class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                                    AppDatabase.class, "notes_database")
                            .fallbackToDestructiveMigration()
                            .build();
                }
            }
        }
        return INSTANCE;
    }
}

4. 创建 Repository

com.yourpackage.repository 包下创建 NoteRepository.java 文件:

import android.app.Application;
import androidx.lifecycle.LiveData;
import com.yourpackage.dao.NoteDao;
import com.yourpackage.database.AppDatabase;
import com.yourpackage.model.Note;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class NoteRepository {
    private NoteDao noteDao;
    private LiveData<List<Note>> allNotes;
    private ExecutorService executorService;
    
    public NoteRepository(Application application) {
        AppDatabase database = AppDatabase.getDatabase(application);
        noteDao = database.noteDao();
        allNotes = noteDao.getAllNotes();
        executorService = Executors.newSingleThreadExecutor();
    }
    
    public LiveData<List<Note>> getAllNotes() {
        return allNotes;
    }
    
    public void insert(Note note) {
        executorService.execute(() -> noteDao.insertNote(note));
    }
    
    public void update(Note note) {
        executorService.execute(() -> {
            note.setUpdated_at(new Date());
            noteDao.updateNote(note);
        });
    }
    
    public void delete(Note note) {
        executorService.execute(() -> noteDao.deleteNote(note));
    }
    
    public Note getNoteById(long id) {
        return noteDao.getNoteById(id);
    }
    
    public LiveData<List<Note>> searchNotes(String query) {
        return noteDao.searchNotes("%" + query + "%");
    }
    
    public void deleteAllNotes() {
        executorService.execute(() -> noteDao.deleteAllNotes());
    }
}

5. 创建 ViewModel

com.yourpackage.viewmodel 包下创建 NoteViewModel.java 文件:

import android.app.Application;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import com.yourpackage.model.Note;
import com.yourpackage.repository.NoteRepository;
import java.util.List;

public class NoteViewModel extends AndroidViewModel {
    private NoteRepository repository;
    private LiveData<List<Note>> allNotes;
    
    public NoteViewModel(Application application) {
        super(application);
        repository = new NoteRepository(application);
        allNotes = repository.getAllNotes();
    }
    
    public void insert(Note note) {
        repository.insert(note);
    }
    
    public void update(Note note) {
        repository.update(note);
    }
    
    public void delete(Note note) {
        repository.delete(note);
    }
    
    public void deleteAllNotes() {
        repository.deleteAllNotes();
    }
    
    public LiveData<List<Note>> getAllNotes() {
        return allNotes;
    }
    
    public Note getNoteById(long id) {
        return repository.getNoteById(id);
    }
    
    public LiveData<List<Note>> searchNotes(String query) {
        return repository.searchNotes(query);
    }
}

6. 实现 UI 层

6.1 创建笔记列表 Activity

创建 NotesListActivity.java 和对应的布局文件 activity_notes_list.xml

activity_notes_list.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.NotesListActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/Theme.SQLiteDemoJava.AppBarOverlay">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/Theme.SQLiteDemoJava.PopupOverlay"
            app:title="@string/app_name" />

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/search_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/search_input"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/search_hint"
                android:imeOptions="actionSearch"
                android:inputType="text" />
        </com.google.android.material.textfield.TextInputLayout>
    </com.google.android.material.appbar.AppBarLayout>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/notes_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false"
        android:paddingBottom="72dp"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab_add_note"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="16dp"
        android:contentDescription="@string/add_note"
        android:src="@drawable/ic_add"
        app:backgroundTint="@color/purple_500"
        app:tint="@android:color/white" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>
NotesListActivity.java
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.yourpackage.R;
import com.yourpackage.adapter.NotesAdapter;
import com.yourpackage.model.Note;
import com.yourpackage.viewmodel.NoteViewModel;
import java.util.Objects;

public class NotesListActivity extends AppCompatActivity {
    private NoteViewModel noteViewModel;
    private NotesAdapter adapter;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_notes_list);
        
        // 设置Toolbar
        setSupportActionBar(findViewById(R.id.toolbar));
        Objects.requireNonNull(getSupportActionBar()).setTitle("Notes");
        
        // 初始化ViewModel
        noteViewModel = new ViewModelProvider(this).get(NoteViewModel.class);
        
        // 设置RecyclerView
        RecyclerView recyclerView = findViewById(R.id.notes_recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setHasFixedSize(true);
        
        adapter = new NotesAdapter();
        recyclerView.setAdapter(adapter);
        
        // 观察笔记列表变化
        noteViewModel.getAllNotes().observe(this, notes -> {
            adapter.setNotes(notes);
        });
        
        // 设置添加笔记按钮
        FloatingActionButton fabAddNote = findViewById(R.id.fab_add_note);
        fabAddNote.setOnClickListener(view -> {
            Intent intent = new Intent(NotesListActivity.this, NoteDetailActivity.class);
            startActivity(intent);
        });
        
        // 设置搜索功能
        EditText searchInput = findViewById(R.id.search_input);
        searchInput.setOnEditorActionListener((v, actionId, event) -> {
            if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                String query = searchInput.getText().toString().trim();
                if (!query.isEmpty()) {
                    noteViewModel.searchNotes(query).observe(this, notes -> {
                        adapter.setNotes(notes);
                    });
                } else {
                    noteViewModel.getAllNotes().observe(this, notes -> {
                        adapter.setNotes(notes);
                    });
                }
                return true;
            }
            return false;
        });
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }
    
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.action_delete_all) {
            noteViewModel.deleteAllNotes();
            Snackbar.make(findViewById(android.R.id.content), "All notes deleted", Snackbar.LENGTH_SHORT).show();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

6.2 创建笔记详情 Activity

创建 NoteDetailActivity.java 和对应的布局文件 activity_note_detail.xml

activity_note_detail.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.NoteDetailActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/Theme.SQLiteDemoJava.AppBarOverlay">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/Theme.SQLiteDemoJava.PopupOverlay" />
    </com.google.android.material.appbar.AppBarLayout>

    <androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:padding="16dp">

            <com.google.android.material.textfield.TextInputLayout
                android:id="@+id/title_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/title_input"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/title_hint"
                    android:inputType="textCapSentences|textAutoCorrect"
                    android:maxLines="1" />
            </com.google.android.material.textfield.TextInputLayout>

            <com.google.android.material.textfield.TextInputLayout
                android:id="@+id/content_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/content_input"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/content_hint"
                    android:inputType="textMultiLine|textCapSentences|textAutoCorrect"
                    android:minLines="5"
                    android:gravity="top" />
            </com.google.android.material.textfield.TextInputLayout>
        </LinearLayout>
    </androidx.core.widget.NestedScrollView>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab_save"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="16dp"
        android:contentDescription="@string/save_note"
        android:src="@drawable/ic_save"
        app:backgroundTint="@color/purple_500"
        app:tint="@android:color/white" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>
NoteDetailActivity.java
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.MenuItem;
import android.widget.EditText;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputLayout;
import com.yourpackage.R;
import com.yourpackage.model.Note;
import com.yourpackage.viewmodel.NoteViewModel;
import java.util.Date;

public class NoteDetailActivity extends AppCompatActivity {
    public static final String EXTRA_NOTE_ID = "extra_note_id";
    
    private NoteViewModel noteViewModel;
    private EditText titleInput, contentInput;
    private TextInputLayout titleLayout, contentLayout;
    private long noteId = -1;
    private boolean isNewNote = true;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_note_detail);
        
        // 设置Toolbar
        setSupportActionBar(findViewById(R.id.toolbar));
        Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
        
        // 初始化ViewModel
        noteViewModel = new ViewModelProvider(this).get(NoteViewModel.class);
        
        // 初始化视图
        titleInput = findViewById(R.id.title_input);
        contentInput = findViewById(R.id.content_input);
        titleLayout = findViewById(R.id.title_layout);
        contentLayout = findViewById(R.id.content_layout);
        
        // 检查是否是编辑现有笔记
        if (getIntent().hasExtra(EXTRA_NOTE_ID)) {
            noteId = getIntent().getLongExtra(EXTRA_NOTE_ID, -1);
            isNewNote = false;
            loadNote();
        }
        
        // 设置保存按钮
        findViewById(R.id.fab_save).setOnClickListener(view -> saveNote());
        
        // 设置输入验证
        setupTextWatchers();
    }
    
    private void loadNote() {
        Note note = noteViewModel.getNoteById(noteId);
        if (note != null) {
            titleInput.setText(note.getTitle());
            contentInput.setText(note.getContent());
        }
    }
    
    private void setupTextWatchers() {
        titleInput.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) {}
            
            @Override
            public void afterTextChanged(Editable s) {
                validateInputs();
            }
        });
        
        contentInput.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) {}
            
            @Override
            public void afterTextChanged(Editable s) {
                validateInputs();
            }
        });
    }
    
    private boolean validateInputs() {
        boolean titleValid = !titleInput.getText().toString().trim().isEmpty();
        boolean contentValid = !contentInput.getText().toString().trim().isEmpty();
        
        titleLayout.setError(titleValid ? null : getString(R.string.title_required));
        contentLayout.setError(contentValid ? null : getString(R.string.content_required));
        
        return titleValid && contentValid;
    }
    
    private void saveNote() {
        if (!validateInputs()) return;
        
        String title = titleInput.getText().toString().trim();
        String content = contentInput.getText().toString().trim();
        
        if (isNewNote) {
            Note note = new Note(title, content);
            noteViewModel.insert(note);
            Snackbar.make(findViewById(android.R.id.content), "Note saved", Snackbar.LENGTH_SHORT).show();
        } else {
            Note existingNote = noteViewModel.getNoteById(noteId);
            if (existingNote != null) {
                existingNote.setTitle(title);
                existingNote.setContent(content);
                existingNote.setUpdated_at(new Date());
                noteViewModel.update(existingNote);
                Snackbar.make(findViewById(android.R.id.content), "Note updated", Snackbar.LENGTH_SHORT).show();
            }
        }
        finish();
    }
    
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            onBackPressed();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

6.3 创建 RecyclerView Adapter

com.yourpackage.adapter 包下创建 NotesAdapter.java 文件:

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.card.MaterialCardView;
import com.yourpackage.R;
import com.yourpackage.model.Note;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

public class NotesAdapter extends RecyclerView.Adapter<NotesAdapter.NoteViewHolder> {
    private List<Note> notes = new ArrayList<>();
    private OnItemClickListener listener;
    
    @NonNull
    @Override
    public NoteViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_note, parent, false);
        return new NoteViewHolder(itemView);
    }
    
    @Override
    public void onBindViewHolder(@NonNull NoteViewHolder holder, int position) {
        Note currentNote = notes.get(position);
        holder.title.setText(currentNote.getTitle());
        holder.content.setText(currentNote.getContent());
        
        SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd, yyyy - hh:mm a", Locale.getDefault());
        holder.date.setText(dateFormat.format(currentNote.getUpdated_at()));
        
        holder.cardView.setOnClickListener(v -> {
            if (listener != null && position != RecyclerView.NO_POSITION) {
                listener.onItemClick(notes.get(position));
            }
        });
    }
    
    @Override
    public int getItemCount() {
        return notes.size();
    }
    
    public void setNotes(List<Note> notes) {
        this.notes = notes;
        notifyDataSetChanged();
    }
    
    public Note getNoteAt(int position) {
        return notes.get(position);
    }
    
    class NoteViewHolder extends RecyclerView.ViewHolder {
        private TextView title, content, date;
        private MaterialCardView cardView;
        
        public NoteViewHolder(@NonNull View itemView) {
            super(itemView);
            title = itemView.findViewById(R.id.note_title);
            content = itemView.findViewById(R.id.note_content);
            date = itemView.findViewById(R.id.note_date);
            cardView = (MaterialCardView) itemView;
        }
    }
    
    public interface OnItemClickListener {
        void onItemClick(Note note);
    }
    
    public void setOnItemClickListener(OnItemClickListener listener) {
        this.listener = listener;
    }
}

创建对应的列表项布局文件 item_note.xml

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 
    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="wrap_content"
    android:layout_margin="8dp"
    app:cardCornerRadius="8dp"
    app:cardElevation="4dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="16dp">

        <TextView
            android:id="@+id/note_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textAppearance="@style/TextAppearance.AppCompat.Headline"
            android:textColor="@android:color/black" />

        <TextView
            android:id="@+id/note_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:ellipsize="end"
            android:maxLines="2"
            android:textAppearance="@style/TextAppearance.AppCompat.Body1"
            android:textColor="@android:color/darker_gray" />

        <TextView
            android:id="@+id/note_date"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:textAppearance="@style/TextAppearance.AppCompat.Caption"
            android:textColor="@android:color/darker_gray" />
    </LinearLayout>
</com.google.android.material.card.MaterialCardView>

7. 添加菜单资源

res/menu 目录下创建 menu_main.xml 文件:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    
    <item
        android:id="@+id/action_delete_all"
        android:icon="@drawable/ic_delete"
        android:title="@string/delete_all"
        app:showAsAction="never" />
</menu>

8. 添加字符串资源

res/values/strings.xml 文件中添加以下字符串:

<resources>
    <string name="app_name">SQLite Notes (Java)</string>
    <string name="title_hint">Title</string>
    <string name="content_hint">Content</string>
    <string name="search_hint">Search notes...</string>
    <string name="add_note">Add new note</string>
    <string name="save_note">Save note</string>
    <string name="delete_all">Delete all notes</string>
    <string name="title_required">Title is required</string>
    <string name="content_required">Content is required</string>
</resources>

9. 添加图标资源

确保在 res/drawable 目录下有以下矢量图标:

  • ic_add.xml (添加按钮图标)
  • ic_save.xml (保存按钮图标)
  • ic_delete.xml (删除按钮图标)

10. 更新 AndroidManifest.xml

确保 AndroidManifest.xml 包含两个 Activity:

<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/Theme.SQLiteDemoJava">
    
    <activity
        android:name=".ui.NoteDetailActivity"
        android:parentActivityName=".ui.NotesListActivity" />
    
    <activity
        android:name=".ui.NotesListActivity"
        android:label="@string/app_name">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

11. 运行和测试应用

现在,您可以运行应用程序并测试以下功能:

  1. 添加新笔记
  2. 查看笔记列表
  3. 编辑现有笔记
  4. 删除笔记
  5. 搜索笔记
  6. 删除所有笔记

12. 数据库调试技巧

12.1 查看数据库内容

  1. 在 Android Studio 中打开 “Device File Explorer” (View -> Tool Windows -> Device File Explorer)
  2. 导航到 /data/data/com.yourpackage/databases/
  3. 找到 notes_database 文件
  4. 右键点击并选择 “Save As” 将其保存到本地
  5. 使用 SQLite 浏览器工具(如 DB Browser for SQLite)打开该文件查看内容

12.2 使用 Stetho 进行调试

添加 Stetho 依赖到 build.gradle:

implementation 'com.facebook.stetho:stetho:1.6.0'

创建一个自定义 Application 类:

import android.app.Application;
import com.facebook.stetho.Stetho;

public class NotesApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Stetho.initializeWithDefaults(this);
    }
}

更新 AndroidManifest.xml 中的 application 标签:

<application
    android:name=".NotesApplication"
    ...>
    ...
</application>

运行应用后,在 Chrome 浏览器中访问 chrome://inspect 可以查看和调试数据库。

13. 数据库迁移

当您需要更改数据库结构时(例如添加新表或修改现有表),需要进行数据库迁移。

13.1 修改实体类

例如,我们要为 Note 添加一个 is_pinned 字段:

@Entity(tableName = "notes")
public class Note {
    // 现有字段...
    private boolean isPinned;
    
    // Getter 和 Setter
    public boolean isPinned() {
        return isPinned;
    }
    
    public void setPinned(boolean pinned) {
        isPinned = pinned;
    }
}

13.2 更新数据库版本

修改 AppDatabase.java:

@Database(entities = {Note.class}, version = 2, exportSchema = false)
@TypeConverters(DateConverter.class)
public abstract class AppDatabase extends RoomDatabase {
    // ...
}

13.3 添加迁移策略

static final Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL("ALTER TABLE notes ADD COLUMN is_pinned INTEGER NOT NULL DEFAULT 0");
    }
};

// 在 databaseBuilder 中添加迁移
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                AppDatabase.class, "notes_database")
        .addMigrations(MIGRATION_1_2)
        .build();

14. 性能优化建议

  1. 使用事务:对于批量操作,使用事务可以显著提高性能:
@Dao
public interface NoteDao {
    @Transaction
    default void insertAll(List<Note> notes) {
        for (Note note : notes) {
            insertNote(note);
        }
    }
}
  1. 索引优化:为常用查询字段添加索引:
@Entity(tableName = "notes", indices = {@Index(value = {"title"}, unique = false)})
public class Note {
    // ...
}
  1. 避免在主线程操作数据库:始终确保数据库操作在后台线程执行。

15. 总结

本指南详细介绍了在 Android Studio 中使用 Java 语言开发 SQLite 数据库应用的完整流程,包括:

  1. 设置项目和依赖
  2. 设计数据库结构
  3. 实现 Room 数据库组件(Entity, DAO, Database)
  4. 创建 Repository 层
  5. 实现 ViewModel
  6. 构建用户界面
  7. 添加数据库迁移支持
  8. 性能优化建议

通过遵循这些步骤,您可以构建一个功能完善、结构清晰的 Android 应用,充分利用 SQLite 数据库的强大功能。

评论 266
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

百锦再@新空间

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值