android本地数据存储
先决条件
要跟随本文,您需要以下技能和工具:
- Java™技术的基本知识以及如何使用Eclipse(或您喜欢的IDE)
- Java开发套件(需要版本5或6)
- Eclipse(版本3.4或3.5)
- Android SDK和ADT插件
有关下载和设置信息,请参阅相关主题在这篇文章的末尾。
样例应用
为了强调Android应用程序开发的本地存储方面,我介绍了一个示例应用程序,该应用程序允许您测试各种类型的API的执行情况。 可以下载源代码 。 该应用程序支持图1中的操作。
图1.用例
![示例应用程序中的用例图](https://i-blog.csdnimg.cn/blog_migrate/58f195cd561161b1f523468e128b1682.png)
图1列出了以下用例:
- 管理和存储首选项
- 从应用程序资产加载信息
- 将信息导出到内部存储器,外部存储器和本地数据库
- 从内部存储器和本地数据库读取信息
- 清除存储的信息
- 在屏幕上查看信息
贯穿本文,您将在应用程序中使用本地存储,如下所示:
- 首选项从用户处捕获,存储在本地,并在整个应用程序中使用。
- 从内部应用程序资产中检索用户的图片,将其存储在本地内部存储器和外部存储器中,并在屏幕上呈现。
- 从应用程序的资产中检索JSON中的朋友列表。 它被解析并存储在本地内部存储器,外部存储器和关系数据库中,并呈现在屏幕上。
该示例应用程序定义了表1中的类。
表1.示例应用程序类
类 | 描述 |
---|---|
MainActivity | 主要活动; 大多数示例代码所在的位置 |
Friend | 代表朋友 |
AppPreferenceActivity | 首选项活动和屏幕 |
DBHelper | 用于管理SQLite数据库的帮助程序类 |
示例应用程序使用两种类型的数据。 第一个是存储为名称-值对的应用程序首选项。 对于首选项,定义以下信息:
- 一个文件名 ,用于加载和存储朋友的名字列表
- 文件名 ,用于为用户加载和存储图片
- 标志 (如果已设置)指示在应用程序启动时自动删除所有存储的数据
第二种数据是朋友列表。 朋友列表最初以Facebook Graph API JSON格式表示,该格式由名称和朋友对象的数组组成(请参见清单1 )。
清单1.朋友列表(Facebook Graph API JSON格式)
{
"data": [
{
"name": "Edmund Troche",
"id": "500067699"
}
]
}
上面的简单格式使Friend
对象和数据库架构变得简单。 清单2显示了Friend
类。
清单2. Friend
类
package com.cenriqueortiz.tutorials.datastore;
import android.graphics.Bitmap;
/**
* Represents a Friend
*/
public class Friend {
public String id;
public String name;
public byte[] picture;
public Bitmap pictureBitmap;;
}
除了ID和名称外,示例应用程序还保留对朋友图片的引用。 当示例应用程序不使用那些应用程序时,您可以轻松地扩展示例应用程序以从Facebook检索图片并将其显示在主屏幕中。
数据库模式由一个用于存储好友信息的表组成。 它包含三列:
- 唯一的ID或密钥
- Facebook ID
- 朋友的名字
清单3显示了对应关系表声明SQL语句。
清单3. Friend数据库表
db.execSQL("create table " + TABLE_NAME + " (_id integer primary key autoincrement, "
+ " fid text not null, name text not null) ");
根据此信息,您可以在主屏幕上显示名称,并使用ID可以检索所选用户的其他详细信息。 在示例应用程序中,您仅显示名称。 您可以尝试获取其他信息。 请注意,您可以轻松更改代码以直接转到Facebook。
存储应用程序首选项
本节介绍了“偏好设置” API和屏幕。 Android API提供了多种处理首选项的方法。 一种方法是直接使用SharedPreferences
并使用您自己的屏幕设计和首选项管理。 第二种方法是使用PreferenceActivity
。 PreferenceActivity
自动处理首选项在屏幕上的呈现方式(默认情况下,它们看起来像系统的首选项),并在用户通过使用SharedPreferences
与每个首选项交互时自动存储或保存首选项。
为了简化示例应用程序,请使用PreferenceActivity
管理首选项和首选项屏幕(请参见图2 )。 首选项屏幕显示两个部分:资产和自动设置。 在“资产”下,您可以为“好友列表”和“图片”选项输入文件名。 在“自动设置”下,您可以选中一个复选框以在启动时删除信息。
图2.实现的Preferences屏幕
在图2中 ,布局是通过XML使用声明性方法(而不是通过编程)定义的。 声明性XML是首选,因为它可以使源代码保持干净和可读性。 清单4显示了Preferences UI的XML声明。
清单4. Preferences屏幕的XML声明
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/prefs_screen"
android:key="preferencescreen"
>
<PreferenceCategory android:title="Assets">
<EditTextPreference
android:key="@string/prefs_assetname_friendslist_key"
android:title="Friends List"
android:summary="Please enter filename"
android:defaultValue="friends.txt"
/>
<EditTextPreference
android:key="@string/prefs_assetname_picture_key"
android:title="Picture"
android:summary="Please enter filename"
android:defaultValue="pict2.jpg"
/>
</PreferenceCategory>
<PreferenceCategory android:title="Auto Settings">
<CheckBoxPreference
android:key="@string/prefs_autodelete_key"
android:title="Delete at Startup"
android:summary="Check to clear at startup"
android:defaultValue="false"
/>
</PreferenceCategory>
</PreferenceScreen>
PreferenceScreen
由两个EditTextPreference
实例,一个CheckBoxPreference
以及两个由PreferenceCategory
定义的类别组组成(一个用于Asset
,另一个用于Auto Settings
)。
在示例应用程序中,设计要求使用菜单项调用“首选项”屏幕。 为此,使用Intent消息调用称为AppPreferenceActivity
的“首选项屏幕活动”(请参见清单5 )。 请注意,我没有详细介绍Intent的工作原理。 请参阅相关主题关于意图的更多信息。
清单5. AppPreferenceActivity
/*
* AppPreferenceActivity is a basic PreferenceActivity
* C. Enrique Ortiz | http://CEnriqueOrtiz.com
*/
package com.cenriqueortiz.tutorials.datastore;
import android.os.Bundle;
import android.preference.PreferenceActivity;
public class AppPreferenceActivity extends PreferenceActivity {
/**
* Default Constructor
*/
public AppPreferenceActivity() {}
/**
* Called when the activity is first created.
* Inflate the Preferences Screen XML declaration.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.prefs); // Inflate the XML declaration
}
}
在示例应用程序中,从Menu项处理程序中调用清单6中的Intent。
清单6.使用一个Intent调用Preference活动
/**
* Invoked when a menu item has been selected
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
// Case: Bring up the Preferences Screen
case R.id.menu_prefs: // Preferences
// Launch the Preference Activity
Intent i = new Intent(this, AppPreferenceActivity.class);
startActivity(i);
break;
case R.id.menu...:
:
break;
}
return true;
}
另外,您必须如清单7所示在AndroidManifest XML文件中定义所有Intent。
清单7.在AndroidManifest.xml中定义意图
:
<application android:icon="@drawable/icon" android:label="@string/app_name">
:
:
<activity
android:name="AppPreferenceActivity"
android:label="Preferences">
</activity>
:
</application>
回想一下,在用户与首选项屏幕交互时, PreferenceActivity
使用SharedPreferences
自动存储首选项。 然后,应用程序在执行以执行其各种任务时会使用这些首选项。 清单8显示了如何直接使用SharedPreferences
来加载存储的首选项。 您可以参考随附的示例代码,了解如何在整个示例代码中使用加载的首选项。 另外, 清单8还显示了如何使用Editor
直接使用SharedPreferences
存储首选项,以防您希望自己管理首选项(而不是通过PrefenceActivity
)。
清单8显示了如何使用SharedPreferences
来加载存储的首选项,以及如何使用Editor
来更改存储的首选项。
清单8.使用SharedPreferences
/
// The following methods show how to use the SharedPreferences
/
/**
* Retrieves the Auto delete preference
* @return the value of auto delete
*/
public boolean prefsGetAutoDelete() {
boolean v = false;
SharedPreferences sprefs =
PreferenceManager.getDefaultSharedPreferences(appContext);
String key = appContext.getString(R.string.prefs_autodelete_key);
try {
v = sprefs.getBoolean(key, false);
} catch (ClassCastException e) {
}
return v;
}
/**
* Sets the auto delete preference
* @param v the value to set
*/
public void prefsSetAutoDelete(boolean v) {
SharedPreferences sprefs =
PreferenceManager.getDefaultSharedPreferences(appContext);
Editor e = sprefs.edit();
String key = appContext.getString(R.string.prefs_autodelete_key);
e.putBoolean(key, v);
e.commit();
}
接下来,您将看到如何使用数据库存储数据。
使用SQLite数据库
Android通过SQLite为本地关系数据库提供支持。 该表(在以下清单中定义)总结了该示例应用程序中使用的重要数据库类。
对于示例应用程序,使用DBHelper
类来封装一些数据库操作(请参见清单9 )。
清单9. DBHelper
package com.cenriqueortiz.tutorials.datastore;
import java.util.ArrayList;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DBHelper extends SQLiteOpenHelper {
为数据库版本,数据库名称和表名称定义了许多常量(请参见清单10 )。
清单10.初始化DBHelper
private SQLiteDatabase db;
private static final int DATABASE_VERSION = 1;
private static final String DB_NAME = "sample.db";
private static final String TABLE_NAME = "friends";
/**
* Constructor
* @param context the application context
*/
public DBHelper(Context context) {
super(context, DB_NAME, null, DATABASE_VERSION);
db = getWritableDatabase();
}
准备创建数据库时,将调用onCreate()
方法。 在这种方法中,创建了表(请参见清单11 )。
清单11.创建数据库表
/**
* Called at the time to create the DB.
* The create DB statement
* @param the SQLite DB
*/
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(
"create table " + TABLE_NAME + " (_id integer primary key autoincrement,
" + " fid text not null, name text not null) ");
}
将信息导出到数据库时, MainActivity
将调用insert()
方法(请参见清单12 )。
清单12.插入一行
/**
* The Insert DB statement
* @param id the friends id to insert
* @param name the friend's name to insert
*/
public void insert(String id, String name) {
db.execSQL("INSERT INTO friends('fid', 'name') values ('"
+ id + "', '"
+ name + "')");
}
清除数据库时, MainActivity
将调用deleteAll()
方法。 它将删除表(请参见清单13 )。
清单13.删除数据库表
/**
* Wipe out the DB
*/
public void clearAll() {
db.delete(TABLE_NAME, null, null);
}
两个SELECT ALL
提供了方法: cursorSelectAll()
它返回一个光标, listSelectAll()
它返回一个ArrayList
的Friend
的对象。 从数据库加载信息时, MainActivity
将调用这些方法(请参见清单14 )。
清单14.运行Select All
,返回一个ArrayList
/**
* Select All returns a cursor
* @return the cursor for the DB selection
*/
public Cursor cursorSelectAll() {
Cursor cursor = this.db.query(
TABLE_NAME, // Table Name
new String[] { "fid", "name" }, // Columns to return
null, // SQL WHERE
null, // Selection Args
null, // SQL GROUP BY
null, // SQL HAVING
"name"); // SQL ORDER BY
return cursor;
}
listSelectAll()
方法在ArrayList
容器内返回选定的行, MainActivity
使用该行将其绑定到MainScreen ListView
(请参见清单15 )。
清单15.运行Select All
,返回一个游标
/**
* Select All that returns an ArrayList
* @return the ArrayList for the DB selection
*/
public ArrayList<Friend> listSelectAll() {
ArrayList<Friend> list = new ArrayList<Friend>();
Cursor cursor = this.db.query(TABLE_NAME, new String[] { "fid", "name" },
null, null, null, null, "name");
if (cursor.moveToFirst()) {
do {
Friend f = new Friend();
f.id = cursor.getString(0);
f.name = cursor.getString(1);
list.add(f);
} while (cursor.moveToNext());
}
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
return list;
}
如果检测到数据库版本更改,那么将调用onUpgrade()
方法(请参见清单16 )。
清单16.检测数据库版本是否更改
/**
* Invoked if a DB upgrade (version change) has been detected
*/
@Override
/**
* Invoked if a DB upgrade (version change) has been detected
*/
@Override
public void onUpgrade(SQLiteDatabase db,
int oldVersion, int newVersion) {
// Here add any steps needed due to version upgrade
// for example, data format conversions, old tables
// no longer needed, etc
}
}
在整个MainActivity
,当您将信息导出到数据库,从数据库中加载信息以及清除数据库时, DBHelper
使用DBHelper
。 首先是在创建MainActivity
时实例化DBHelper
。 在onCreate()
执行的其他任务包括初始化不同的屏幕视图(请参见清单17 )。
清单17.初始化数据库的MainActivity onCreate()
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
appContext = this;
setContentView(R.layout.main);
dbHelper = new DBHelper(this);
listView = (ListView) findViewById(R.id.friendsview);
friendsArrayAdapter = new FriendsArrayAdapter(
this, R.layout.rowlayout, friends);
listView.setAdapter(friendsArrayAdapter);
:
:
}
清单18显示了如何从资产中加载好友列表,以及如何解析并将其插入数据库。
清单18. MainActivity
插入数据库
String fname = prefsGetFilename();
if (fname != null && fname.length() > 0) {
buffer = getAsset(fname);
// Parse the JSON file
String friendslist = new String(buffer);
final JSONObject json = new JSONObject(friendslist);
JSONArray d = json.getJSONArray("data");
int l = d.length();
for (int i2=0; i2<l; i2++) {
JSONObject o = d.getJSONObject(i2);
String n = o.getString("name");
String id = o.getString("id");
dbHelper.insert(id, n);
}
// Only the original owner thread can touch its views
MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
friendsArrayAdapter.notifyDataSetChanged();
}
});
}
清单19显示了如何执行SELECT ALL
以及如何将数据绑定到主屏幕ListView
。
清单19. MainActivity
Select All
并将数据绑定到ListView
final ArrayList<Friend> dbFriends = dbHelper.listSelectAll();
if (dbFriends != null) {
// Only the original owner thread can touch its views
MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
friendsArrayAdapter =
new FriendsArrayAdapter(
MainActivity.this, R.layout.rowlayout, dbFriends);
listView.setAdapter(friendsArrayAdapter);
friendsArrayAdapter.notifyDataSetChanged();
}
});
}
接下来,看看将内部存储API与示例应用程序一起使用。
使用设备的内部存储空间获取私有数据
使用数据存储API,您可以使用内部存储器存储数据。 该信息可以是私有的,您可以选择让其他应用程序对其进行读取或写入访问。 本部分介绍使用android.content.Context.openFileInput
, openFileOutput
和getCacheDir()
来缓存数据而不是持久存储数据的API。
清单20中显示的代码片段显示了如何从内部私有存储中进行读取。 使存储成为私有的原因是将openFileOutput()
与MODE_PRIVATE
一起MODE_PRIVATE
。
清单20.从本地私人商店中读取
/**
* Writes content to internal storage making the content private to
* the application. The method can be easily changed to take the MODE
* as argument and let the caller dictate the visibility:
* MODE_PRIVATE, MODE_WORLD_WRITEABLE, MODE_WORLD_READABLE, etc.
*
* @param filename - the name of the file to create
* @param content - the content to write
*/
public void writeInternalStoragePrivate(
String filename, byte[] content) {
try {
//MODE_PRIVATE creates/replaces a file and makes
// it private to your application. Other modes:
// MODE_WORLD_WRITEABLE
// MODE_WORLD_READABLE
// MODE_APPEND
FileOutputStream fos =
openFileOutput(filename, Context.MODE_PRIVATE);
fos.write(content);
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
清单21中的代码片段显示了如何从内部私有存储中读取数据; 查看openFileInput()
的用法。
清单21.从本地私人商店读取
/**
* Reads a file from internal storage
* @param filename the file to read from
* @return the file content
*/
public byte[] readInternalStoragePrivate(String filename) {
int len = 1024;
byte[] buffer = new byte[len];
try {
FileInputStream fis = openFileInput(filename);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int nrb = fis.read(buffer, 0, len); // read up to len bytes
while (nrb != -1) {
baos.write(buffer, 0, nrb);
nrb = fis.read(buffer, 0, len);
}
buffer = baos.toByteArray();
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return buffer;
}
清单22显示了如何从内部私有存储中删除。
清单22.从本地私人商店中删除
/**
* Delete internal private file
* @param filename - the filename to delete
*/
public void deleteInternalStoragePrivate(String filename) {
File file = getFileStreamPath(filename);
if (file != null) {
file.delete();
}
}
现在您可以看到如何使用外部存储来存储公共数据。
使用设备的外部存储来获取公共数据
使用数据存储API,您可以使用外部存储设备存储数据。 该信息可以是私有的,您可以选择让其他应用程序对其进行读取或写入访问。 在本部分中,您将对API进行编码,以使用许多API(包括getExternalStorageState()
, getExternalFilesDir()
, getExternalStorageDirectory()
和getExternalStoragePublicDirectory()
来存储公共数据。 您将以下路径用于公共数据: /Android/data/<package_name>/files/
。
在使用外部存储器之前,必须先查看它是否可用以及是否可写。 以下两个代码段显示了用于测试这种情况的辅助方法。 清单23测试了外部存储是否可用。
清单23.测试外部存储是否可用
/**
* Helper Method to Test if external Storage is Available
*/
public boolean isExternalStorageAvailable() {
boolean state = false;
String extStorageState = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(extStorageState)) {
state = true;
}
return state;
}
清单24测试了外部存储是否为只读。
清单24.测试外部存储是否为只读
/**
* Helper Method to Test if external Storage is read only
*/
public boolean isExternalStorageReadOnly() {
boolean state = false;
String extStorageState = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(extStorageState)) {
state = true;
}
return state;
}
清单25显示了如何写入外部存储以存储公共数据。
清单25.写入外部存储器
/**
* Write to external public directory
* @param filename - the filename to write to
* @param content - the content to write
*/
public void writeToExternalStoragePublic(String filename, byte[] content) {
// API Level 7 or lower, use getExternalStorageDirectory()
// to open a File that represents the root of the external
// storage, but writing to root is not recommended, and instead
// application should write to application-specific directory, as shown below.
String packageName = this.getPackageName();
String path = "/Android/data/" + packageName + "/files/";
if (isExternalStorageAvailable() &&
!isExternalStorageReadOnly()) {
try {
File file = new File(path, filename);
file.mkdirs();
FileOutputStream fos = new FileOutputStream(file);
fos.write(content);
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
清单26显示了如何从外部存储读取。
清单26.从外部存储器读取
/**
* Reads a file from internal storage
* @param filename - the filename to read from
* @return the file contents
*/
public byte[] readExternallStoragePublic(String filename) {
int len = 1024;
byte[] buffer = new byte[len];
String packageName = this.getPackageName();
String path = "/Android/data/" + packageName + "/files/";
if (!isExternalStorageReadOnly()) {
try {
File file = new File(path, filename);
FileInputStream fis = new FileInputStream(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int nrb = fis.read(buffer, 0, len); //read up to len bytes
while (nrb != -1) {
baos.write(buffer, 0, nrb);
nrb = fis.read(buffer, 0, len);
}
buffer = baos.toByteArray();
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return buffer;
}
清单27的片段显示了如何从外部存储器中删除文件。
清单27.从外部存储器中删除文件
/**
* Delete external public file
* @param filename - the filename to write to
*/
void deleteExternalStoragePublicFile(String filename) {
String packageName = this.getPackageName();
String path = "/Android/data/" + packageName + "/files/"+filename;
File file = new File(path, filename);
if (file != null) {
file.delete();
}
}
使用外部存储需要特殊权限WRITE_EXTERNAL_STORAGE
,可以通过AndroidManifest.xml进行请求(请参见清单28 )。
清单28. WRITE_EXTERNAL_STORAGE
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
外部存储API允许您通过将文件存储在基于图片,铃声等类型的预定义目录中来公开存储文件。 本文未介绍此方法,但是您应该熟悉它。 此外,请记住,外部存储中的文件可以随时消失。
相关方法
如果您有不需要长期持久性的临时文件,则可以将这些文件存储在缓存中。 高速缓存是一种特殊的内存,可用于存储中型或小型数据(小于1兆字节),但是您必须注意,取决于可用的内存量,可以随时清除高速缓存的内容。
清单29显示了一个帮助程序方法,该方法返回内部存储器中缓存的路径。
清单29.检索内部存储器缓存的路径
/**
* Helper method to retrieve the absolute path to the application
* specific internal cache directory on the file system. These files
* will be ones that get deleted when the application is uninstalled or when
* the device runs low on storage. There is no guarantee when these
* files will be deleted.
*
* Note: This uses a Level 8+ API.
*
* @return the absolute path to the application specific cache
* directory
*/
public String getInternalCacheDirectory() {
String cacheDirPath = null;
File cacheDir = getCacheDir();
if (cacheDir != null) {
cacheDirPath = cacheDir.getPath();
}
return cacheDirPath;
}
清单30显示了一个辅助方法,该方法返回到外部存储器中的缓存的路径。
清单30.检索外部存储器缓存的路径
/**
* Helper method to retrieve the absolute path to the application
* specific external cache directory on the file system. These files
* will be ones that get deleted when the application is uninstalled or when
* the device runs low on storage. There is no guarantee when these
* files will be deleted.
*
* Note: This uses a Level 8+ API.
*
* @return the absolute path to the application specific cache
* directory
*/
public String getExternalCacheDirectory() {
String extCacheDirPath = null;
File cacheDir = getExternalCacheDir();
if (cacheDir != null) {
extCacheDirPath = cacheDir.getPath();
}
return extCacheDirPath;
}
通过使用示例应用程序,您现在应该对如何使用设备的外部存储来获取公共数据有很好的了解。
结论
本文介绍了Android存储API,从偏好设置到使用SQLite以及内部和外部内存。 使用首选项API,您可以让您的应用程序收集和存储简单的首选项信息。 使用SQLite API,您可以存储更复杂的数据,并通过内部和外部存储,可以存储对应用程序私有或对其他应用程序公开可用的文件。 跨会话持久存储的存储数据使您的应用程序即使在与网络断开连接的情况下也能正常工作。 现在,当您开发Android应用程序时,您应该具备可以利用所有这些类型存储的专业知识。
翻译自: https://www.ibm.com/developerworks/xml/library/x-androidstorage/index.html
android本地数据存储