Android 創建SQLite數據庫(一)


  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數據庫了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM