文件存储
前言
手机自身带的存储叫机身存储,SD卡存储(小小黑色卡)不能叫机身存储,它和手机是可以分离的。机身存储因手机体统的不同被划分的也不同。
内部存储和外部存储
- 内部存储
内部存储位于系统中很特殊的一个位置,如果你想将文件存储于内部存储中,那么文件默认只能被你的应用访问到,且一个应用所创建的所有内部存储文件都在和应用包名相同的目录下。也就是说应用创建于内部存储的文件,与这个应用是关联起来的。当一个应用卸载之后,内部存储中的这些文件也被删除。从技术上来讲如果你在创建内部存储文件的时候将文件属性设置成可读,其他app能够访问自己应用的数据,前提是他知道你这个应用的包名,如果一个文件的属性是私有(private),那么即使知道包名其他应用也无法访问。 内部存储空间十分有限,因而显得可贵,另外,它也是系统本身和系统应用程序主要的数据存储所在地,一旦内部存储空间耗尽,手机也就无法使用了。所以对于内部存储空间,我们要尽量避免使用。Shared Preferences和SQLite数据库都是存储在内部存储空间上的。内部存储一般用Context来获取和操作。 - 外部存储
最容易混淆的是外部存储,因为老的Android系统的跟新的Android系统是有差别的,很多人去网上查找资料,看了一下以前的资料,又看了一下现在的资料,但是发现它们说法不一样然后就困惑了。首先说一个大家普遍的概念“如果在pc机上是区分外部存储和内部存储的话,那么电脑自带的硬盘算是内部存储,U盘或者移动硬盘就是外部存储了。”因此很多人带着这样的理解去看待安卓手机,把内置存储(机身存储)当做内部存储,而把扩展的SD卡当做是外部存储。这么认为确实没错,因为在4.4(API19)以前的手机上确实是这样的,手机自身带的存储卡就是内部存储,而扩展的SD卡就是外部存储。但是从4.4的系统开始,很多的中高端机器都将自己的机身存储扩展到了8G以上,比如有的人的手机是16G的,有的人的手机是32G的,但是这个16G,32G是内部存储吗,不是的!它们依然是外部存储,也就是说4.4系统及以上的手机将机身存储存储(手机自身带的存储叫做机身存储)在概念上分成了”内部存储internal” 和”外部存储external” 两部分。既然16G,32G是外部存储,那有人又有疑惑了,那4.4系统及以上的手机要是插了SD卡呢,SD卡又是什么呢,如果SD卡也是外部存储的话,那怎么区分机身存储的外部存储跟SD卡的外部存储呢?对,SD卡也是外部存储,那怎么区分呢,在4.4以后的系统中,API提供了这样一个方法来遍历手机的外部存储路径:
File[] files;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
files = getExternalFilesDirs(Environment.MEDIA_MOUNTED);
for(File file:files){
Log.e("main",file);
}
}
如果你的手机插了SD卡的话,那么它打印的路径就有两条了,它的结果如下:
com.files是我的应用包名
/storage/emulated/0/Android/data/com.file/files/mounted
/storage/S5X5-1283/Android/data/com.file/files/mounted
其中/storage/emulated/0目录就是机身存储的外部存储路径
/storage/S5X5-1283/就是SD卡的路径 (手机不同结果可能不同)
他们统称为外部存储
注:4.4以前,机身存储就是内部存储,SD卡存储就是外部存储。4.4以后机身存储分为内部存储和外部存储,SD卡属于外部存储。
各方法介绍
直接看代码:
if(v.getId() == R.id.getPath_bt){
Log.d("TAG 0" , mContext.getApplicationInfo().dataDir);
Log.d("TAG 1" , mContext.getDataDir().getAbsolutePath());
Log.d("TAG 2" , mContext.getCacheDir().getAbsolutePath());
Log.d("TAG 3" , mContext.getFilesDir().getAbsolutePath());
// 获取默认的数据库
Log.d("TAG 4" , mContext.getDatabasePath("name").getAbsolutePath());路径
Log.d("TAG 5" , mContext.getDir("name1" , MODE_PRIVATE).getAbsolutePath());
Log.d("TAG 6" , mContext.getCodeCacheDir().getAbsolutePath());
Log.d("TAG 7" , mContext.getPackageCodePath());
Log.d("TAG 8" , mContext.getExternalCacheDir().getAbsolutePath());
File [] files = mContext.getExternalCacheDirs() ;
for (int i = 0 ; i < files.length ; i++ ){
Log.d("TAG 9" , files[i].getAbsolutePath());
}
Log.d("TAG 10" , mContext.getExternalFilesDir(Environment.MEDIA_MOUNTED).getAbsolutePath());
File[] files1 = mContext.getExternalFilesDirs(Environment.MEDIA_MOUNTED);
for(int i = 0 ; i< files1.length ; i++){
Log.d("TAG 11" ,files1[i].getAbsolutePath() );
}
File[] files2 = mContext.getExternalMediaDirs() ;
for (int i = 0 ; i < files2.length ; i++){
Log.d("TAG 12" ,files2[i].getAbsolutePath());
}
Log.d("TAG 13" , mContext.getObbDir().getAbsolutePath());
Log.d("TAG 13" , Environment.getDataDirectory().getAbsolutePath());
Log.d("TAG 14" , Environment.getExternalStorageDirectory().getAbsolutePath());
Log.d("TAG 15" , Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath());
Log.d("TAG 16" , Environment.getDownloadCacheDirectory().getAbsolutePath());
Log.d("TAG 17" , Environment.getRootDirectory().getAbsolutePath());
Log.d("TAG 18" , Environment.getExternalStorageState());
}
看打印出来的日志:
TAG 0: /data/user/0/com.file
TAG 1: /data/user/0/com.file
TAG 2: /data/user/0/com.file/cache
TAG 3: /data/user/0/com.file/files
TAG 4: /data/user/0/com.file/databases/name
TAG 5: /data/user/0/com.file/app_name1
TAG 6: /data/user/0/com.file/code_cache
TAG 7: /data/app/com.file-2/base.apk
TAG 8: /storage/emulated/0/Android/data/com.file/cache
TAG 9: /storage/emulated/0/Android/data/com.file/cache
TAG 10: /storage/emulated/0/Android/data/com.file/files/mounted
TAG 11: /storage/emulated/0/Android/data/com.file/files/mounted
TAG 12: /storage/emulated/0/Android/media/com.file
TAG 13: /storage/emulated/0/Android/obb/com.file
TAG 13: /data
TAG 14: /storage/emulated/0
TAG 15: /storage/emulated/0/Pictures
TAG 16: /data/cache
TAG 17: /system
TAG 18: mounted
上面提到,我的应用包名是com.file 。从打印结果来看:
1.获取与应用有关的路径使用context对象来获取,与应用无关的使用Environment类来获取。
2.获取内部存储方法中无:External; 获取外部存储方法中有:External。
以上一方便记住方法使用做个简单总结。
- 只看日志是不能理解的,要知其所以然就必须要看源码。
/**
*ContextImpl 实现了Context类
*所以上面mContext对象调用的方法就直接在ContextImpl类中找
*/
class ContextImpl extends Context {
...
...
...
}
接下来看TAG1处的源码:
@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());
}
}
/**
*mFlags = 0 ,所以上面方法中,调用到最后的else除
*/
@Override
public boolean isDeviceProtectedStorage() {
return (mFlags & Context.CONTEXT_DEVICE_PROTECTED_STORAGE) != 0;
}
@Override
public boolean isCredentialProtectedStorage() {
return (mFlags & Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE) != 0;
}
- mPackageInfo是LoadedApk类的对象。看LoadedApk的源码:
private File mDataDirFile;
private void setApplicationInfo(ApplicationInfo aInfo) {
final int myUid = Process.myUid();
aInfo = adjustNativeLibraryPaths(aInfo);
mApplicationInfo = aInfo;
mAppDir = aInfo.sourceDir;
mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir;
mOverlayDirs = aInfo.resourceDirs;
mSharedLibraries = aInfo.sharedLibraryFiles;
mDataDir = aInfo.dataDir;
mLibDir = aInfo.nativeLibraryDir;
// ApplicationInfo 对象的dataDir属性来创建mDataDirFile文件,所以要知道dataDir值
mDataDirFile = FileUtils.newFileOrNull(aInfo.dataDir);
mDeviceProtectedDataDirFile = FileUtils.newFileOrNull(aInfo.deviceProtectedDataDir);
mCredentialProtectedDataDirFile = FileUtils.newFileOrNull(aInfo.credentialProtectedDataDir);
mSplitNames = aInfo.splitNames;
mSplitAppDirs = aInfo.splitSourceDirs;
mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs;
if (aInfo.requestsIsolatedSplitLoading() && !ArrayUtils.isEmpty(mSplitNames)) {
mSplitLoader = new SplitDependencyLoaderImpl(aInfo.splitDependencies);
}
}
public File getDataDirFile() {
return mDataDirFile;
}
TAG0 出打印出了ApplicationInfo的dataDir属性值为:/data/user/0/com.file 。 com.file为应用包名,应用不同,应用的包名也不同。
getCacheDir()、getFilesDir()、getDatabasePath()、getDir()方法内部都调用了getDataDir()方法。接下来看TAG 8处的getExternalCacheDir()方法。看源码:
/** * */ @Override public File getExternalCacheDir() { // Operates on primary external storage // 获取数组的第一个。获取的是机身存储外部路劲的Cache路径。 return getExternalCacheDirs()[0]; } /** *调Environment类,但传入了包名作为参数 *返回的是数组,获取的是所有的外部存储与应用有关的Cache路径。 */ @Override public File[] getExternalCacheDirs() { synchronized (mSync) { File[] dirs = Environment.buildExternalStorageAppCacheDirs(getPackageName()); return ensureExternalDirsExistOrFilter(dirs); } }
getExternalFilesDir()和getExternalFilesDirs()方法类似。
清楚数据和清楚缓存
- 清楚数据
清除数据才是真正的删除了我们保存在文件中的数据(永久性数据,如果不人为删除的话会一直保存在文件中)例如当我们在设置里面清除了某个应用的数据,那么/data/user/0/packname/和/storage/emulated/0/Android/data/packname/下的文件里面的数据会全部删除,包括cache,files,lib,shared_prefs等等。 - 清楚缓存
程序在运行过程中需要经过很多过程,比如读入程序,计算,输入输出等等,这些过程中肯定会产生很多的数据,它们在内存中,以供程序运行时调用。所以清除缓存清除的是APP运行过程中所产生的临时数据。
可以打开手机设置下面,找到自己的测试应用进行验证。