0%

Android-存储

Android-存储

数据库这部分在暑假的时候就折腾了很久,有点乱,这次终于可以写一个系统的笔记了

Android的物理存储介质

通常分为

  • Device Storage 指设备内置的存储空间
  • Portable Storage 通常指外置SD卡
    现在大多数手机都只有第一项,Portable Storage很少了

Android逻辑分区

Internal Storage External Storage
一般只存在于Device Storage,App-private,用户不可以直接读写,保证可用,有且仅有一个,刷过机应该知道这个需要root后才能修改 一般只存在于 Device 和Portable Storage,Word-accessible,用户可以直接读写,可以卸载口仍保留,不保证可用性(可能被挂载或物理移除),可以有多个

App-specific Directory结构

  • Internal Storage/External Storage
    • App-specific directory
      • files
      • cache

Public Directory结构

  • External Storage
    • Public directory
      • DCIM
      • Download
      • Movies

权限

Internal Private External Private External Public
本应用可访问 Yes Yes(4.4以前需要授权) 有授权时Yes
其他应用可访问 No 有授权时Yes 有授权时Yes
用户可访问 No(除非root) Yes Yes
可用性保证 Yes No No
卸载后自动清除 Yes Yes No

App-specific Directory获取

  • Internal
    1. file目录: context.getFilesDir()
    2. cache目录:context.getCacheDir()
    3. 自定义目录:context.getDir(name,mode)
  • External
    1. files目录:context.getExternalFilesDir(String type)
    2. cache目录:context.getExternalCacheDir()

Public Directory获取

  • 标准目录:Environment.getExternalStoragePublicDirectory(String type) 这里的type填"DCIM"、"Downloads"之类
  • 根目录:Environment.getExternalStorageDirectory()

检查可用性

Android APP存储方案

1.SharedPreferences

由于原理是读取XML文件,每次都需要一次性读取到内存,适用于保存较少量的数据,如用户的开关偏好等

获取SharedPreferences

1
2
3
context.getSharedPreferences(name, Context.MODE_PRIVATE);
getActivity().getPreferences(Context.MODE_PRIVATE);
//Mode只能填MODE_PRIVATE

读SharedPreferences

1
2
3
4
5
6
7
//各种数据类型的读取方法
String getString(String key, String defValue);
Set<String> getStringSet(String key, Set<String> defValues);
int getInt(String key, int defValue);
long getLong(String key, long defValue);
float getFloat(String key, float defValue);
boolean getBoolean(String key, boolean defValue);

写SharedPreferences

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//通过Editor类提交修改事务
SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedPref.edit(); editor.putInt(getString(R.string.saved_high_score_key), newHighScore);
editor.commit(); // editor.apply();

/*commit和apply区别
*commit()
*同步写入内存和磁盘
*有返回值
*同时调用时,最后一次调用获胜
*
*apply()
*同步写入内存,异步保存磁盘
*无返回值
*同时调用时,最后一次调用覆盖
*/

注意事项

  • sp 适合小量存储数据, 否则加载慢,占用大量内存
  • sp每次写入都是全量写入
  • 不要将太大的配置项(key和value)存储到sp中,否则会占用大量内存
  • 获取SharedPreferences对象的时候会读取sp文件,如果文件还没有读完,就执行了get和put操作,可能会出现等待的情况。

2.Database

SP是用来保存少量数据,那么更多的数据就要由数据库来保存了,在配置数据库读写和增删改查的时候需要配置很多类,就当作是通用的套路吧

首先是两个和数据库结构相关的类,用于定义数据库结构和创建数据库的表

Contract用于定义数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public final class TodoContract {

//创建数据库文件的语句
public static final String SQL_CREATE_TODOS =
"CREATE TABLE " + TodoEntry.TABLE_NAME + " (" +
TodoEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
TodoEntry.COLUMN_NAME_DATE + " TEXT," +
TodoEntry.COLUMN_NAME_STATE + " TEXT," +
TodoEntry.COLUMN_NAME_CONTENT + " TEXT," +
TodoEntry.COLUMN_NAME_LEVEL + " TEXT)";

private TodoContract() {
}

public static class TodoEntry implements BaseColumns {
//代表数据库的各列
public static final String TABLE_NAME = "todo";
public static final String COLUMN_NAME_DATE = "date";
public static final String COLUMN_NAME_STATE = "state";
public static final String COLUMN_NAME_CONTENT = "content";
public static final String COLUMN_NAME_LEVEL = "level";

}
}

DBHelper用于创建、打开和升级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TodoDbHelper extends SQLiteOpenHelper {

public String TABLE_NAME = "todo";
// 定义数据库名、版本;创建数据库

public TodoDbHelper(Context context) {
super(context, "todo.db", null, 1);
}

@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL_CREATE_TODOS);
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

}
}

获取数据库实例

1
2
TodoDbHelper dbHelper=new TodoDbHelper(getContext());
SQLiteDatabase db = dbHelper.getReadableDatabase();

增删改查参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//增
private boolean saveNote2Database(String content,String level) {
// TODO 插入一条新数据,返回是否插入成功

SQLiteDatabase db = dbHelper.getWritableDatabase();

// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();


values.put(TodoEntry.COLUMN_NAME_DATE, currentTimeMillis());


values.put(TodoEntry.COLUMN_NAME_STATE, State.TODO.intValue);
values.put(TodoEntry.COLUMN_NAME_CONTENT, content);
values.put(TodoEntry.COLUMN_NAME_LEVEL, level);

// Insert the new row, returning the primary key value of the new row
long newRowId = db.insert(TodoEntry.TABLE_NAME, null, values);
Log.i(TAG, "perform add data, result:" + newRowId);

if(newRowId!=-1)
return true;
else
return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//删
private void deleteNote(Note note) {
// TODO 删除数据
// Gets the data repository in write mode
SQLiteDatabase db = dbHelper.getWritableDatabase();
String selection = TodoEntry._ID + " LIKE ?";
int result = db.delete(TodoEntry.TABLE_NAME, selection, new String[] {String.valueOf(note.id)} );
db.close();

if(result>0)
Toast.makeText(MainActivity.this,
"No Error", Toast.LENGTH_SHORT).show();
else
Toast.makeText(MainActivity.this,
"Error", Toast.LENGTH_SHORT).show();

//刷新数据
notesAdapter.refresh(loadNotesFromDatabase());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//改
private void updateNode(Note note) {
// 更新数据
SQLiteDatabase db = dbHelper.getWritableDatabase();

// New value for one column
ContentValues values = new ContentValues();
values.put(TodoEntry.COLUMN_NAME_STATE, note.getState().intValue);

String selection = TodoEntry._ID + " LIKE ?";
String[] selectionArgs = { String.valueOf(note.id) };

int count = db.update(
TodoEntry.TABLE_NAME,
values,
selection,
selectionArgs);
Log.i(TAG , "perform update data, result:" + count);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//查(这里借用老师的)
private void queryData() {
SQLiteDatabase db = dbHelper.getReadableDatabase();

// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
BaseColumns._ID,
FeedEntry.COLUMN_NAME_TITLE,
FeedEntry.COLUMN_NAME_SUBTITLE
};

// Filter results WHERE "title" = 'My Title'
String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?";
String[] selectionArgs = {"my_title"};

// How you want the results sorted in the resulting Cursor
String sortOrder =
FeedEntry.COLUMN_NAME_SUBTITLE + " DESC";

Cursor cursor = db.query(
FeedEntry.TABLE_NAME, // The table to query
projection, // The array of columns to return (pass null to get all)
null, // The columns for the WHERE clause
null, // The values for the WHERE clause
null, // don't group the rows
null, // don't filter by row groups
sortOrder // The sort order
);

Log.i(TAG, "perfrom query data:");
while (cursor.moveToNext()) {
long itemId = cursor.getLong(cursor.getColumnIndexOrThrow(FeedEntry._ID));
String title = cursor.getString(cursor.getColumnIndex(FeedEntry.COLUMN_NAME_TITLE));
String subTitle = cursor.getString(cursor.getColumnIndex(FeedEntry.COLUMN_NAME_SUBTITLE));
Log.i(TAG, "itemId:" + itemId + ", title:" + title + ", subTitle:" + subTitle);
}
cursor.close();
}

使用后关闭数据库连接

1
2
3
4
5
@Override
protected void onDestroy(){
dbHelper.close();
super.onDestroy();
}

Room Library

JavaBeans封装:定义Note类,相当于把数据库每一行封装保存起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Note {

public final long id;
private Date date;
private State state;
private String content;
private int level;

public Note(long id) {
this.id = id;
}

public Date getDate() {
return date;
}

public void setDate(Date date) {
this.date = date;
}

public State getState() {
return state;
}

public void setState(State state) {
this.state = state;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

public int getLevel(){ return level; }

public void setLevel(int level){ this.level=level; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//上面提到的枚举类
public enum State {
TODO(0), DONE(1);

public final int intValue;

State(int intValue) {
this.intValue = intValue;
}

public static State from(int intValue) {
for (State state : State.values()) {
if (state.intValue == intValue) {
return state;
}
}
return TODO; // default
}
}

Dao(Data Acess Objext)

使用Dao似乎可以大大简化对数据库操作的方法,这次作业没有涉及到Dao的处理,
这一部分还没弄懂,有待学习。

数据库的调试

在build.gradle添加

1
implementation 'com.facebook.stetho:stetho:1.3.1'

并新建TodoListApplication.java

1
2
3
4
5
6
7
8
public class TodoListApplication extends Application {

@Override
public void onCreate() {
super.onCreate();
Stetho.initializeWithDefaults(this);
}
}

使用方式:在Chrome地址栏输入chrome://inspect/#devices ,Device栏最下面inspect,点开后选择Web SQL