Android中5种数据存储方式
1 概述
SharedPreferences存储数据。
ContentProvider存储
文件存储
SQLlite存储
网络存储 PreferenceFile DataBase这三种方式分别对应的目录是:
/data/data/Package Name/Shared_Pref
/data/data/Package Name/files
/data/data/Package Name/database
关于这五种数据存储方式根据实际情况选择最合适的秉持最简单原则也就是说能用简单的
方式处理就不要用复杂的方式。比如存储几个数据或简单对象用SharedPreference也能做到
就没必要写个ContentProvider。
简单数据和配置信息SharedPreference是首选
如果SharedPreferences不够用那么就创建一个数据库
结构化数据一定要创建数据库虽然这稍显烦锁但是好处无穷
文件就是用来存储文件(也即非配置信息或结构化数据)如文本文件二进制文件PC文件
多媒体文件下载的文件等等
尽量不要创建文件
如果创建文件如果是私密文件或是重要文件就存储在内部存储否则放到外部存储。 2 SharedPreferences存储数据 SharedPreferences 可以将数据保存到应用程序的私有存储区这些存储区中的数据只能被写入
这些数据的软件读取。
它的本质是基于XML文件存储key-value键值对数据通常用来存储一些简单的配置信息。
其存储位置在 /data/data/<包名>/shared_prefs 目录下。
使用SharedPreferences是有些限制的只能在同一个包内使用不能在不同的包之间使用。
例如登录用户的用户名与密码。
步骤如下
1 使用Activity类的getSharedPreference 方法获得SharePreferences对象。其中存储
key-value 的文件名称由getSharedPreferences方法的第一个参数指定第二个参数表示所创建的
数据文件的访问权限“MODE_WORLD_READABLE”表示其他用户有“读”的权限
“MODE_WORLD_WRITEABLE ” 表示其他用户有 “写”权限MODE_PRIVATE 和
MODE_APPEND创建的文件对其他用户都是不可访问的
2使用SharedPreferences 接口的edit 获得SharedPreferences.Editor对象
3 通过Sharedreferences.Editor接口的putXxx方法保存key-value对。其中Xxx表示value
不同数据类型。例如Boolean类型的value需要用putBoolean方法字符串类型的value需要用
putString方法
4通过Sharedreferences.Editor接口的commit方法保存key-value对。commit方法相当
于数据库事务中的提交(commit)操作只有在事务结束后进行提交才会将数据真正保存在数据库中。
保存key-value也是一样在使用putXxx方法指定了key-value对后必须调用commit方法才能
将key-value对真正保存在相应的文件中。
运行该程序并在相应的组建输入值然后退出应用程序再次进入程序系统会将上次输入的
数据显示在相应组件中。
*由于应用程序在退出时会将组件的值保存在文件中因此需要将保存的Key-value对的代码写在
Activity类的onStop方法中。
private final String PREFERENCES_NAME = "bjh";
public void onStop() {
// 第1步获得SharedPreferences对象
SharedPreferences mySharedPreferences = getSharedPreferences(
PREFERENCES_NAME, Activity.MODE_PRIVATE);
// (第2步) 获得SharedPreferences.Editor对象
SharedPreferences.Editor editor = mySharedPreferences.edit();
// (第3步) 保存组件中的值
editor.putString("name", myName.getText().toString());
editor.putString("habit", myHabit.getText().toString());
editor.putBoolean("employee", myEmployee.isChecked());
editor.putInt("companyTypeId", myCompanyTypeId.getCheckedRadioButtonId());
editor.commit();
super.onStop();
}
从bjh.xml中获得数据
SharedPreferences sharedPreferences = getSharedPreferences(
PREFERENCES_NAME, Activity.MODE_PRIVATE);
// 使用getXxx方法获得value,getXxx方法的第2个参数是value的默认值
myName.setText(sharedPreferences.getString("name", ""));
myHabit.setText(sharedPreferences.getString("habit", ""));
myEmployee.setChecked(sharedPreferences.getBoolean("employee", false));
myCompanyTypeId.check(sharedPreferences.getInt("companyTypeId", -1)); 3 ContentProvider存储 3.1 用途 ContentProvider可以解决两个程序之间数据进行交换问题
ContentProvider提供了一种多应用间数据共享的方式比如联系人信息可以被多个应用
程序访问
ContentProvider是个实现了一组用于提供其他应用程序存取数据的标准方法的类。 应用程
序可以在ContentProvider中执行如下操作: 查询、修改、添加、删除
标准的ContentProvider: Android提供了一些已经在系统中实现的标准ContentProvider
比如联系人信息图片库等等你可以用这些ContentProvider来访问设备上存储的联系人
信息图片等等。 3.2 好处 当应用继承ContentProvider类并重写该类用于提供数据和存储数据的方法就可以向其他应
用共享其数据。虽然使用其他方法也可以对外共享数据但数据访问方式会因数据存储的方式而不同
如采用文件方式对外共享数据需要进行文件操作读写数据采用sharedpreferences共享数据
需要使用sharedpreferences API读写数据。而使用ContentProvider共享数据的好处是统一了数据
访问方式。
Android 这个系统和其他的操作系统还不太一样我们需要记住的是数据在Android当中是私
有的当然这些数据包括文件数据和数据库数据以及一些其他类型的数据。要想解决两个程序之间数
据交换问题主要靠ContentProvider。一个ContentProvider类实现了一组标准的方法接口从而能
够让其他的应用保存或读取此ContentProvider的各种数据类型。也就是说一个程序可以通过实现
一个ContentProvider的抽象接口将自己的数据暴露出去。外界根本看不到也不用看到这个应用暴
露的数据在应用当中是如何存储的或者是用数据库存储还是用文件存储还是通过网上获得这些
一切都不重要重要的是外界可以通过这一套标准及统一的接口和程序里的数据打交道可以读取程序的数据也可以删除程序的数据当然中间也会涉及一些权限的问题。一个程序可以通过实现
ContentProvider的抽象接口将自己的数据完全暴露出去而且ContentProviders是以类似数据库
中表的方式将数据暴露也就是说ContentProvider就像一个“数据库”。那么外界获取其提供的数
据也就应该与从数据库中获取数据的操作基本一样只不过是采用 URI来表示外界需要访问的“数
据库”。
ContentResolver通过ContentProvider来获取其他与应用程序共享的数据。其中
ContentProvider 负责
组织应用程序的数据
向其他应用程序提供数据
ContentResolver则负责
获取ContentProvider提供的数据
修改/添加/删除更新数据等 3.3 例如 3.3.1 依次读取联系人信息表中的指定数据列name和number
public class ContentProviderDemo extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
displayRecords();
}
private void displayRecords() {
//该数组中包含了所有要返回的字段
String columns[] = new String[] { People.NAME, People.NUMBER };
Uri mContacts = People.CONTENT_URI;
Cursor cur = managedQuery(
mContacts,
columns, // 要返回的数据字段
null, // WHERE子句 null, // WHERE 子句的参数
null // Order-by子句
);
if (cur.moveToFirst()) {
String name = null;
String phoneNo = null;
do {
// 获取字段的值
name = cur.getString(cur.getColumnIndex(People.NAME));
phoneNo = cur.getString(cur.getColumnIndex(People.NUMBER));
Toast.makeText(this, name + " " + phoneNo,
Toast.LENGTH_LONG).show();
} while (cur.moveToNext());
}
}
}
3.3.2 使用ContentResolver.update()方法来修改数据
使用下面的方法可以更新指定记录 updateRecord(10, ”XYZ”); //更改第10条记录的name
字段值为“XYZ”
private void updateRecord(int recNo, String name) {
Uri uri = ContentUris.withAppendedId(
People.CONTENT_URI, recNo);
ContentValues values = new ContentValues();
values.put(People.NAME, name);
getContentResolver().update(uri, values, null, null);
}
3.3.3 添加记录
private void insertRecords(String name, String phoneNo) {
ContentValues values = new ContentValues();
values.put(People.NAME, name); Uri uri = getContentResolver().insert(
People.CONTENT_URI, values);
Log.d(”ANDROID”, uri.toString());
Uri numberUri = Uri.withAppendedPath(uri,
People.Phones.CONTENT_DIRECTORY);
values.clear();
values.put(Contacts.Phones.TYPE, People.Phones.TYPE_MOBILE);
values.put(People.NUMBER, phoneNo);
getContentResolver().insert(numberUri, values);
}
3.3.4 删除记录
private void deleteRecords() {
Uri uri = People.CONTENT_URI;
getContentResolver().delete(uri, null, null);
}
4 文件存储 默认位置/data/data/<包>/files/***.***。
openFileOutput 和 openFileInput 方法于 SharedPreferences 在某些方面非常类似。 4.1 使用 getSharedPreference 方法获得 SharePreferences 对象
SharedPreferences mySharedPreferences
= getSharedPreferences(PREFERENCES_NAME,Activity.MODE_PRIVATE);
4.2 使用openFileOutput 方法返回一个OutputStream对象 OutputStream os = openFileOutput(“file.txt”,Activity.MODE_PRIVATE);
4.3 使用openFileInput方法获得 InputStream对象 InputStream is=openFileInput(“file.txt”);
openFileOutput() 方法的第一参数用于指定文件名称不能包含路径分隔符“/” 如果文件不
存在Android 会自动创建它。
创建的文件保存在 /data/data/<package name>/files目录如
/data/data/cn.itcast.action/files/itcast.txt 通过点击Eclipse菜单“Window”-“Show View”-“Other”在对话窗口中展开android文件夹选择下面的File Explorer视图然后在File
Explorer视图中展开/data/data/<package name>/files目录就可以看到该文件。
openFileOutput()方法的第二参数用于指定操作模式有四种模式分别为
Context.MODE_PRIVATE = 0
Context.MODE_APPEND = 32768
Context.MODE_WORLD_READABLE = 1
Context.MODE_WORLD_WRITEABLE = 2 Context.MODE_PRIVATE为默认操作模式代表该文件是私有数据只能被应用本身访问
在该模式下写入的内容会覆盖原文件的内容如果想把新写入的内容追加到原文件中。可以使用
Context.MODE_APPEND Context.MODE_APPEND模式会检查文件是否存在存在就往文件追加内容否则就创建
新文件。 Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE用
来控制其他应用是否有权限读写该文件。 MODE_WORLD_READABLE表示当前文件可以被其他应用读取 MODE_WORLD_WRITEABLE表示当前文件可以被其他应用写入。
如果希望文件被其他应用读和写可以传入 openFileOutput(“itcast.txt”,
Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE); android有一套自己的安全模型当应用程序(.apk)在安装时系统就会分配给他一个userid当
该应用要去访问其他资源比如文件的时候就需要userid匹配。默认情况下任何应用创建的文件
sharedpreferences数据库都应该是私有的位于/data/data /<package name>/files其他程
序无法访问。
除非在创建时指定了Context.MODE_WORLD_READABLE或者
Context.MODE_WORLD_WRITEABLE 只有这样其他程序才能正确访问。
Activity 还提供了getCacheDir() 和 getFilesDir() 方法
getCacheDir()方法用于获取/data/data/<package name>/cache目录
getFilesDir()方法用于获取/data/data/<package name>/files目录 4.4 向文件写入内容
FileOutputStream outStream = this.openFileOutput(
"a.txt", Context.MODE_WORLD_READABLE);
outStream.write(text.getText().toString().getBytes());
outStream.close();
4.5 读取文件中的数据
FileInputStream inStream = this.openFileInput("a.txt");
ByteArrayOutputStream stream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length = -1;
while ((length = inStream.read(buffer)) != -1) {
stream.write(buffer, 0, length);
}
stream.close();
inStream.close(); text.setText(stream.toString());
4.6 把文件存入SDCard *注意访问SDCard必须在AndroidManifest.xml中加入访问SDCard的权限。
使用Activity的openFileOutput() 方法保存文件文件是存放在手机空间上一般手机的存储
空间不是很大存放些小文件还行如果要存放像视频这样的大文件是不可行的。对于像视频这样
的大文件我们可以把它存放在SDCard。
在AndroidManifest.xml中加入访问SDCard的权限如下:
<!– 在SDCard中创建与删除文件权限 –>
<uses-permission android:name=”android.permission.MOUNT_UNMOUNT_FILESYSTEMS”/>
<!– 往SDCard写入数据权限 –>
<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE”/>
要往SDCard存放文件程序必须先判断手机是否装有SDCard并且可以进行读写。
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
// 获取SDCard目录
File sdCardDir = Environment.getExternalStorageDirectory();
File saveFile = new File(sdCardDir, "a.txt");
FileOutputStream outStream = new FileOutputStream(saveFile);
outStream.write("test".getBytes());
outStream.close();
} 5 SQLite数据库存储数据 SQLite是轻量级嵌入式数据库引擎它支持 SQL 语言 并且只利用很少的内存就有很好的性能。
数据库存储在 data/<项目文件夹>/databases/ 下。 5.1 派生自 SQLiteOpenHelper Android 提供了 SQLiteOpenHelper 帮助你创建一个数据库你只要继承
SQLiteOpenHelper 类就可以轻松的创建数据库。
SQLiteOpenHelper 的子类至少需要实现三个方法
构造函数调用父类 SQLiteOpenHelper 的构造函数。这个方法需要四个参数上下文环
境例如一个 Activity数据库名字一个可选的游标工厂通常是 Null一个代表
你正在使用的数据库模型版本的整数。
onCreate方法它需要一个 SQLiteDatabase 对象作为参数根据需要对这个对象填
充表和初始化数据。
onUpgrage() 方法它需要三个参数一个 SQLiteDatabase 对象一个旧的版本号和一
个新的版本号这样你就可以清楚如何把一个数据库从旧的模型转变到新的模型。
5.2 数据库操作 5.2.1 onOpen() 每次成功打开数据库后首先被执行。
创建表、插入数据、删除表等等。 调用 getReadableDatabase() 或 getWriteableDatabase()
方法你可以得到 SQLiteDatabase 实例具体调用那个方法取决于你是否需要改变数据库的内容 db = (new DatabaseHelper(getContext())).getWritableDatabase();
上面这段代码会返回一个 SQLiteDatabase 类的实例使用这个对象你就可以查询或者修改数
据库。 当你完成了对数据库的操作例如你的 Activity 已经关闭需要调用 SQLiteDatabase 的
Close() 方法来释放掉数据库连接。 创建表和索引 为了创建表和索引需要调用 SQLiteDatabase
的 execSQL() 方法来执行 DDL 语句。如果没有异常这个方法没有返回值。 5.2.2 执行SQL语句 创建表 db.execSQL("CREATE TABLE mytable " +
"(_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"title TEXT, value REAL);");
插入数据 db.execSQL("INSERT INTO widgets (name, inventory)"
+ "VALUES ('Sprocket', 5)");
另一种方法可以使用 SQLiteDatabase 对象的 insert(), update(), delete() 方法。这些方法把
SQL 语句的一部分作为参数。 5.2.3 insert()
ContentValues cv = new ContentValues();
cv.put(Constants.TITLE, "example title");
cv.put(Constants.VALUE, SensorManager.GRAVITY_DEATH_STAR_I);
db.insert("mytable", getNullColumnHack(), cv);
5.2.4 update update 方法有四个参数分别是表名列名和值的 ContentValues 对象可选的 WHERE
条件和可选的填充 WHERE 语句的字符串这些字符串会替换 WHERE 条件中的“”标记。
update() 根据条件更新指定列的值所以用 execSQL() 方法可以达到同样的目的。 WHERE
条件和其参数和用过的其他 SQL APIs 类似。
String[] parms = new String[] { "this is a string" };
db.update("widgets", replacements, "name=?", parms);
5.2.5 delete() delete() 方法的使用和 update() 类似使用表名可选的 WHERE 条件和相应的填充 WHERE
条件的字符串。 查询数据库 类似 INSERT, UPDATE, DELETE。 5.2.6 检索数据 有两种方法使用 SELECT 从 SQLite 数据库检索数据。
5.2.6.1 rawQuery()
使用 rawQuery() 直接调用 SELECT 语句 使用 query() 方法构建一个查询。
Raw Queries 正如 API 名字rawQuery() 是最简单的解决方法。通过这个方法你就可以调用
SQL SELECT 语句。
Cursor c = db.rawQuery("SELECT name " +
"FROM sqlite_master " + "WHERE type=’table’ " +
"AND name='mytable'", null);
在上面例子中我们查询 SQLite 系统表sqlite_master检查 table 表是否存在。返回值是一个 cursor 对象这个对象的方法可以迭代查询结果。 如果查询是动态的使用这个方法就会非常
复杂。
例如当你需要查询的列在程序编译的时候不能确定这时候使用 query() 方法会方便很多。
5.2.6.2 Regular Queries query()
Regular Queries query() 方法用 SELECT 语句段构建查询。SELECT 语句内容作为 query() 方
法的参数 比如要查询的表名要获取的字段名WHERE 条件包含可选的位置参数去替代
WHERE 条件中位置参数的值 GROUP BY 条件 HAVING 条件。 除了表名 其他参数可以是
null。所以以前的代码段可以可写成
String[] columns = { "ID", "inventory" };
String[] parms = { "snicklefritz" };
Cursor result = db.query("widgets", columns, "name=?", parms,
null, null, null);
5.2.6.3
使用游标
不管你如何执行查询都会返回一个 Cursor这是 Android 的 SQLite 数据库游标使用游标
你可以
通过使用 getCount() 方法得到结果集中有多少记录 通过 moveToFirst(), moveToNext(), 和 isAfterLast() 方法遍历所有记录
通过 getColumnNames() 得到字段名
通过 getColumnIndex() 转换成字段号
通过 getString()getInt() 等方法得到给定字段当前记录的值
通过 requery() 方法重新执行查询得到游标
通过 close() 方法释放游标资源
6 网络存储 6.1 访问网络 6.1.1 在配置文件中设置访问网络权限 <uses-permission android:name="android.permission.INTERNET" />
6.1.2 获取数据 可以通过调用WebService返回的数据或是解析HTTP协议实现网络数据交互。
以下展示了Android SDK 中一些与网络有关的package。
java.net 提供与联网有关的类包括流和数据包datagramsockets、Internet 协议和常见
HTTP 处理。该包是一个多功能网络资源。有经验的 Java 开发人员可以立即使用这个熟悉的包创建
应用程序。
java.io 虽然没有提供显式的联网功能但是仍然非常重要。该包中的类由其他 Java 包中提供的
socket 和连接使用。它们还用于与本地文件在与网络进行交互时会经常出现的交互。
java.nio 包含表示特定数据类型的缓冲区的类。适合用于两个基于 Java 语言的端点之间的通信。
org.apache.* 表示许多为 HTTP 通信提供精确控制和功能的包。 可以将 Apache 视为流行的
开源 Web 服务器。 android.net 除核心 java.net.* 类以外包含额外的网络访问 socket。该包包括 URI 类后者
频繁用于 Android 应用程序开发而不仅仅是传统的联网方面。
android.net.http 包含处理 SSL 证书的类。
android.net.wifi 包含在 Android 平台上管理有关 WiFi802.11 无线 Ethernet所有方面的
类。
android.telephony.gsm 包含用于管理和发送 SMS文本消息的类。一段时间后可能会引
入额外的包来来为非 GSM 网络提供类似的功能比如 CDMA 或 android.telephony.cdma 等网
络。
下面是一个通过地区名称查询该地区的天气预报以POST发送的方式发送请求到
webservicex.net站点访问WebService.webservicex.net站点上提供查询天气预报的服务。
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import android.app.Activity;
import android.os.Bundle;
public class MyAndroidWeatherActivity extends Activity {
//定义需要获取的内容来源地址
private static final String SERVER_URL =
"http://www.webservicex.net/WeatherForecast.asmx/GetWeatherByPlaceName";
/** Called when the activity is first created. */ @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
HttpPost request = new HttpPost(SERVER_URL); //根据内容来源地址创建一个Http请求
// 添加一个变量
List<NameValuePair> params = new ArrayList<NameValuePair>();
// 设置一个地区名称
params.add(new BasicNameValuePair("PlaceName", "NewYork")); //添加必须的参数
try {
//设置参数的编码
request.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));
//发送请求并获取反馈
HttpResponse httpResponse = new DefaultHttpClient().execute(request);
// 解析返回的内容
if(httpResponse.getStatusLine().getStatusCode() != 404){
String result = EntityUtils.toString(httpResponse.getEntity());
System.out.println(result);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}