Android -- (12) ,数据的存储

本文详细介绍了Android应用中数据的存储方法,包括存储和载入用户配置,内部储存器、SD卡及静态文件的使用,以及创建和操作数据库的步骤,如DBAdapter helper类的创建和数据的增删查改。

2013/1/31


一,数据的存储

数据的存储是很重要的,由于某些数据要在未来reuse。所以有必要将数据保存起来。
安卓里面有三种基本保存数据的办法:
(1)shared preferences机制,用来保存一小撮数据。
(2)传统的File System
(3)通过sqlite的支持用DB来存储数据

1.1,存储和载入用户的配置

一般来说,以上三种方法都可以用来存储用户配置,不过后两者明显有点大题小做了。我们可以简单的用SharedPreferences对象来存储一对对的name/value的值,这些值会自动的存到一个xml文件中去。

1.1.1 存储用户配置

首先我们要创建一个xml文件,里面是你想要存储的配置的类型等等等:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android">
    <PreferenceCategory android:title="Category 1">
        <CheckBoxPreference
            android:title="CheckBox"
            android:defaultValue="false"
            android:summary="True Or False"
            android:key="checkboxPref"
            />
    </PreferenceCategory>
    <PreferenceCategory android:title="Category 2">
        <EditTextPreference
            android:summary="ENter a text"
            android:defaultValue="[enter a string here]"
            android:title="Edit text"
            android:key="editTextPref"
            />
       	<RingtonePreference
       	    android:summary="select a ringtone"
       	    android:title="RingTones"
       	    android:key="ringtonepref"
       	    />
       	<PreferenceScreen
       	    android:title="Second Prefercence Screen"
       	    android:summary="Click here to go to second PreScreen"
       	    android:key="Second Pref Sceen"
       	    >
       	<EditTextPreference
       	    android:summary="enter a string"
       	    android:title="Edit text sp"
       	    android:key="secondEditTextPref"
       	    />
       	</PreferenceScreen>
    </PreferenceCategory>
</PreferenceScreen>

通过上面的xml文件,可以看出:
(1)有两个preference categories来为配置分组
(2)两个checkbox--checkboxpref和secondEdittextpref
(3)一个选择ringtone的选项,ringtonepref
(4)另外一个preferenceScreen包含另外一个配置eidtTExt

注意上面每个控件都有一个key的属性,这个属性是用来保存或取出对应的配置信息的关键字。会自动的存储用户选择的配置。

为了让os去展示出配置的页面,我们新建了一个活动,继承于PreferenceActivity:

public class AppPreferenceActivity extends PreferenceActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//---load the preferences from an XML file---
addPreferencesFromResource(R.xml.myapppreferences);
}
}

最后用之前讲过的intent方法来开始新活动:

Intent i = new Intent(“net.learn2develop.AppPreferenceActivity”);
startActivity(i);

注意也要在androidmainfest文件中加入这个活动的相关信息(之前的文章有讲)。这样就可以打开配置页面了。


OK,那么当你打开并且更改了一个配置之后,修改后的信息会自动保存在/data/data/net.learn2develop.UsingPreferences/shared_prefs里面


1.1.2取出用户的配置


--存储了之后读取用户的配置。

假设我们在主活动里面添加几个按钮来进入配置页面和保存读取存储的信息:

 <Button
        android:id="@+id/btnPreferences"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="onClickLoad"
        android:text="Load Preferences Screen" />

<Button
android:id="@+id/btnDisplayValues"
android:text="Display Preferences Values"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="onClickDisplay"/>
<EditText
android:id="@+id/txtString"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btnModifyValues"
android:text="Modify Preferences Values"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="onClickModify"/>

第一个button用来载入配置页面,第二个button用来展示存储的信息,然后是一个edittext,用来输入信息,最后button是用来存储信息,给第二个button展示用。

这几个button对应的方法如下:

 public void onClickLoad(View view) {
    	Intent i = new Intent("net.learn2develop.AppPreferenceActivity");
    	startActivity(i);
    	}
    public void onClickDisplay(View view) {
    	SharedPreferences appPrefs =
    	getSharedPreferences("net.learn2develop.UsingPreferences_preferences",
    	MODE_PRIVATE);
    	DisplayText(appPrefs.getString("editTextPref", ""));
    	}
    public void onClickModify(View view) {
    	SharedPreferences appPrefs =
    	getSharedPreferences("net.learn2develop.UsingPreferences_preferences",
    	MODE_PRIVATE);
    	SharedPreferences.Editor prefsEditor = appPrefs.edit();
    	prefsEditor.putString("editTextPref",
    	((EditText) findViewById(R.id.txtString)).getText().toString());
    	prefsEditor.commit();
    	}
    private void DisplayText(String str) {
    	Toast.makeText(getBaseContext(), str, Toast.LENGTH_LONG).show();
    	}

这几个方法分别用来载入配置页面(上一个小节中设置的配置页面),展示存储的配置信息,确定保存输入的配置信息。

在onClcikDIsplay方法中,我们用getSharedPreferences方法来获取之前存储的配置(这里是“net.learn2develop.UsingPreferences_preferences,”,用<PackageName>_preferences来指定),这里的MODE_private指定这个配置文件只能由创建他的活动打开。然后在用SharedPreferences对象的getString方法,传入想要获得的对象的key,再获得对应的对象。

在onCLickModify方法中,首先还是要获得一个SharedPreferences对象,然后获得它的SharedPreferences.Editor对象,用这个对象来存储对应的数据,该类的putString方法用的就是一对name/value的方法来存储值。

1.1.3自定义存储文件名字


--在上一个例子中,存储配置的文件是net.learn2develop.UsingPreferences_preferences,有点长而且不好记住。我们可以在配置选择的活动中(也就是上面的AppPreferenceActivity类中)的onCreate方法中添加如下代码:

	PreferenceManager prefMgr = getPreferenceManager();
    prefMgr.setSharedPreferencesName("YourName");

这样就把存储的xml文件改名为了YourName了。

在读取的时候只要把YourName当做参数提供到相应的方法中即可,如下:
public void onClickDisplay(View view) {
    	SharedPreferences appPrefs =
    	getSharedPreferences("YourName",
    	MODE_PRIVATE);
    	DisplayText(appPrefs.getString("editTextPref", ""));
    	}
    public void onClickModify(View view) {
    	SharedPreferences appPrefs =
    	getSharedPreferences("YourName",
    	MODE_PRIVATE);
    	SharedPreferences.Editor prefsEditor = appPrefs.edit();
    	prefsEditor.putString("editTextPref",
    	((EditText) findViewById(R.id.txtString)).getText().toString());
    	prefsEditor.commit();
    	}

就可以往自己定义的配置xml文件中读取配置了。


1.2 将数据存储到文件上面


--在上一种用SharedPreferences 对象来存储,我们可以用name/value这种数据对的形式来存储数据,下面介绍传统的文件系统存储。

1.2.1存到内部储存器中(internal Storage)

要把内容写入文件中,要用FileOutputStream类,这个类打开一个新文件,将输入的内容写入其中:
FileOutputStream fOut =
openFileOutput(“textfile.txt”,
MODE_WORLD_READABLE);
这样就把文件写入到了一个叫做textfile.txt的文件中了。这里除了MODE_WORLD_READABLe之外,还可以选择:MODE_PRIVATE(只能有创建文件的应用访问),MODE_APPEND(把内容追加到已有的文件中去),MODE_WORLD_WRITEABLE(所有的应用都可以访问)。

把文件创建好了之后,就要用OutputStreamWriter类把字符流转换为比特流:

OutputStreamWriter osw = new
OutputStreamWriter(fOut);

将这个OutputStreamWriter类跟一个FileOutputStream绑定起来之后,用他的write(String str)方法将字符串写入文件中去:

//---write the string to the file---
osw.write(str);
osw.flush();
osw.close();

为了确保字符都写入到文件中去了,可以用flush方法。最后用close方法来关闭文件。

对应的,读取文件要用FileInputStream类:

FileInputStream fIn =
openFileInput(“textfile.txt”);
其中openFileInput方法提供的是文件的名字(由于是内部存储空间,不用提供绝对路径)。
再用InputStreamReader跟对应的FileInputStream绑定起来:

InputStreamReader isr = new
InputStreamReader(fIn);

因为并不知道输入的字符串的长度,定义最大长度为100.可以用循环来读取字符串内容:

char[] inputBuffer = new char[READ_BLOCK_SIZE];
String s = “”;
int charRead;
while ((charRead = isr.read(inputBuffer))>0)
{
//---convert the chars to a String---
String readString =
String.copyValueOf(inputBuffer, 0,
charRead);
s += readString;
inputBuffer = new char[READ_BLOCK_SIZE];
}

其中read()方法读取一个字符,如果到末尾了就返回-1,
当存储了文件之后,可以到ddms去看看文件的内容。绝对路径是/data/data/net.learn2develop.Files/files/textFile.txt


1.2.2存储到sd卡上

--外部存储,大多数就是SDcard啦。

File sdCard = Environment.getExternalStorageDirectory();
File directory = new File (sdCard.getAbsolutePath() +
“/MyFiles”);
directory.mkdirs();
File file = new File(directory, “textfile.txt”);
FileOutputStream fOut = new FileOutputStream(file);
OutputStreamWriter osw = new
OutputStreamWriter(fOut);
//---write the string to the file---
osw.write(str);
osw.flush();
osw.close();

这里用import android.os.Environment;的Environment类中的getExternalStorageDirectory方法来获取外部存储的路径,这里是/mnt/sdcard,然后再在这个目录下面创建目录(MyFile)和文件(textFile.txt).然后就是用FileOutputStream类来写入文件。

读取方式也类似:
try
    	{
    		File sdCard=Environment.getExternalStorageDirectory();
    		File directory=new File(sdCard.getAbsolutePath()+"/MyFile");
    		File file=new File(directory,"textFile.txt");
    		FileInputStream fin=new FileInputStream(file);
    		InputStreamReader isr=new InputStreamReader(fin);
    		char[] inputBuffer = new char[READ_BLOCK_SIZE];
    		String s = "";
    		int charRead;
    		while((charRead=isr.read(inputBuffer))>0)
    		{
    			String readString =String.copyValueOf(inputBuffer,0,charRead);
    			s+=readString;
    			inputBuffer = new char[READ_BLOCK_SIZE];
    		}
    		textBox.setText(s);
    		Toast.makeText(getBaseContext(), "FIle load succ", Toast.LENGTH_SHORT).show();
    		
    	}catch (IOException ioe) {
    		ioe.printStackTrace();
    	}

也是用Environment类来获取外部路径的信息。再用InputStreamReader读入文件内容。

注意:要读取外部文件时候要再AndroidManifest中添加:<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE” />

1.2.3使用静态的文件。


--除了可以在程序运行的时候动态的添加文件之外,还可以在设计阶段往Package里面添加文件。 -- 例如添加某些帮助文件。

例如:在Package里面的/res中添加文件夹raw,然后再往下面添加textfile.txt.就可以在程序里面将该文件的内容读取了:(当然要敲对应的代码)
InputStream is = this.getResources().openRawResource(R.raw.textfile);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String str = null;
try {
while ((str = br.readLine()) != null) {
Toast.makeText(getBaseContext(),
str, Toast.LENGTH_SHORT).show();
}
is.close();
br.close();
} catch (IOException e) {
e.printStackTrace();
}

这里用getResources()(Activity类的)返回一个Resources的对象,然后用openRawResource方法打开raw目录下面的文件。


1.3创建和使用数据库

--存储大量相关的数据,还是数据库好。

--Android使用的是SQLite数据库,应用创建的数据库只能由它自己访问,别的应用都不能访问。

1.3.1创建DBAdapter helper类


--贴出代码如下:
package com.example.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class DBAdapter {
	static final String KEY_ROWID="_id";
	static final String KEY_NAME="name";
	static final String KEY_EMAIL="email";
	static final String TAG="DBAdapter";
	static final String DATABASE_NAME = "MyDB";
	static final String DATABASE_TABLE = "contacts";
	static final int DATABASE_VERSION = 1;
	static final String DATABASE_CREATE =
			"create table contacts (_id integer primary key autoincrement, "
			+ "name text not null, email text not null);";
	final Context context;
	DatabaseHelper DBHelper;
	SQLiteDatabase db;
	public DBAdapter(Context ctx)
	{
		this.context=ctx;
		DBHelper=new DatabaseHelper(context);
		
	}
	private static class DatabaseHelper extends SQLiteOpenHelper
	{
		DatabaseHelper(Context context)
		{
			super(context,DATABASE_NAME,null,DATABASE_VERSION);
		}
		public void onCreate(SQLiteDatabase db)
		{
			try
			{
				db.execSQL(DATABASE_CREATE);
				
			}catch(SQLException e)
			{
				e.printStackTrace();
			}
		}
		public void onUpgrade(SQLiteDatabase db,int oldVersion,int newVersion)
		{
			Log.w(TAG, "Upgrading database from "+oldVersion+" to "+newVersion+" which will destory all data.");
			db.execSQL("DROP TABLE IF EXISTS contacts");
			onCreate(db);
		}
		
	}
	public DBAdapter open() throws SQLException
	{
		db = DBHelper.getWritableDatabase();
		return this;
	}
	public void close()
	{
		DBHelper.close();
	}
	public long insertContact(String name,String email)
	{
		ContentValues initialValues=new ContentValues();
		initialValues.put(KEY_NAME, name);
		initialValues.put(KEY_EMAIL, email);
		return db.insert(DATABASE_TABLE, null, initialValues);
		
	}
	public boolean deleteContact(long rowId)
	{
		return db.delete(DATABASE_TABLE,KEY_ROWID+"="+rowId,null) >0;
	}
	public Cursor getAllContacts()
	{
		return db.query(DATABASE_TABLE, new String[] {KEY_ROWID, KEY_NAME,
				KEY_EMAIL}, null, null, null, null, null);
	}
	public Cursor getContact(long rowId) throws SQLException
	{
		Cursor mCursor =
				db.query(true, DATABASE_TABLE, new String[] {KEY_ROWID,
				KEY_NAME, KEY_EMAIL}, KEY_ROWID + "=" + rowId, null,
				null, null, null, null);
				if (mCursor != null) {
				mCursor.moveToFirst();
				}
				return mCursor;
	}
	public boolean updateContact(long rowId, String name, String email)
	{
	ContentValues args = new ContentValues();
	args.put(KEY_NAME, name);
	args.put(KEY_EMAIL, email);
	return db.update(DATABASE_TABLE, args, KEY_ROWID + "=" + rowId, null) > 0;
	}
	
}

首先建议数据库表的一系列域(属性),然后再将创建数据库需要的sql语句写出来:

static final String KEY_ROWID = “_id”;
static final String KEY_NAME = “name”;
static final String KEY_EMAIL = “email”;
static final String TAG = “DBAdapter”;
static final String DATABASE_NAME = “MyDB”;
static final String DATABASE_TABLE = “contacts”;
static final int DATABASE_VERSION = 1;
static final String DATABASE_CREATE =
“create table contacts (_id integer primary key autoincrement, “
+ “name text not null, email text not null);”;

在DBAdapter类里面,我们创建了一个私有类--DatabaseHelper extends SQLiteOpenHelper,而SQLiteOpenHelper则是Android里面用于创建和管理数据库的类。我们重写了onCreate方法和onUpgrade方法:

private static class DatabaseHelper extends SQLiteOpenHelper
{
DatabaseHelper(Context context)
{
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db)
{
try {
db.execSQL(DATABASE_CREATE);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
Log.w(TAG, “Upgrading database from version “ + oldVersion + “ to “
+ newVersion + “, which will destroy all old data”);
db.execSQL(“DROP TABLE IF EXISTS contacts”);
onCreate(db);
}
}
onCreate方法创建一个数据库(如果原来数据库不存在的话),onUpgrade当数据库要被更新的时候被调用。这里只是简单的删除和重新创建数据库。

最后我们创建了一系列的操作数据库的函数:

//---opens the database---
public DBAdapter open() throws SQLException
{
db = DBHelper.getWritableDatabase();
return this;
}
//---closes the database---
public void close()
{
DBHelper.close();
}
//---insert a contact into the database---
public long insertContact(String name, String email)
{
ContentValues initialValues = new ContentValues();
initialValues.put(KEY_NAME, name);
initialValues.put(KEY_EMAIL, email);
return db.insert(DATABASE_TABLE, null, initialValues);
}
//---deletes a particular contact---
public boolean deleteContact(long rowId)
{
return db.delete(DATABASE_TABLE, KEY_ROWID + “=” + rowId, null) > 0;
}
//---retrieves all the contacts---
public Cursor getAllContacts()
{
return db.query(DATABASE_TABLE, new String[] {KEY_ROWID, KEY_NAME,
KEY_EMAIL}, null, null, null, null, null);
}
//---retrieves a particular contact---
public Cursor getContact(long rowId) throws SQLException
{
Cursor mCursor =
db.query(true, DATABASE_TABLE, new String[] {KEY_ROWID,
KEY_NAME, KEY_EMAIL}, KEY_ROWID + “=” + rowId, null,
null, null, null, null);
if (mCursor != null) {
mCursor.moveToFirst();
}
return mCursor;
}
//---updates a contact---
public boolean updateContact(long rowId, String name, String email)
{
ContentValues args = new ContentValues();
args.put(KEY_NAME, name);
args.put(KEY_EMAIL, email);
return db.update(DATABASE_TABLE, args, KEY_ROWID + “=” + rowId, null) > 0;
}

注意上面的cursor变量返回一个游标,可以让用户遍历全部数据库的内容。用ContentValue类可以存储一对name/value变量,它的put方法可以插入一对不同数据类型的name/value的值。

其实DBAdapter就是为了让DBhelper更加方便的给用户使用而构造的一个类。于是我们可以直接用DBAdapter来创建和管理数据库:
public DBAdapter(Context ctx)
{
this.context = ctx;
DBHelper = new DatabaseHelper(context);
}

创建数据库=创建已给DBAdapter的实例。
其他操作同理。

1.3.2 往DB中添加查询数据


--往DB中添加数据操作。

        DBAdapter db = new DBAdapter(this);
	db.open();
        long id=db.insertContact("Wei-Meng Lee", "testData1");
        id=db.insertContact("Marry Jackson", "testData2");
        db.close();

先创建一个db例子,然后用其insertContact方法来往里插入数据即可。


--从DB中读取数据。

        db.open();
        Cursor c=db.getAllContacts();
        if(c.moveToFirst())
        {
        	do{
        		DisplayContact(c);
        		
        	}while(c.moveToNext());
        }
        

    public void DisplayContact(Cursor c)
    {
    	Toast.makeText(this, "id:"+c.getString(0)+"\n"+
						   "Name:"+c.getString(1)+"\n"+
						  "Email:"+c.getString(2)+"\n"
    			, Toast.LENGTH_SHORT).show();
    }

用cursor对象c可以遍历表中的每个triple,然后用moveToNext来去往下一个。在DisplayContact中用cursor的getString方法,传入列号,就能获得对应的数据。

--获取其中一个数据:

	db.open();
        Cursor c = db.getContact(number);
        if (c.moveToFirst())
        DisplayContact(c);
        else
        Toast.makeText(this, "No contact found", Toast.LENGTH_LONG).show();
        db.close();

可以用db的getContact方法来获取其中一行的数据,提供的是行号。然后cursor c里面就有一个对象昂,然后直接调用DisplayContact方法即可。注意行号从0开始。

--余下的还有删除和更新数据,这里只贴出来:

//更新数据
db.open();
if (db.updateContact(1, “Wei-Meng Lee”, “weimenglee@gmail.com”))
Toast.makeText(this, “Update successful.”, Toast.LENGTH_LONG).show();
else
Toast.makeText(this, “Update failed.”, Toast.LENGTH_LONG).show();
db.close();

//删除数据
db.open();
if (db.deleteContact(1))
Toast.makeText(this, “Delete successful.”, Toast.LENGTH_LONG).show();
else
Toast.makeText(this, “Delete failed.”, Toast.LENGTH_LONG).show();
db.close();

1.3.3预先创建数据库

--在实际的应用中,预先建立应用需要的数据库是比较高效的。可以用SQLite Database Browser等工具来帮助设计。
假设我们设计好了一个数据库文件mydb,只需要将其放入项目的assets文件夹中。再用如下代码读取即可:

DBAdapter db = newDBAdapter(this); 
try{
String destPath = “/data/data/”+ getPackageName() +
“/databases”;
File f = newFile(destPath);
if(!f.exists()) { 
f.mkdirs();
f.createNewFile();
//---copy the db from the assets folder into 
// the databases folder---
CopyDB(getBaseContext().getAssets().open(“mydb”),
newFileOutputStream(destPath + “/MyDB”));
}
} catch(FileNotFoundException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
}
用到的copyDb函数如下:
public voidCopyDB(InputStream inputStream, 
OutputStream outputStream) throwsIOException {
//---copy 1K bytes at a time---
byte[] buffer = new byte[1024];
int length;
while((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
inputStream.close();
outputStream.close();
}
注意在这个函数里面直接用InputStream来读取源文件,并用outputStream来输出到指定文件里面。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值