Android 开发中的单例模式(Singleton Pattern)

本科三年级的 Android 开发课程。

这周 Android 实验用到了 SQLite 数据库,验收的时候师姐问我对 SQLiteOpenHelper 实例是怎么处理的。我说那肯定是每一个 Activity 一个 SQLiteOpenHelper 实例,然后师姐说这样不太好让我改成单例的。

说起来羞愧,大一下学期买的设计模式到大三了还没看完,只记得观察者模式那几个常用的。回到去查了下文档就把数据库改成了单例。

一开始代码是这样的

class Database extends SQLiteOpenHelper {
    private final String TABLE_NAME = "birthday_log";

    Database(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }
}

然后我每次在 Activity 里面操作数据库都 new 一个 Database 实例,如果在 Service 里面这样干,很容易会翻车的。我们希望全局只有一个 Database 实例。

防止被创建实例,我们可以把构造函数设置成 private 的,这样就不会被创建了。

class Database extends SQLiteOpenHelper {
    private final String TABLE_NAME = "birthday_log";

    private Database(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }
}

这样怎样创建实例呢?Private 当然是 Class 内部调用了。

class Database extends SQLiteOpenHelper {
    private final String TABLE_NAME = "birthday_log";
    private static Database database = null;

    private Database(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    static Database getDatabase(Context context) {
        if (database == null) {
            database = new Database(context, DB_NAME, null, DB_VERSION);
        }
        return database;
    }
}

怎样理解 database 这个变量呢?这个是自身实例的引用,可以理解为 static class 里的 this 指针,当然实际上 static 的函数是没有 this 指针的。

以后要获取 Database 实例只需要 Database.getDatabase(context) 即可。

最后考虑一个极端的情况,假如 Database.getDatabase(context) 被两个进程并发执行怎么办,那样还是有可能创建两个 Database 实例。在 Java 中用 synchronized 关键字修饰函数,代表在同一时间只能有一个函数并发执行。

class Database extends SQLiteOpenHelper {
    private final String TABLE_NAME = "birthday_log";
    private static Database database = null;

    private Database(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    static synchronized Database getDatabase(Context context) {
        if (database == null) {
            database = new Database(context, DB_NAME, null, DB_VERSION);
        }
        return database;
    }
}

当然这样处理是会有性能损耗的,每次运行 Database.getDatabase(context) 都会有一个获取锁和释放锁的过程,弥补这样的方法是直接 private static Database database = new Database(context, DB_NAME, null, DB_VERSION);,这样会在运行时把对象构造好,然而 SQLiteOpenHelper 需要 context,暂时不清楚直接赋值如何获取 context

这样一番操作之后,以后就可以在任意地方通过 Database.getDatabase(context) 共享一个 SQLiteOpenHelper 实例了。

最后,还有,看来不能只顾着拿 Kindle 看小说了,要把设计模式拿回宿舍看了