【Android】数据存储

  • 摘自老师文档

1. 保存数据到文件


1.1. 内部、外部存储区

Internal 内部存储区

  • 非易失内存,保存应用的私有文件

External 外部存储区

  • 可移除的存储媒介,world-readable 世界可读,不具有保密性

一些设备把持久的存储空间分为了 intenal 和 external 分区,所以即使没有可移除的存储媒介,也有两种存储空间,并且不管是不是可移除的,API 的行为也是一致的

app 默认安装到 Internal 存储区,可通过清单文件中的android:installLocation属性更改

卸载 app 时,系统会移除internal中你的app的所有文件 (Internal)
系统移除你app在通过 getExternalFilesDir() 方法取到的目录中的文件 (External)

  • 简单来说:Internal 中的都删,External 中 public 文件保留,private 文件删除

1.2. 在内部存储区 (internal) 存放文件

  • 内部存储区目录通常为 data/data/app_name/
  • 对于内部存储区的文件的读写,不需要向系统申请权限

. 打开目录

  • 获取合适的目录作为 File 对象
getFilesDir() 		返回一个代表 internal 目录的 File 对象
getDir(name,mode) 	在 internal 目录中创建或者打开一个目录
getCacheDir() 		返回一个用于存放你的 app 临时 缓存文件的 internal 目录

File file = new File(getApplicationContext().getFilesDir(), filename);

. 写文件

  • 文件设置为 MODE_PRIVATE 后,别的 app 就无法存取你的内部存储
  • 主要用 fos.write(content.getBytes()); 将字符串 content 写入文件
public void save(View view) {
     EditText et_input = (EditText) findViewById(R.id.et_input);
     String content = et_input.getText().toString();
     String filename = "data.txt";
     FileOutputStream fos;

     try {
         fos = openFileOutput(filename, Context.MODE_PRIVATE);
         fos.write(content.getBytes());
         fos.close();
         Toast.makeText(this, getString(R.string.success), Toast.LENGTH_SHORT).show();
     } catch (IOException e) {
         e.printStackTrace();
     }
 }

. 写一个缓存文件

public File getTempFile(Context context, String url) {
    File file = null;
    try {
        String filename = Uri.parse(url).getLastPathSegment();	//从 URL 中提取文件名
        assert filename != null;	//断言,这里为 true 后边的语句才会执行
        file = File.createTempFile(filename, null, context.getCacheDir());
        //在指定目录中创建前缀为 filename,后缀为 null 的空文件
    } catch (IOException e) {
        e.printStackTrace();
    }
    return file;
}

. 打开一个已存在的文件

  • 主要,读文件内容到 buffer 中
byte[] buffer = new byte[fis.available()];      //测试 byte 数组的大小
fis.read(buffer);
content = new String(buffer);

还有
//按字符读
int ch;
ch = fis.read();
while(ch != -1) {
	fileContent += (char)ch;
	ch = fis.read();
}
  • 详细
public void getInfo(View view) {
    String filename = "data.txt";
    FileInputStream fis;
    EditText et_input = (EditText) findViewById(R.id.et_input);
    String content = null;

    try {
        fis = openFileInput(filename);
        byte[] buffer = new byte[fis.available()];      //测试 byte 数组的大小
        fis.read(buffer);
        content = new String(buffer);
        et_input.setText(content);
        fis.close();
        Toast.makeText(this, getString(R.string.success), Toast.LENGTH_SHORT).show();
    } catch (IOException e) {
        e.printStackTrace();
    }

}

1.3. 在外部存储区 (external) 存放文件

  • 外部存储区分为 可移除 (存储介质) 和 不可移除 (内部分区) 两种
    存储介质:如SD卡
    内部分区:设备将内部内存分配做外部存储
  • 所有应用都能读取放在 外部存储 的文件,并且用户可以移除
  • 可以存储两种类型的文件
    1.public文件 属于公共,卸载 app 无影响
    2.private 文件,属于该 app,卸载 app 会删除这些文件

. 获取外部存储的访问权限

  • 写权限隐含读权限
  • 读取 私有文件 无需权限

在 manifest 中声明权限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

. 校验外部存储可用

  • 因为外部存储可能不可用 例如移除SD卡
  • 检验是否可写
public boolean isExternalStorageWritable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state)) {
        return true;
    }
    return false;
}
  • 检验是否可读
    和可写一样,不过 if 语句中多了 || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)

. 保存文件到公共目录

  • 用 getExternalStoragePublicDirectory 方法
public File getExternalDir(String directoryName) {
    File file = new File(Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_PICTURES), directoryName);
	//File file = Environment.getExternalStoragePublicDirectory(directoryName);
    if (!file.exists()) {
    	if(!file.mkdirs()) {
        	Log.e(LOG_TAG, "Directory not created");
    	}
    }
    return file;
}
  • 外部目录中若包含 .nomedia 空文件,媒体扫描程序就无法读取你的媒体文件
  • 可用 file.getAbsolutePath() 获取文件绝对路径 (外部存储位置)

. 保存应用私有文件

  • 用 getExternalFilesDir 方法创建目录
public File getAlbumStorageDir(Context context, String albumName) {
    // Get the directory for the app's private pictures directory.
    File file = new File(context.getExternalFilesDir(
            Environment.DIRECTORY_PICTURES), albumName);
    if (!file.mkdirs()) {
        Log.e("LOG_TAG", "Directory not created");
    }
    return file;
}
  • 读写 私有文件 无需权限

注:不管 public 还是 private ,用类似 DIRECTORY_PICTURES 这样的 API 常量来创建能够确保文件被系统正确对待

  • 若设备同时有两个外部存储目录,需用 getExternalFilesDirs 方法同时访问两个位置
    返回的 File 数组中,第一条认为是主存储,满了或不可用才会用下一个

. 保存缓存文件

  • 用 getExternalCacheDir 方法,同上,有 getExternalCacheDirs 方法
  • 通常需要手动删除
  • 为节省文件空间并保持应用性能,你应该在应用的整个生命周期内仔细管理您的缓存文件并移除其中不再需要的文件

. 查询剩余空间

  • 方法 getFreeSpace 或 getTotalSpace

. 删除文件

  • 文件的 delete 方法
  • internal storage 中的文件还可以这样删
myContext.deleteFile(fileName);

2. 使用共享首选项(Shared Preferences)


  • 一个Shared Preferences对象指向一个保存键值对的文件,并且提供了简单的方法来读写它们
  • SharedPreferences 类提供了一个通用框架,能够保存和检索原始数据类型的永久性键值对
  • 用来保存任何原始数据 (基本类型),该数据会跨多个用户会话永久保留

. 获取Shared Preferences的handle (创建)

  • getSharedPreferences 通过名字区分
  • getPreferences 只需一个共享 preference 文件时
Context context = getActivity();
SharedPreferences sharedPref = context.getSharedPreferences(
        getString(R.string.preference_file_key), Context.MODE_PRIVATE);
SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);

. 写入Shared Preferences

  • edit 获取 editor,写入用 putString 等,提交用 commit 方法
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(getString(R.string.saved_high_score), newHighScore);
editor.commit();

. 从Shared Preferences中读取数据

  • 用 getString、getInt 方法
int defaultValue = getResources().getInteger(R.string.saved_high_score_default);
int highScore = sharedPref.getInt(getString(R.string.saved_high_score), defaultValue);

. 例 保存登录账号密码
即,关闭应用后再次打开会

  • 工具类
public class Utils {
	public static boolean saveUserInfo(Context context, String account, String password) {
		SharedPreferences sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE);
		SharedPreferences.Editor editor = sharedPref.edit();

		editor.putString("username", account);
		editor.putString("password", password);
		return editor.commit();
	}
	public static Map<String, String> getUserInfo(Context context) {
		SharedPreferences sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE);
		Map<String, String> userMap = new HashMap<>();
		userMap.put("username", sharedPref.getString("username", null));	//若取不到,null作为默认值取出
		userMap.put("password", sharedPref.getString("password", null));
		return userMap;
	}
}
  • 初始化显示
Map<String, String> userInfo = Utils.getUserInfo(this);

if(userInfo != null) {
	etAccount.setText(userInfo.get("username"));
	etPassword.setText(userInfo.get("password"));
}
  • 登录并保存
public void login(View view) {
	String number = etAccount.getText().toString().trim();
	String password = etPassword.getText().toString();
	
	if(TextUtils.isEmpty(number)) {		//系统提供的类 TextUtils
		Toast.makeText(this, "请输入账号", Toast.LENGTH_SHORT).show();
		return;
	}
	if(TextUtils.isEmpty(password)) {
		Toast.makeText(this, "请输入密码", Toast.LENGTH_SHORT).show();
		return;
	}
	Toast.makeText(this, "登陆成功", Toast.LENGTH_SHORT).show();
	
	boolean isSaveSuccess = Utils.saveUserInfo(this, number, password);
	if(isSaveSuccess) {
		Toast.makeText(this, "保存成功", Toast.LENGTH_SHORT).show();
	} else {
		Toast.makeText(this, "保存失败", Toast.LENGTH_SHORT).show();
	}
}

3. 序列化


  • 序列化是将对象状态转换为可保存或传输的格式的过程,可将对象序列化成不同的格式

3.1. XML序列化

  • 把对象的成员变量转化为XML格式,用Xml序列化器(XmlSerializer类),序列化之后的对象以XML文件的形式保存

. XML序列化过程描述

  • 1.创建文件
    2.打开文件输出流
    3.创建相应的序列化器
File file = new File(Environment.getExternalStorageDirectory(),"Person.xml");
FileOutputStream fos = new FileOutputStream(file);
XmlSerializer serializer = Xml.newSerializer();
  • 然后
1.设置文件编码方式:serializer.setOutput(fos,"utf-8");

2.写入xml文件的开始标签:serializer.startDocument("utf-8",true); 第一个参数设置文档的编码格式,第二个参数设置是否是一个独立的文档,一般设置为true

3.依次写入各元素 (如果有多个元素则可以使用迭代的方式写入,如果标签是嵌套的,则在写入顺序上也是嵌套的):
a) 写入开始标签:serializer.startTag(null,"Persons"); 命名空间,没有可以用null;标签名
b) 如果该标签有属性:serializer.attribute(null,"id",1); 命名空间;属性名;属性值
c) 写入元素内容:serializer.text(person.getName()); 该参数为实例对象中的某个属性值
d) 写入结束标签:serializer.endTag(null,"Persons"); 命名空间,一般为null;结束标签的标签名

4.文档的写入结束:serializer.endDocument()

5.通过 serializer.flush() 将流写入文件中
最后,关闭输出流:fos.close()

3.2. XML解析

  • DOM解析 只能解析较小的文件 内存消耗大
  • SAX解析 逐行扫描,可以解析超大的 速度快且占用内存少,不过只能读无法增删改XML数据
  • PULL解析 和SAX差不多 是Android内置的,最常用

. PULL解析过程

  • 1.创建解析器
Xml.newPullParser() 或 XmlPullParserFactory.newInstance().newPullParser()
  • 2.设置解析器的 xml 来源
abstract void setInput(Reader in) 该方法的源为字符流
abstract void setInput(InputStream inputStream, String inputEncoding) 字节流;编码
  • 3.获取当前事件类型,解析器状态
parser.getEventType()
  • 4.循环处理 xml
    在循环体中根据解析器状态决定操作,然后通过调用解析器的next()方法获取下一个事件,并转入下一次循环

4. SQLite数据库


  • SQLite 是一款轻型的数据库,遵守 ACID,占用资源很低,处理速度快于 Mysql

4.1. SQLite的命令行操作

  • 命令行工具 sqlite3,用于输入和执行 SQL 命令
首先打开虚拟机,
adb shell
sqlite3 数据库名		//打开或创建数据库
//若省略数据库名,则创建临时数据库,退出时删除该库
  • 点命令,是一类由 sqlite3 直接执行的系统命令
点命令:1.命令名和点之间没有空白符 2.必须在同一行 3.不能在普通SQL语句中 4.不接受注释
.databases 列出数据库
.tables 列出数据库中的表
  • chcp 65001 把控制台的字符编码切换为utf-8的编码

4.2. 在SQL数据库中存放数据

. 定义 schema 与 contract

  • schema ,一种数据库组织结构的正式声明.
  • contract 类,一些常量的容器,它用一种系统化并且自动生成文档的方式显式指定 schema 的布局.
    实现 BaseColumns 接口有助于数据库和 framework 相容.

. 使用SQL Helper创建数据库

  • 在后台线程中调用比较耗时的 getReadableDatabase 和 getWriteableDatabase

实现 SQLiteOpenHelper 类的例子

public class FeedReaderDbHelper extends SQLiteOpenHelper {
    // If you change the database schema, you must increment the database version.
    public static final int DATABASE_VERSION = 1;
    public static final String DATABASE_NAME = "FeedReader.db";

    public FeedReaderDbHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(SQL_CREATE_ENTRIES);
    }
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL(SQL_DELETE_ENTRIES);
        onCreate(db);
    }
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        onUpgrade(db, oldVersion, newVersion);
    }
}

//实例化
FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());

注:其中 onCreate 方法并没有实际创建或打开数据库,实际是在调用 getReadableDatabase 或 getWriteableDatabase 时.

. 对数据库中的数据进行操作

  • 首先使用 helper 获取 SQLiteDatabase 类对象
SQLiteDatabase db = helper. getWritableDatabase();

. 把信息放入数据库

  • public long insert,返回插入行的ID
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_ENTRY_ID, id);
values.put(FeedEntry.COLUMN_NAME_TITLE, title);
values.put(FeedEntry.COLUMN_NAME_CONTENT, content);

long newRowId;
newRowId = db.insert(FeedEntry.TABLE_NAME, FeedEntry.COLUMN_NAME_NULLABLE, values);

. 从数据库中读取信息

  • public Cursor query,返回 Cursor 对象,其中存放查询的结果集
String[] projection = {
    FeedEntry._ID,
    FeedEntry.COLUMN_NAME_TITLE,
    FeedEntry.COLUMN_NAME_UPDATED,
    ...
    };
    
String sortOrder =
    FeedEntry.COLUMN_NAME_UPDATED + " DESC";
    
Cursor c = db.query(
    FeedEntry.TABLE_NAME,  // The table to query
    projection,                               // The columns to return
    selection,                                // The columns for the WHERE clause
    selectionArgs,                            // The values for the WHERE clause
    null,                                     // don't group the rows
    null,                                     // don't filter by row groups
    sortOrder                                 // The sort order
    );
//cursor 起始位置在 -1 处
cursor.moveToFirst();			//将读取点放在入口位置,read position
long itemId = cursor.getLong(
    cursor.getColumnIndexOrThrow(FeedEntry._ID)
);
List itemIds = new ArrayList<>();
while(cursor.moveToNext()) {
  long itemId = cursor.getLong(
   	cursor.getColumnIndexOrThrow(FeedEntry._ID));
	itemIds.add(itemId);
}
cursor.close();

rawQuery

  • rawQuery方法中的查询类似于execSQL中的使用SQL的select语句,前者会返回一个 Cursor 对象

. 从数据库中删除信息

  • public int delete,返回被删的行数
String selection = FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?";
String[] selectionArgs = { String.valueOf(rowId) };
db.delete(table_name, selection, selectionArgs);

. 更新一个数据库

  • public int update,返回受影响的行数
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_TITLE, title);

String selection = FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?";
String[] selectionArgs = { String.valueOf(rowId) };

int count = db.update(
    FeedReaderDbHelper.FeedEntry.TABLE_NAME,
    values,
    selection,
    selectionArgs);

4.3. 数据库的事务

所谓事务,就是针对数据库的一组操作,具有原子性
若有语句没能成功执行,则已执行的语句发生回滚

   db.beginTransaction();
   try {
     ...
     db.setTransactionSuccessful();
   } finally {
     db.endTransaction();
   }

End.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值