《第一行代码》读书笔记(六)

《第一行代码》读书笔记(六)
wshunli《第一行代码》读书笔记 – 数据存储方案
《第一行代码》读书笔记(一)– 平台架构 (第1章)
《第一行代码》读书笔记(二)– 应用组件之 Activity (第2、4章)
《第一行代码》读书笔记(三)– 应用组件之 Service (第10章)
《第一行代码》读书笔记(四)– 应用组件之 BroadcastReceiver (第5章)
《第一行代码》读书笔记(五)– 应用组件之 ContentProvider (第7章)
《第一行代码》读书笔记(六)– 数据存储方案 (第6章)
《第一行代码》读书笔记(七)– 多媒体资源 (第8章)
《第一行代码》读书笔记(八)– 网络编程 (第9章)
第6章 数据存储全方案
Android 系统中主要提供了 3 种方式:文件存储、SharedPreference 以及数据库存储。
文件存储
对于文件存储,Android 系统不对数据进行格式化处理,适合一些简单的文本数据或者二进制数据。
1、将数据保存到文件中
String FILENAME = "hello_file"; |
书上是这样写的,主要为了提高性能:
String FILENAME = "hello_file"; |
其中 MODE_PRIVATE 表示新创建文件,已经存在的文件会覆盖掉;
如果使用 MODE_APPEND 表示向存在的文件中追加内容,如果文件不存在会新建。
还有 MODE_WORLD_READABLE 和 MODE_WORLD_WRITEABLE 已经过时了。
2、从文件中读取数据
int SIZE = 4096; |
书上是这样写的,也是为了提高性能:
FileInputStream fileInputStream; |
3、Android 系统数据存储位置
对于 Android 设备可分为 内部存储 和 外部存储。
3.1、内部存储
内部存储默认应用私有,其他 应用(和用户) 不能访问这些文件。卸载应用系统会自动删除。
应用的内部存储空间一般会有 files 和 cache 两个文件夹,分别代表永久存储和缓存数据。
File Context.getFilesDir() 方法 /data/user/0/com.wshunli.store.demo/files
File Context.getCacheDir() 方法 /data/user/0/com.wshunli.store.demo/cache
其中 0 代表不同用户,Android 6.0 以前不存在。
文件在 /data/data/com.wshunli.store.demo/ 目录下可以看到。
3.2、外部存储
外部存储可能是可移除的存储介质(例如 SD 卡)或内部(不可移除)存储,用户可以删除。
使用外部存储前应 检查介质可用性 :
/* Checks if external storage is available for read and write */ |
这部分比较特殊,也可以分为两种,可以保存与其他应用共享的文件,也可以保存应用私有文件(类似于内部存储)。
(1)对于外部存储的 私有 文件夹,一般也会有 files 和 cache 两个文件夹,4.4 及以后读写不再需要权限。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" |
访问私有文件夹主要有两种方法:
File Context.getExternalFilesDir() 方法 /storage/emulated/0/Android/data/com.wshunli.store.demo/files
File Context.getExternalCacheDir() 方法 /storage/emulated/0/Android/data/com.wshunli.store.demo/cache
其中 getExternalFilesDir() 方法需要传入目录类型。传入 null 表示获取根目录。
目录类型 包括以下几种: DIRECTORY_MUSIC, DIRECTORY_PODCASTS, DIRECTORY_RINGTONES, DIRECTORY_ALARMS, DIRECTORY_NOTIFICATIONS, DIRECTORY_PICTURES, DIRECTORY_MOVIES.
https://developer.android.com/reference/android/os/Environment#DIRECTORY_MUSIC
有时,已分配某个内部存储器 分区 用作外部存储的设备可能还提供了 SD 卡槽。
在 Android 4.3 以前 Context.getExternalFilesDir() 只能获取内部分区的访问权限;
从 Android 4.4 开始 Context.getExternalFilesDirs() 可以同时访问两个位置,及内部分区和 SD 卡。
对于 Android 4.3 或者更低版本使用 ContextCompat.getExternalFilesDirs() 方法有同样的效果,可以同时访问两个位置。
(2)对于外部存储的 共享 文件夹,必须申请读写权限,而且 6.0 以后要申请运行时权限。
Environment.getExternalStorageDirectory() 方法返回值 /storage/emulated/0
Android 7.0 提供简化的 API 来访问常见的外部存储目录。
https://developer.android.com/training/articles/scoped-directory-access
Environment.getExternalStoragePublicDirectory() 返回设备上的 “公共” 位置。
同样需要传入目录类型,例如:
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM); |
返回相机文件所在目录 /storage/emulated/0/DCIM
在外部文件目录中包含名为 .nomedia 的空文件(注意文件名中的点前缀),可在媒体扫描程序中隐藏文件。
4、应用安装包内文件数据访问
安装包内数据文件有很多种,这里主要介绍两部分:assets 和 raw 。
这两部分都会原封不动地打包进 apk 安装包,并不会编译成 二进制文件。
assets 文件夹在 app/src/main/assets/ 允许创建目录结构。
AssetManager assetManager = getResources().getAssets(); |
raw 文件夹在 app/src/main/res/raw/ 不允许创建目录结构,但会在 R.java 中自动进行资源标识。
InputStream inputStream = getResources().openRawResource(R.id.data); |
SharedPreferences 存储
SharedPreferences 主要用于保存检索原始数据类型的永久性键值对。
1、使用 SharedPreferences 存储数据首先应 获取 SharedPreferences 对象,主要有三种方法:
(1)Context 类中 的 getSharedPreferences 方法,按照文件名称识别 preferences 。
SharedPreferences getSharedPreferences (String name, int mode)
SharedPreferences sharedPreferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); |
其中 PREFS_NAME 表示 preferences 文件名称,在 /data/data/com.wshunli.store.demo/shared_prefs/ 路径下 。
MODE_PRIVATE 表示操作模式,目前只有这一种模式可选,其他 MODE_WORLD_READABLE, MODE_WORLD_WRITEABLE , MODE_MULTI_PROCESS 已经废弃。
(2)Activity 类中的 getPreferences 方法,按照 Activity 识别 preferencs ,仅用于本 Activity 。
SharedPreferences getPreferences (int mode)
SharedPreferences preferences = getPreferences(MODE_PRIVATE); |
仅指定 操作模式 即可。
(3)PreferenceManager 类中的 getDefaultSharedPreferences 静态方法,以应用包名作为前缀识别。
SharedPreferences getDefaultSharedPreferences (Context context)
SharedPreferences defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); |
2、获取 SharedPreferences 对象后就可以 存储和检索 数据了。
对于 数据存储 需要使用 SharedPreferences.Editor 接口。
public static final String PREFS_NAME = "wshunli"; |
我们可以在 shared_prefs 文件夹下发现 wshunli.xml 文件:
|
前面三种方式在 xml 文件命名不太一样,内容是一样的:
/data/data/com.wshunli.store.demo/shared_prefs/wshunli.xml
/data/data/com.wshunli.store.demo/shared_prefs/SPActivity.xml
/data/data/com.wshunli.store.demo/shared_prefs/com.wshunli.store.demo_preferences.xml
SharedPreferences.Editor 提交数据有两种方法,apply() 和 commit() ,两者主要区别有两点:
(1) commit() 有返回值,apply() 没有返回值。apply() 失败了是不会报错的。
(2) apply() 写入文件的操作是 异步 的,会把 Runnable 放到线程池中执行,而 commit() 的写入文件的操作是在当前线程 同步 执行的。
因此当两者都可以使用的时候还是推荐使用 apply() ,因为 apply() 写入文件操作是异步执行的,不会占用主线程资源。
3、从 SharedPreferences 中读取数据
直接使用 SharedPreferences 对象实例方法即可。
String getString (String key, String defValue)
String website = sharedPreferences.getString("website", ""); |
其他方法还有:
Map<String, ?> getAll () // 获取所有键值对
boolean contains (String key) // 是否包含某键值数据
数据库存储
Android 提供了对 SQLite 数据库的完全支持。
也有很多其他数据库可以使用,比如 Realm 、ObjectBox 等等。
一些数据库 ORM 框架也要学习,比如 GreenDao 、LitePal 等等。
这里只介绍 SQLite 数据库。
1、创建数据库
在 Android 中操纵 SQLite 数据库可以使用 SQLiteOpenHelper 类。
public class MSQLiteOpenHelper extends SQLiteOpenHelper { |
其中构造函数:context 即上下文;name 为数据库名称;factory 为自定义 Cursor ,一般传入 null ;version 为数据库版本号,用于数据库升级。
然后调用 SQLiteOpenHelper 对象实例的 getReadableDatabase() 或者 getWritableDatabase() 即可创建数据库。
MSQLiteOpenHelper dbHelper = new MSQLiteOpenHelper(this, "book.db", null, 1); |
数据库文件存储位置:/data/data/com.wshunli.sqlite.demo/databases/book.db
2、添加表结构
前面只是创建了空数据库,下面添加表。
使用 SQLiteDatabase 对象的 execSQL(String sql) 方法。
public class MSQLiteOpenHelper extends SQLiteOpenHelper { |
3、升级数据库
在添加数据库表时,非常有用。其实就是实现 onUpgrade 方法。
public class MSQLiteOpenHelper extends SQLiteOpenHelper { |
这时我们在构造函数中 版本号大于 1 即可。
dbHelper = new MSQLiteOpenHelper(this, "book.db", null, 2); |
如果在 onCreate 方法里直接添加 db.execSQL(CREATE_CATEGORY); 语句是不会执行的。
4、添加数据
对数据库的操作无非就是 CRUD :C 添加、R 查询、U 更新、D 删除。
添加数据使用 SQLiteDatabase 对象的 insert 方法。
long insert(String table, String nullColumnHack, ContentValues values)
SQLiteDatabase db = dbHelper.getWritableDatabase(); |
其中第二个参数 nullColumnHack 用于未指定添加数据的情形,一般直接传入 null 即可。
5、更新数据
更新数据使用 SQLiteDatabase 对象的 update 方法。
int update(String table, ContentValues values, String whereClause, String[] whereArgs)
SQLiteDatabase db = dbHelper.getWritableDatabase(); |
其中 whereClause 、 whereArgs 用于约束数据第几行或者某几行,不指定则更新所有行。
6、删除数据
删除数据使用 SQLiteDatabase 对象的 delete 方法。
int delete(String table, String whereClause, String[] whereArgs)
SQLiteDatabase db = dbHelper.getWritableDatabase(); |
其中 whereClause 、 whereArgs 用于约束数据第几行或者某几行,不指定则删除所有行。
7、查询数据
查询数据使用 SQLiteDatabase 对象的 query 方法。
Cursor query(String table, String[] columns, // 指定表名、列名 |
其中 selection 、 selectionArgs 用于约束数据第几行或者某几行,不指定则查询所有行。
然后 groupBy 指定 group by 的列,having 对 group by 结果进一步约束。
最后 orderBy 指定排序方式。
SQLiteDatabase db = dbHelper.getWritableDatabase(); |
8、使用 SQL 语句操纵数据库
使用 SQLiteDatabase 对象的 execSQL 方法,还是挺麻烦的,可以使用一些 ORM 框架。
参考资料
1、存储选项 | Android Developers
https://developer.android.com/guide/topics/data/data-storage
2、FileInputStream读取文件数据的两种方式 - CSDN博客
https://blog.csdn.net/a909301740/article/details/52574602
3、全面的Android文件目录解析和获取方法(包含对6.0系统的说明) - CSDN博客
https://blog.csdn.net/zhangbuzhangbu/article/details/23257873
4、android 目录/data/data/ 跟 /data/user/0/ 差别 - V2EX
https://www.v2ex.com/t/259080
5、提供资源 | Android Developers
https://developer.android.com/guide/topics/resources/providing-resources
6、Android数据存储之Assets、Raw - CSDN博客
https://blog.csdn.net/sjm19901003/article/details/47026503
7、SharePreferences源码分析(commit与apply的区别以及原理) - CSDN博客
https://blog.csdn.net/Double2hao/article/details/53871640
8、Room,Realm,,ObjectBox 你选择哪个? - 泡在网上的日子
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2017/0926/8551.html
9、【Android 数据库框架总结,总有一个适合你!】 - CSDN博客
https://blog.csdn.net/da_caoyuan/article/details/61414626
10、android基础—->SQLite数据库的使用 - huhx - 博客园
http://www.cnblogs.com/huhx/p/sqliteDatabase.html






