MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Android平台SQLite开发组件添加指南

2022-09-233.7k 阅读

1. 准备工作

在Android平台上使用SQLite进行开发,首先要确保开发环境的搭建是正确的。这包括安装最新版本的Android Studio,因为新版本通常会对SQLite的支持进行优化和更新。同时,Android SDK也需要保持最新,特别是与数据库操作相关的部分。

1.1 检查Android SDK

打开Android Studio,点击 File -> Project Structure。在弹出的窗口中,选择 SDK Location。在这里,你可以看到当前安装的Android SDK的路径。确保 Android SDK Platform - ToolsAndroid SDK Build - Tools 都是最新版本。如果不是,可以点击 SDK Update Sites 中的 Sources 链接,前往SDK管理器进行更新。

1.2 项目配置

在你的Android项目的 build.gradle 文件中,确保 minSdkVersion 满足SQLite使用的基本要求。一般来说,SQLite在Android 1.0(API Level 1)及以上版本都有支持,但为了获取更好的兼容性和功能,建议将 minSdkVersion 设置为14(Android 4.0)及以上。

android {
    compileSdkVersion 33
    defaultConfig {
        applicationId "com.example.yourpackage"
        minSdkVersion 14
        targetSdkVersion 33
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

2. SQLiteOpenHelper类的使用

SQLiteOpenHelper 是Android中用于管理数据库创建和版本管理的一个重要类。通过继承这个类,我们可以方便地创建、升级和管理SQLite数据库。

2.1 创建SQLiteOpenHelper子类

创建一个新的Java类,例如 MyDatabaseHelper,继承自 SQLiteOpenHelper。在这个类中,需要实现两个重要的方法:onCreateonUpgrade

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class MyDatabaseHelper extends SQLiteOpenHelper {

    // 数据库名称
    private static final String DATABASE_NAME = "my_database.db";
    // 数据库版本
    private static final int DATABASE_VERSION = 1;

    // 构造函数
    public MyDatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        // 创建表的SQL语句
        String createTableQuery = "CREATE TABLE users (" +
                "id INTEGER PRIMARY KEY AUTOINCREMENT," +
                "name TEXT," +
                "age INTEGER)";
        db.execSQL(createTableQuery);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // 当数据库版本更新时执行的操作,这里简单地删除旧表并创建新表
        db.execSQL("DROP TABLE IF EXISTS users");
        onCreate(db);
    }
}

在上述代码中:

  • MyDatabaseHelper 类的构造函数接收一个 Context 参数,并调用父类的构造函数,传入数据库名称、游标工厂(这里为 null,表示使用默认的游标工厂)和数据库版本。
  • onCreate 方法在数据库首次创建时被调用。在这个方法中,我们执行了一条SQL语句来创建一个名为 users 的表,该表包含 id(自增长的主键)、name(文本类型)和 age(整数类型)三个字段。
  • onUpgrade 方法在数据库版本发生变化时被调用。这里的示例代码只是简单地删除旧表并重新创建新表,实际应用中可能需要更复杂的数据迁移逻辑。

2.2 获取数据库实例

在需要使用数据库的地方,例如Activity或Service中,可以通过创建 MyDatabaseHelper 的实例来获取数据库对象。

import android.os.Bundle;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

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

        // 创建MyDatabaseHelper实例
        MyDatabaseHelper dbHelper = new MyDatabaseHelper(this);
        // 获取可写数据库
        SQLiteDatabase db = dbHelper.getWritableDatabase();

        if (db != null) {
            Toast.makeText(this, "数据库已创建或打开", Toast.LENGTH_SHORT).show();
            db.close();
        }
    }
}

在上述代码中,通过 MyDatabaseHelper 的实例调用 getWritableDatabase 方法来获取一个可写的数据库实例。如果数据库不存在,getWritableDatabase 方法会先调用 onCreate 方法创建数据库。获取数据库实例后,我们可以进行各种数据库操作,操作完成后记得关闭数据库以释放资源。

3. 基本的数据库操作

3.1 插入数据

向SQLite数据库中插入数据可以使用 SQLiteDatabaseinsert 方法。

import android.content.ContentValues;
import android.database.sqlite.SQLiteDatabase;

public class DataInsertion {

    public static void insertUser(SQLiteDatabase db, String name, int age) {
        // 创建ContentValues对象
        ContentValues values = new ContentValues();
        values.put("name", name);
        values.put("age", age);

        // 插入数据
        long newRowId = db.insert("users", null, values);
        if (newRowId != -1) {
            System.out.println("数据插入成功,新行ID:" + newRowId);
        } else {
            System.out.println("数据插入失败");
        }
    }
}

在上述代码中:

  • ContentValues 类用于存储要插入的数据,它类似于一个键值对的映射。
  • db.insert 方法的第一个参数是表名,第二个参数是 nullColumnHack,当 ContentValues 为空时,需要指定一个列名,这里因为 ContentValues 不为空,所以设置为 null。第三个参数是 ContentValues 对象。
  • insert 方法返回插入行的ID,如果返回 -1,表示插入失败。

3.2 查询数据

查询数据可以使用 SQLiteDatabasequery 方法。

import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

public class DataQuery {

    public static void queryAllUsers(SQLiteDatabase db) {
        // 选择所有列
        String[] projection = {"id", "name", "age"};
        // 不使用where子句
        String selection = null;
        String[] selectionArgs = null;
        // 不进行分组
        String groupBy = null;
        String having = null;
        // 按id升序排序
        String orderBy = "id ASC";

        Cursor cursor = db.query(
                "users",
                projection,
                selection,
                selectionArgs,
                groupBy,
                having,
                orderBy
        );

        if (cursor != null) {
            while (cursor.moveToNext()) {
                int id = cursor.getInt(cursor.getColumnIndexOrThrow("id"));
                String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
                int age = cursor.getInt(cursor.getColumnIndexOrThrow("age"));
                System.out.println("用户ID:" + id + ",姓名:" + name + ",年龄:" + age);
            }
            cursor.close();
        }
    }
}

在上述代码中:

  • projection 定义了要查询的列。
  • selectionselectionArgs 用于构建 WHERE 子句,如果不使用 WHERE 子句,可以设置为 null
  • groupByhaving 用于分组查询,这里不使用,设置为 null
  • orderBy 用于指定排序方式。
  • query 方法返回一个 Cursor 对象,通过 Cursor 可以遍历查询结果集。

3.3 更新数据

更新数据可以使用 SQLiteDatabaseupdate 方法。

import android.content.ContentValues;
import android.database.sqlite.SQLiteDatabase;

public class DataUpdate {

    public static void updateUserAge(SQLiteDatabase db, int newAge, int userId) {
        // 创建ContentValues对象
        ContentValues values = new ContentValues();
        values.put("age", newAge);

        // 更新数据的条件
        String selection = "id =?";
        String[] selectionArgs = {String.valueOf(userId)};

        int count = db.update(
                "users",
                values,
                selection,
                selectionArgs
        );

        if (count > 0) {
            System.out.println("数据更新成功,更新行数:" + count);
        } else {
            System.out.println("数据更新失败");
        }
    }
}

在上述代码中:

  • ContentValues 对象用于存储要更新的数据。
  • selectionselectionArgs 定义了更新的条件,这里表示更新 id 等于指定值的记录。
  • update 方法返回更新的行数,如果返回 0,表示没有记录被更新。

3.4 删除数据

删除数据可以使用 SQLiteDatabasedelete 方法。

import android.database.sqlite.SQLiteDatabase;

public class DataDelete {

    public static void deleteUser(SQLiteDatabase db, int userId) {
        // 删除数据的条件
        String selection = "id =?";
        String[] selectionArgs = {String.valueOf(userId)};

        int count = db.delete(
                "users",
                selection,
                selectionArgs
        );

        if (count > 0) {
            System.out.println("数据删除成功,删除行数:" + count);
        } else {
            System.out.println("数据删除失败");
        }
    }
}

在上述代码中:

  • selectionselectionArgs 定义了删除的条件,这里表示删除 id 等于指定值的记录。
  • delete 方法返回删除的行数,如果返回 0,表示没有记录被删除。

4. 事务处理

在数据库操作中,事务是非常重要的概念。事务可以确保一组数据库操作要么全部成功,要么全部失败,从而保证数据的一致性。

4.1 手动事务处理

在Android中,可以通过 SQLiteDatabase 的方法手动管理事务。

import android.database.sqlite.SQLiteDatabase;

public class TransactionExample {

    public static void performTransaction(SQLiteDatabase db) {
        db.beginTransaction();
        try {
            // 插入一条数据
            ContentValues values1 = new ContentValues();
            values1.put("name", "张三");
            values1.put("age", 25);
            long newRowId1 = db.insert("users", null, values1);

            // 更新一条数据
            ContentValues values2 = new ContentValues();
            values2.put("age", 26);
            String selection = "id =?";
            String[] selectionArgs = {String.valueOf(newRowId1)};
            int count = db.update("users", values2, selection, selectionArgs);

            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
    }
}

在上述代码中:

  • beginTransaction 方法开始一个事务。
  • try 块中执行一系列数据库操作。如果所有操作都成功,调用 setTransactionSuccessful 方法标记事务成功。
  • 无论 try 块中的操作是否成功,finally 块中的 endTransaction 方法都会被执行。如果事务标记为成功,endTransaction 方法会提交事务;否则,会回滚事务。

4.2 使用事务提升性能

事务不仅可以保证数据一致性,还可以提升数据库操作的性能。例如,批量插入数据时,使用事务可以减少磁盘I/O操作次数。

import android.content.ContentValues;
import android.database.sqlite.SQLiteDatabase;

public class BatchInsertion {

    public static void batchInsertUsers(SQLiteDatabase db, String[] names, int[] ages) {
        db.beginTransaction();
        try {
            for (int i = 0; i < names.length; i++) {
                ContentValues values = new ContentValues();
                values.put("name", names[i]);
                values.put("age", ages[i]);
                db.insert("users", null, values);
            }
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
    }
}

在上述代码中,通过事务将批量插入操作作为一个整体,大大提高了插入效率。

5. SQLite数据库的优化

5.1 索引的使用

索引可以加快数据库查询的速度。在创建表时,可以同时创建索引。

CREATE TABLE users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT,
    age INTEGER
);

CREATE INDEX idx_name ON users (name);

在上述代码中,通过 CREATE INDEX 语句创建了一个名为 idx_name 的索引,该索引基于 users 表的 name 列。这样在查询 name 列时,数据库可以利用索引快速定位数据,提高查询效率。

5.2 避免频繁的数据库操作

频繁地打开和关闭数据库会消耗大量的系统资源,降低应用性能。可以考虑在应用的生命周期中,尽量保持数据库连接的打开状态,并合理复用数据库操作对象。

public class DatabaseManager {

    private static DatabaseManager instance;
    private MyDatabaseHelper dbHelper;
    private SQLiteDatabase db;

    private DatabaseManager(Context context) {
        dbHelper = new MyDatabaseHelper(context);
        db = dbHelper.getWritableDatabase();
    }

    public static DatabaseManager getInstance(Context context) {
        if (instance == null) {
            instance = new DatabaseManager(context);
        }
        return instance;
    }

    public SQLiteDatabase getDatabase() {
        return db;
    }

    public void closeDatabase() {
        if (db != null) {
            db.close();
        }
        if (dbHelper != null) {
            dbHelper.close();
        }
    }
}

在上述代码中,通过单例模式创建一个 DatabaseManager 类,负责管理数据库的打开和关闭。应用中可以通过 DatabaseManager.getInstance(context) 获取数据库实例,这样可以避免频繁地创建和销毁数据库连接。

5.3 合理使用缓存

在某些情况下,对于一些不经常变化的数据,可以使用缓存来减少对数据库的查询次数。例如,可以使用Android的 SharedPreferences 或内存缓存(如 LruCache)来缓存部分数据。

import android.content.Context;
import android.content.SharedPreferences;

public class DataCache {

    private static final String PREF_NAME = "data_cache";
    private static final String KEY_USER_NAME = "user_name";

    public static void saveUserName(Context context, String name) {
        SharedPreferences.Editor editor = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit();
        editor.putString(KEY_USER_NAME, name);
        editor.apply();
    }

    public static String getUserName(Context context) {
        SharedPreferences preferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
        return preferences.getString(KEY_USER_NAME, null);
    }
}

在上述代码中,通过 SharedPreferences 缓存了用户的姓名。在需要获取用户姓名时,先从缓存中获取,如果缓存中没有,则再从数据库中查询。

6. 多线程下的SQLite使用

6.1 线程安全问题

SQLite本身是线程安全的,但在Android应用中,由于UI线程和后台线程的存在,需要注意数据库操作的线程问题。如果在UI线程中执行长时间的数据库操作,会导致应用卡顿,影响用户体验。

6.2 使用AsyncTask进行数据库操作

AsyncTask 是Android提供的一个方便的异步任务类,可以在后台线程执行数据库操作,并在UI线程更新结果。

import android.os.AsyncTask;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

public class UserQueryTask extends AsyncTask<Void, Void, List<String>> {

    private SQLiteDatabase db;
    private TextView resultTextView;

    public UserQueryTask(SQLiteDatabase db, TextView resultTextView) {
        this.db = db;
        this.resultTextView = resultTextView;
    }

    @Override
    protected List<String> doInBackground(Void... voids) {
        List<String> userList = new ArrayList<>();
        String[] projection = {"name", "age"};
        Cursor cursor = db.query("users", projection, null, null, null, null, null);
        if (cursor != null) {
            while (cursor.moveToNext()) {
                String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
                int age = cursor.getInt(cursor.getColumnIndexOrThrow("age"));
                userList.add("姓名:" + name + ",年龄:" + age);
            }
            cursor.close();
        }
        return userList;
    }

    @Override
    protected void onPostExecute(List<String> userList) {
        StringBuilder result = new StringBuilder();
        for (String user : userList) {
            result.append(user).append("\n");
        }
        resultTextView.setText(result.toString());
    }
}

在上述代码中:

  • UserQueryTask 继承自 AsyncTaskdoInBackground 方法在后台线程执行数据库查询操作。
  • onPostExecute 方法在UI线程执行,用于更新UI,将查询结果显示在 TextView 中。

6.3 使用线程池

除了 AsyncTask,还可以使用线程池来管理数据库操作的线程。ExecutorService 是Java提供的一个线程池框架。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class DatabaseThreadPool {

    private static final int THREAD_POOL_SIZE = 5;
    private static ExecutorService executorService;

    static {
        executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
    }

    public static void executeTask(Runnable task) {
        executorService.submit(task);
    }

    public static void shutdownThreadPool() {
        if (executorService != null &&!executorService.isShutdown()) {
            executorService.shutdown();
        }
    }
}

在上述代码中:

  • 创建了一个固定大小为5的线程池。
  • executeTask 方法用于提交任务到线程池执行,shutdownThreadPool 方法用于关闭线程池。

7. SQLite与ContentProvider结合使用

7.1 ContentProvider简介

ContentProvider 是Android四大组件之一,它主要用于在不同的应用程序之间共享数据。通过 ContentProvider,其他应用可以访问本应用的SQLite数据库中的数据。

7.2 创建ContentProvider

创建一个继承自 ContentProvider 的类,例如 MyContentProvider

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;

public class MyContentProvider extends ContentProvider {

    private static final String AUTHORITY = "com.example.mycontentprovider";
    private static final String PATH_USERS = "users";
    private static final int USERS = 1;
    private static final int USER_ID = 2;

    private static final UriMatcher uriMatcher;
    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHORITY, PATH_USERS, USERS);
        uriMatcher.addURI(AUTHORITY, PATH_USERS + "/#", USER_ID);
    }

    private MyDatabaseHelper dbHelper;

    @Override
    public boolean onCreate() {
        dbHelper = new MyDatabaseHelper(getContext());
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        int match = uriMatcher.match(uri);
        switch (match) {
            case USERS:
                return db.query("users", projection, selection, selectionArgs, null, null, sortOrder);
            case USER_ID:
                long id = ContentUris.parseId(uri);
                String idSelection = "_id =?";
                String[] idSelectionArgs = {String.valueOf(id)};
                return db.query("users", projection, idSelection, idSelectionArgs, null, null, sortOrder);
            default:
                throw new IllegalArgumentException("未知的URI:" + uri);
        }
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int match = uriMatcher.match(uri);
        if (match == USERS) {
            long newRowId = db.insert("users", null, values);
            if (newRowId > 0) {
                Uri newUri = ContentUris.withAppendedId(Uri.parse("content://" + AUTHORITY + "/" + PATH_USERS), newRowId);
                getContext().getContentResolver().notifyChange(newUri, null);
                return newUri;
            }
        }
        throw new IllegalArgumentException("未知的URI:" + uri);
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int match = uriMatcher.match(uri);
        switch (match) {
            case USERS:
                return db.update("users", values, selection, selectionArgs);
            case USER_ID:
                long id = ContentUris.parseId(uri);
                String idSelection = "_id =?";
                String[] idSelectionArgs = {String.valueOf(id)};
                return db.update("users", values, idSelection, idSelectionArgs);
            default:
                throw new IllegalArgumentException("未知的URI:" + uri);
        }
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int match = uriMatcher.match(uri);
        switch (match) {
            case USERS:
                return db.delete("users", selection, selectionArgs);
            case USER_ID:
                long id = ContentUris.parseId(uri);
                String idSelection = "_id =?";
                String[] idSelectionArgs = {String.valueOf(id)};
                return db.delete("users", idSelection, idSelectionArgs);
            default:
                throw new IllegalArgumentException("未知的URI:" + uri);
        }
    }

    @Override
    public String getType(Uri uri) {
        int match = uriMatcher.match(uri);
        switch (match) {
            case USERS:
                return "vnd.android.cursor.dir/vnd.com.example.mycontentprovider.users";
            case USER_ID:
                return "vnd.android.cursor.item/vnd.com.example.mycontentprovider.users";
            default:
                throw new IllegalArgumentException("未知的URI:" + uri);
        }
    }
}

在上述代码中:

  • 定义了 AUTHORITYPATH_USERS 等常量,用于构建 ContentProvider 的URI。
  • UriMatcher 用于匹配不同的URI,根据不同的URI执行不同的数据库操作。
  • 实现了 ContentProvider 的各个抽象方法,如 queryinsertupdatedelete,在这些方法中进行数据库操作,并处理不同的URI情况。

7.3 在AndroidManifest.xml中注册ContentProvider

AndroidManifest.xml 文件中注册 MyContentProvider

<provider
    android:name=".MyContentProvider"
    android:authorities="com.example.mycontentprovider"
    android:exported="true" />

在上述代码中,android:name 指定了 ContentProvider 的类名,android:authorities 指定了 ContentProvider 的权限,android:exported 设置为 true 表示该 ContentProvider 可以被其他应用访问。

7.4 其他应用访问ContentProvider

其他应用可以通过 ContentResolver 来访问 MyContentProvider 提供的数据。

import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;

public class OtherAppAccess {

    private static final Uri CONTENT_URI = Uri.parse("content://com.example.mycontentprovider/users");

    public static void insertUser(ContentResolver resolver, String name, int age) {
        ContentValues values = new ContentValues();
        values.put("name", name);
        values.put("age", age);
        Uri newUri = resolver.insert(CONTENT_URI, values);
        if (newUri != null) {
            System.out.println("数据插入成功,新行URI:" + newUri);
        } else {
            System.out.println("数据插入失败");
        }
    }

    public static void queryAllUsers(ContentResolver resolver) {
        String[] projection = {"id", "name", "age"};
        Cursor cursor = resolver.query(CONTENT_URI, projection, null, null, null);
        if (cursor != null) {
            while (cursor.moveToNext()) {
                int id = cursor.getInt(cursor.getColumnIndexOrThrow("id"));
                String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
                int age = cursor.getInt(cursor.getColumnIndexOrThrow("age"));
                System.out.println("用户ID:" + id + ",姓名:" + name + ",年龄:" + age);
            }
            cursor.close();
        }
    }
}

在上述代码中:

  • 定义了 CONTENT_URI 作为访问 MyContentProvider 的URI。
  • insertUser 方法通过 ContentResolverinsert 方法向 MyContentProvider 插入数据。
  • queryAllUsers 方法通过 ContentResolverquery 方法查询 MyContentProvider 中的数据。

通过以上步骤,我们完成了在Android平台上添加SQLite开发组件,并进行了各种数据库操作、优化以及与 ContentProvider 的结合使用。希望这些内容能帮助你在Android开发中更好地利用SQLite数据库。