Android本地数据存储之SQLite

转载请标明出处:http://blog.csdn.net/wu_wxc/article/details/49476779
本文出自【吴孝城的CSDN博客】

Android给我们提供了一个轻量级数据库SQLite。

SQLite是一个轻量级关系型数据库,

Android内置的是SQLite3版本

SQLite支持五种数据类型:NULL,INTEGER,REAL(浮点数),TEXT(字符串文本)BLOB(二进制对象)

虽然只有五种,但也可以保存archar,char等其他数据类型的数据

SQLite还可以将各种数据类型的数据保存到任何字段中而不用关心字段声明的数据类型是什么

例如:可以在Integer类型的字段中存放字符串

声明为主键INTEGER PRIMARY KEY的字段只能够存储64位整数


QLite 在解析CREATE TABLE 语句时, 

会忽略 CREATE TABLE 语句中跟在字段名后面的数据类型信息

如下面语句会忽略 name字段的类型信息: 

CREATE TABLE person (personid integer primary key autoincrement, name varchar(20))

SQLite相关的类:

SQLiteOpenHelper:抽象类,通过继承该类,重写数据库创建以及更新的方法, 

我们还可以通过该类的对象获得数据库实例,或者关闭数据库

SQLiteDatabase:数据库访问类:通过该类的对象来对数据库做一些增删改查的操作

Cursor:游标,类似于JDBC里的resultset(结果集),可以简单理解为指向数据库中某一个记录的指针


SQL给我们提供了两个方法:

execSQL()方法可以执行insert、delete、update和CREATE TABLE之类有更改行为的SQL语句

rawQuery()方法用于执行select语句。


方法解析:
onCreate(database):数据库文件不存在时调用

SQLiteOpenHelper会自动检测数据库文件是否存在,如果数据库文件存在,就不调用,

如果不存在,就会调用这个方法,创建数据表、视图等数据库组件


onUpgrade(database,oldVersion,newVersion):数据库文件存在时调用

在数据库的版本发生变化时会被调用,

一般在软件升级时才需改变版本号,而数据库的版本是由程序员控制的,

数据库版本更改时调用,注意,只能往高版本更新,不能降低版本
例如从版本2降到版本1会报错:Can't downgrade database from version 2 to 1

假设数据库现在的版本是1(没创建数据库时版本为0),由于业务的变更,修改了数据库表结构,这时候就需要升级软件,

升级软件时希望更新用户手机里的数据库表结构,为了实现这一目的,

可以把原来的数据库版本设置为2 或者其他与旧版本号不同的数字即可

在onUpgrade()方法中除了创建表、视图等组件外,还需要先删除这些相关的组件,

因此,在调用onUpgrade()方法前,数据库是存在的,里面还原许多数据库组件


先看看SQLiteOpenHelper类的构造方法再解释onUpdate()方法何时会被调用。

public SQLiteOpenHelper(Context context,String name,CursorFactory factory,int version);

其中name参数表示数据库文件名(不包括文件路径),SQLiteOpenHelper会根据这个文件名创建数据库文件。

version表示数据库的版本号。如果当前传入的数据库版本号比上次创建或升级的版本号高,

SQLiteOpenHelper就会调用onUpdate()方法。也就是说,当数据库第一次创建时会有一个初始的版本号。

当需要对数据库中的表、视图等组建升级时可以增大版本号,再重新创建它们。


update()修改

public int update (String table, ContentValues values, String whereClause, String[] whereArgs);

table:要更新的表名;

values:要更新的行的内容,即列名和列值的映射。

whereClause:更新的字段,指定符符合更新条件的行SQL Where语句,为一个字符串。若取值为null,则更新所有行。

whereArgs:字符串数组,和whereClause配合使用。有两种用法,如果whereClause的条件已经直接给出,

如“_id = “ + num,num是传入的参数,则whereArgs可设为null。如果是”_id = ?“,

则?会被whereArgs这个数组中对应的值替换,whereArgs给出?代表的值,有多个?的,字符串数组里的值依次填入




创建和更新数据库的代码举例

public class MyDBOpenHelper extends SQLiteOpenHelper {
    public MyDBOpenHelper(Context context, String name,
                          SQLiteDatabase.CursorFactory factory, int version) {
        super(context, "my.db", null, 1);
    }

    //第一次创建数据库时
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE person(_id INTEGER PRIMARY KEY AUTOINCREMENT," +
                "name INT, age VARCHAR(10) NULL)");
    }

    //数据库版本更改时
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("ALTER TABLE person ADD phone Varchar(12) NULL");
    }
}


上面的代码会创建my.db数据库这个文件

里面有一个名称为person的表


在Android Studio中查看生成的数据库文件的方法,打开

Tool >> Android >> Android Device >> Monitor

Android本地数据存储之SQLite_第1张图片

第一次打开会有一个发送什么到谷歌的,直接回车就行,


打开后在DDMS >> File Explore >> data >> data >> (包名) >> databases

默认是没有databases这个文件夹的

如果没有开模拟器的话,打开后看到的内容是空白的
可以点击右边指出的推送将选中的xml文件推到电脑上打开查看,也可点击拉入一个文件,可删除选中的文件,旁边的“+”只有在选中文件夹时才有效 
Android本地数据存储之SQLite_第2张图片

my.db是创建的数据库文件

my.db-joumal是为了能让数据库支持事务而产生的 临时的日志文件,一般的大小是0字节

将my.db推送到桌面,就可以用数据库软件打开查看了,




下面是一个小例子

需要编辑的文件如下


activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal">
        <EditText
            android:id="@+id/title"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:ems="6"
            android:hint="标题"
            android:layout_weight="1"/>

        <EditText
            android:id="@+id/time"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:ems="6"
            android:hint="时间"
            android:layout_weight="1"/>

    </LinearLayout>
    <EditText
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="输入内容"/>

    <Button
        android:id="@+id/add"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="添加" />

</LinearLayout>



daily_list .xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/tvTime"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/tvContent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

update.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <EditText
        android:id="@+id/uptitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="标题"/>
    <EditText
        android:id="@+id/uptime"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="时间"/>
    <EditText
        android:id="@+id/upcontent"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="内容"/>

</LinearLayout>



MainActivity.java

package cn.wuxiaocheng.mysqlite;

import android.app.AlertDialog;
import android.app.ListActivity;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.SimpleCursorAdapter;

public class MainActivity extends ListActivity {

    private Context mcontext;
    private Button add;
    private EditText title, time, content, uptitle, uptime, upcontent;
    private MyDBOpenHelper mydb;
    private SQLiteDatabase dbWrite, dbRead;
    private SimpleCursorAdapter adapter;        //简单适配器

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mcontext = MainActivity.this;
        //创建MyDBOpenHelper类的费用对象,传入所需的值,创建名为my.db的数据库,版本号为1
        mydb = new MyDBOpenHelper(mcontext, "my.db", null, 1);
        binView();

        //用可读方法打开数据库
        dbRead = mydb.getReadableDatabase();
        //用可写方法打开数据库
        dbWrite = mydb.getWritableDatabase();
        adapter = new SimpleCursorAdapter(mcontext, R.layout.daily_list, null,
                new String[]{"title", "time", "content"}, new int[]{R.id.tvTitle, R.id.tvTime, R.id.tvContent});
        setListAdapter(adapter);

        //刷新列表
        refresh();

        //长按执行
        getListView().setOnItemLongClickListener(listViewItemLongClickListener);

    }

    private void binView() {
        add = (Button) findViewById(R.id.add);
        title = (EditText) findViewById(R.id.title);
        time = (EditText) findViewById(R.id.time);
        content = (EditText) findViewById(R.id.content);

        add.setOnClickListener(btnAddDB);
    }

    //添加
    private View.OnClickListener btnAddDB = new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            ContentValues cv = new ContentValues();
            //将获得的值加入到集合中
            cv.put("title", title.getText().toString());
            cv.put("time", time.getText().toString());
            cv.put("content", content.getText().toString());
            dbWrite.insert("daily", null, cv);
            //刷新
            refresh();
        }
    };

    //刷新
    private void refresh() {
        //查询
        Cursor c = dbRead.query("daily", null, null, null, null, null, null);
        adapter.changeCursor(c);
    }


    //长按
    private AdapterView.OnItemLongClickListener listViewItemLongClickListener = new AdapterView.OnItemLongClickListener() {

        @Override
        public boolean onItemLongClick(AdapterView<?> parent, View view, final int position, long id) {


            new AlertDialog.Builder(MainActivity.this).setTitle("操作提醒")
                    .setMessage("修改或删除").setNegativeButton("修改", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {

                    LayoutInflater inflater = getLayoutInflater();
                    final View layout = inflater.inflate(R.layout.update, null);
                    AlertDialog ddialog = new AlertDialog.Builder(MainActivity.this).create();
                    ddialog.setView(layout);
                    //点击确定后执行的内容
                    DialogInterface.OnClickListener ls = new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {

                            //这里是Dialog自定义的布局,上面用了Vew layout = inflater.inflate(R.layout.update, null);
                            //在调用AlertDialog里的控件时,要显示的用View对象来调用,
                            // 上面用了View layout,所以这里用layout.findViewById
                            uptitle = (EditText) layout.findViewById(R.id.uptitle);
                            uptime = (EditText) layout.findViewById(R.id.uptime);
                            upcontent = (EditText) layout.findViewById(R.id.upcontent);

                            ContentValues cv = new ContentValues();
                            //将获得的值加入到集合中
                            cv.put("title", uptitle.getText().toString());
                            cv.put("time", uptime.getText().toString());
                            cv.put("content", upcontent.getText().toString());
                            Cursor c = adapter.getCursor();
                            c.moveToPosition(position);

                            int itemId = c.getInt(c.getColumnIndex("_id"));
                            //修改字段
                            dbWrite.update("daily", cv, "_id=?", new String[]{itemId + ""});
                            refresh();
                        }
                    };
                    //设置两个按钮
                    ddialog.setButton(DialogInterface.BUTTON_POSITIVE, "确定", ls);
                    ddialog.show();

                }
            })
                    .setPositiveButton("删除", new DialogInterface.OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            Cursor c = adapter.getCursor();
                            c.moveToPosition(position);

                            int itemId = c.getInt(c.getColumnIndex("_id"));
                            dbWrite.delete("daily", "_id=?", new String[]{itemId + ""});
                            refresh();
                        }
                    }).show();

            return true;
        }
    };


}






MyDBOpenHelper.java

package cn.wuxiaocheng.mysqlite;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;


public class MyDBOpenHelper extends SQLiteOpenHelper {
    /*
    * String name为数据库文件名,SQLiteOpenHelper会根据这个文件名创建数据库文件
    * SQLiteDatabase.CursorFactory factory可选的数据库游标工厂类,
    *       当查询(query)被提交时,该对象会被调用来实例化一个游标。
    *       默认为null,可以理解为查询结果集
    * int version为数据库的版本号
    * */
    public MyDBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    //第一次创建数据库时调用,用于创建表和初始化表中数值,如果数据为已经创建,则不会执行,
    //参数 db 表示SQLite数据库对象,在方法中根据需要对该对象填充表和初始化数据
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE daily(_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
                "title VARCHAR(10), time VARCHAR(10), content VARCHAR(60))");
    }

    //数据库版本更改时调用,注意,只能往高版本更新,不能降低版本
    //例如从版本2降到版本1会报错:Can't downgrade database from version 2 to 1
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("ALTER TABLE daily ADD weather Varchar");
    }
}



运行结果

Android本地数据存储之SQLite_第3张图片


用SQLite打开数据库查看

Android本地数据存储之SQLite_第4张图片


部分代码解析

创建ContentValues用于存储键值对

ContentValues cv = new ContentValues();

将获得的值加入到集合中

cv.put("name", edname.getText().toString());

cv.put("age", edage.getText().toString());

插入内容

 insert(String table, String nullColumnHack, ContentValues values)

table表名,第三个参数:插入的内容,

第二个参数:当第三个参数为空或者没有内容时,无法插入,为防止报错,我们要指定一个列名,

当插入的值为空时,就会将该列名值设置为null再插入

 dbWrite.insert("person", null, cv);


Cursor query (String table, String[] columns, String selection, String[] selectionArgs,

              String groupBy, String having, String orderBy, String limit)

table:查询的表名

columns:查询返回的列,为null时返回所有列

selection:指定返回哪些行的where语句(不包括SQL的Where关键字),为null时返回所有行

selectionArgs:表示where语句中表达式的 ? 占位参数列表,参数只能为String类型

groupBy:表示对结果集进行分组的group by语句(不包括SQL的GROUP BY关键字),为null时不对结果集进行分组

having:表示对分组结果集设置条件的having语句(不包括SQL的HAVING关键字),

          必须配合groupBy参数使用,为null时不对分组结果进行设置

orderBy:表示对结果集进行排序的order by语句(不包括SQL的ORDER BY关键字),为null时默认排序,通常不排序

limit:限制查询返回行数,null为不限制返回行数


SQLite数据库的几个操作

插入数据

public void save(Person p)
{
    SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
    db.execSQL("INSERT INTO person(name, age) values(?,?)",
                new String[]{p.getName(),p.getAge()});
}

删除数据

public void update(Person p)
{
    SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
    db.execSQL("UPDATE person SET name = ?,age = ? WHERE personid = ?",
        new String[]{p.getName(),p.getAge(),p.getId()});
}

查询数据

public Person find(Integer id)
{
    SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
    Cursor cursor =  db.rawQuery("SELECT * FROM person WHERE personid = ?",
            new String[]{id.toString()});
    //存在数据才返回true
    if(cursor.moveToFirst())
    {
        int personid = cursor.getInt(cursor.getColumnIndex("personid"));
        String name = cursor.getString(cursor.getColumnIndex("name"));
        String age = cursor.getString(cursor.getColumnIndex("age"));
        return new Person(personid,name,age);
    }
    cursor.close();
    return null;
}

查询记录数

public long getCount()
{
    SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
    Cursor cursor =  db.rawQuery("SELECT COUNT (*) FROM person",null);
    cursor.moveToFirst();
    long result = cursor.getLong(0);
    cursor.close();
    return result;      
}

数据分页

public List<Person> getScrollData(int offset,int maxResult)
{
    List<Person> person = new ArrayList<Person>();
    SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
    Cursor cursor =  db.rawQuery("SELECT * FROM person ORDER BY personid ASC LIMIT= ?,?",
        new String[]{String.valueOf(offset),String.valueOf(maxResult)});
    while(cursor.moveToNext())
    {
        int personid = cursor.getInt(cursor.getColumnIndex("personid"));
        String name = cursor.getString(cursor.getColumnIndex("name"));
        String age = cursor.getString(cursor.getColumnIndex("age"));
        person.add(new Person(personid,name,age)) ;
    }
    cursor.close();
    return person;
}



SQLite大二进制文件存储

一般来说,我们不会在数据库中存储大二进制文件,如图片,音频,视频等,

这些我们一般都是存储文件路径

如果想要将这些数据存储到数据库中,在创建数据库时就需要创建一个BLOB字段

用于存储二进制的值

创建数据库

db.execSQL("Create table express ( _id INTEGER PRIMARY KEY AUTOINCREMENT,express_img BLOB );");

bitmap 变为 Blob  

compress(CompressFormat format, int quality, OutputStream stream);

 format  图像的压缩格式;

quality 图像压缩比的值,0-100。 0 意味着小尺寸压缩,100意味着高质量压缩。

对于有些格式,比如无损压缩的PNG,它就会忽视quality这个参数设置

20%表示压缩20%

stream  写入压缩数据的输出流

如果成功地把压缩数据写入输出流,则返回true

ContentValues values = new ContentValues();  
final ByteArrayOutputStream bo = new ByteArrayOutputStream();  
Bitmap bmp = BitmapFactory.decodeFile("/mnt/sdcard/face.jpg"); 
bmp.compress(Bitmap.CompressFormat.PNG, 100, bo);    
values.put("express_img", os.toByteArray());   
getContentResolver().insert("express", values);

从SQLite中读取Bitmap

SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
cursor cursor = db.rawQuery("SELECT face FROM express", null);
if(cursor != null){
	if(cursor.moveToFirst()){
		//取出图片保存到字节数组中
		byte[] ing=cur.getBlob(cur.getColumnIndex("express_img"));
	}
}

显示在ImageView上

if(cursor != null)
	cursor.close();	
if(img != null){
	ByteArrayInputStream stream = new ByteArrayInputStream(img);
	imageView.setImageDrawable(Drawable.createFromStream(stream, "img"));
}


inputStream: 作为数据缓存,数据供别的对象读取,其方法为read();

outputStream:作为数据缓存,向别的对象写内容!其方法write();



SQLite读取Bitmap的另一种方法

BitmapFactory.decodeByteArray(byte[] data,int offset,int length,Options opts)
byte[] data: 是要进行decode的资源数据

int offset:decode的位移量,一般为0

int length:decode的数据长度一般为data数组的长度

Options opts:设置显示图片的参数,压缩,比例等




如果运行程序时出现

MainActivity (server)' ~ Channel is unrecoverably broken and will be disposed!

看看是不是打开了Android Device Monitor

关闭它就行,或者是数据库链接没加端口号,或者是哪里有数据写入或输出的地方出错。




源码下载


你可能感兴趣的:(channel,is,unrecover,Android图片存储,SQLite图片存储,Android音频视频文件存储)