废话不多说了,紧接着来讲数据库的操作吧。Come On!
提到数据存储问题,数据库是不得不提的。数据库是用来存储关系型数据的不二利器。Android为开发者提供了强大的数据库支持,可以用来轻松地构造基于数据库的应用。Android的数据库应用,依托于当下最流行的开源嵌入式数据库SQLite。在Android中,应用的数据库文件是该应用私有的,存储在应用数据目录下的databases子目录内。从代码结构来看,Android的数据库实现可以分成两个层次,在底层通过C++调用SQLite的接口来执行SQL语句,并通过JNI向上暴露Java可访问的接口。

一、Android数据库使用

Android中使用android.database.sqlite.SQLiteDatabase来表示一个数据库对象,它提供了两种模式来帮助开发者进行增删改查等基本数据库操作。

  1. 利用SQL语句描述操作
    利用SQL语句调用SQLiteDatabase.execSql或SQLiteDatabase.rawQuery来执行操作。

    //利用sql查询数据
    Cursor data = db.rawQuery("select id,name from table"); //利用sql插入数据
    db.execSql("insert into contacts (id,name) values (2,'cpacm')");

    稍微学过sql语句的人应该都看的懂上面的代码(其实看语句的意思也能知道个大概~)
    在这里我来解释一下Cursor(游标)的作用吧,游标不能顾名思义(up主当时学习数据库时一度将游标当做与C语言里面的指针变量一样,虽然有点对,但意思还是理解错了),Cursor它是系统为用户开设的一个数据缓冲区,是的,它是一块数据区域,存放SQL语句的执行结果。但是它也提供了能从包括多条数据记录的结果集中每次提取一条记录的机制,这一点也跟指针很像。游标总是与一条SQL选择语句相关联因为游标由结果集(可以是零条、一条或由相关的选择语句检索出的多条记录)和结果集中指向特定记录的游标位置组成。当决定对结果集进行处理 时,必须声明一个指向该结果集的游标。用C语言作比较的话,如果写过对文件进行处理的程序,那么游标就像您打开文件所得到的文件句柄一样,只要文件打开成功,该文件句柄就可代表该文件。总之记住,游标是一块有着特有记号的一块数据区域,能够让用户逐条从中读取出数据。

  2. 结构化的方式描述数据库的操作
    这样即使我们不熟悉SQL语句,也能使用最熟悉的面向对象的方式进行数据库操作。
    //结构化的方式查询数据
    Cursor data = db.query("contacts",new String[]{"id","name"},null,null,null,null,null); //结构化方式插入数据
    ContentValue values = new ContentValues();
    values.put("id",2);
    values.put("name","cpacm");
    db.insert("table",null,values);
    /**
    * 参数说明
    * table:数据表名,columns:需要显示的列名,如果为null则相当与*
    * selection:相当于sql语句的where条件;selectionArgs数组放的是where条件要替换的?号
    * groupBy:SQL语句的Group, orderBy: 排序,默认asc
    **/
    public Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy){
    }

    比如说我要查询的SQL语句为

    SELECT CustomerName, SUM(OrderPrice) FROM Orders WHERE Country=?
    GROUP BY CustomerName
    HAVING SUM(OrderPrice)>500
    ORDER BY CustomerName

    那么我写的代码如下

    //数据表名
    String table = "Orders" ;
    //要显示的列名
    String[] columns = new String[] { "CustomerName" , "SUM(OrderPrice)" };
    //选择条件
    String selection = "Country=?" ;
    //里面的变量对应条件中的问号,多个的时候请一一入座。
    String[] selectionArgs = new String[]{ "China" };
    //分组名
    String groupBy = "CustomerName" ;
    //分组的条件
    String having = "SUM(OrderPrice)>500" ;
    //按字段排序
    String orderBy = "CustomerName" ;
    Cursor c = db.query(table, columns, selection, selectionArgs, groupBy, having, orderBy);

    这样就能实现数据库的查询了。其它的语句参数都是差不多的,这里就不一一介绍了。

    public long insert (String table, String nullColumnHack, ContentValues values)
    public int delete(String table, String whereClause, String[] whereArgs)
    public int update(String table, ContentValues values, String whereClause, String[] whereArgs)

    课外小知识:关于GroupBy和Having的使用
    group by 顾名思义就是按照xxx进行分组,它必须有“聚合函数”来配合才能使用,使用时至少需要一个分组标识字段。聚合函数有:sum()、count()、avg()等,使用group by目的就是要将数据分组进行汇总操作。比如上面sql语句的CustomerName,如果它有四个行{“张三”,“李四”,“张三”,“李四”},那么此时就会分成两组,分别为张三组和李四组,然后统计出他们使用的orderprice总和。
    HAVING作用就是为每一个组指定条件,像where指定条件一样,也就是说,可以根据你指定的条件来选择行。如果你要使用HAVING子句的话,它必须处在GROUP BY子句之后。还是上面的SQL语句,如果张三的SUM(OrderPrice)没有超过500,那么张三组就不会显示。

  3. SQL语句的预编译
    在实践中,有的SQL语句需要被反复使用,为了避免反复解析SQL语句产生的开销,可以对需要复用的SQL语句进行预编译,来提高数据库操作的执行效率。
    //编译复杂的SQL语句
    SQLiteStatement compiledSql = db.compileStatement(aSQL);
    //执行SQL
    compiledSql.execute();

    除此以外,Android还提供了丰富的高级数据库功能,比如支持触发器、支持复合索引以及支持对数据库事务的处理。

         try{
    db.beginTransaction();
    //执行相关的数据库操作,如有异常,直接进入finally部分。
    }finally{
    //不论成功都要调用endTransaction来结束事务
    db.endTransaction();
    }

    课外小知识:所谓事务是用户定义的一个数据库操作序列,这些操作要么全做要么全不做,是一个不可分割的工作单位。例如,在关系数据库中,一个事务可以是一条SQL语句、一组SQL语句或整个程序。 简单举个例子就是你要同时修改数据库中两个不同表的时候,如果它们不是一个事务的话,当第一个表修改完,可是第二表改修出现了异常而没能修改的情况下,就只有第二个表回到未修改之前的状态,而第一个表已经被修改完毕。 而当你把它们设定为一个事务的时候,当第一个表修改完,可是第二表改修出现了异常而没能修改的情况下,第一个表和第二个表都要回到未修改的状态!这就是所谓的事务回滚。

  4. SQLiteOpenHelper
    在SQLiteOpenHelper中,封装了一个SqliteDatabase对象,使用着可以通过使用此类来进行数据库的操作。
    package com.example.notebook;
    
    import android.content.Context;
    import android.database.sqlite.SQLiteDatabase;
    import android.database.sqlite.SQLiteOpenHelper;
    import android.database.sqlite.SQLiteDatabase.CursorFactory; public class DBHelper extends SQLiteOpenHelper{
    private static final int VERSION=1;
    /**
    * 在SQLiteOpenHelper的子类当中,必须有该构造函数
    * @param context 上下文对象
    * @param name 数据库名称
    * @param factory
    * @param version 当前数据库的版本,值必须是整数并且是递增的状态
    */
    public DBHelper(Context context,String name,CursorFactory factory,int version){
    super(context,name,factory,version);
    }
    public DBHelper(Context context, String name, int version){
    this(context,name,null,version);
    } public DBHelper(Context context, String name){
    this(context,name,VERSION);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
    // 数据库首次构造时,会调用该函数,可以在这里构造表、索引,等等
    System.out.println("create a database");
    //execSQL用于执行SQL语句
    db.execSQL("create table notebook(_id integer primary key autoincrement,pic varchar(50),title varchar(20),content text,time varchar)"); }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    // 如果给定的当前数据库版本高于已有数据库版本,调用该函数
    System.out.println("upgrade a database");
    } }
  5. SQLiteOpenHelper的应用
    新建一个数据库管理类DBManager
    package com.example.notebook;
    
    import android.content.ContentValues;
    import android.content.Context;
    import android.database.Cursor;
    import android.database.sqlite.SQLiteDatabase;
    import android.database.sqlite.SQLiteException;
    import android.util.Log; public class DBManager { private Context mContext = null; private SQLiteDatabase mSQLiteDatabase = null;//用于操作数据库的对象
    private DBHelper dh = null;//用于创建数据库的对象 private String dbName = "note.db";//数据库的名称
    private int dbVersion = 1;//数据库的版本
    public DBManager(Context context){
    mContext = context;
    } public void open(){
    try{
    dh = new DBHelper(mContext, dbName, null, dbVersion);//建立数据库
    if(dh == null){
    Log.v("msg", "is null");
    return ;
    }
    mSQLiteDatabase = dh.getWritableDatabase();//以可写方式打开数据库
    //dh.onOpen(mSQLiteDatabase);
    }catch(SQLiteException se){
    se.printStackTrace();
    }
    }
    public void close(){ mSQLiteDatabase.close();//关闭数据库
    dh.close(); }
    public Cursor selectAll(){
    Cursor cursor = null;
    try{
    //sql语句操作
    String sql = "select * from notebook";
    cursor = mSQLiteDatabase.rawQuery(sql, null);
    }catch(Exception ex){
    ex.printStackTrace();
    cursor = null;
    }
    return cursor;
    }
    public Cursor selectById(int id){ //String result[] = {};
    Cursor cursor = null;
    try{
    //sql语句操作
    String sql = "select * from notebook where _id='" + id +"'";
    cursor = mSQLiteDatabase.rawQuery(sql, null);
    }catch(Exception ex){
    ex.printStackTrace();
    cursor = null;
    } return cursor;
    }
    public long insert(String title, String content,String pic){ long datetime = System.currentTimeMillis();
    long l = -1;
    try{
    //结构化方式操作
    ContentValues cv = new ContentValues();
    cv.put("title", title);
    cv.put("content", content);
    cv.put("time", datetime);
    cv.put("pic", pic);
    l = mSQLiteDatabase.insert("notebook", null, cv);
    // Log.v("datetime", datetime+""+l);
    }catch(Exception ex){
    ex.printStackTrace();
    l = -1;
    }
    return l; }
    public int delete(int id){
    int affect = 0;
    try{
    //结构化方式操作
    affect = mSQLiteDatabase.delete("notebook", "_id=?", new String[]{String.valueOf(id)});
    }catch(Exception ex){
    ex.printStackTrace();
    affect = -1;
    } return affect;
    }
    public int update(int id, String title, String content,String pic){
    int affect = 0;
    try{
    //结构化方式操作
    ContentValues cv = new ContentValues();
    cv.put("title", title);
    cv.put("content", content);
    cv.put("pic", pic);
    String w[] = {String.valueOf(id)};
    affect = mSQLiteDatabase.update("notebook", cv, "_id=?", w);
    }catch(Exception ex){
    ex.printStackTrace();
    affect = -1;
    }
    return affect;
    } }

    获取数据示例

                private DBManager dm = null;// 数据库管理对象
    private Cursor cursor = null;
    dm = new DBManager(this);//数据库操作对象
    dm.open();//打开数据库操作对象
    cursor = dm.selectAll();//获取所有数据
    cursor.moveToFirst();//将游标移动到第一条数据,使用前必须调用 int count = cursor.getCount();//个数
    ArrayList<String> contents = new ArrayList<String>();//图片的所有集合
    ArrayList<String> imgs = new ArrayList<String>();//图片的所有集合
    ArrayList<String> items = new ArrayList<String>();//标题的所有集合
    ArrayList<String> times = new ArrayList<String>();//时间的所有集合
    for(int i= 0; i < count; i++){
    contents.add(cursor.getString(cursor.getColumnIndex("content")));
    imgs.add(cursor.getString(cursor.getColumnIndex("pic")));
    items.add(cursor.getString(cursor.getColumnIndex("title")));
    times.add(cursor.getString(cursor.getColumnIndex("time")));
    //cursor.getInt(cursor.getColumnIndex("_id"))
    cursor.moveToNext();//将游标指向下一个
    }
    dm.close();//关闭数据操作对象
  6. 数据库的并发问题
    并发问题是使用数据库过程中最容易碰到的问题,如果在开发中碰到了android.database.SQLException异常,并提示"database is locked",那很有可能是出现了数据库的死锁导致无法访问。原因是Sqlite会对文件的读写进行加锁,防止数据被破坏。而在Android框架层SqliteDatabase会对所有数据库对象进行加锁保护,一旦出现了指向同一个数据库的多个SqliteDatabase对象同时在多个线程中被使用,那就跳脱了SqliteDatabase锁保护,就会导致数据库出现被锁的异常。因此在实践中,需要保证同时访问数据库的SqliteDatabase对象仅有一个。(可以使用全局变量来保存数据库对象,在整个数据源对象中使用同一个连接)
    课外小知识:在Android SDK中提供了工具Sqlite3,在shell模式下,可以对数据库进行增删改查。   
    cmd->adb shell ->sqlite3 <路径>/<数据库名> ->sqlite > select * from sqmple;

二、Android数据的云端服务

本质上而言,云端存储就是通过网络将移动设备上的数据存储到远端服务器上。在Android中,增加了一些辅助功能,使得整个流程的实现变得更为简单。首先是通过Google账号来标识用户身份。在android中,默认支持使用Google账号作为用户身份的标识,系统上各个应用都可以通过账号系统获得用户的登录信息。其次,有了Google账号,使得开发者不需要自行构建后台服务系统。

Android的云端数据存取由系统服务BackupManagerService来统一管理。当应用提交备份数据请求时,BackupManagerService会将该请求放入备份队列中,该队列会按照一定的控制逻辑定时提交到云端。当有新应用安装到系统时,会触发数据恢复事件,BackupManagerService会凭借应用包名和用户账号从云端取出相应的备份数据,尝试恢复。

在实践中,Android会构造一个派生自BackupAgent类的子类android.app.backup.BackupAgentHelper的对象,来更方便地构建云端存储组件。

import java.io.File;
import java.io.IOException; import android.app.backup.BackupAgentHelper;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.app.backup.FileBackupHelper;
import android.os.ParcelFileDescriptor; public class MyBackupAgent extends BackupAgentHelper { private static final String KEY = "my_backup"; @Override
public void onCreate() {
//构造文件读写对象,声明需要备份的文件
FileBackupHelper helper = new FileBackupHelper(this,"backup_file");
addHelper(KEY,helper);
super.onCreate();
} @Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException {
//调用父类方法,提交整个文件到云端
super.onBackup(oldState, data, newState);
} @Override
public void onRestore(BackupDataInput data, int appVersionCode,
ParcelFileDescriptor newState) throws IOException {
// 调用父类方法,将从云端获取的文件覆盖本地文件
super.onRestore(data, appVersionCode, newState);
} @Override
public void onRestoreFile(ParcelFileDescriptor data, long size,
File destination, int type, long mode, long mtime)
throws IOException {
// TODO Auto-generated method stub
super.onRestoreFile(data, size, destination, type, mode, mtime);
}

Android不会自行将数据提交到云端,开发者需要显性调用android.app.backup.BackupManager的dataChanged函数来触发。

和所有组件一样,云端存储组件是由系统进行托管的。这就需要把组件的相关信息放入配置文件中。

<application android:backupAgent = "MyBackupAgent"
...>
到这里,数据的部分就讲解的差不多了,剩下的就要回到我们的数据源组件ContentProvider了。
数据这一块对于开发者来说至关重要,怎样使用,如何做最有效率,这些问题也是我们技术员要一直研究的重点。
 
参考文章:Android 的Backup服务管理机制与架构分析     http://blog.csdn.net/goohong/article/details/8026045
资源下载:(数据库+文件)demo
 
 ========================================
作者:cpacm
出处:(http://www.cpacm.net/2015/03/22/Android开发日记(六)——Android数据存储(下)/
 
 

【Android开发日记】之入门篇(八)——Android数据存储(下)的更多相关文章

  1. 【Android开发日记】第一个任务Android Service!Service靴+重力感应器+弹出窗口+保持执行

    前言: 近期在写一个小程序,需求是手机摇一摇就弹窗出来.第一次使用了Service,学习了两天,实现了Service弹窗,开机启动,Service启动和销毁,Service保持一直执行. 满足了自己的 ...

  2. 【Android开发日记】之入门篇(十二)——Android组件间的数据传输

    组件我们有了,那么我们缺少一个组件之间传递信息的渠道.利用Intent做载体,这是一个王道的做法.还有呢,可以利用文件系统来做数据共享.也可以使用Application设置全局数据,利用组件来进行控制 ...

  3. 【Android开发日记】之入门篇(十一)——Android的Intent机制

    继续我们的Android之路吧.今天我要介绍的是Android的Intent. 对于基于组件的应用开发而言,不仅需要构造和寻找符合需求的组件,更重要的是要将组件有机的连接起来,互联互通交换信息,才能够 ...

  4. 【Android开发日记】之入门篇(七)——Android数据存储(上)

    在讲解Android的数据源组件——ContentProvider之前我觉得很有必要先弄清楚Android的数据结构. 数据和程序是应用构成的两个核心要素,数据存储永远是应用开发中最重要的主题之一,也 ...

  5. 【Android开发日记】之入门篇(九)——Android四大组件之ContentProvider

    数据源组件ContentProvider与其他组件不同,数据源组件并不包括特定的功能逻辑.它只是负责为应用提供数据访问的接口.Android内置的许多数据都是使用ContentProvider形式,供 ...

  6. 【Android开发日记】之入门篇(十四)——Button控件+自定义Button控件

        好久不见,又是一个新的学期开始了,为什么我感觉好惆怅啊!这一周也发生了不少事情,节假日放了三天的假(好久没有这么悠闲过了),实习公司那边被组长半强制性的要求去解决一个后台登陆的问题,结果就是把 ...

  7. 【Android开发日记】之入门篇(一)——开发环境的搭建

    写给自己的话:至此,大学的时光已经剩下一年的时光,下一年等毕业设计结束后就算是正式地踏入社会.自己学android也不过几个月的时间,为了更好管理文档,写点东西记录下自己曾经做过的点点滴滴是一个不错的 ...

  8. 【Android开发日记】之入门篇(五)——Android四大组件之Service

    这几天忙着驾校考试,连电脑都碰不到了,今天总算告一段落了~~Service作为Android的服务组件,默默地在后台为整个程序服务,辅助应用与系统中的其他组件或系统服务进行沟通.它跟Activity的 ...

  9. 【Android开发日记】之入门篇(六)——Android四大组件之Broadcast Receiver

    广播接受者是作为系统的监听者存在着的,它可以监听系统或系统中其他应用发生的事件来做出响应.如设备开机时,应用要检查数据的变化状况,此时就可以通过广播来把消息通知给用户.又如网络状态改变时,电量变化时都 ...

随机推荐

  1. Alpha 完结撒花 —— 事后诸葛亮

    写在前面 林燊大哥 一路走来,好不容易,终于完结了. 设想和目标 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 解决的问题 用户在进店之前无法得知店铺的优劣,通过 ...

  2. Alpha 冲刺 —— 十分之五

    队名 火箭少男100 组长博客 林燊大哥 作业博客 Alpha 冲鸭鸭鸭鸭鸭! 成员冲刺阶段情况 林燊(组长) 过去两天完成了哪些任务 协调各成员之间的工作 协助测试的进行 测试项目运行的服务器环境 ...

  3. 使用SUID二进制文件进行Linux权限升级技巧

      0x00 基础知识 众所周知,在Linux中一切都以文件存在,包括具有允许或限制三个执行操作(即读/写/执行)权限的目录和设备.因此,当给任何文件设置权限时,应该需要了解允许的Linux用户或限制 ...

  4. JAVA代码保护从入门到放弃

    java语言开发的产品,需要部署到客户现场服务器.产生了对代码进行保护的需求,开始研究代码加密方式. 经过研究分析后有两种思路,混淆和加密.两者各自适应不同的情况. 由于大量spring注解功能,并且 ...

  5. Docker Swarm 服务编排之命令

    一.简介 Docker有个编排工具docker-compose,可以将组成某个应该的多个docker容器编排在一起,同时管理.同样在Swarm集群中,可以使用docker stack 将一组相关联的服 ...

  6. Python【面向对象编程】

    #1.python中,类名首字母都大写#2.在python3中,经典类和新式类没有任何区别#3.在python2中,经典类和新式类的区别主要体现在多继承上,经典类是深度优先,新式类是广度优先#4.在p ...

  7. P4310 绝世好题

    P4310 绝世好题 题目描述 给定一个长度为n的数列ai,求ai的子序列bi的最长长度,满足bi&bi-1!=0(2<=i<=len). 说明 对于100%的数据,1<=n ...

  8. Hadoop生态圈-注册并加载协处理器(coprocessor)的三种方式

    Hadoop生态圈-注册并加载协处理器(coprocessor)的三种方式 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 到目前为止,大家已经掌握了如何使用过滤器来减少服务器端通过 ...

  9. Java基础-日期格式化DateFormat类简介

    Java基础-日期格式化DateFormat类简介 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.DateFormat类概述 DateFormat 是日期/时间格式化子类的抽象 ...

  10. 用canvas绘制验证码

    在通常的登录界面我们都可以看到验证码,验证码的作用是检测是不是人在操作,防止机器等非人操作,防止数据库被轻而易举的攻破. 验证码一般用PHP和java等后端语言编写: 但是在前端,用canva或者SV ...