2011-06-23 10:14:00 wuchuanpingstone 阅读数 12685

1,进入到控制台中,输入adb shell,进入到命令模式的环境中

2,输入:cd   /data/data/

3, 选择你所在的数据库文件,比如我的com.android.homework,   输入命令:cd  com.android.homework

4,  可以使用ls -l 命令查看当前目录中的文件

5,输入: cd  databases 进入到数据库文件中

6, ls -l  显示你数据库中你建立的数据库

7, sqlite3   info.db   进入到你选择的数据库中

8, .tables   :查看你建的表

9, select * from  table_name;s   可以查看整个表的信息

10,  使用其他的SQL语句可以进一步对表进行操作,注意SQL语句必须用分号(;)结尾

 

 

如下图所示:

 

 

2016-03-12 15:13:00 honeysx 阅读数 2750

1、数据库升级概念

在开发应用时,我们经常会用数据库来保存数据。 但是随着应用的版本不断升级, 之前的数据库结构可能不太适应当前版本, 这时就需要升级数据库, 使之符合当前需求。类似应用升级, 数据库的升级也需要version来标明。 不同的是应用版本的升级, 只需更改AndroidManifest.xml文件中的versionCode与versionName即可。 但是数据库的升级需要在代码里面修改。

2、数据库升级原理

2.1、升级原理分析

在使用数据库时, 我们会定义一个扩展了抽象类SQLiteOpenHelper的子类。例如,

图1

图1 DatabaseHelper

其中, 构造函数调用了父类即SQLiteOpenHelper的构造方法, 其中包含了一个version的参数。这个参数即是数据库的版本。 所以,我们可以通过修改version来实现数据库的升级。 一般的,在数据库初始开发时设置为1,然后每次升级时递增。
其次,在SQLiteOpenHelper中还定义了两个抽象的方法,onCreate( )、onUpgrade( )。
这两个方法顾名知义对应着数据库的创建和升级两个过程。我们一般在onCreate( )方法中进行创建表等操作,在onUpgrade( )方法中进行修改表等操作。 例如,

图2

图2 实现的方法

当在某个应用1.0版时, 设置构造方法中的参数version为1。我们在使用SQLiteOpenHelper访问数据库时,系统会读取该参数。如果该参数为0,表示之前没有数据库文件, 则将version为1写入数据库,并调用onCreate( )方法创建数据库。当需要升级数据库时将version设置为2。在应用访问数据库时就会去读取数据库中存储的数据库版本,如果发现不一样(变大), 则会调用onUpgrade( )方法,触发升级逻辑。

2.2、具体代码分析

2.2.1、SQLiteOpenHelper类构造方法

图3

图3 SQLiteOpenHelper构造方法

其中, CursorFactory和DatabaseErrorHandler一般是传null。 我们可以看到,构造SQLiteOpenHelper对象时如果数据库版本号低于1是会报异常的。

2.2.2、SQLiteOpenHelper的使用

在访问数据库时,我们一般会new一个SQLiteHelper的对象。

图4

图4 SQLiteOpenHelper的使用

其中,getWritableDatabase( )会调用getDatabaseLocked(false)方法,在该方法中实现了对数据库版本检查和升级等的逻辑。其中部分代码如下,

图5

图5 数据库升级逻辑

我们可以看到,系统会先调用getVersion( )获取当前数据库版本。如果没有数据库文件存在,则getVersion( )方法会返回0. 从而调用onCreate( )方法。如果version不为0,则会比较当前version与数据库中保存的version值从而决定是升级还是降级。对于降级系统会抛出异常,如下

图6

图6 降级逻辑

而升级的操作,则需要我们在子类中实现。最后,系统会将当前数据库版本version值保存到数据库中。

3、数据库升级注意事项

在升级数据库时,如果我们需要在原有表的基础上增加一个字段,那么需要注意的是,一定要为老数据的的新添字段设置默认值。否则,在查询该数据表时会返回空。增加列并设置默认值的方法如下:

图7

图7 增加字段并设置默认值

其次,数据库的升级是对于升级用户的而言的。但是对于新用户系统会新创建数据库,所以在增加字段的同时也要在onCreate( )方法中修改创建该表的SQL语句,从而增加字段。这样那么不管是升级用户还是新用户,数据库中的该表都增加了该字段。

4、结论

1.数据库的升级是通过修改version值实现的。
2.新用户会通过onCreate()方法产生数据库。对于升级用户而言,如果没有升级数据库,则不会进行数据库创建及升级操作。如果升级了数据库,则会调用onUpgrade( )方法。
3.应用的升级与数据库升级无必然的联系。

5、参考

  1. 网址:http://blog.csdn.net/jiangwei0910410003/article/details/39670813
2018-11-01 10:56:43 lovelixue 阅读数 1972

大家在开发的时候相信都用到了数据库,那么我在数据库里面添加的数据之后,我怎么看到这些图形数据呢,这个时候就需要用上一个数据库查看工具了,步骤相当简单

  • 项目添加依赖:implementation 'com.facebook.stetho:stetho:1.5.0'
  • 添加初始化(application或者activity)Stetho.initializeWithDefaults(this);
  • 在google浏览器中打开chrome://inspect/

三步搞定,是不是相当简单,打开谷歌那个界面就是下面,点击inspect然后选择resource,就可以看到你的数据库数据了,如下,非常实用

2015-09-09 11:23:27 haohaoxuexi320 阅读数 490

GreenDao是android数据库开发目前比较流行的ORM框架,一些概念就不说了,网上都有的,今天在这里主要写一下它的用法。

1、首先去网站https://github.com/greenrobot/greenDAO上下载示例工程,建立java工程,生成DaoMaster,Daosession,...Dao,...Entity。代码如下:

public class GreenDaoTest {
public static void main(String[] args) throws Exception {
//数据库版本号是1,liyang.com.myapptest.daos是最后Dao生成的包路径
//这里最好和项目里面的包一致,这样省得麻烦

Schema schema = new Schema(1, "liyang.com.myapptest.daos");
addCities(schema);
addPortAndCabinet(schema);
//generate all daos,F:/greenDaoProduct是生成的文件所在目录
new DaoGenerator().generateAll(schema, "F:/greenDaoProduct");
}

private static void addCities(Schema schema) {
Entity city = schema.addEntity("CityEntity");
city.addIdProperty();
city.addStringProperty("name");
city.addIntProperty("level");
city.addStringProperty("pingyin");
}

private static void addPortAndCabinet(Schema schema) {
Entity customer = schema.addEntity("Customer");
customer.addIdProperty();
customer.addStringProperty("name").notNull();
Entity order = schema.addEntity("Order");
order.setTableName("ORDERS"); // "ORDER" is a reserved keyword
order.addIdProperty();
Property orderDate = order.addDateProperty("date").getProperty();
Property customerId = order.addLongProperty("customerId").notNull().getProperty();
order.addToOne(customer, customerId);
ToMany customerToOrders = customer.addToMany(order, customerId);
customerToOrders.setName("orders");
customerToOrders.orderAsc(orderDate);

}
}

这里主要生成三张表,第一张是城市表,第二张是顾客表,第三张是订单表。顾客和订单是一对多的关系,通过在订单表里面加上外键

Property customerId = order.addLongProperty("customerId").notNull().getProperty();然后通过

order.addToOne(customer, customerId);
ToMany customerToOrders = customer.addToMany(order, customerId);

两句话确定一对多关系。这样如果有一条顾客记录,这位顾客有多个订单,那么就可以通过getOrders()获取她的所有订单了,非常方便,当然也可以通过getCustomer()获取一个订单的所属顾客。

运行该段代码后如果没什么问题就会如下图所示:


然后在F:/greenDaoProduct里面在liyang.com.myapptest.daos包下面会生成Dao类等文件如下图


2、建立android工程,然后把上图里面的几个java文件放到对于的包下面,记得包名要一样啊,不然还要改,如下图


然后再建立两个数据库操作公共类DBUtil和DBUtilBase,代码如下:

public class DBUtilBase {

        private DaoSession daoSession = null;
        private DaoMaster daoMaster = null;
        public static String DB_NAME = "/sdcard/myapp_test.db";//数据库建立在sdcard上面,名字叫myapp_test.db
        private static DBUtilBase singleDB = new DBUtilBase();
        //私有默认构造子
        private DBUtilBase(){
        }
// 静态工厂方法
        public static DBUtilBase getInstance(){
            return singleDB;
        }
        public DaoMaster getDaoMaster(Context context){
            if(daoMaster == null){
                DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(context, DB_NAME, null);
                SQLiteDatabase db = helper.getWritableDatabase();
                daoMaster = new DaoMaster(db);
            }
            return daoMaster;
        }
        public DaoSession getDaoSession(Context context){
            if(daoSession == null){
                daoSession = getDaoMaster(context).newSession();
            }
            return daoSession;
        }
}

然后建立DBUtil类,主要实现操作数据库的具体方法,主要是对三张表的增删改查

public class DBUtil {

    private static DBUtil singleDB=null;
    private DaoSession daoSession=null;
    private CityEntityDao cityEntityDao=null;
    private CustomerDao customerDao=null;
    private OrderDao orderDao=null;
    private DBUtil(Context context){
        daoSession=DBUtilBase.getInstance().getDaoSession(context);
        cityEntityDao=daoSession.getCityEntityDao();
        customerDao=daoSession.getCustomerDao();
        orderDao=daoSession.getOrderDao();
    }
    public static DBUtil getDbInstance(Context context){
        if (singleDB==null){
            singleDB=new DBUtil(context);
        }
        return singleDB;
    }
    public void insertCity(CityEntity cityEntity){
        if (cityEntity!=null){
            cityEntityDao.insert(cityEntity);
        }
    }
    public void deleteCity(CityEntity cityEntity){
        if (cityEntity!=null){
            cityEntityDao.delete(cityEntity);
        }
    }
    public void updateCity(CityEntity cityEntity){
        if (cityEntity!=null){
            cityEntityDao.update(cityEntity);
        }
    }
    public List<CityEntity> getAllCity(){
        return cityEntityDao.loadAll();
    }
    public CityEntity getCityByPrimaryKey(long id){
        return cityEntityDao.load(id);
    }
    public List<CityEntity> getCityByLevel(int level){
        if (level<0)
            return null;
        String where = "WHERE "+CityEntityDao.Properties.Level.columnName+" =?";
        List<CityEntity> cityEntities=cityEntityDao.queryRaw(where,level+"");
        if (cityEntities==null)
            return null;
        return cityEntities;
    }
    ///////////////////////////////////////////////////////////////////
    public void insertCustomer(Customer customer){
        if (customer!=null){
            customerDao.insert(customer);
        }
    }
    public void deleteCustomer(Customer customer){
        if (customer!=null){
            customerDao.delete(customer);
        }
    }
    public void updateCustomer(Customer customer){
        if (customer!=null){
            customerDao.update(customer);
        }
    }
    public List<Customer> getAllCustomers(){
        return customerDao.loadAll();
    }
    //////////////////////////////////////////////////////////////////////////
    public void insertOrder(Order order){
        if (order!=null){
            orderDao.insert(order);
        }
    }
    public void deleteOrder(Order order){
        if (order!=null){
            orderDao.delete(order);
        }
    }
    public void updateOrder(Order order){
        if (order!=null){
            orderDao.update(order);
        }
    }
    public List<Order> getAllOrderS(){
        return orderDao.loadAll();
    }
}

下面建立一个Activity,主要测试使用数据库,代码如下:

public class GreenDaoTestActivity extends ActionBarActivity {

    private DBUtil db;
    private Context ctx;
    private TextView showText=null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_green_dao_test);
        ctx=this;
        db=DBUtil.getDbInstance(ctx);
        showText=(TextView)findViewById(R.id.show_data_text);
    }
    public void showData(View view){
        List<CityEntity> cities= db.getAllCity();
        String cityStr="";
        for(CityEntity cityEntity:cities){
            cityStr+=cityEntity.getId()+"  "+cityEntity.getName()+" "+cityEntity.getLevel()+" "+cityEntity.getPingyin()+"\n";
        }
        showText.setText(cityStr);
    }
    public void insertDataBtn(View view){
        CityEntity city=new CityEntity();
        city.setName("苏州");
        city.setLevel(1);
        city.setPingyin("suzhou");
        db.insertCity(city);
        showData(view);
    }
    public void deleteDataBtn(View view){
        List<CityEntity> cities= db.getAllCity();
        CityEntity city=cities.get(cities.size()-1);
        db.deleteCity(city);
        showData(view);
    }
    public void updateDataBtn(View view){
        List<CityEntity> cities= db.getAllCity();
        CityEntity city=cities.get(cities.size()-1);
        city.setName("上海");
        city.setPingyin("shangai");
        city.setLevel(0);
        db.updateCity(city);
        showData(view);
    }
    public void selectLevelCities(View view){
        List<CityEntity> cities=db.getCityByLevel(0);
        String cityStr="";
        for(CityEntity cityEntity:cities){
            cityStr+=cityEntity.getId()+"  "+cityEntity.getName()+" "+cityEntity.getLevel()+" "+cityEntity.getPingyin()+"\n";
        }
        showText.setText(cityStr);
    }
    public void selectIdCity(View view){
        CityEntity cityEntity=db.getCityByPrimaryKey(2);
        String cityStr=cityEntity.getId()+"  "+cityEntity.getName()+" "+cityEntity.getLevel()+" "+cityEntity.getPingyin();
        showText.setText(cityStr);
    }
////////////////////////////////////////////////////////////////////////////////////////
    public void showCustomerData(View view){
        List<Customer> customers=db.getAllCustomers();
        String str="";
        for(Customer customer:customers){
            str+=customer.getId()+"  "+customer.getName()+"\n";
        }
        showText.setText(str);
    }
    public void insertCustomer(View view){
        Customer customer=new Customer();
        customer.setName("小李");
        db.insertCustomer(customer);
        showCustomerData(view);
    }
    public void deleteCustomer(View view){
        List<Customer> customers=db.getAllCustomers();
        Customer customer=customers.get(customers.size()-1);
        db.deleteCustomer(customer);
        showCustomerData(view);
    }
    public void updateCustomer(View view){
        List<Customer> customers=db.getAllCustomers();
        Customer customer=customers.get(customers.size()-1);
        customer.setName("小王");
        db.updateCustomer(customer);
        showCustomerData(view);
    }
    public void getOrdersBelongCustomer(View view){
        List<Customer> customers=db.getAllCustomers();
        Customer customer=customers.get(customers.size()-1);
        List<Order> orders=customer.getOrders();
        String str="";
        for(Order order:orders){
            str+=order.getId()+"  "+order.getCustomerId()+"  "+order.getDate().toString()+"\n";
        }
        showText.setText(str);
}   

//////////////////////////////////////////////////////////////////////////////////////
    public void showOrderData(View view){
        List<Order> orders=db.getAllOrderS();
        String str="";
        for(Order order:orders){
            str+=order.getId()+"  "+order.getCustomerId()+"  "+order.getDate().toString()+"\n";
        }
        showText.setText(str);
    }
    public void insertOrder(View view){
        Order order=new Order();
        order.setCustomerId(2);
        order.setDate(new Date());
        db.insertOrder(order);
        showOrderData(view);
    }
    public void deleteOrder(View view){
        List<Order> orders=db.getAllOrderS();
        Order order=orders.get(orders.size()-1);
        db.deleteOrder(order);
        showOrderData(view);
    }
    public void updateOrder(View view){
        List<Order> orders=db.getAllOrderS();
        Order order=orders.get(orders.size()-1);
        order.setCustomerId(3);
        db.updateOrder(order);
        showOrderData(view);
    }
    public void selectCustomerByOrder(View view){
        List<Order> orders=db.getAllOrderS();
        Order order=orders.get(0);
        Customer customer=order.getCustomer();
        String str=customer.getId()+"  "+customer.getName();
        showText.setText(str);
    }
}

布局文件如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center_horizontal"
    tools:context="liyang.com.myapptest.GreenDaoTestActivity">
    <TextView
        android:id="@+id/show_data_text"
        android:text="数据显示区域"
        android:textColor="#000000"
        android:gravity="center"
        android:background="#ffffff"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    <LinearLayout
        android:gravity="center_horizontal"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button
            android:layout_marginTop="20dp"
            android:onClick="showData"
            android:text="显示所有城市"
            android:layout_width="wrap_content"
            android:layout_height="40dp" />
        <Button
            android:text="插入城市"
            android:onClick="insertDataBtn"
            android:layout_width="100dp"
            android:layout_height="40dp" />
        <Button
            android:onClick="deleteDataBtn"
            android:text="删除城市"
            android:layout_width="100dp"
            android:layout_height="40dp" />
        <Button
            android:onClick="updateDataBtn"
            android:text="修改城市"
            android:layout_width="100dp"
            android:layout_height="40dp" />
        <Button
            android:onClick="selectLevelCities"
            android:text="查询level为0的城市"
            android:layout_width="wrap_content"
            android:layout_height="40dp" />
        <Button
            android:onClick="selectIdCity"
            android:text="查询id为2的城市"
            android:layout_width="wrap_content"
            android:layout_height="40dp" />
        <Button
            android:onClick="showCustomerData"
            android:text="显示所有消费者"
            android:layout_width="wrap_content"
            android:layout_height="40dp" />
        <Button
            android:onClick="insertCustomer"
            android:text="插入消费者"
            android:layout_width="wrap_content"
            android:layout_height="40dp" />
        <Button
            android:onClick="deleteCustomer"
            android:text="删除消费者"
            android:layout_width="wrap_content"
            android:layout_height="40dp" />
        <Button
            android:onClick="updateCustomer"
            android:text="更新消费者"
            android:layout_width="wrap_content"
            android:layout_height="40dp" />
        <Button
            android:onClick="getOrdersBelongCustomer"
            android:text="获取最后一个消费者所有的订单"
            android:layout_width="wrap_content"
            android:layout_height="40dp" />
        <Button
            android:onClick="showOrderData"
            android:text="显示所有订单"
            android:layout_width="wrap_content"
            android:layout_height="40dp" />
        <Button
            android:onClick="insertOrder"
            android:text="插入订单"
            android:layout_width="wrap_content"
            android:layout_height="40dp" />
        <Button
            android:onClick="deleteOrder"
            android:text="删除订单"
            android:layout_width="wrap_content"
            android:layout_height="40dp" />
        <Button
            android:onClick="updateOrder"
            android:text="更新订单"
            android:layout_width="wrap_content"
            android:layout_height="40dp" />
        <Button
            android:onClick="selectCustomerByOrder"
            android:text="获取第一个订单所属的消费者"
            android:layout_width="wrap_content"
            android:layout_height="40dp" />
    </LinearLayout>
    </ScrollView>
</LinearLayout>

在Manifest.xml里面记得加入权限<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

在gradle里面加入compile 'de.greenrobot:greendao:2.0.0'导入greenDao包

测试的一些效果图如下:













2018-06-19 17:15:15 weiyj2015 阅读数 622

在android中和SQLite数据库相关的几大类有SQLiteOpenHelper、SQLiteDatabase、SQLiteConnection、SQLiteSession,本文主要从1.数据库的创建过程;2.对数据库的CRUP操作;两个方面来分析android数据库运行机制。先概述下这几个类,让大家对它们有个了解;

SQLiteOpenHelper

封装管理数据库的创造和版本管理类,主要封装了数据库的创建和获取的方法,一般继承该类实现onCreate()、onUpdate()方法

,在onCreate创建数据库,在onUpdate进行数据库升级操作。还有onConfigure()、onDowngrade()、onOpen()等方法。

SQLiteDatabase
    应用使用SQLiteDatabase来操作数据库,例如增删改查事务等操作。调用SQLiteOpenHelper.getWritableDatabase()/getReadableDatabase()打开数据库时,便会调用到SQLiteDatabase.openDatabase()来创建一个SQLiteDatabase对象。

SQLiteConnectionPool
    数据库连接池,管理SQLiteConnection(数据库连接)。应用对数据库的操作被执行之前需获取一个SQLiteConnection连接,最终会通过SQLiteConnection所对应的native层SQLiteConnection对象来完成数据库的最终操作。SQLiteDatabase对象创建完成后,会立即调用SQLiteDatabase.open()函数来创建一个SQLiteConnectionPool对象。

SQLiteConnection(java)
    数据库连接。线程操作数据库必须通过SQLiteSession来获取一个SQLiteConnection连接,通过连接来执行数据库操作。创建连接池SQLiteConnectionPool后,会立即调用其open()函数创建一个主连接SQLiteConnection。

SQLiteSession

    线程数据库会话。线程操作数据必须通过SQLiteSession来获取一个SQLiteConnection连接,每个线程都有自己的SQLiteSession对象,属于线程私有数据,需要通过SQLiteDatabase.getThreadSession()静态函数来获取。线程第一次调用getThreadSession()便会创建一个SQLiteSession对象。例如:进程PID=911中tid=911的线程第一次调用SQLiteDatabase.getThreadSession()时,便会创建一个SQLiteSession对象。tid=912的线程第一次调用SQLiteDatabase.getThreadSession()时,同样也会创建一个SQLiteSession对象。

SQLiteConnection(native

     数据库操作最终通过native SQLiteConnection对象调用sqlite库接口来完成。每个Java层SQLiteConnection对象,都对应有一个native层SQLiteConnection对象,native层SQLiteConnection对象的地址保存在java SQLiteConnection.mConnectionPtr中。


对数据库相关的几个类有了一定的认识之后,我们下面从1.数据库的创建过程;2.对数据库的CRUP操作;两个方面来分析android数据库运行机制。

数据库的创建

两个方法:getReadableDatabase()、getWritableDatabase()。需要注意的一点是这两个方法都加锁,是线程安全的。这两个方法最终调用getDatabaseLocked(boolean writable):

SQLiteOpenHelper

public SQLiteDatabase getReadableDatabase() {
        synchronized (this) {
            return getDatabaseLocked(false);
        }
    }
public SQLiteDatabase getWritableDatabase() {
        synchronized (this) {
            return getDatabaseLocked(true);
        }
    }
private SQLiteDatabase getDatabaseLocked(boolean writable) {
    if (mDatabase != null) {

        if (!mDatabase.isOpen()) {  // 判断数据库是否已经关闭
            // Darn!  The user closed the database by calling mDatabase.close().
            mDatabase = null;
        } else if (!writable || !mDatabase.isReadOnly()) { //判断数据库是否符合要求,如果数据库可读可写则返回,即!mDatabase.isReadOnly()一直为true
            // The database is already open for business.
            return mDatabase;
        }
    }

    // 正在初始化中
    if (mIsInitializing) {
        throw new IllegalStateException("getDatabase called recursively");
    }

    SQLiteDatabase db = mDatabase;
    try {
        mIsInitializing = true;

        if (db != null) { // 数据库不为null,需要重新开启读写数据库使得符合要求
            if (writable && db.isReadOnly()) {
                db.reopenReadWrite();
            }
        } else if (mName == null) {
            db = SQLiteDatabase.create(null);
        } else {
            try {
                if (DEBUG_STRICT_READONLY && !writable) {
                    final String path = mContext.getDatabasePath(mName).getPath();
                    db = SQLiteDatabase.openDatabase(path, mFactory,
                            SQLiteDatabase.OPEN_READONLY, mErrorHandler);
                } else {
                    // 通过mContext.openOrCreateDatabase创建数据库,其实还是调用SQLiteDatabase.openDatabase(..)创建数据库
                    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
        onConfigure(db);

        final int version = db.getVersion();
        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 {
                // 当第一次创建数据库时DataBase的版本为0,会调用onCreate()方法
                if (version == 0) {
                    onCreate(db);
                } else {  // 判断数据库版本升降级,调用相应方法
                    if (version > mNewVersion) {
                        onDowngrade(db, version, mNewVersion);
                    } else {
                        onUpgrade(db, version, mNewVersion);
                    }
                }
                db.setVersion(mNewVersion);
                db.setTransactionSuccessful();
            } finally {
                db.endTransaction();
            }
        }

        // 调用onOpen()方法
        onOpen(db);

        if (db.isReadOnly()) {
            Log.w(TAG, "Opened " + mName + " in read-only mode");
        }

        mDatabase = db;
        return db;
    } finally {
        mIsInitializing = false;
        // 数据库创建失败时,进行close操作
        if (db != null && db != mDatabase) {
            db.close();
        }
    }
}

这里调用了SQLiteDatabase.openDatabase(..)创建数据库

public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags,
        DatabaseErrorHandler errorHandler) {
    SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler);
    db.open();
    return db;
}

可以看到new一个SQLiteDatabase对象,并调用open(),再返回该数据库对象,先看open()函数:

private void open() {
    try {
        try {
            openInner();
        } catch (SQLiteDatabaseCorruptException ex) {
            onCorruption();
            openInner();
        }
    } catch (SQLiteException ex) {
        // .... 
    }
}

private void openInner() {
    synchronized (mLock) {
        assert mConnectionPoolLocked == null;
        mConnectionPoolLocked = SQLiteConnectionPool.open(mConfigurationLocked);
        mCloseGuardLocked.open("close");
    }

    synchronized (sActiveDatabases) {
        sActiveDatabases.put(this, null);
    }
}

// 可以看到调用SQLiteConnectionPool.open(mConfigurationLocked):
public static SQLiteConnectionPool open(SQLiteDatabaseConfiguration configuration) {
    if (configuration == null) {
        throw new IllegalArgumentException("configuration must not be null.");
    }

    // Create the pool.
    SQLiteConnectionPool pool = new SQLiteConnectionPool(configuration);
    pool.open(); // might throw
    return pool;
}
// 可以看到其中是创建一个SQLiteConnectionPool,并且调用open操作:

// Might throw
private void open() {
    // Open the primary connection.
    // This might throw if the database is corrupt.
    mAvailablePrimaryConnection = openConnectionLocked(mConfiguration,
            true /*primaryConnection*/); // might throw

   // ...
}

// 可以看到创建了主连接mAvailablePrimaryConnection:
private SQLiteConnection openConnectionLocked(SQLiteDatabaseConfiguration configuration,
        boolean primaryConnection) {
    final int connectionId = mNextConnectionId++;
    return SQLiteConnection.open(this, configuration,
            connectionId, primaryConnection); // might throw
}

// 调用了SQLiteConnection.open()创建主连接:
static SQLiteConnection open(SQLiteConnectionPool pool,
        SQLiteDatabaseConfiguration configuration,
        int connectionId, boolean primaryConnection) {
    SQLiteConnection connection = new SQLiteConnection(pool, configuration,
            connectionId, primaryConnection);
    try {
        connection.open();
        return connection;
    } catch (SQLiteException ex) {
        connection.dispose(false);
        throw ex;
    }
}

private void open() {
    mConnectionPtr = nativeOpen(mConfiguration.path, mConfiguration.openFlags,
            mConfiguration.label,
            SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME);

    setPageSize();
    setForeignKeyModeFromConfiguration();
    setWalModeFromConfiguration();
    setJournalSizeLimit();
    setAutoCheckpointInterval();
    setLocaleFromConfiguration();

    // Register custom functions.
    final int functionCount = mConfiguration.customFunctions.size();
    for (int i = 0; i < functionCount; i++) {
        SQLiteCustomFunction function = mConfiguration.customFunctions.get(i);
        nativeRegisterCustomFunction(mConnectionPtr, function);
    }
}

// 可以看到最终调用了nativeOpen打开一个主数据库连接,并且设置各自sqlite的属性。

创建流程:


总结:

可以看出,创建一个数据库对象,会创建一个数据库连接池,并且会创建出一个主连接。数据库连接池用于管理数据库连接对象。而数据库连接SQLiteConnection则在其中包装了native的sqlite3对象,数据库sql语句最终会通过sqlite3对象执行。


数据库的CRUP操作

那么我们就以Insert为例,理一下整个过程。

SqliteDatabase的insert方法

public long insert(String table, StringnullColumnHack, ContentValues values) {

try {

return insertWithOnConflict(table, nullColumnHack, values,CONFLICT_NONE);

} catch (SQLException e) {

Log.e(TAG, "Error inserting " + values, e);

return -1;

}
}

Ok... 继续往下面分析insertWithOnConflict方法

public long insertWithOnConflict(Stringtable, String nullColumnHack,

ContentValues initialValues, int conflictAlgorithm) {

acquireReference();

try {

StringBuilder sql = new StringBuilder();

sql.append("INSERT");

sql.append(CONFLICT_VALUES[conflictAlgorithm]);

sql.append(" INTO ");

...

sql.append(')');



SQLiteStatement statement = new SQLiteStatement(this, sql.toString(),bindArgs);

try {

returnstatement.executeInsert();

} finally {

statement.close();

}

} finally {

releaseReference();

}

}

这个方法首先构造insertsql语句,然后调用statementexecuteInsert()方法,那继续跟下去

frameworks\base\core\java\android\database\sqlite\SQLiteStatement.java

public long executeInsert() {

...

return getSession().executeForLastInsertedRowId(

getSql(), getBindArgs(), getConnectionFlags(), null);

...

}

这个方法比较重要,分成两步来分析:
首先分析getSession()方法,

然后再去看看executeForLastInsertedRowId方法。

getSession分析

frameworks\base\core\java\android\database\sqlite\SQLiteProgram.java

protected final SQLiteSession getSession(){

return mDatabase.getThreadSession();
}

调用的是SqliteDatabase.getThreadSession()方法,继续分析

SQLiteSession getThreadSession() {

return mThreadSession.get(); // initialValue() throws if database closed

}

调用的是mThreadSession.get(),看看mThreadSession是什么东西

private final ThreadLocal<SQLiteSession>mThreadSession = new ThreadLocal<SQLiteSession>() {

protected SQLiteSession initialValue() {

return createSession();

}
};


SQLiteSession createSession() {

final SQLiteConnectionPool pool;

synchronized (mLock) {

pool = mConnectionPoolLocked;

}

return new SQLiteSession(pool);

}


public SQLiteSession(SQLiteConnectionPoolconnectionPool) {

if (connectionPool == null) {

throw new IllegalArgumentException("connectionPool must not benull");

}

mConnectionPool = connectionPool;

}
嗯,非常明白,这是一个ThreadLocal类型的变量,ThreadLocalJava提供的一个用于多线程之间隔离数据的东西,避免多线程访问同一个变量导致冲突。

也就是ThreadLocal是为每个线程保存一份变量,这样就不会引起冲突了。这个ThreadLocal如果有不理解的,可以百度下ThreadLocal的使用。


Ok..理解完ThreadLocal之后,继续看createSession方法
它是使用mConnectionPoolLocked这么个SQLiteConnectionPool变量来实例化一个SQLiteSession对象。而这个mConnectionPoolLocked对象是SqliteDatabase打开的时候实例化的,也就是说一个database只有一个这样连接。
那么,这里我们可以理清楚一个关系
a.
一个线程会对应一个SqliteSession对象,这个是用ThreadLocal对象来维持的。
b.
一个数据库对应唯一一个sqliteDabase对象,以及唯一一个SQLiteConnectionPool对象,然后各个线程之间共享这个SQLiteConnectionPool对象。

Ok..理清楚这几个关系之后,返回去分析executeForLastInsertedRowId方法

executeForLastInsertedRowId

frameworks\base\core\java\android\database\sqlite\SQLiteSession.java

public long executeForLastInsertedRowId(Stringsql, Object[] bindArgs, int connectionFlags,

CancellationSignal cancellationSignal) {

...

acquireConnection(sql, connectionFlags, cancellationSignal); // mightthrow

try {

return mConnection.executeForLastInsertedRowId(sql, bindArgs,

cancellationSignal); //might throw

} finally {

releaseConnection(); // might throw

}

}
这个方法主要分析两个操作
acquireConnection
releaseConnection
因为中间的mConnection.executeForLastInsertedRowId语句主要是通过Jni往底层调用,插入数据库数据,不是今天我们讨论的话题。

Ok.. 那这两个操作,一个个来分析

acquireConnection

frameworks\base\core\java\android\database\sqlite\SQLiteSession.java

private void acquireConnection(String sql,int connectionFlags,

CancellationSignal cancellationSignal) {


if (mConnection == null) {

assert mConnectionUseCount == 0;

mConnection = mConnectionPool.acquireConnection(sql, connectionFlags,

cancellationSignal); // mightthrow

mConnectionFlags = connectionFlags;

}

mConnectionUseCount += 1;


}
Ok..首先会去判断当前线程的Session里面的mConnection是否为null,如果不为null,就只是简单的把连接个数mConnectionUseCount1

如果为null,也就是当前线程的Session没有连接数据库,那么就要去申请一个连接。


所以这里的逻辑特别重要,就是一个对于一个线程而已,它只会去获取一次数据库连接。即使你调用再多的beginTranscation以及query,第一次调用的时候会去获取连接,以后就是让mConnectionUseCount 1
当然,你使用beginTranscation需要手动调用endTranscation,不然不会去释放连接。
调用其他的,比如queryinsert之类的,不需要手动释放。系统会帮你去调用释放连接。


Ok.. 接下来分析mConnectionPool.acquireConnection的流程.

调用mConnectionPool.acquireConnection,顾名思义,它是向连接池申请一个连接;根据之前的分析,这个连接池是唯一的,是多个线程之间共享的。

frameworks\base\core\java\android\database\sqlite\SQLiteConnectionPool.java

public SQLiteConnectionacquireConnection(String sql, int connectionFlags,

CancellationSignal cancellationSignal) {

return waitForConnection(sql, connectionFlags, cancellationSignal);

}




private SQLiteConnectionwaitForConnection(String sql, int connectionFlags,

CancellationSignal cancellationSignal) {

final boolean wantPrimaryConnection =

(connectionFlags &CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY) != 0;



final ConnectionWaiter waiter;

final int nonce;

synchronized (mLock) {

throwIfClosedLocked();



// Abort if canceled.

if (cancellationSignal != null) {

cancellationSignal.throwIfCanceled();

}



// Try to acquire a connection.

SQLiteConnection connection = null;

if (!wantPrimaryConnection) {
//尝试获取非主连接

connection =tryAcquireNonPrimaryConnectionLocked(

sql, connectionFlags);// might throw

}

if (connection == null) {
//尝试去获取主连接

connection =tryAcquirePrimaryConnectionLocked(connectionFlags); // might throw

}

if (connection != null) {
//获取到连接后,返回连接

return connection;

}

//没有获取到连接,新建一个waiter对象,加入等待队列


// No connections available.
Enqueue a waiter in priority order.

final int priority = getPriority(connectionFlags);

final long startTime = SystemClock.uptimeMillis();

waiter = obtainConnectionWaiterLocked(Thread.currentThread(), startTime,

priority,wantPrimaryConnection, sql, connectionFlags);

ConnectionWaiter predecessor = null;

ConnectionWaiter successor = mConnectionWaiterQueue;

while (successor != null) {


if (priority >successor.mPriority) {

waiter.mNext = successor;

break;

}

predecessor = successor;

successor = successor.mNext;

}

if (predecessor != null) {

predecessor.mNext = waiter;

} else {

mConnectionWaiterQueue =waiter;

}



nonce = waiter.mNonce;

}



....



//把当前线程睡眠30s,然后尝试去获取连接,如果没有获取到连接,则打印当前线程的等待时间。

try {

// Park the thread until a connection is assigned or the pool is closed.

// Rethrow an exception from the wait, if we got one.

long busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS;
//30s

long nextBusyTimeoutTime = waiter.mStartTime + busyTimeoutMillis;

for (;;) {

// Detect and recover fromconnection leaks.

if(mConnectionLeaked.compareAndSet(true, false)) {


synchronized (mLock) {

wakeConnectionWaitersLocked();
}

}



// Wait to be unparked (mayalready have happened), a timeout, or interruption.

LockSupport.parkNanos(this,busyTimeoutMillis * 1000000L);
//当前线程停止30s



// Clear the interrupted flag,just in case.

Thread.interrupted();



// Check whether we are donewaiting yet.

synchronized (mLock) {

throwIfClosedLocked();

final SQLiteConnectionconnection = waiter.mAssignedConnection;

final RuntimeException ex =waiter.mException;

if (connection != null ||ex != null) {

recycleConnectionWaiterLocked(waiter);

if (connection != null){

return connection;

}

throw ex; // rethrow!

}




final long now =SystemClock.uptimeMillis();

if (now <nextBusyTimeoutTime) {

busyTimeoutMillis = now- nextBusyTimeoutTime;

} else {

logConnectionPoolBusyLocked(now- waiter.mStartTime, connectionFlags);
//打印出当前线程等待的时间

busyTimeoutMillis =CONNECTION_POOL_BUSY_MILLIS;

nextBusyTimeoutTime =now + busyTimeoutMillis;

}

}

}

} finally {

...

}

}
这个方法比较多,我在里面做了一点注释,大概包括下面几个步骤。同时要明确一个概念,主连接和非主连接。
其实他们没有本质的区别,主连接是一定有的,在初始化的时候就实例化完毕;而非主连接是一个连接集合,也就是说非主连接可以有很多个。不过一般有个最大大小,我们可以配置的。

Ok...明白这个概念之后,讲讲步骤

如果没有指定要获取主连接的话,首先尝试获取非主连接

private SQLiteConnectiontryAcquireNonPrimaryConnectionLocked(

String sql, int connectionFlags) {

// Try to acquire the next connection in the queue.

SQLiteConnection connection;

final int availableCount = mAvailableNonPrimaryConnections.size();

if (availableCount > 1 && sql != null) {

// If we have a choice, then prefer a connection that has the

// prepared statement in its cache.


for (int i = 0; i <availableCount; i++) {

connection =mAvailableNonPrimaryConnections.get(i);

if(connection.isPreparedStatementInCache(sql)) {

mAvailableNonPrimaryConnections.remove(i);


finishAcquireConnectionLocked(connection,connectionFlags); // might throw

return connection;

}

}

}

if (availableCount > 0) {

// Otherwise, just grab the next one.


connection =mAvailableNonPrimaryConnections.remove(availableCount - 1);

finishAcquireConnectionLocked(connection, connectionFlags); // mightthrow

return connection;

}



// Expand the pool if needed.

int openConnections = mAcquiredConnections.size();

if (mAvailablePrimaryConnection != null) {

openConnections += 1;


}



if (openConnections >= mMaxConnectionPoolSize) {

return null;

}

connection = openConnectionLocked(mConfiguration,

false /*primaryConnection*/);// might throw

finishAcquireConnectionLocked(connection, connectionFlags); // mightthrow

return connection;

}
获取非主连接的时候,首先会判断已有的连接中有没有相同的sql,如果有的话,就直接返回这个连接。
然后如果第一步没有成功的话,比如传入的sqlnull,那么就会尝试获取队列里面的最后一个连接,然后返回。
如果第二步也没有成功,那么它会尝试去扩充非主连接集合

但是,它会去判断是否超过了最大的连接数,是已经到达最大的连接数,那么就返回null

if (openConnections >=mMaxConnectionPoolSize) {

return null;

}

如果还没有到达最大连接数,那么就把连接放入非主连接集合,然后返回这个扩容的连接。

如果没有获取到非主连接,或者指定要获取主连接,那么就要去尝试获取主连接。

private SQLiteConnectiontryAcquirePrimaryConnectionLocked(int connectionFlags) {

// If the primary connection is available, acquire it now.

SQLiteConnection connection = mAvailablePrimaryConnection;

if (connection != null) {

mAvailablePrimaryConnection = null;

finishAcquireConnectionLocked(connection, connectionFlags); // mightthrow

return connection;

}



// Make sure that the primary connection actually exists and has justbeen acquired.

for (SQLiteConnection acquiredConnection :mAcquiredConnections.keySet()) {

if (acquiredConnection.isPrimaryConnection()) {

return null;

}


}



// Uhoh.
No primaryconnection!
Either this is the firsttime we asked

// for it, or maybe it leaked?

connection = openConnectionLocked(mConfiguration,

true /*primaryConnection*/); //might throw

finishAcquireConnectionLocked(connection, connectionFlags); // mightthrow

return connection;

}
由于主连接只有一个,所以它用一个变量来表示 --- mAvailablePrimaryConnection

如果主连接为null就返回主连接,并把主连接设置成null,也就是主连接被占用。

if (connection != null) {

mAvailablePrimaryConnection = null;

finishAcquireConnectionLocked(connection, connectionFlags); // mightthrow

return connection;

}
如果主连接为null,那么它会去查看是否已经有人获取了主连接,如果是,那么返回null;这样做就是确保主连接是存在的。

如果经过上面一步确认,还没有返回,那么说明主连接没有创建;这个一般是不可能的,因为主连接时数据库初始化的时候创建的。这个一般是第一次访问,或者出现了程序异常。那么就新建一个主连接,然后返回。

如果获取到连接,那么返回这个连接

if (connection != null) {

return connection;
}
如果没有获取到连接,那么新建一个waiter对象,并进入等待队列。
e.

线程进入死循环,不断休眠(30s),然后重新获取连接。直到获取到连接后返回。否则,记录下当前线程等待的时间。不过这里要注意,这里打印的等待时间并不包括系统睡眠的时间。比如,下午1点进入等待,2-4点手机睡眠,那么到5点的时候打印,只能算两个小时。

打印的日志为(最后的总结会对此bug做解析):

04-2314:25:48.522 W/SQLiteConnectionPool( 3681): The connection pool for database'/data/user/0/com.android.providers.contacts/databases/contacts2.db' has beenunable to grant a connection to thread 371 (ContactsProviderWorker) with flags0x1 for 30.000002 seconds.

04-23 14:25:48.522 W/SQLiteConnectionPool( 3681):Connections: 0 active, 1 idle, 0 available.

Ok...至此,整个申请数据库连接过程分析基本完毕,下面分析释放的过程

释放连接

frameworks\base\core\java\android\database\sqlite\SQLiteSession.java

finally {
releaseConnection();// might throw
}


private void releaseConnection() {


assert mConnection != null;

assert mConnectionUseCount > 0;

if (--mConnectionUseCount == 0) {


try {

mConnectionPool.releaseConnection(mConnection); // might throw

} finally {

mConnection = null;

}

}

}
首先会去判断当前使用数是否为大于0,还记得前面我们说过如果线程第一次申请连接,那么就去申请,然后使用数+1;但是如果线程已经拥有了连接,那么我们只是简单的把连接数+1
所以,这里要判断这个连接数是否>0
然后把连接数减去1,看是否等于0;这是什么意思呢?就是说当前线程已经没有使用数据库的操作了。
如果减去1之后>0,那么说明当前线程还要使用这个连接操作数据库,还不能释放。
这里也可以看出,申请连接和释放连接一定要是一一对应的。申请了,一定要释放。当然,对于应用程序来说,只有beginTranscation需要手动去释放,也就是调用endTranscation,而且必须要调用(一般写在finally里面)
Ok...一切都ok的话,正式去释放连接

mConnectionPool.releaseConnection(mConnection);// might throw

正式释放连接

frameworks\base\core\java\android\database\sqlite\SQLiteConnectionPool.java

public voidreleaseConnection(SQLiteConnection connection) {


synchronized (mLock) {

....

if (!mIsOpen) {

closeConnectionAndLogExceptionsLocked(connection);

} else if (connection.isPrimaryConnection()) {

if(recycleConnectionLocked(connection, status)) {

assert mAvailablePrimaryConnection== null;

mAvailablePrimaryConnection= connection;

}

wakeConnectionWaitersLocked();

} else if (mAvailableNonPrimaryConnections.size() >=mMaxConnectionPoolSize - 1) {

closeConnectionAndLogExceptionsLocked(connection);

} else {

if(recycleConnectionLocked(connection, status)) {

mAvailableNonPrimaryConnections.add(connection);

}


wakeConnectionWaitersLocked();

}

}

}

这里会去根据当前连接是否是主连接而选择释放方法,然后通知那些正在等待的线程(waiter)去获取连接。


总结

数据库使用报错1

android开发过程中,突然碰到了这个错误,数据库连接分配不到,日志如下:

W/SQLiteConnectionPool( 3681): Theconnection pool for database '/data/user/0/com.android.providers.contacts/databases/contacts2.db'has been unable to grant a connection to thread 371 (ContactsProviderWorker)with flags 0x1 for 30.000002 seconds.
W/SQLiteConnectionPool( 3681): Connections:0 active, 1 idle, 0 available.
1这个bug不是由于在beginTranscation里面执行一个execuSql(sql)所导致的,因为在同一个线程里面,即使申请两次数据库连接,那也只是让使用数+1而已。它只会去申请一个连接。除非beginTranscationexecuSql(sql) 不在一个线程,另外开线程去execuSql(sql).
2那么怎么会导致这个bug呢?
a. 时间过长的操作,某个操作持续很长时间,那么其他线程等待的时候就会打印这个信息。不过,一般不会等待太久。因为一般不会有这么长时间的数据库操作。
b. 忘记调用endTranscation,如前面所说,只有beginTranscation需要程序员手动去调用endTransction来释放连接,其他操作不需要。
那么,有个时候会疏忽忘记调用endTransction了。
或者endTranscation没有放在finally里面,导致出现异常而没有调用endTransction
c.
死锁,这个比较复杂,需要具体问题具体分析。

如果出现死锁,线程互相等待,这样也没有去释放连接;那么后面的线程自然也拿不到数据库连接了。

(注:我这边出现这个问题的原因是在子线程对数据库执行了耗时操作占用了数据库连接,而此时在主线程又有一个方法在获取数据库连接,此时就需要等子线程对数据库操作完成释放数据库连接,由于是在主线程执行的操作,导致ANR)

Android数据库加密

阅读数 200

Android 内存数据库

阅读数 4253

Android 数据库

阅读数 440

没有更多推荐了,返回首页