一、安卓4种存储方式
安卓系统4种基本的存储方式
- 使用SharedPreferences存储数据
- 文件存储数据
- SQLite数据库存储数据
- 使用ContentProvider存储数据
1、 使用SharedPreferences存储数据
- 安卓轻量级的存储类,使用简单,速度快
- 常用于存储配置、状态信息,如onSaveInstanceState
- 主要保存类型有:boolean,int,float,long和String。以Key-Value键值对形式存储
- 存储位置在:/data/data/<包名>/shared_prefs目录下。
- 处理方式
以XML形式保存,xml 处理时Dalvik会通过自带底层的本地XML Parser解析,比如XMLpull方式,这样对于内存资源占用比较好。
相关API和参数
1、getSharedPreferences(String name, int mode)
获取SharedPreferences实例,name 要操作的xml文件名,modec参数具体如下:
Context.MODE_PRIVATE: 默认状态,只能由创建该文件的应用程序调用,即为私有的。
Context.MODE_APPEND :如果改文件已经存在,就将数据写入,而不是抹掉现有文件的末尾。
Context.MODE_WORLD_READABLE: 允许所有其他应用程序读取和创建文件的权限。
Context.MODE_WORLD_WRITEABLE: 允许所有其他应用程序写入、访问、创建文件的权限。
2、SharedPreferences 主要重要方法
Editor edit() //获取Editor对象
getXXX() //获取数据
3、Editor主要重要方法
SharedPreferences.Editor clear():清空SharedPreferences里所有数据
SharedPreferences.Editor putXxx(String key , xxx value): 向SharedPreferences存入指定key对应的数据,其中xxx 可以是boolean,float,int等各种基本类型据
SharedPreferences.Editor remove(): 删除SharedPreferences中指定key对应的数据项
boolean commit(): 当Editor编辑完成后,使用该方法同步提交修改
void apply();当Editor编辑完成后,使用该方法异步提交修改
原本只有commit()这一种提交方法,但因为这是一个同步的方法,若数据量大会堵塞UI线程,所以Google后来折腾出了apply()这么一个异步的提交方法,因此推荐使用apply方法进行提交
实现步骤
数据存储
- 根据Context.getSharedPreferences(String name, int mode)方法来获取SharedPreferences实例
- 调用edit()获取Editor对象
- 通过Editor存储key-value键值对数据
- 调用Editor对象的apply/commit方法提交数据,完成写入
SharedPreferences sp = ctx.getSharedPreferences("lock", MODE_PRIVATE);
//存入数据
Editor editor = sp.edit();
editor.putString("STRING_KEY", "string");
editor.putInt("INT_KEY", 0);
editor.putBoolean("BOOLEAN_KEY", true);
editor.apply();
代码执行后,会在/data/data/<包名>/shared_prefs目录下生产lock.xml文件。
数据读取
- 根据Context.getSharedPreferences(String name, int mode)方法来获取SharedPreferences实例
- 调用getXXX读取数据
//步骤1:创建一个SharedPreferences接口对象
SharedPreferences read = getSharedPreferences("lock", MODE_WORLD_READABLE);
//步骤2:获取文件中的值
String value = read.getString(STRING_KEY, "");
SharedPreferences对象与SQLite数据库相比
优点:操作简单,方便。因为免去了创建数据库、建表、写sql语句的麻烦
缺点:只能存储boolean,int,float,long和String五种简单的数据类型,进行简单的存取,无法按条件查询
2 、文件存储数据
- 适用于读写大量的流式数据,如媒体文件(图片,音视频等)和其他网络传输内容
- 缺点是更新数据麻烦
首先弄清楚内存、内部存储、外部存储、机身存储(内置存储)。
1、内存:Ram是计算机中重要的部件之一,它是与CPU沟通的桥梁。计算机中所有程序运行都是在内存中进行,所以说它是用于计算机运行时的,它不是用来存储数据的。
2、机身存储:也就是Rom,4.4以前只有内部存储,4.4以后包含内部存储和外部存储。
3、机身外部存储、外置SD卡都是安卓系统的外部存储
也就是说外部存储可以是机身外部存储和SD卡。
内部存储和外部存储的区别
- 内部存储,保存的文件只有自己应用才能访问
- 内部存储,在应用卸载时,系统会从内部存储中删除所有应用程序的文件。
- 外部存储,是可以移除的,所以使用外部存储,需要判断是否已挂载
应用的配置文件可以放内部存储,因为不需要访问限制,需要分享给其他应用的文件则放在外部存储。
应用默认安装到内存存储,可以在清单文件中指定android:installLocation属性来使应用安卓到外部存储空间
- 机身内存存储包含:持久存储和缓存
- 机身外部存储包含:公共存储(所有应用都可以使用),私有存储。而私有存储包含持久存储和缓存,私有存储是应用私有的,当应用卸载,所有文件也跟着删除。
1、内部存储
方法 | 说明 | 路径 |
---|---|---|
getFilesDir | 返回File表示应用程序的内部目录。 | /data/data/包名/files |
getCacheDir | 返回File表示应用程序临时缓存文件的内部目录。 | /data/data/包名/cache |
A、持续化存储
可以使用File()或者调用openFileOutput()以获取FileOutputStream
public FileOutputStream openFileOutput(String name, int mode) 的 mode
- MODE_PRIVATE:其成为您应用的私密状态
- MODE_WORLD_READABLE以及 MODE_WORLD_WRITEABLE:已弃用,可以用内容提供者来实现这方面功能
部分代码:
// 调用openFileOutput()以获取FileOutputStream 写入内部目录中的文件的内容
FileOutputStream outputStream = this.openFileOutput("test.txt", Context.MODE_PRIVATE);
//或者在目录中创建新文件
File file = new File(getFilesDir(), "test.txt");
outputStream = new FileOutputStream(file);
保存结果:
B、非持续化存储(缓存)
确保在不再需要时删除每个文件,并对在任何给定时间使用的内存量(例如1MB)实施合理的大小限制。
系统存储空间不足,则可能会在没有警告的情况下删除缓存文件。
目录获取 | 路径 |
---|---|
getCacheDir | /data/data/包名/cache |
getCodeCacheDir | /data/data/包名/code_cache |
文件获取方式一:
public static File createTempFile(String prefix, String suffix,File directory)
prefix:前缀,suffix:后缀,directory:目录
createTempFile获取的文件,为确保唯一性,会在prefix后面增加一串数据。
部分代码:
File file = File.createTempFile("testCache", ".txt", getCacheDir());
保存结果:
文件获取方式二:
在目录中创建新文件
部分代码
File file = new File(getFilesDir(), "test.txt");
结果
2、外部存储
- 分为公共存储和私有存储
- 需要请求存储权限和验证存储是否可用
注意事项:外部文件并非始终可用的,建议将重要的文件存储在内部存储。
请求存储权限
<manifest ... >
<uses-permission android:name = “android.permission.WRITE_EXTERNAL_STORAGE” /> ...
</ manifest>
注意:
- 请求写权限,会隐式拥有读取外部存储的权限READ_EXTERNAL_STORAGE
- 从Android 4.4(API级别19)开始,getExternalFilesDir()-does 访问 不需要READ_EXTERNAL_STORAGE 或WRITE_EXTERNAL_STORAGE 权限。、
验证外部存储是否可用
通过调用查询外部存储状态getExternalStorageState()。如果返回状态为MEDIA_MOUNTED,则可以读取和写入文件。如果是MEDIA_MOUNTED_READ_ONLY,则只能读取文件。
/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
A、公共存储
使用getExternalStoragePublicDirectory()方法获取File表示外部存储上相应目录
public static File getExternalStoragePublicDirectory(String type)
type有:DIRECTORY_MUSIC、DIRECTORY_PODCASTS、DIRECTORY_RINGTONES、DIRECTORY_ALARMS、DIRECTORY_NOTIFICATIONS、DIRECTORY_PICTURES、DIRECTORY_MOVIES、DIRECTORY_DOWNLOADS、DIRECTORY_DCIM、DIRECTORY_DOCUMENTS
注意事项:正确传递type参数很重要,确保系统正确处理文件。例如,保存在的文件 DIRECTORY_RINGTONES被系统媒体扫描仪分类为铃声而不是音乐。
部分代码:
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),albumName);
结果
B、私有存储
- 私有存储,只能自己访问,别的程序不能访问。
- 程序卸载的时候,会将外部私有存储文件也删除。
a、持续化存储
部分代码
File file = new File(getExternalFilesDir(DOWNLOAD_SERVICE), "test.txt");
效果:
b、非持续化存储(缓存)
文件保存部分代码
File file = new File(getExternalCacheDir(), "test.txt");
效果
还可以通过调用getExternalFilesDir()并向其传递一个名称来指定自己喜欢的目录类型,从而获取仅由您的应用程序使用的目录。
部分代码
file = new File(getExternalFilesDir(DOWNLOAD_SERVICE), "test111");
效果
文件存储目录如下:
目录获取 | 说明 | 路径 |
---|---|---|
getDataDir | 内部存储目录 | /data/data/包名 |
getFilesDir | 内部存储下的files目录 | /data/data/包名/files |
getCacheDir | 内部存储下的cache目录 | /data/data/包名/cache |
getCodeCacheDir | 内部存储下的code_cache目录 | /data/data/包名/code_cache |
getNoBackupFilesDir | 内部存储下的no_backup目录 | /data/data/包名/no_backup |
getExternalMediaDirs()[0] | 外部media存储目录 | /storage/emulated/0/Android/media/包名 |
getObbDir | 外部obb存储目录 | /storage/emulated/0/Android/obb/包名 |
getExternalCacheDir | 外部存储下的cache目录 | /storage/emulated/0/Android/data/包名/cache |
getExternalFilesDir(DOWNLOAD_SERVICE) | 外部存储下的files/download目录 | /storage/emulated/0/Android/data/包名/files/download |
Environment.getDataDirectory | data目录 | /data |
Environment.getRootDirectory | system目录 | /system |
Environment.getDownloadCacheDirectory | data/cache目录 | /data/cache |
Environment.getExternalStorageDirectory | 外部存储目录 | /storage/emulated/0 |
Environment.getExternalStoragePublicDirectory(XXX) | 外部公共存储目录 | /storage/emulated/0/XXX |
三、补充
1、在多个存储位置之间选择
外部存储有机身外部存储和SD存储
这意味着该设备有两个不同的外部存储目录,因此您需要选择在将“私有”文件写入外部存储时使用哪个目录。
- 从Android 4.4(API级别19)开始,您可以通过调用访问这两个位置getExternalFilesDirs(),该位置 返回一个File包含每个存储位置条目的数组。阵列中的第一个条目被视为主要外部存储,您应该使用该位置,除非它已满或不可用。
- 如果您的应用支持Android 4.3及更低版本,则应使用支持库的静态方法ContextCompat.getExternalFilesDirs()。这总是返回一个File数组,但是如果设备运行的是Android 4.3及更低版本,那么它只包含一个主外部存储条目(如果有第二个存储位置,则无法在Android 4.3及更低版本上访问它)。
推荐阅读:彻底搞懂Android文件存储---内部存储,外部存储以及各种存储路径解惑
2、删除文件和查询可用空间
1、删除文件
myFile.delete ();
或者
myContext.deleteFile (fileName );
2、查询可用空间
用File类的getFreeSpacegetTotalSpace和方法
- public long getFreeSpace() 返回指定的分区中未分配的字节数。
- public long getTotalSpace () 返回指定的分区的大小。
File dir = getExternalCacheDir();
Log.e(TAG,"TotalSpace="+dir.getTotalSpace());
Log.e(TAG,"FreeSpace="+dir.getFreeSpace());
3、卸载应用会删除哪些文件?
- 您在内部存储上保存的所有文件。
- 使用保存外部存储的所有文件getExternalFilesDir()。
但是,我们应手动删除getCacheDir()定期创建的所有缓存文件, 并定期删除不再需要的其他文件。
4、java mkdir()和mkdirs()区别
mkdirs()可以建立多级文件夹, mkdir()只会建立一级的文件夹, 如下:
new File("/tmp/one/two/three").mkdirs();
执行后, 会建立tmp/one/two/three四级目录
new File("/tmp/one/two/three").mkdir();
则不会建立任何目录, 因为找不到/tmp/one/two目录, 结果返回false
3、SQLite
(1)SQLite介绍
SQLite是一个轻量级的数据库,不是一个C/S结构的数据库引擎,而是被集成到用户程序中。这个库也被动态链接,应用程序中直接调用相关API来使用SQLite功能,比跨进程通通信更有效率。SQLite将整个数据库作为一个单独的、可跨平台使用的文件存储在主机中
SQLite 是在世界上最广泛部署的 SQL 数据库引擎。像Android、IOS等移动操作系统中的数据库实现都是用SQLite
优点:
- 无服务器、零配置、事务性。
- 存储在一个单一磁盘文件中的一个完整数据库
- 数据库文件可以在不同字节顺序的机器自由共享
- 支持数据库大小2TB
- 足够小,全部源代码250KB
- 对数据操作快
- 开源
特点:轻量级、独立性、隔离性、跨平台、多语言接口、线程安全性
优势:
- 多线程访问数据、
- 需要事务处理、
- 应用需要处理变化复杂的数据结构、
- 数据库对于创建它们的包套件是私有的。
结构
1.接口(Interface)
接口由SQLite C API组成,也就是说不管是程序、脚本语言还是库文件,最终都是通过它与SQLite交互的(我们通常用得较多的ODBC/JDBC最后也会转化为相应C API的调用)。
2.编译器(Compiler)
在编译器中,分词器(Tokenizer)和分析器(Parser)对SQL进行语法检查,然后把它转化为底层能更方便处理的分层的数据结构---语法树,然后把语法树传给代码生成器(code generator)进行处理。而代码生成器根据它生成一种针对SQLite的汇编代码,最后由虚拟机(Virtual Machine)执行。
3.虚拟机(Virtual Machine)
架构中最核心的部分是虚拟机,或者叫做虚拟数据库引擎(Virtual Database Engine,VDBE)。它和Java虚拟机相似,解释执行字节代码。VDBE的字节代码由128个操作码(opcodes)构成,它们主要集中在数据库操作。它的每一条指令都用来完成特定的数据库操作(比如打开一个表的游标)或者为这些操作栈空间的准备(比如压入参数)。总之,所有的这些指令都是为了满足SQL命令的要求(关于VM,后面会做详细介绍)。
4.后端(Back-End)
后端由B-树(B-tree),页缓存(page cache,pager)和操作系统接口(即系统调用)构成。B-tree和page cache共同对数据进行管理。B-tree的主要功能就是索引,它维护着各个页面之间的复杂的关系,便于快速找到所需数据。而pager的主要作用就是通过OS接口在B-tree和Disk之间传递页面。
参考:
(2)SQL语言学习
参考:
(3)SQListeOpenHelper
是一个数据库创建和版本管理的帮助类
SQListeOpenHelper子类需实现onCreate和onUpgrade方法
getWritableDatabase() / getReadableDatabase() 第一次被调用时才会进行数据库创建 / 打开
/**
* 创建数据库子类,继承自SQLiteOpenHelper类
* 需 复写 onCreate()、onUpgrade()
*/
public class MySQListOpenHelper extends SQLiteOpenHelper {
public static final String USER_TABLE_NAME = "user";
public static final String JOB_TABLE_NAME = "job";
//数据库版本号
private static Integer Version = 1;
public MySQListOpenHelper(Context context,String name,int version)
{
this(context,name,null,version);
}
public MySQListOpenHelper(Context context,String name)
{
this(context, name, Version);
}
public MySQListOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
/**
* 数据库第1次创建时 则会调用
* 即 第1次调用 getWritableDatabase() / getReadableDatabase()时调用
**作用:创建数据库 表 & 初始化数据
* SQLite数据库创建支持的数据类型: 整型数据、字符串类型、日期类型、二进制
*/
@Override
public void onCreate(SQLiteDatabase db) {
// 创建数据库1张表
// 通过execSQL()执行SQL语句
String sql = "create table "+USER_TABLE_NAME+"(id int primary key,name varchar(200))";
String sql1 = "create table "+JOB_TABLE_NAME+"(id int primary key,name varchar(200))";
db.execSQL(sql);
db.execSQL(sql1);
// 注:数据库实际上是没被创建 / 打开的(因该方法还没调用)
// 直到getWritableDatabase() / getReadableDatabase() 第一次被调用时才会进行创建 / 打开
}
/**
* 复写onUpgrade()
* 调用时刻:当数据库升级时则自动调用(即 数据库版本 发生变化时)
* 作用:更新数据库表结构
* 注:创建SQLiteOpenHelper子类对象时,必须传入一个version参数,该参数 = 当前数据库版本, 若该版本高于之前版本, 就调用onUpgrade()
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 参数说明:
// db : 数据库
// oldVersion : 旧版本数据库
// newVersion : 新版本数据库
// 使用 SQL的ALTER语句
String sql = "alter table user add sex varchar(8)";
db.execSQL(sql);
}
}
因为SQLite对多线程支持并不完善,所以需要用一个单例来管理SQListeOpenHelper,以保证线程安全。
package com.example.lenovo.mpplication.data.provider;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class BDManager {
private static SQLiteOpenHelper mMySQListOpenHelper;
private static SQLiteDatabase mDB;
private static SQLiteOpenHelper getDatabaseHelper(Context context, String name) {
if (null == mMySQListOpenHelper) {
mMySQListOpenHelper = new MySQListOpenHelper(context, name)
}
return mMySQListOpenHelper;
}
public static synchronized SQLiteDatabase getDB(Context context, String name) {
if (null == mDB) {
mDB = getDatabaseHelper(context, name).getReadableDatabase();
}
return mDB;
}
public static synchronized void initDB(Context context, String name) {
getDB(context, name);
}
}
(4)SQLiteDatabase
SQLiteDatabase提供了基本的增删改查方法,可以执行SQL命令,执行其他常见数据库管理任务的方法。
常用方法:
close() // 关闭数据库
(Cursor) rawQuery(String sql, String[] selectionArgs) //运行一个预置的SQL语句,返回带游标的数据集(与上面的语句最大的区别 = 防止SQL注入)
(int) delete(String table,String whereClause,String[] whereArgs) // 删除数据行
(long) insert(String table,String nullColumnHack,ContentValues values) // 添加数据行
(int) update(String table, ContentValues values, String whereClause, String[] whereArgs) // 更新数据行
(void) execSQL(String sql) // 执行一个SQL语句,可以是一个select or 其他sql语句
(Cursor) query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) // 查询指定的数据表返回一个带游标的数据集。
// 各参数说明:
// table:表名称 ,colums:列名称数组 ,selection:条件子句,相当于where ,selectionArgs:条件语句的参数数组
// groupBy:分组 , having:分组条件 , orderBy:排序类 , limit:分页查询的限制 , Cursor:返回值,相当于结果集ResultSet
注意:对SQLiteDatabase数据库操作需统一到同一个线程队列管理,而业务层使用缓存同步,避免多线程操作数据库导致不同步和死锁问题
参考:
- android应用性能优化最佳实践(罗老师)
- Android :SQLlite数据库 使用手册
5、ContentProvider
定义:内容提供者,是 Android 四大组件之一
作用:对外共享数据
使用ContentProvider的好处是统一了数据访问方式,实际上是对SQListOpenHelper的进一步封装,通过Uri映射来判断要操作数据库哪个表
(1)统一资源标识符(URI)
- schema:固定为content://
- authority:ContentProvider的唯一标记,调用者可以通过这个找到它
- path:要操作的数据库表
- id:表中的某个记 录
// 设置URI
Uri uri = Uri.parse("content://com.carson.provider/User/1")
// 上述URI指向的资源是:名为 `com.carson.provider`的`ContentProvider` 中表名 为`User` 中的 `id`为1的数据
// 特别注意:URI模式存在匹配通配符* & #
// *:匹配任意长度的任何有效字符的字符串
// 以下的URI 表示 匹配provider的任何内容
content://com.example.app.provider/*
// #:匹配任意长度的数字字符的字符串
// 以下的URI 表示 匹配provider中的table表的所有行
content://com.example.app.provider/table/#
(2)ContentResolver
通过Uri来定位注册到系统的ContentProvider,找到ContentProvider之后通过ContentResolver来操作对应的数据库
获取方式:Context.getContentResolver()
常用方法:insert、query、delete、update等待
例如:读取联系人
(3)自定义ContentProvider
ContentProvider实际上是对SQLiteOpenHelper的进一步封装,通过Uri映射要判断选择需要操作的数据库中哪个表,并进行增删查改。
重写insert、query、update、delete、getType方法
public class MyProvider extends ContentProvider {
private SQLiteDatabase db;
public static final String AUTOHORITY = "cn.scu.myprovider";
public static final int User_Code = 1;
public static final int Job_Code = 2;
private static final UriMatcher mUriMatcher;
static {
mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 初始化
mUriMatcher.addURI(AUTOHORITY,"user",User_Code);
mUriMatcher.addURI(AUTOHORITY,"job",Job_Code);
// 若URI资源路径 = content://cn.scu.myprovider/user ,则返回注册码User_Code
// 若URI资源路径 = content://cn.scu.myprovider/job ,则返回注册码Job_Code
}
/**
* 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名
*/
private String getTableName(Uri uri){
String tableName = null;
switch (mUriMatcher.match(uri)) {
case User_Code:
tableName = MySQListOpenHelper.USER_TABLE_NAME;
break;
case Job_Code:
tableName = MySQListOpenHelper.JOB_TABLE_NAME;
break;
}
return tableName;
}
@Override
public boolean onCreate() {
// 在ContentProvider创建时对数据库进行初始化
// 运行在主线程,故不能做耗时操作,此处仅作展示
SQLiteOpenHelper mDbHelper= new MySQListOpenHelper(getContext(),"test_person");
db = mDbHelper.getWritableDatabase();
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
return db.query(getTableName(uri),projection,selection,selectionArgs,null,null,sortOrder,null);
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
// 得到数据类型,即返回当前 Url 所代表数据的MIME类型
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
db.insert(getTableName(uri),null,values);
// 当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)
getContext().getContentResolver().notifyChange(uri, null);
return uri;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
}
UriMatcher
作用:
- 在ContentProvider 中注册URI
- 根据 URI 匹配 ContentProvider 中对应的数据表
ContentObserver
定义:内容观察者
作用:观察 Uri引起 ContentProvider 中的数据变化 & 通知外界(即访问该数据访问者)
// 步骤1:注册内容观察者ContentObserver
getContentResolver().registerContentObserver(uri);
// 通过ContentResolver类进行注册,并指定需要观察的URI
// 步骤2:当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)
public class UserContentProvider extends ContentProvider {
public Uri insert(Uri uri, ContentValues values) {
db.insert("user", "userid", values);
getContext().getContentResolver().notifyChange(uri, null);
// 通知访问者
}
}
// 步骤3:解除观察者
getContentResolver().unregisterContentObserver(uri);
// 同样需要通过ContentResolver类进行解除
Android:关于ContentProvider的知识都在这里了!
二、存储优化
1、序列化
Serializable、Parcelable
Serializable是java序列化接口,Parcelable是安卓序列化接口
- Serializable会产生大量的临时变量,序列化过程会消耗更多内存,造成频繁的GC
- Parcelable不能将数据存储在磁盘中(因为Parcel本质是为了更好的实现对象在IPC间传递,不是一个通用的序列化机制)
Gson
Gson实现JSON的序列化和反序列化
优点:执行速度快,内存使用效率高
主要方法是fromJson和toJson
Nano Proto Buffers
FlatBuffers
FlatBuffers是一个高效的跨平台序列化类库,是Google开发的,是为了应用在游戏开发,以及其他注重性能的应用上。比JSON快得多。
https://www.oschina.net/news/75092/android-flatbuffers-json
注意:为了避免序列化带来的性能问题,可以考虑使用SharedPefrence或者sqlite来存储数据,避免先把复杂数据进行序列化的操作。
2、SharedPerences优化
Editor的commit和apply方法中,commit是同步写入,apply是异步写入
以下方式可提升性能
- 不需要返回值使用apply
- 在批量操作,先获取一个editor,进行批量操作,然后调用apply
- 避免频繁读写SharedPerences(因为commitToMemory会锁定SharedPerences对象,put和getEditor会锁定Editor对象,写入磁盘会锁定一个写入锁)
3、数据库优化
SQliteStatement
作用:提高性能,也解决SQL注入问题
/**
* 使用SQLiteStatement的executeInsert方法插入数据
* @return 返回执行所需要的时间
*/
public long insertStatement()
{
long start = System.currentTimeMillis();
for(User user:users){
SQLiteStatement statement= db.compileStatement(sql_insert.toString());
statement.bindString(1, user.getName());
statement.bindLong(2, user.getGender());
statement.bindLong(3, user.getAge());
statement.bindString(4, user.getPhoneNumber());
statement.bindString(5, user.getAddress());
statement.executeInsert();
}
long end = System.currentTimeMillis();
return end - start;
}
}
使用事务
特征:原子提交、性能好
使用方式
db.beginTransaction();
xxxx….
db.setTransactionSuccessful();
db.endTransaction();
在endTransaction之前调用setTransactionSuccessful将事务标记成功,从beginTransaction开始的操作都会被提交,没有调用setTransactionSuccessful则回滚事务
使用索引
作用:提升查找效率
缺点:
- 插入、更新、删除数据慢
- 增加数据库大小
以下场景不建议使用索引
- 表较小
- 大量null值列
- 频繁插入、删除、更新
- 频繁操作列
异步线程管理对数据库的操作
数据库操作是耗时的,异步线程管理可以提高性能,保证数据的同步避免死锁。
提升查询性能
影响查询性能方面
- 查询数据量大小
- 排序复杂度
- 查询数据的列数
数据库第三方框架
OrmLite 、GreenDao、LitePal、afinal
- OrmLite、LitePal、afinal根据反射进行数据库的各个具体操作
- GreenDao不是根据反射,而是直接使用人工生产业务需要的DAO和Model文件,避免因反射带来的性能损耗
参考:
安卓开发进阶从小工到专家
android应用性能优化最佳实践