安卓存储有内外之分,同时还提供了一存储键值对的 SharedPreference ,SQLite等共五种存储方式。

SharedPreferences

支持数据类型:布尔值、浮点值、整型值、长整型和字符串。
一个应用可拥有多个。

使用方法:

  • getSharedPreferences() - 如果您需要多个按名称(使用第一个参数指定)识别的首选项文件,请使用此方法。
  • getPreferences() - 如果您只需要一个用于 Activity 的首选项文件,请使用此方法。 由于这将是用于 Activity 的唯一首选项文件,因此无需提供名称。

要写入值:

  1. 调用 edit() 以获取 SharedPreferences.Editor。
  2. 使用 putBoolean() 和 putString() 等方法添加值。
  3. 使用 commit() 提交新值

要读取值,请使用 getBoolean() 和 getString() 等 SharedPreferences 方法。

ContextImpl/Context

我们在程序的始终,都会一直遇到一个叫做上下文的东西。

Context 是一个抽象类,而我们的 Activity 就继承自 Context。所以可以使用 Context 定义的很多方法,也就是后面我们会说道的很多关于存储的方法。

但是 Context 的实现,实际上是位于 ContextImpl.java 中,这个类,在开发代码的时候没有看到,只在谷歌的源码目录上找到了:

https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/ContextImpl.java

内部存储

内部存储是设备内部自身的存储,是由安卓系统严格进行管理的。保存在内部存储中的文件,其他应用和用户是无法访问的。文件会随着APP的卸载而删除。

要创建私有文件并写入到内部存储:

  1. 使用文件名称和操作模式调用 openFileOutput()。 这将返回一个 FileOutputStream。
  2. 使用 write() 写入到文件。
  3. 使用 close() 关闭流式传输。

我们来看一下代码的实现:


final LoadedApk packageInfo = systemContext.mPackageInfo;

@Override
public FileInputStream openFileInput(String name)
throws FileNotFoundException {
File f = makeFilename(getFilesDir(), name);
return new FileInputStream(f);
}

private File makeFilename(File base, String name) {
if (name.indexOf(File.separatorChar) < 0) {
return new File(base, name);
}
throw new IllegalArgumentException(
"File " + name + " contains a path separator");
}

@Override
public File getFilesDir() {
synchronized (mSync) {
if (mFilesDir == null) {
mFilesDir = new File(getDataDir(), "files");
}
return ensurePrivateDirExists(mFilesDir);
}
}

@Override
public File getDataDir() {
if (mPackageInfo != null) {
File res = null;
if (isCredentialProtectedStorage()) {
res = mPackageInfo.getCredentialProtectedDataDirFile();
} else if (isDeviceProtectedStorage()) {
res = mPackageInfo.getDeviceProtectedDataDirFile();
} else {
res = mPackageInfo.getDataDirFile();
}
if (res != null) {
if (!res.exists() && android.os.Process.myUid() == android.os.Process.SYSTEM_UID) {
Log.wtf(TAG, "Data directory doesn't exist for package " + getPackageName(),
new Throwable());
}
return res;
} else {
throw new RuntimeException(
"No data directory found for package " + getPackageName());
}
} else {
throw new RuntimeException(
"No package details found for package " + getPackageName());
}
}

可以看到,我们打开的输入文件目录应该是位于 /data/data/applicaiontid/files/ 目录下的。(小米手机是这样,其他手机就不知道了,要看手机定义的数据目录)

要从内部存储读取文件:

  1. 调用 openFileInput() 并向其传递要读取的文件名称。 这将返回一个 FileInputStream。
  2. 使用 read() 读取文件字节。
  3. 然后使用 close() 关闭流式传输。

对于位于 res/raw 中的文件,可以用 openRawResource(R.raw.<filename>) 来打开。

缓存文件

如果您想要缓存一些数据,而不是永久存储这些数据,应该使用 getCacheDir() 来打开一个 File,它表示您的应用应该将临时缓存文件保存到的内部目录。

当设备的内部存储空间不足时,Android 可能会删除这些缓存文件以回收空间。 但您不应该依赖系统来为您清理这些文件, 而应该始终自行维护缓存文件,使其占用的空间保持在合理的限制范围内(例如 1 MB)。 当用户卸载您的应用时,这些文件也会被移除。

@Override
public File getCacheDir() {
synchronized (mSync) {
if (mCacheDir == null) {
mCacheDir = new File(getDataDir(), "cache");
}
return ensurePrivateCacheDirExists(mCacheDir, XATTR_INODE_CACHE);
}
}

/data/data/applicaiontid/cache 目录。

其他方法

  • getFilesDir() 获取 /data/data/applicaiontid/files 目录
  • getDir(String name, int mode) 获取/data/data/applicaiontid/app_name 目录
  • deleteFile() 删除/data/data/applicaiontid/files 下文件
  • fileList() 列出 /data/data/applicaiontid/files

外部存储

在使用之前我们都要检查介质是否可用:

/* 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;
}

更多内容请参考:Android的文件系统探究 一文。