Android本地存储方式有5种,分别是SharedPreferences存储、文件存储、SQLite存储、ContentProvider和网络存储方式。
存储方式 | 简介 | 特点 | 默认存储路径 | 项目应用 |
---|---|---|---|---|
SharedPreferences | SharedPreferences是一种轻量级存储类,数据存储格式为键值对 | 保存一些简单的配置参数等轻量级数据 | /data/data/packageName/shared_prefs/xxx.xml | 登录界面保存上次登录成功的用户名和密码 |
文件存储 | 文件存储是通过I/O流从内部存储或SD卡(外存)中读写数据 | 内存中存储一些较小、安全性较高的数据 外存存储较大的文件或简单的文本/二进制数据 |
/data/data/packageName/files | 项目所需图片、音频文件 较大的数据信息(.json/.xml) |
SQLite | 通过SQLite,一种轻型、嵌入式的ACID关系型数据库对数据存储,使用SQL语言 Android为此数据库提供了SQLiteDatabase类,封装了操作数据库的API |
数据量不是很大且逻辑关系较为复杂的数据(结构性数据) | /data/data/packageName/databases | 存储本地数据信息(结构性数据) |
ContentProvider | 作为Android四大组件之一,ContentProvider一般为存储和获取数据提供统一的接口,可以在不同的应用程序之间共享数据 仅作为传输数据的媒介,数据源具有多样性 |
Android手机系统数据 跨进程数据 |
通过URI对象 | 获取手机短信、联系人等 进程间数据共享、交换 |
网络存储 | 与后台交互,将数据存储在后台数据库中 | 数据量大,逻辑关系复杂的数据交给后台处理 | 远程服务器 | 庞大的数据库 较大的音频、图片 |
//获取一个文件名为test、权限为private的xml文件的SharedPreferences对象
SharedPreferences sharedPreferences = getSharedPreferences("test", MODE_PRIVATE);
//得到SharedPreferences.Editor对象,并保存key-value键值对数据到该对象中
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("username", et_username.getText().toString().trim());
editor.putString("password", et_password.getText().toString().trim());
//apply提交数据,保存key-value对到文件中
editor.apply();
// editor.commit();
// apply没有返回值而commit返回boolean表明修改是否提交成功
// apply是将修改数据原子提交到内存, 而后异步真正提交到硬件磁盘,
// 而commit是在当前线程同步的提交到硬件磁盘,会阻塞调用它的线程。
// 因此,推荐使用apply:在多个并发的提交commit的时候,他们会等待正在处理的commit保存到磁盘后在操作,从而降低了效率。而apply只是原子的提交到内容,后面有调用apply的函数的将会直接覆盖前面的内存数据,这样从一定程度上提高了很多效率。
super.onStop();
获取数据
et_username = (EditText) findViewById(R.id.et_username);
et_password = (EditText) findViewById(R.id.et_password);
SharedPreferences sharedPreferences = this.getSharedPreferences("test", MODE_PRIVATE);
et_username.setText(sharedPreferences.getString("username",""));
et_password.setText(sharedPreferences.getString("password",""));
SP 调用 apply 方法,会创建一个等待锁mcr.writtenToDiskLatch.await()放到 QueuedWork 中,并将真正数据持久化封装成一个任务放到异步队列中执行,任务执行结束会释放锁。
Activity onStop 以及 Service 处理 onStop,onStartCommand 时,执行 QueuedWork.waitToFinish() 等待所有的等待锁释放。
方法 | 路径 | 解释 |
---|---|---|
getFilesDir() | /data/data/package-name/files | 返回应用内部存放文件的目录的绝对路径。 |
getCacheDir() | /data/data/package-name/cache | 返回应用内部存储的临时目录。系统内部存储即将耗尽的时候,可能会删除这个目录下的文件。 |
getDir(String name, int mode) | /data/data/package-name/name | 可用于在应用内部存储根目录下创建或打开自定义的文件目录。name表示自定义的文件目录名。mode表示操作模式,用来控制该目录的读写权限,默认为MODE_PRIVATE,表示仅仅应用自身可以访问。 |
附:/data/user/0/packname/目录(系统创建App专属文件):
cache下存放缓存数据,databases下存放使用SQLite存储的数据,files下存放普通数据(log数据,json型数据等),shared_prefs下存放使用SharedPreference存放的数据。这些文件夹都是由系统创建的。
public static void writeInternal(String fileName, String content) throws IOException {
// 获取文件绝对路径
String filePathName = context.getFilesDir().getAbsolutePath()+"/"+fileName;
// 打开文件输出流
FileOutputStream fileOutputStream = new FileOutputStream(filePathName);
// 写数据到文件中
fileOutputStream.write(content.getBytes());
// 关闭输出流
fileOutputStream.close();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
files = getExternalFilesDirs(Environment.MEDIA_MOUNTED);
for(File file:files){
Log.e("main",file);
}
}
// 如果有SD卡,打印路径有2条:
// /storage/emulated/0/Android/data/packname/files/mounted 机身存储的外部存储路径
// /storage/sdcard/0/Android/data/packname/files/mounted SD卡存储的外部存储路径
方法 | 路径 | 解释 |
---|---|---|
Environment.getExternalStoragePublicDirectory(Environment.Type) Environment.getExternalStorageDirectory(Environment.Type) |
/storage/emulated/0 | 获取外部存储的公共文件路径 |
getExternalFilesDir(Environment.Type) | /storage/emulated/0/Android/data/package-name/files | 获取某个应用在外部存储的私有文件路径 |
getExternalCacheDir() | /storage/emulated/0/Android/data/package-name/cache | 获取某个应用在外部存储的cache路径 |
其中,Environment的Type参数有:
Environment的Type参数 | 对应模拟路径 | 解释说明 |
---|---|---|
DIRECTORY_DCIM | /storage/emulated/0/DCIM | 相册 |
DIRECTORY_DOCUMENTS | /storage/emulated/0/Documents | 文件 |
DIRECTORY_DOWNLOADS | /storage/emulated/0/Download | 下载文件 |
DIRECTORY_MUSIC | /storage/emulated/0/Music | 音乐 |
DIRECTORY_PICTURES | /storage/emulated/0/Pictures | 图片 |
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
(2)检测外部存储是否可用(外部存储可能不可用,比如用户将其挂载到了电脑或者移除了提供外部存储的SD卡)
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
(3)公共文件目录的获取
公共文件目录可以通过getExternalStoragePublicDirectory()方法获取,需要指定文件类型参数,以便外部统一处理。比如DIRECTORY_MUSIC或DIRECTORY_PICTURES。比如:
public File getAlbumStorageDir(String albumName) {
// Get the directory for the user's public pictures directory.
File file = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), albumName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
应用卸载时,系统不会删除这些文件。
(4)私有文件目录的获取
调用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;
}
上述方法会在Environment.DIRECTORY_PICTURES目录下创建albumName值的目录,当然你也可以将第一个参数传为null,则会在你应用外部存储私有目录的根目录下创建。
路径 | 系统文件 | 缓存文件 |
---|---|---|
路径 | /system | /cache |
获取方式 | Environment.getRootDirectory() | Environment.getDownloadCacheDirectory() |
Environment.getExternalStorageDirectory().getAbsolutePath();// /storage/emulated/0
String dir = Environment.getExternalStorageDirectory().getAbsolutePath() + "/tencent/MicroMsg/WeiXin/";
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
//获取内部存储状态
String state = Environment.getExternalStorageState();
//如果状态不是mounted,无法读写
if (!state.equals(Environment.MEDIA_MOUNTED)) {
return;
}
Calendar now = new GregorianCalendar();
SimpleDateFormat simpleDate = new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault());
String fileName = simpleDate.format(now.getTime());
(2)文件URL命名
每张网络图片都有一个对应的图片URL,可以根据图片的URL来对图片命名。
try {
File file = new File(dir + fileName + ".jpg");
FileOutputStream out = new FileOutputStream(file);
mBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);//将Bitmap压缩到一个文件输出流
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
//保存图片后发送广播通知更新系统图库(将图片保存在系统图库)
Uri uri = Uri.fromFile(file);
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
public class ImgUtils {
//保存文件到指定路径
public static boolean saveImageToGallery(Context context, Bitmap bmp) {
// 首先保存图片
String storePath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "dearxy";
File appDir = new File(storePath);
if (!appDir.exists()) {
appDir.mkdir();
}
String fileName = System.currentTimeMillis() + ".jpg";
File file = new File(appDir, fileName);
try {
FileOutputStream fos = new FileOutputStream(file);
//通过io流的方式来压缩保存图片
boolean isSuccess = bmp.compress(Bitmap.CompressFormat.JPEG, 60, fos);
fos.flush();
fos.close();
//把文件插入到系统图库
//MediaStore.Images.Media.insertImage(context.getContentResolver(), file.getAbsolutePath(), fileName, null);
//保存图片后发送广播通知更新数据库
Uri uri = Uri.fromFile(file);
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
if (isSuccess) {
return true;
} else {
return false;
}
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
}
一个共享锁允许多个数据库联接在同一时刻从这个数据库文件中读取信息。“共享”锁将不允许其他联接针对此数据库进行写操作。
一个临界锁允许其他所有已经取得共享锁的进程从数据库文件中继续读取数据。但是它会阻止新的共享锁的生成。也就说,临界锁将会防止因大量连续的读操作而无法获得写入的机会。
public class DatabaseHelper extends SQLiteOpenHelper {
// 数据库版本号
private static Integer Version = 1;
/**
* 构造函数
* 在SQLiteOpenHelper的子类中,必须有该构造函数
*/
public DatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory,
int version) {
// 参数说明
// context:上下文对象 name:数据库名称 param:一个可选的游标工厂(通常是 Null) version:当前数据库的版本,值必须是整数并且是递增的状态
// 必须通过super调用父类的构造函数
super(context, name, factory, version);
}
/**
* 复写onCreate()
* 调用时刻:当数据库第1次创建时调用
* 作用:创建数据库 表 & 初始化数据(getWritableDatabase() / getReadableDatabase() 第一次被调用时)
* SQLite数据库创建支持的数据类型: 整型数据、字符串类型、日期类型、二进制
*/
@Override
public void onCreate(SQLiteDatabase db) {
// 创建数据库1张表
// 通过execSQL()执行SQL语句(此处创建了1个名为person的表)
String sql = "create table person(id integer primary key autoincrement,name varchar(64),address varchar(64))";
db.execSQL(sql);
}
/**
* 复写onUpgrade()
* 调用时刻:当数据库升级时则自动调用(即 数据库版本 发生变化时)
* 作用:更新数据库表结构
* 注:创建SQLiteOpenHelper子类对象时,必须传入一个version参数,该参数 = 当前数据库版本, 若该版本高于之前版本, 就调用onUpgrade()
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 参数说明:
// db : 数据库 oldVersion : 旧版本数据库 newVersion : 新版本数据库
// 使用 SQL的ALTER语句,在person表中增加 sex 列
String sql = "alter table person add sex varchar(8)";
db.execSQL(sql);
}
}
// 步骤1:创建DatabaseHelper对象(注:此时还未创建数据库)
SQLiteOpenHelper dbHelper = new DatabaseHelper(SQLiteActivity.this,"test_carson");
// 步骤2:真正创建 / 打开数据库
SQLiteDatabase sqliteDatabase = dbHelper.getWritableDatabase(); // 创建 or 打开 可读/写的数据库
SQLiteDatabase sqliteDatabase = dbHelper.getReadableDatabase(); // 创建 or 打开 可读的数据库
// 插入数据
String sql = "insert into user (id,name) values (1,'carson')";
db.execSQL(sql) ;
// 修改数据
String sql = "update [user] set name = 'zhangsan' where id="1";
db.execSQL(sql);
// 删除数据
String sql = "delete from user where id="1";
db.execSQL(sql);
// 查询数据
// db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy);
Cursor cursor = sqliteDatabase.query("user", new String[] { "id","name" }, "id=?", new String[] { "1" }, null, null, null);
if(cursor.moveToFirst()) {
String name = cursor.getString(cursor.getColumnIndex("name"));
}
// 关闭数据库
db.close();