Android中資料持久化技術包括檔案儲存、SharedPreferences以及資料庫儲存,對於大量複雜的關係型資料,資料庫無疑是最合適的選擇。

SQLite是一個輕量級的關係型資料庫,運算速度快,佔用資源少,適合在移動裝置上使用。SQLite不僅支援SQL語法,還遵循資料庫的ACID事務,使得本地持久化產生了質的飛躍。

首先我們建立類繼承SQliteOpenHelper抽象類,重寫onCreate和onUpgrade方法實現資料庫的建立和升級。

這裡為了方便多執行緒併發訪問資料庫,將類設計為單例模式。

package com.sdu.runningsdu.Utils;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Build;
import android.util.Log;

/**
 * Created by FTDsm on 2018/6/4.
 */

public class DatabaseHelper extends SQLiteOpenHelper {

    private static String DB_NAME = "xxx.db";
    private static final int DB_VERSION = 1;
    private static DatabaseHelper databaseHelper;

    public DatabaseHelper(Context context, String name) {
        super(context, name, null, DB_VERSION);
        if(Build.VERSION.SDK_INT >= 11){
            getWritableDatabase().enableWriteAheadLogging();
        }
    }

    /**
     * 單例模式
     * @param context
     * @param name
     * @return DatabaseHelper
     */
    public static synchronized DatabaseHelper getInstance(Context context, String name) {
        if (databaseHelper == null) {
            databaseHelper = new DatabaseHelper(context, name);
        }
        return databaseHelper;
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        String userSQL = "create table if not exists user " +
                "(sid varchar(20) primary key, " +
                "name varchar(255), " +
                "password varchar(255), " +
                "imagePath varchar(255), " +
                "image blob)";
        sqLiteDatabase.execSQL(userSQL);
        Log.d("database", "create table user");

        String friendSQL = "create table if not exists friend " +
                "(sid varchar(20) primary key, " +
                "name varchar(255), " +
                "imagePath varchar(255), " +
                "unread integer, " +
                "image blob)";
        sqLiteDatabase.execSQL(friendSQL);
        Log.d("database", "create table friend");

        String groupSQL = "create table if not exists groups " +
                "(gid integer primary key, " +
                "name varchar(255), " +
                "creator varchar(255), " +
                "imagePath varchar(255), " +
                "unread varchar(255), " +
                "image blob)";
        sqLiteDatabase.execSQL(groupSQL);
        Log.d("database", "create table groups");

        String groupMemberSQL = "create table if not exists groupmember " +
                "(gid integer, " +
                "sid varchar(20)," +
                "primary key(gid, sid) )";
        sqLiteDatabase.execSQL(groupMemberSQL);
        Log.d("database", "create table groupmember");

        String friendMessageSQL = "create table if not exists friendmessage " +
                "(mid integer primary key, " +
                "sid varchar(20), " +
                "type integer, " +  /* 0接收 1傳送 */
                "content varchar(255), " +
                "time timestamp)";
        sqLiteDatabase.execSQL(friendMessageSQL);
        Log.d("database", "create table friendmessage");

        String groupMessageSQL = "create table if not exists groupmessage " +
                "(mid integer primary key, " +
                "gid integer, " +
                "sid varchar(20), " +
                "type integer, " +
                "content varchar(255), " +
                "time timestamp)";
        sqLiteDatabase.execSQL(groupMessageSQL);
        Log.d("database", "create table groupmessage");

        String requestSQL = "create table if not exists request " +
                "(rid integer primary key, " +
                "receiver varchar(20), " +
                "sender varchar(20), " +
                "message varchar(255), " +
                "time timestamp, " +
                "state integer)";
        sqLiteDatabase.execSQL(requestSQL);
        Log.d("database", "create table request");
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
        String userSQL = "drop table if exists user";
        sqLiteDatabase.execSQL(userSQL);
        String friendSQL = "drop table if exists friend";
        sqLiteDatabase.execSQL(friendSQL);
        String groupSQL = "drop table if exists group";
        sqLiteDatabase.execSQL(groupSQL);
        String groupMemberSQL = "drop table if exists groupmember";
        sqLiteDatabase.execSQL(groupMemberSQL);
        String friendMessageSQL = "drop table if exists friendmessage";
        sqLiteDatabase.execSQL(friendMessageSQL);
        String groupMessageSQL = "drop table if exists groupmessage";
        sqLiteDatabase.execSQL(groupMessageSQL);
        String requestSQL = "drop table if exists request";
        sqLiteDatabase.execSQL(requestSQL);
        onCreate(sqLiteDatabase);
    }

    @Override
    public synchronized void close() {
        super.close();
    }
}

然後我們建立Database Access Object類,完成資料增刪改查等操作,藉助SQLiteOpenHelper的getReadableDatabase()和getWritableDatabase()方法獲取對資料庫可讀或可寫操作物件。為了確保資料庫可併發訪問,有兩種解決方案,一是以單例模式例項化SQLiteOpenHelper,每次使用完不執行db.close(),以免另一個操作正在進行時關閉資料庫導致操作失敗,二是每次例項化新的SQLiteOpenHelper,並在使用結束後呼叫db.close(),這裡推薦第一種,效能更高且節省資源。

這裡以user為例,介紹資料庫的增刪改查操作。

package com.sdu.runningsdu.Utils;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import com.sdu.runningsdu.JavaBean.Friend;
import com.sdu.runningsdu.JavaBean.Group;
import com.sdu.runningsdu.JavaBean.Message;
import com.sdu.runningsdu.JavaBean.Request;
import com.sdu.runningsdu.JavaBean.User;

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

/**
 * Created by FTDsm on 2018/6/4.
 */

public class MyDAO {

    private Context context;

    private String name;

    private DatabaseHelper databaseHelper;

    public MyDAO(Context context, String name) {
        this.context = context;
        this.name = name;
        this.databaseHelper = DatabaseHelper.getInstance(context, name);
        Log.d("database", "database name: " + databaseHelper.getDatabaseName());
    }

    /**
     * 查詢所有表格
     */
    public void findTable() {
        SQLiteDatabase db = this.databaseHelper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select name from sqlite_master where type='table' order by name", null);
        Log.d("database", "table:\n");
        while (cursor.moveToNext()) {
            Log.d("database", cursor.getString(0));
        }
        cursor.close();
    }

    /**
     * 新增使用者
     * @param user User物件
     */
    public void addUser(User user) {
        SQLiteDatabase db = this.databaseHelper.getWritableDatabase();
        Object[] objects = new Object[3];
        objects[0] = user.getSid();
        objects[1] = user.getName();
        objects[2] = user.getPassword();
        String sql = "insert into user(sid, name, password) values(?,?,?)";
        db.execSQL(sql, objects);
        Log.d("database", "add user: " + user.getName());
    }

    /**
     * 刪除使用者
     * @param sid 使用者id
     */
    public void deleteUser(String sid) {
        SQLiteDatabase db = this.databaseHelper.getWritableDatabase();
        String sql = "delete from user where sid = ?";
        db.execSQL(sql, new Object[]{sid});
        Log.d("database", "delete user: " + sid);
    }

    /**
     * 更新使用者資訊
     * @param user User物件
     */
    public void updateUser(User user) {
        SQLiteDatabase db = this.databaseHelper.getWritableDatabase();
        Object[] objects = new Object[3];
        objects[0] = user.getName();
        objects[1] = user.getPassword();
        objects[2] = user.getSid();
        String sql = "update user set name=?, password=? where sid=?";
        db.execSQL(sql, objects);
        Log.d("database", "update user: " + user.getName());
    }

    /**
     * 查詢是否有使用者
     * @return 當前是否存在使用者
     */
    public boolean hasUser() {
        SQLiteDatabase db = this.databaseHelper.getReadableDatabase();
        Cursor cursor = db.query("user",
                null, null, null, null, null, null);
        Log.w("has user", ""+cursor.getCount());
        if (cursor.getCount() > 0) {
            return true;
        }
        cursor.close();
        return false;
    }

    /**
     * 查詢使用者資訊
     * @param sid 使用者id
     * @return User物件
     */
    public User findUser(String sid) {
        User user = new User();
        SQLiteDatabase db = this.databaseHelper.getReadableDatabase();
        Cursor cursor = db.query("user",
                new String[]{"sid", "name", "password", "imagePath"},
                "sid = ?",
                new String[]{sid},
                null, null, null);
        if (cursor.moveToNext()) {
            user.setSid(cursor.getString(cursor.getColumnIndex("sid")));
            user.setName(cursor.getString(cursor.getColumnIndex("name")));
            user.setPassword(cursor.getString(cursor.getColumnIndex("password")));
            user.setImagePath(cursor.getString(cursor.getColumnIndex("imagePath")));
        }
        Log.d("database", "find user: " + user.toString());
        cursor.close();
        return user;
    }

    /**
     * 查詢所有使用者
     * @return User列表
     */
    public List<User> findAllUser() {
        List<User> users = new ArrayList<>();
        SQLiteDatabase db = this.databaseHelper.getReadableDatabase();
        Cursor cursor = db.query("user",
                new String[]{"sid", "name", "password", "imagePath"},
                null, null, null, null, null);
        while (cursor.moveToNext()) {
            User user = new User();
            user.setSid(cursor.getString(cursor.getColumnIndex("sid")));
            user.setName(cursor.getString(cursor.getColumnIndex("name")));
            user.setPassword(cursor.getString(cursor.getColumnIndex("password")));
            user.setImagePath(cursor.getString(cursor.getColumnIndex("imagePath")));
            Log.d("database", "find user: " + user.toString());
            users.add(user);
        }
        cursor.close();
        return users;
    }
}

正如groupmember表中所定義,SQLite聯合主鍵採用如下寫法:

String sql= "create table if not exists groupmember " +
        "(gid integer, " +
        "sid varchar(20)," +
        "primary key(gid, sid) )";
sqLiteDatabase.execSQL(sql);

SQLite允許儲存varchar、char、integer、real、text、blob、date、time等型別,儲存圖片通常使用blob儲存byte陣列

/**
 * 更新使用者頭像
 * @param sid
 * @param image
 */
public void updateUserImage(String sid, String imagePath, byte[] image) {
    SQLiteDatabase db = this.databaseHelper.getWritableDatabase();
    ContentValues cv = new ContentValues();
    cv.put("imagePath", imagePath);
    cv.put("image", image);
    db.update("user", cv, "sid = ?", new String[]{sid});
}