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数据库了。

Android 创建SQLite数据库(一)的更多相关文章

  1. Android实现SQLite数据库联系人列表

    Android实现SQLite数据库联系人列表 开发工具:Andorid Studio 1.3 运行环境:Android 4.4 KitKat 工程内容 实现一个通讯录查看程序: 要求使用SQLite ...

  2. android基础---->SQLite数据库的使用

    SQLite 一个非常流行的嵌入式数据库,它支持 SQL 语言,并且只利用很少的内存就有很好的性能.此外它还是开源的,任何人都可以使用它.许多开源项目((Mozilla, PHP, Python)都使 ...

  3. Android中SQLite数据库操作(1)——使用SQL语句操作SQLite数据库

    下面是最原始的方法,用SQL语句操作数据库.后面的"Android中SQLite数据库操作(2)--SQLiteOpenHelper类"将介绍一种常用的android封装操作SQL ...

  4. android中sqlite数据库的基本使用和添加多张表

    看了很多关于android使用sqlite数据库的文章,很多都是介绍了数据库的建立和表的建立,而表通常都是只建立一张,而实际情况我们用到的表可能不止一张,那这种情况下我们又该怎么办呢,好了,下面我教大 ...

  5. android 一个SQLite数据库多个数据表的基本使用框架 (带demo)

    android 一个SQLite数据库多个数据表(带demo) 前言        demo演示        一.搭建        二.建立实体类        三.建立数据库操作类        ...

  6. Qt for Android 打包 SQLite 数据库

    Qt for Android 调用 SQLite 数据库时, 怎样将已经存在的数据库附加到 APK 中? 直接在你项目里面的Android源码的根目录下新建一个文件夹assets, 数据库就可以放里面 ...

  7. Android SQLiteOpenHelper Sqlite数据库的创建与打开

    Android Sqlite数据库是一个怎样的数据库? 答:是一种嵌入式小型设备,移动设备,的数据库,应用在穿戴设备(例如:智能手表,计算手环 等等),移动设备(例如:Android系统类型的手机 等 ...

  8. Android开发SQLite数据库的创建

    package com.example.db; import android.content.Context; import android.database.sqlite.SQLiteDatabas ...

  9. Android之SQLite数据库篇

    一.SQLite简介 Google为Andriod的较大的数据处理提供了SQLite,他在数据存储.管理.维护等各方面都相当出色,功能也非常的强大. 二.SQLite的特点 1.轻量级使用 SQLit ...

随机推荐

  1. DiscuzX的目录权限设置1

    经常有朋友遇到Discuz目录权限设置出错的问题,网上千奇百怪的教程非常多,所谓的终极安全的教程更是满天飞,各种所谓的安全加强软件也随处可见,可实际过程中发现,老手用不上,新手则只会因为这些东西徒增麻 ...

  2. IP地址转、整数互相转换

    知识点:一个二进制数,按位左移n位,就是把该数的值乘以2的n次方                  二进制除二即右移一位 1.IP地址转换为整数 原理:IP地址每段可以看成是8位无符号整数即0-255 ...

  3. c#图片生成png格式和原图不同

    下面这种,会生成和原图类似的图片,png格式的图片该是空的地方仍旧是空的

  4. 【Twitter接口】网站嵌入推特信息

    提示:要 翻 墙 的,墙内的去玩新浪微博吧 方法 1  打开链接:  https://publish.twitter.com  ,输入链接.在下边选择时间表格式 复制这段代码.粘贴到你的网站,就可以使 ...

  5. Android 设置wifi共享电脑服务器资源

    其实win7本身就自带无线热点的功能,按下面的方法设置. 开启windows 7的隐藏功能:虚拟WiFi和SoftAP(即虚拟无线AP),就可以让电脑变成无线路由器,实现共享上网,节省网费和路由器购买 ...

  6. js的字符串charAt()方法

    //字符中的字符从左向右进行索引,由0开始,字符串中的空格也算在内 var string = "charAt find word position"; document.write ...

  7. NodeJS路由(server.js + router.js)

    手动路由... server.js 创建http服务器,解析pathname,传递给router处理. var http = require("http"); var url = ...

  8. 设计模式之一:单例模式(Singleton Pattern)

    写这个系列的文章,只为把所学的设计模式再系统的整理一遍.错误和不周到的地方欢迎大家批评.点击这里下载源代码. 什么时候使用单例模式 在程序运行时,某种类型只需要一个实例时,一般采用单例模式.为什么需要 ...

  9. 【BZOJ4423】[AMPPZ2013]Bytehattan 对偶图+并查集

    [BZOJ4423][AMPPZ2013]Bytehattan Description 比特哈顿镇有n*n个格点,形成了一个网格图.一开始整张图是完整的.有k次操作,每次会删掉图中的一条边(u,v), ...

  10. 微信小程序 --- 设置页面的标题

    第一种方式:修改 page.json文件 { "navigationBarTitleText": "豆瓣 - 电影" } 第二种方式:使用 JS 修改: wx. ...