【Android开发日记】之入门篇(八)——Android数据存储(下)
一、Android数据库使用
Android中使用android.database.sqlite.SQLiteDatabase来表示一个数据库对象,它提供了两种模式来帮助开发者进行增删改查等基本数据库操作。
- 利用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语言作比较的话,如果写过对文件进行处理的程序,那么游标就像您打开文件所得到的文件句柄一样,只要文件打开成功,该文件句柄就可代表该文件。总之记住,游标是一块有着特有记号的一块数据区域,能够让用户逐条从中读取出数据。
- 结构化的方式描述数据库的操作
 这样即使我们不熟悉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,那么张三组就不会显示。
- SQL语句的预编译
 在实践中,有的SQL语句需要被反复使用,为了避免反复解析SQL语句产生的开销,可以对需要复用的SQL语句进行预编译,来提高数据库操作的执行效率。//编译复杂的SQL语句 
 SQLiteStatement compiledSql = db.compileStatement(aSQL);
 //执行SQL
 compiledSql.execute();除此以外,Android还提供了丰富的高级数据库功能,比如支持触发器、支持复合索引以及支持对数据库事务的处理。 try{
 db.beginTransaction();
 //执行相关的数据库操作,如有异常,直接进入finally部分。
 }finally{
 //不论成功都要调用endTransaction来结束事务
 db.endTransaction();
 }课外小知识:所谓事务是用户定义的一个数据库操作序列,这些操作要么全做要么全不做,是一个不可分割的工作单位。例如,在关系数据库中,一个事务可以是一条SQL语句、一组SQL语句或整个程序。 简单举个例子就是你要同时修改数据库中两个不同表的时候,如果它们不是一个事务的话,当第一个表修改完,可是第二表改修出现了异常而没能修改的情况下,就只有第二个表回到未修改之前的状态,而第一个表已经被修改完毕。 而当你把它们设定为一个事务的时候,当第一个表修改完,可是第二表改修出现了异常而没能修改的情况下,第一个表和第二个表都要回到未修改的状态!这就是所谓的事务回滚。 
- 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");
 } }
- SQLiteOpenHelper的应用
 新建一个数据库管理类DBManagerpackage 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();//关闭数据操作对象
- 数据库的并发问题
 并发问题是使用数据库过程中最容易碰到的问题,如果在开发中碰到了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"
...>
资源下载:(数据库+文件)demo ======================================== 作者:cpacm
出处:(http://www.cpacm.net/2015/03/22/Android开发日记(六)——Android数据存储(下)/)
【Android开发日记】之入门篇(八)——Android数据存储(下)的更多相关文章
- 【Android开发日记】第一个任务Android Service!Service靴+重力感应器+弹出窗口+保持执行
		前言: 近期在写一个小程序,需求是手机摇一摇就弹窗出来.第一次使用了Service,学习了两天,实现了Service弹窗,开机启动,Service启动和销毁,Service保持一直执行. 满足了自己的 ... 
- 【Android开发日记】之入门篇(十二)——Android组件间的数据传输
		组件我们有了,那么我们缺少一个组件之间传递信息的渠道.利用Intent做载体,这是一个王道的做法.还有呢,可以利用文件系统来做数据共享.也可以使用Application设置全局数据,利用组件来进行控制 ... 
- 【Android开发日记】之入门篇(十一)——Android的Intent机制
		继续我们的Android之路吧.今天我要介绍的是Android的Intent. 对于基于组件的应用开发而言,不仅需要构造和寻找符合需求的组件,更重要的是要将组件有机的连接起来,互联互通交换信息,才能够 ... 
- 【Android开发日记】之入门篇(七)——Android数据存储(上)
		在讲解Android的数据源组件——ContentProvider之前我觉得很有必要先弄清楚Android的数据结构. 数据和程序是应用构成的两个核心要素,数据存储永远是应用开发中最重要的主题之一,也 ... 
- 【Android开发日记】之入门篇(九)——Android四大组件之ContentProvider
		数据源组件ContentProvider与其他组件不同,数据源组件并不包括特定的功能逻辑.它只是负责为应用提供数据访问的接口.Android内置的许多数据都是使用ContentProvider形式,供 ... 
- 【Android开发日记】之入门篇(十四)——Button控件+自定义Button控件
		好久不见,又是一个新的学期开始了,为什么我感觉好惆怅啊!这一周也发生了不少事情,节假日放了三天的假(好久没有这么悠闲过了),实习公司那边被组长半强制性的要求去解决一个后台登陆的问题,结果就是把 ... 
- 【Android开发日记】之入门篇(一)——开发环境的搭建
		写给自己的话:至此,大学的时光已经剩下一年的时光,下一年等毕业设计结束后就算是正式地踏入社会.自己学android也不过几个月的时间,为了更好管理文档,写点东西记录下自己曾经做过的点点滴滴是一个不错的 ... 
- 【Android开发日记】之入门篇(五)——Android四大组件之Service
		这几天忙着驾校考试,连电脑都碰不到了,今天总算告一段落了~~Service作为Android的服务组件,默默地在后台为整个程序服务,辅助应用与系统中的其他组件或系统服务进行沟通.它跟Activity的 ... 
- 【Android开发日记】之入门篇(六)——Android四大组件之Broadcast Receiver
		广播接受者是作为系统的监听者存在着的,它可以监听系统或系统中其他应用发生的事件来做出响应.如设备开机时,应用要检查数据的变化状况,此时就可以通过广播来把消息通知给用户.又如网络状态改变时,电量变化时都 ... 
随机推荐
- 20135239 益西拉姆 linux内核分析 进程的切换和系统的一般执行过程
			week 8 进程的切换和系统的一般执行过程 [ 20135239 原文请转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course ... 
- gitlab相关
			1.gitlab的概述 1.gitlab是什么 是一个用于仓库管理系统的开源项目,使用Git作为代码管理工具,并在此基础上搭建起来的web服务. 基础功能免费,高级功能收费 2.为什么要使用gitla ... 
- Web项目开发中用到的缓存技术
			在WEB开发中用来应付高流量最有效的办法就是用缓存技术,能有效的提高服务器负载性能,用空间换取时间.缓存一般用来 存储频繁访问的数据 临时存储耗时的计算结果 内存缓存减少磁盘IO 使用缓存的2个主要原 ... 
- 小技巧--tab键自动补齐Git命令
			Git是什么,你不清楚? 好吧,那么该篇内容对你也木有帮助,请绕道而行.. 我们在使用Git命令时,可以通过tab键,自动补齐Git,特别是在切换分支时特别有用. 如下,当我们想将当前分支切换到bug ... 
- golang单元测试
			使用testing进行单元测试 golang的测试库testing 测试文件与被测试文件在同一个包中 测试文件名为被测试文件名(去后缀)_test.go 测试用例函数以Test开头,TestFunc1 ... 
- MVC4.0中cshtml中怎么解析html编码
			http://bbs.csdn.net/topics/391060108?page=1 问题描述: 数据库中存储带有格式的文本,如 <span style="color:#333333 ... 
- CH3101 阶乘分解
			题目链接 分解\(n!\)的质因数,输出相应的\(p_i\)和\(c_i\). 其中\(1\leq n\leq 10^6\). 考虑每一个质因子 \(p\) 在 \(n!\) 中出现的次数.显然, ... 
- python学习(25) BeautifulSoup介绍和实战
			BeautifulSoup是python的html解析库,处理html非常方便 BeautifulSoup 安装 pip install beautifulsoup4 BeautifulSoup 配合 ... 
- Java基础-Collection子接口之Set接口
			Java基础-Collection子接口之Set接口 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 学习Collection接口时,记得Collection中可以存放重复元素,也可 ... 
- 蓝桥杯 算法提高 学霸的迷宫  经典BFS问题
			算法提高 学霸的迷宫 时间限制:1.0s 内存限制:256.0MB 问题描述 学霸抢走了大家的作业,班长为了帮同学们找回作业,决定去找学霸决斗.但学霸为了不要别人打扰,住在一个城 ... 
