Titanium教程day3-使用本地数据

原文:http://wiki.appcelerator.org/display/guides/Working+with+Local+Data

使用本地数据

内容速览

    * 要点
    * 使用本地数据
          o 选用哪种类型的数据存储?
    * App属性
          o 读写属性
          o 以JSON形式,把JS对象存储在属性中
    * 文件系统存储
          o 对象
          o 属性
          o 方法
          o 写文件
          o 读文件
          o 移除文件
          o 完整代码
    * SQLite 数据库
          o SQL基础和更多阅读
          o 创建数据库
          o 准备好数据库
                + 把数据库包含在App包中
                + 从远程下载数据库
          o 存数据
          o 读数据
          o 更新数据
          o 关闭数据库和数据集

要点

    * 本地存储的不同模式,何时使用
    * 怎样用程序属性
    * 怎样从本地文件系统中存取数据
    * 怎样和本地SQLite 数据库交互

使用本地数据

一般程序都有本地存储需求。Titanium 提供统一方便的接口。使用以下对象:
    * Titanium.App.Properties 用来存储App相关设置
    * Titanium.Filesystem 文件和目录操作
    * Titanium.Database 操作本地SQLite3数据库

选用哪种类型的数据存储?

取决如下因素:
   1. App属性 - 当满足下面一条或全部满足时:
          * 数据只是简单的键值对
          * 数据和App本身相关而不是和用户相关
          * 该数据独立,不再需要其他数据才变得有意义或有用
          * 任何时候,只需要数据的一个版本
   2. 文件系统- 当满足下面一条或全部满足时:
          * 数据以文件形式提供
          * 数据是图片文件
   3. 数据库 -当满足下面一条或全部满足时:
          * 有很多条类似的数据item
          * 数据item和其他item相关
          * 取数据时要求灵活提取
          * 数据随时间而积累,如历史事务清单, 登录或档案数据
注意:尽管图片数据可以以2进制(blob )形式保存在数据库中。但建议不要这样做。而应用Titanium.Database存储图片文件路径和图片名,用Titanium.Filesystem管理物理文件。
想要本地文件系统仅仅有条,易于管理和备份。最好把类似的文件放在一起,比如图片,放到同一位置。这样一来,就用不着在数据库存路径。程序中所有图片都可以这样做,只用App属性设置就好了。

App属性

iOS和Android 都有特定文件用来存储App属性。iOS 是NSUserDefaults,在应用程序library 目录下的.plist 文件中。Android则是标准的XML文件,位于/data/data/com.domainname.appname/shared_prefs/titanium.xml.
App一加载,App属性就存到内存。等App关掉,就自动从内存释放。理论上访问很快,但会受到App占有内存的影响。

读写属性

Titanium.App.Properties有6个get/set方法,分别操作6种不同数据:
    * getBool() / setBool(): 布尔值 (true, false)
    * getDouble() / setDouble(): 双精度浮点数值
    * getInt() / setInt(): 整型
    * getList() / setList():数组
    * getString() / setString(): 字符串
get方法接受 属性名 参数,并返回默认值,如果属性从未被设置,就返回默认值。每一个 set 方法都要传进 属性名-属性值 对。范例如下:
var window = Titanium.UI.createWindow({
  backgroundColor:'#999'
});
var myArray = [ { name:'Name 1', address:'1 Main St'}, {name:'Name 2', address:'2 Main St'}, {name:'Name 3', address:'3 Main St'}, {name:'Name 4', address:'4 Main St' } ];
Ti.App.Properties.setString('myString','This is a string');
Ti.App.Properties.setInt('myInt',10);
Ti.App.Properties.setBool('myBool',true);
Ti.App.Properties.setDouble('myDouble',10.6);
Ti.App.Properties.setList('myList',myArray);
// **********************************************
// Notice the use of the second argument of the get* methods below
// that would be returned if no property exists with that name
// **********************************************
Ti.API.info("String: "+Ti.App.Properties.getString('myString','This is a string default'));
Ti.API.info("Integer: "+Ti.App.Properties.getInt('myInt',20));
Ti.API.info("Boolean: "+Ti.App.Properties.getBool('myBool',false));
Ti.API.info("Double: "+Ti.App.Properties.getDouble('myDouble',20.6));
Ti.API.info("List: "+Ti.App.Properties.getList('myList'));
window.open();
执行输出:
String: This is a string
Integer: 10
Boolean: true
Double: 10.600000381469727
List:
  {  'address' :  '1 Main St' 'name' :  'Name 1', },
  {  'address' :  '2 Main St' 'name' :  'Name 2', },
  {  'address' :  '3 Main St' 'name' :  'Name 3', },
  {  'address' :  '4 Main St' 'name' :  'Name 4', }

以JSON形式,把JS对象存储在属性中

如果是复杂的js对象,要用Titanium.JSON的JSON.stringify()把js对象转成JSON字符串。这样一来,就能用the Titanium.App.Properties.setString() 存到数据库中了。
var window = Titanium.UI.createWindow({
  backgroundColor:'#999'
});
var weatherData = { "reports" : [ { "city": "Mountain View", "condition": "Cloudy", "icon": "http://www.google.com/weather/cloudy.gif" }, { "city": "Washington, DC", "condition": "Mostly Cloudy", "icon": "http://www.google.com/weather/mostly_cloudy.gif" }, { "city": "Brasilia", "condition": "Thunderstorm", "icon": "http://www.google.com/weather/thunderstorm.gif" } ] };
Ti.App.Properties.setString('myJSON', JSON.stringify(weatherData));
var retrievedJSON=Ti.App.Properties.getString('myJSON', 'myJSON not found');
Ti.API.info("The myJSON property contains: " + retrievedJSON);
window.open();
测试输出:
The myJSON property contains: {"reports":[{"icon":"http:\/\/www.google.com\/ig\/images\/weather\/cloudy.gif","condition":"Cloudy","city":"Mountain View"},{"icon":"http:\/\/www.google.com\/ig\/images\/weather\/mostly_cloudy.gif","condition":"Mostly Cloudy","city":"Washington, DC"},{"icon":"http:\/\/www.google.com\/ig\/images\/weather\/thunderstorm.gif","condition":"Thunderstorm","city":"Brasilia"}]}
取值时再用JSON.parse()还原成JS对象:
var myObject = JSON.parse(Ti.App.Properties.getString('myJSON'));

文件系统存储

用Titanium 作CRUD(增删)操作很简单。在看Demo前先看下常用属性和方法:

对象

Titanium.Filesystem是顶级的文件系统模块。用来读写设备上的文件和目录.
Titanium.Filesystem.File,文件对象,支持文件的创建,读写,删除操作。

属性

数据存储位置:
    * applicationDataDirectory应用程序数据目录: 常量,只读。指定数据目录位置。应用程序位于此目录下.
    * resourcesDirectory资源目录: 常量,只读。即应用程序的资源目录。
    * tempDirectory缓存目录:  常量,只读。放缓存文件的地方。

方法

    * getFile(): 返回Titanium.Filesystem.File对象,完整格式的文件路径
    * deleteFile(): 删除文件
    * exists(): 如果文件货目录存在返回true。
    * extension(): 返回文件扩展名
    * move(): 移动文件到其他路径
    * rename(): 给文件重命名
    * nativePath(): 返回完整的原生路径
    * read(): 以blob形式返回文件内容
    * write(): 写文件
    * writeable(): 是否可写
    * spaceAvailable(): 路径是否有空间存储。

写文件

用简单例子解释.先创建JS对象:
var dataToWrite = {"en_us":{"foo":"bar"}};
接着创建目录:
// NOTE: use applicationDataDirectory for writes!!
var newDir = Titanium.Filesystem.getFile(Titanium.Filesystem.applicationDataDirectory,'mydir');
newDir.createDirectory();
Ti.API.info('Path to newdir: ' + newDir.nativePath);
现在有了特定的applicationDataDirectory作为新目录。程序可以对其读写操作。通常都得这样做。
注意,程序在运行时,对于文件系统的resourcesDirectory资源目录,可读不可写!
在刚刚创建的文件夹中添加新的文件:
var newFile = Titanium.Filesystem.getFile(newDir.nativePath,'newfile.json');
这样就添加了空白文件,但最好加点数据。初始化 dataToWrite 对象。和前面App属性做的一样。用Titanium.JSON。用JS对象写:
//Stringify the JavaScript object we created earlier and write it out to the new file
newFile.write(JSON.stringify(dataToWrite));

读文件

这样更新dataToWrite.en_us.foo呢?这样做:
   1. 读文件
   2. 反序列化存储在文件中的对象
   3. 更新相关属性
   4. 序列化对象
   5. 把序列化后的对象写回文件
var newFile = Titanium.Filesystem.getFile(newDir.nativePath,'newfile.json');
var resources = JSON.parse(newFile.read().text);
 
resources.en_us.foo = 'baz'; //bar becomes baz
newFile.write(JSON.stringify(resources));

移除文件

没有人要垃圾。我们自己得注意收检。先移除文件,再移除目录。
//We already have references to the file and directory objects.
//We just need to call their cooresponding delete methods.
newFile.deleteFile();
newDir.deleteDirectory();

完整代码

var dataToWrite = {"en_us":{"foo":"bar"}};
 
//NOTE: remember to use applicationDataDirectory for writes
var newDir = Titanium.Filesystem.getFile(Titanium.Filesystem.applicationDataDirectory,'mydir');
newDir.createDirectory();
Ti.API.info('Path to newdir: ' + newDir.nativePath);
 
var newFile = Titanium.Filesystem.getFile(newDir.nativePath,'newfile.json');
 
//Stringify the JavaScript object we created earlier and write it out to the new file
newFile.write(JSON.stringify(dataToWrite));
 
var newFile = Titanium.Filesystem.getFile(newDir.nativePath,'newfile.json');
 
Ti.API.info('JSON.parse(newFile.read().text).en_us.foo = ' + JSON.parse(newFile.read().text).en_us.foo);
 
var resources = JSON.parse(newFile.read().text);
resources.en_us.foo = 'baz'; //bar becomes baz
newFile.write(JSON.stringify(resources));
 
Ti.API.info('JSON.parse(newFile.read().text).en_us.foo = ' + JSON.parse(newFile.read().text).en_us.foo);
 
//We already have references to the file and directory objects.
//We just need to call their cooresponding delete methods.
newFile.deleteFile();
newDir.deleteDirectory();

SQLite 数据库

SQLite3 是SQLite的SQL基础型相关数据管理系统(RDMS)第3版。Apple,Google 和RIM 都在选择它,来进行本地数据存储。
SQLite 是当今世界使用最为广泛的数据库。得益于其开源,小型架构(footprint ),不用像其他DBMSs那样安装服务,设置和维护也超级简单。
第一次使用SQLite要注意以下几点,这会影响到你的开发方式:
    *其数据库文件就是一个简单的文本文件. 不存在安全域(granular security )或者用户权限(user privileges).因为通常没有编码, 文件系统中的任何人都可以访问文件读取内容。
    * 只有5种基础类型:TEXT, NUMERIC, INTEGER, REAL, NONE. 更多细节参考SQLite帮助。
    * 因其存储为2进制对象(BLOBs) 并用文本形式表现,进入BLOBs并不理想. 所以, 建议把文件系统的二进制地址存到数据库, 而不是二进制数据自身。
    * SQLite支持同时多人读操作,但是,一次只能有一个用户执行写操作. 因为当进行写操作时,文件就做了文件系统锁定.这一点在做多线程App时要死死的记住。
    * 默认不支持引用完整性(referential integrity).详见SQLite Foreign Key Support
    * 不支持 RIGHT and FULL OUTER JOINs
    * 支持部分 ALTER TABLE; 不能修改删除列

SQL基础和更多阅读

因为很多人都在用SQLite,所以网上资料非常多。我们整理这些链接对初学者很有用:
    * SQLite官网
    * SQLite官网的入门教程
    * SQLite视频教程
    * 我们的 第3方工具指南, 介绍了一些图形化工具,使SQLite更易使用
此外,在freenode IRC server的#sqlite栏目有很多人讨论。
用Titanium.Database模块访问SQLite,返回Titanium.Database.DB对象,可以安装和打开数据库,执行命令。做查询命令时,则返回Titanium.Database.ResultSet。

创建数据库

新建Titanium.Database实例对象,调用open() 或 install()方法来打开数据库文件。open()打开数据库文件,位于设备下应用程序目录的数据库目录中。安卓就是applicationDataDirectory/../databases/,和applicationDataDirectory属于同一级目录。如果数据库文件不存在,就会自动创建一个新的,空的SQLite 数据库文件。
var db = Ti.Database.open('weatherDB');
这将创建名为weatherDB的文件。最好加上.sqlite或者.db后缀名,当然这不是必须的。
另一方面,install() 会从Titanium的资源目录复制已有的数据库,或者一部分(descendants)。applicationDataDirectory/../databases/返回对打开的数据库的引用。如果同名文件已经存在,就会不做复制,而仅仅打开数据库。
var db = Ti.Database.install('/mydata/weatherDB', 'weatherDB');
这里,weatherDB在Resources/mydata/ directory目录下初始,再复制到App的数据库目录中。
打开数据库后,用execute()方法创建表:
db.execute('CREATE TABLE IF NOT EXISTS city (id INTEGER PRIMARY KEY, name VARCHAR(16) NOT NULL, continent VARCHAR(16) NOT NULL, temp_f VARCHAR(4), temp_c VARCHAR(4), condition_id INTEGER NOT NULL)');
db.execute('CREATE TABLE IF NOT EXISTS condition (id INTEGER PRIMARY KEY, summary VARCHAR(16) NOT NULL, icon TEXT NOT NULL)');
IF NOT EXISTS部分是标准的SQLite 语法,来确保不会重写覆盖表。以上这些,就是SQLite操作的常用套路,用来防止因为丢失数据库或数据表而无法启动App的情况发生。

准备好数据库

把数据库包含在App包中

如果程序中已经有数据库,要用到里面的数据。有2种办法。
1 是嵌入到App的发布包中,放到Titanium的资源文件夹。因为设备上的资源目录可读不可写,一旦安装就无删除。install() 会把文件复制到applicationDataDirectory/../databases/ 文件夹中,结果就是有2份文件副本。
2 如果数据库对你App的使用真的很重要。就要考虑在启动App时加载完整的数据库,这样就不用担心在初始化安装后被替换掉。

从远程下载数据库

作为上面做法的候补项,你可以加载“骨感”数据库文件。只有少量数据以供app运行。再通过用户授权,从远程下载数据库,像这样:
buttonInstallRemote.addEventListener('click', function(){
  var fileWeatherDB = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory,'../databases/'+filename);
  var c = Ti.Network.createHTTPClient();
  c.setTimeout(10000);
  c.onload = function(e){
    fileWeatherDB.write(this.responseData);
    Ti.API.info("ONLOAD = "+e);
    Ti.UI.createAlertDialog({title:'Info', message:'Database installed', buttonNames: ['OK']}).show();
  };
  c.onerror = function(e){
    Ti.UI.createAlertDialog({title:'Error', message:'Error: ' + e.error, buttonNames: ['OK']}).show();
  };
  c.open('GET',"http://wiki.appcelerator.org/download/attachments/6261518/weatherDB");
  c.send();
});

存数据

要往数据库插入一条记录,调用execute()方法传递带SQL INSERT语句的参数,用问号作为替换符:
db.execute('INSERT INTO city (name,continent,temp_f,temp_c,condition_id) VALUES (?,?,?,?,?)',importName,importContinent,importTempF,importTempC,dbConditionId);
记住SQL的特性:一次只能加一条记录,一次只能加一个表。

读数据

给execute()方法传递SQL SELECT语句作为参数,得到数据集对象,一般要赋给变量,好访问数据。数据集对象是Titanium.Database.ResultSet。下面代码遍历每一行,用isValidRow()返回结束。fieldByName()通过列名返回值,也可以在SQL 语句中就指定好。使用next()方法把ResultSet行的指针移到下一行。
var cityWeatherRS = db.execute('SELECT id,name,continent FROM city');
while (cityWeatherRS.isValidRow())
{
  var cityId = cityWeatherRS.fieldByName('id');
  var cityName = cityWeatherRS.fieldByName('name');
  var cityContinent = cityWeatherRS.fieldByName('continent');
  Ti.API.info(cityId + ' ' + cityName + ' ' + cityContinent);
  cityWeatherRS.next();
}
cityWeatherRS.close();
像标准SQL一样,可以把数据连接起来,下面返回city记录,和weather 记录。
db.execute('SELECT city.id,name,cond.id,cond.summary,cond.icon FROM city LEFT JOIN condition cond WHERE city.condition_id = cond.id');
或者仅返回天气是"Partly Cloudy"的行:
var cityWeatherRS = db.execute('SELECT city.id AS city_id,name,cond.id AS cond_id,cond.summary,cond.icon FROM city LEFT JOIN condition cond WHERE city.condition_id = cond.id WHERE cond.summary=?', "Partly Cloudy");
注意city.id和cond.id使用了别名。因为如果你写着这样: fieldByName(id). 仅仅传进名字的参数, fieldByName() 方法不可能区分它们。数据集代码如下:
while (cityWeatherRS.isValidRow())
{
  var cityId = cityWeatherRS.fieldByName('city_id');
  var cityName = cityWeatherRS.fieldByName('name');
  var cityConditionId = cityWeatherRS.fieldByName('cond_id');
  var cityConditionSummary = cityWeatherRS.fieldByName('summary');
  var cityConditionIcon = cityWeatherRS.fieldByName('icon');
  Ti.API.info(cityId + ' ' + cityName + ' ' + cityConditionId + ' ' + cityConditionSummary + ' ' + cityConditionIcon);
  cityWeatherRS.next();
}
cityWeatherRS.close();

更新数据

和插入数据类似,和SQL 稍有不同
db.execute('UPDATE condition SET icon=? WHERE id=?',importIcon,dbConditionId);

关闭数据库和数据集

前面讲过,SQLite 一次只能有一个进程写操作。所以当你完成了任何的INSERT 或 UPDATE 操作,关闭数据库极其重要。否则你会在下次写入的时候收到"DatabaseObjectNotClosed" 的错误。
db.close();
开启数据集不是一个大问题,但最好养成习惯关闭它以释放系统资源。
cityWeatherRS.close();
(完)




你可能感兴趣的:(sql,数据库,sqlite,Integer,存储,resources)