Android內置了輕量級的數據庫SQLite,這里將自己理解作個記錄,方便自己復習。
一.首先,創建SQLite數據庫比較常見的方式是通過Android提供的SQLiteOpenHelper來實現,先貼一段代碼:
import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.database.sqlite.SQLiteDatabase.CursorFactory; import java.io.File; public class MainActivity extends AppCompatActivity implements View.OnClickListener{ private static final String TAG = "CustomSQLiteOpenHelper"; private Button mButton; private CustomSQLiteOpenHelper customSQLiteOpenHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mButton = (Button)findViewById(R.id.btn1); mButton.setOnClickListener(this); customSQLiteOpenHelper = new CustomSQLiteOpenHelper(this);//創建SQLIteOpenHelper對象(1) } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn1: customSQLiteOpenHelper.getWritableDatabase();//通過getWritableDatabase()方式來新建SQLite數據庫(2) break; default: break; } } class CustomSQLiteOpenHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "book_store.db";//數據庫名字 private static final int DATABASE_VERSION = 1;//數據庫版本號 private static final String CREATE_TABLE = "create table bookStore (" + "id integer primary key autoincrement," + "book_name text, " + "author text, " + "price real)";//數據庫里的表 public CustomSQLiteOpenHelper(Context context) { this(context, DATABASE_NAME, null, DATABASE_VERSION); } private CustomSQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version);//調用到SQLiteOpenHelper中 Log.d(TAG,"New CustomSQLiteOpenHelper"); } @Override public void onCreate(SQLiteDatabase db) { Log.d(TAG,"onCreate"); db.execSQL(CREATE_TABLE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } } }
在MainActivity布局文件activity_main.xml里,只有一個Button控件,點擊該Button,就會通過調用SQLiteOpenHelper.getWritableDatabase()的方式來創建名為DATABASE_NAME的數據庫。數據庫是否新建成功,可以在/data/data/<Package_Name>/databse/目錄下確認。

用於創建SQLite數據庫比較重要的代碼:(1)建立SQLiteOpenHelper對象;(2)調用getWritableDatabase()來建立SQLite數據庫。下面來看看這兩句代碼主要做了什么。
二.代碼分析
(1)建立SQLiteOpenHelper對象
/** * Create a helper object to create, open, and/or manage a database. * The database is not actually created or opened until one of * {@link #getWritableDatabase} or {@link #getReadableDatabase} is called.*/ public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version, DatabaseErrorHandler errorHandler) { if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version); mContext = context; mName = name; mFactory = factory; mNewVersion = version; mErrorHandler = errorHandler; }
從注釋中就可以看出,該構造函數只是建立SQLiteOpenHelper對象,以及進行了一些變量的初始化動作,所以正真創建SQLite數據庫地方不是在這里,而是在getWritableDatabase() 或者 getReadableDatabase()中實現的。接下來看這兩個函數是如何創建SQLite數據庫的。
(2)調用getWritableDatabase()函數
/** * Create and/or open a database that will be used for reading and writing. * The first time this is called, the database will be opened and * {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be * called. * * <p>Once opened successfully, the database is cached, so you can * call this method every time you need to write to the database. * (Make sure to call {@link #close} when you no longer need the database.) * Errors such as bad permissions or a full disk may cause this method * to fail, but future attempts may succeed if the problem is fixed.</p> * * <p class="caution">Database upgrade may take a long time, you * should not call this method from the application main thread, including * from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}. **/ public SQLiteDatabase getWritableDatabase() { synchronized (this) { return getDatabaseLocked(true); } } /** * Create and/or open a database. This will be the same object returned by * {@link #getWritableDatabase} unless some problem, such as a full disk, * requires the database to be opened read-only. In that case, a read-only * database object will be returned. If the problem is fixed, a future call * to {@link #getWritableDatabase} may succeed, in which case the read-only * database object will be closed and the read/write object will be returned * in the future. * * <p class="caution">Like {@link #getWritableDatabase}, this method may * take a long time to return, so you should not call it from the * application main thread, including from * {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}. **/ public SQLiteDatabase getReadableDatabase() { synchronized (this) { return getDatabaseLocked(false); } }
getWritableDatabase() & getReadableDatabase()函數中都是調用getDatabaseLocked()函數,僅僅是傳的參數不同,這兩個函數的功能基本上是相同的。注釋中也舉例說明了當磁盤滿了是兩者間的區別。下面接着看getDatabaseLocked()中的內容。
private SQLiteDatabase getDatabaseLocked(boolean writable) { if (mDatabase != null) {//第一次進來時,mDatabase為空 if (!mDatabase.isOpen()) { // Darn! The user closed the database by calling mDatabase.close(). mDatabase = null;//當mDatabase被close后,再次打開時需要重新創建 } else if (!writable || !mDatabase.isReadOnly()) { // The database is already open for business. return mDatabase;//直接返回當前mDatabase } } if (mIsInitializing) { throw new IllegalStateException("getDatabase called recursively"); } SQLiteDatabase db = mDatabase; try { mIsInitializing = true; if (db != null) { if (writable && db.isReadOnly()) {
//數據庫從ReadOnly變為ReadWrite時調用 db.reopenReadWrite(); } } else if (mName == null) { db = SQLiteDatabase.create(null); } else { try {
//DEBUG_STRICT_READONLY 默認為FALSE,因為實際應用過程中,只讀數據庫不實用,自己Debug可以設置為TRUE測試。 if (DEBUG_STRICT_READONLY && !writable) { final String path = mContext.getDatabasePath(mName).getPath(); db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY, mErrorHandler); } else { db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ? Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0, mFactory, mErrorHandler); } } catch (SQLiteException ex) { if (writable) { throw ex; } Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", ex); final String path = mContext.getDatabasePath(mName).getPath(); db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY, mErrorHandler); } } onConfigure(db); //下面數據庫版本信息發生變化時調用 final int version = db.getVersion();//第一次創建,version值為0 if (version != mNewVersion) { if (db.isReadOnly()) { throw new SQLiteException("Can't upgrade read-only database from version " + db.getVersion() + " to " + mNewVersion + ": " + mName); } db.beginTransaction(); try { if (version == 0) { onCreate(db);//該函數為抽象函數,可以在重載該函數時執行創建數據庫命令 } else { if (version > mNewVersion) {
//版本降低時調用,但是默認不支持該操作,會報“SQLiteException:Can't downgrade database from version” onDowngrade(db, version, mNewVersion); } else {
//該函數為抽象函數,可以添加自己想實現的效果代碼 onUpgrade(db, version, mNewVersion); } } db.setVersion(mNewVersion); db.setTransactionSuccessful(); } finally { db.endTransaction(); } } onOpen(db); if (db.isReadOnly()) { Log.w(TAG, "Opened " + mName + " in read-only mode"); } mDatabase = db; return db; } finally { mIsInitializing = false; if (db != null && db != mDatabase) { db.close(); } } }
其中DEBUG_STRICT_READONLY變量默認為False,所以正常建立的數據庫都是Readable & Writable。所以上面新建數據庫代碼主要可以分為三個部分:
1.如果mDataBase不為空,並且處於打開狀態時,直接返回,所以多次調用getWritableDatabase()或getReadableDatabase()和只調用一次的效果是一樣的。
2.如果mDataBase為空,則調用mContext.openOrCreateDatabase()來創建數據庫。
3.當數據庫版本信息發生變化時,做相應的升/降版本處理。
那么mContext.openOrCreateDatabase()函數里又做了哪些事情呢?進入到ContextImpl.java文件中一探究竟。
@Override public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler) { File f = validateFilePath(name, true);//判斷數據庫name是否有效 int flags = SQLiteDatabase.CREATE_IF_NECESSARY; if ((mode & MODE_ENABLE_WRITE_AHEAD_LOGGING) != 0) { flags |= SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING; }
//openDatabase SQLiteDatabase db = SQLiteDatabase.openDatabase(f.getPath(), factory, flags, errorHandler); setFilePermissionsFromMode(f.getPath(), mode, 0);//設置文件讀寫權限Mode return db; }
private File validateFilePath(String name, boolean createDirectory) {
File dir;
File f;
if (name.charAt(0) == File.separatorChar) {
//可以自己定義數據庫所在目錄,但是要注意目錄權限,比如以User身份往System所屬的目錄里添加文件
String dirPath = name.substring(0, name.lastIndexOf(File.separatorChar));
dir = new File(dirPath);
name = name.substring(name.lastIndexOf(File.separatorChar));
f = new File(dir, name);
} else {
dir = getDatabasesDir();
f = makeFilename(dir, name);
}
if (createDirectory && !dir.isDirectory() && dir.mkdir()) {
FileUtils.setPermissions(dir.getPath(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
-1, -1);
}
return f;
}
private File getDatabasesDir() {
synchronized (mSync) {
if (mDatabasesDir == null) {
//這個得到的結果就是/data/data/<PackageName>/database
mDatabasesDir = new File(getDataDirFile(), "databases");
}
if (mDatabasesDir.getPath().equals("databases")) {
mDatabasesDir = new File("/data/system");
}
return mDatabasesDir;
}
}
@SuppressWarnings("deprecation")
static void setFilePermissionsFromMode(String name, int mode,
int extraPermissions) {
int perms = FileUtils.S_IRUSR|FileUtils.S_IWUSR
|FileUtils.S_IRGRP|FileUtils.S_IWGRP
|extraPermissions;//默認權限為-rx-rx---
if ((mode&MODE_WORLD_READABLE) != 0) {
perms |= FileUtils.S_IROTH;
}
if ((mode&MODE_WORLD_WRITEABLE) != 0) {
perms |= FileUtils.S_IWOTH;
}
if (DEBUG) {
Log.i(TAG, "File " + name + ": mode=0x" + Integer.toHexString(mode)
+ ", perms=0x" + Integer.toHexString(perms));
}
FileUtils.setPermissions(name, perms, -1, -1);
}
從上面代碼可以知道為何新建的數據庫的目錄為/data/data/<PackageName>/database/,並且權限為-rx-rx--,再貼下之前新建數據庫得到的結果:

其中SQLiteDatabase db = SQLiteDatabase.openDatabase(f.getPath(), factory, flags, errorHandler);應該是正真建立數據庫的地方。
public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags, DatabaseErrorHandler errorHandler) { SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler); db.open(); return db; }
這樣最終就可以得到SQLite數據庫了。
