安卓存储目录分为 内部存储 和 外部存储。
内部存储目录:即 /data/ 目录,
未root的手机无法查看。外部存储目录:根据厂家不同,可能是
/storage 或
/mnt 文件夹。
先来一张结论图:
我们可在Android Studio 右下角,使用 Device File Explorer 工具来查看。
内部存储
我们可以直接使用内部存储保存文件。
对于内部存储,只有本App才可以访问,其他程序无法访问,而当用户卸载该程序的时候,这些文件也会随之被删除。/ data / app:系统安装一个apk时,会先将apk文件复制到这个目录下。当我们debug一个app的时候,可以看到控制台输出的内容,有一项是uploading …..就是上传我们的apk到这个文件夹,上传成功之后才开始安装。
这些不同包名的内部存储了两大块内容,一个so文件;另一个是oat文件,它们是系统能运行此app的基础,app的机器代码都保存在这里。 为安全着想,没有root的手机即使借助 Device File Explorer 也不能查看 oat里存储的 odex文件。
oat历史介绍 ART和Dalvik都算是一种Android运行时环境,或者叫做虚拟机,用来解释dex类型文件。但是ART是安装时解释,Dalvik是运行时解释。 4.4 以前的版本使用的是 dalvik 虚拟机,在4.4版本上,两种运行时环境共存,可以相互切换,但是在5.0+,Dalvik虚拟机则被彻底的丢弃,全部采用ART。dalvik: 5.0 以前 的 Dalvik时代,app每次启动时,系统都需要通过 即时编译器jit(Just-In-Time实时编译) 将dex文件或odex翻译成能被虚拟机加载的native code , 最终产物是相同名称的 dey文件(表示这是一个优化过的dex),这样使得 dalvik 虚拟机的 app启动速度很慢。oat 是 AOT 在安装apk时 生成的 native code,对应的文件后缀为 *.oat(实际上是一个自定义的elf文件,里面包含的都是本地机器指令)), o是optimize(优化)的缩写,a是android的缩写,t是runTime的意思,oat 是在apk安装时通过dexopt工具将dex文件优化成二进制格式的文件,然后再通过AOT(Ahead-Of-Time 预先编译)生成 能被art虚拟机执行的机器 吗,从而加快app的启动速度。
在4.4版本上,两种运行时环境共存,可以相互切换,但是在5.0+,Dalvik虚拟机则被彻底的丢弃,全部采用ART。
/ data / data 文件夹里边都是一些包名,打开这些包名之后我们会看到这样的一些文件: 1、 data / data / 包名 / shared_prefs 保存的是sharedPreferenced,其实是一个xml文件 2、data / data / 包名 / databases 保存的是App里边的数据库文件 3、data / data / 包名 / cache 保存的是缓存文件
内部存储
获取并操作 内部存储 空间方法:
Context.getFilesDir()Context.getCacheDir()Context.deleteFile()Context.fileList()Environment.getDataDirectory()
1)一般情况下,有包名的路径,我们都是调用Context中的方法来获取,没有的话,直接调用Environment中的方法获得。2)Google表示:内部存储任意目录中写入数据不受限制。 目的是,系统可以在应用程序被卸载后清除遗留文件。
外部存储
在Android较老的版本中,外置存储器路径一般是指的/mnt/sdcard,而我们知道当前的部分Android手机支持存储卡扩展功能,于是Android将相关的路径挪到了/storage下面统一管理,打开Eclipse的DDMS->File Explorer,可以看到:
在/mnt/sdcard的目录属性中可以看到这个目录其实是一个连接,关于存储的文件目录树如下图
可以看出:
Android对存储器的管理都统一到/storage下面,外存储器对应了sdcard0,sdcard1,sdcard2,sdcard3....之前的/mnt/sdcard指向/storage/emulated/legacy,同时sdcard0也是指向/storage/emulated/legacy,说明两者都是指的手机自带的外置存储器(手机自带的内存)。在/storage下有一个sdcard1目录,这个目录对应的就是手机的TF存储扩展卡。
目前Android手机外部储存分两种,先来明确一下概念:
手机内部焊接了一张存储卡,谷歌称之为
primary external storage(这里称之为:内置TF卡,下同),只能有一个。手机有TF卡扩展卡槽,谷歌称之为
secondary external storage(这里称之为:外置TF卡,下同),可以有多个。
一句话:外部存储包括了 内置TF卡 和 外置TF卡
1)外部存储空间 ---> 特性
在 storage文件夹--- sdcard文件夹,子文件夹又分为两类: 公有目录:有九大类,比如DCIM、DOWNLOAD等这种系统为我们创建的文件夹。 私有目录:路径为 "Android / data / 包名",再往下一级的子文件夹,需要我们手动创建。 内部存储空间和外置TF卡私有目录,由于其特有的生命周期(
随着应用卸载而自动清除)只适合存储应用相关数据。
2)外部存储 ---> 权限
在
2.x 的版本中,android手机只有内部存储目录、外置TF扩展卡槽,要使用外置TF卡需要申请权限: 读:android.permission.READ_EXTERNAL_STORAGE 写:android.permission.WRITE_EXTERNAL_STORAGE(
其实只需设置可写,因为可写必定可读)
从 4.0 - 4.4 之前,android有了内外存储卡之分,无论是访问内置TF卡还是外置TF卡,都需要申请权限 读 :android.permission.READ_EXTERNAL_STORAGE
内置TF卡(写):android.permission.WRITE_EXTERNAL_STORAGE
外置TF卡(写):android.permission.WRITE_MEDIA_STORAGE 从 4.4 及之后, 读 :无论内置、外置TF卡,都
不需要权限内置TF卡(写):不需要权限外置TF卡(写):分两种情况 1)公有目录:需要权限 2)私有目录( / storage / sdcard1 / Android / data / [com.package.name] / files / )不需要权限 在
6.0 以后,新增了动态权限申请机制,而外置TF卡读、写权限属于危险权限,所以除了在AndroidMenifest.xml中配置外,还需动态的申请外置TF卡的读、写权限。动态权限申请代码请看文末。 在
7.0 后, 其他 App 要读写 外置TF卡的私有目录, 可通过 FileProvider,而 file:// 这种形式的 Uri 已经失效。
3)外部存储空间 ---> 常用API
获取了所有外置存储器的应用私有目录路径
File[] filearray = getExternalFilesDirs(null);
for (File file : filearray) {
System.out.println(file.toString());
}
路径数组可能是
/storage/emulated/0/Android/data/com.example.httpdownloadtest/files <=内外置存储器路径
/storage/sdcard1/Android/data/com.example.httpdownloadtest/files <=TF卡路径
外部存储空间的私有目录中创建一个文件夹: File externalFilesDir = context.
getExternalFilesDir(
Environment.DIRECTORY_PICTURES); 返回值:一个file对象,这个对象的路径:/storage/emulated/0/Android/data/packagename/files/
Pictures 同理:创建任何名称的文件 File myself = context.getExternalFilesDir(
"myself"); 返回:/storage/emulated/0/Android/data/packagename/files/
myself 创建一个固定名称的文件夹 File externalCacheDir = context.
getExternalCacheDir(); 返回值:一个file对象,这个对象的路径是/storage/emulated/0/Android/data/packagename/cache 获取内置TF卡存储目录: Environment.getExternalStorageDirectory(); 获取外置TF卡存储目录: 可通过 Environment.getExternalDirs遍历来获取
"清除数据" 和 “清理缓存”有什么区别?
"清除数据" 对应的目录, 以及代码中的调用API /data/data/[包名]/files ---> String path = context.getFilesDir().getPath(); /sdcard/Android/data/[包名]/files ---> String path = context.getExternalFilesDir(null).getPath();
即:内置存储私有目录 + 外部存储空间的外置TF卡 私有目录 的files文件夹 "清除缓存" 对应的目录, 以及代码中的调用API. /data/data/[包名]/cache ---> String path = context.getCacheDir().getPath(); /sdcard/Android/data/[包名]/cache ---> String path = context.getExternalCacheDir().getPath();
即:内置存储私有目录 + 外部存储空间的外置TF卡 私有目录 的缓存文件夹
/** 注意Android四种存储目录的区别 */
public class FileHelper {
private static String path="";
/**
* 获取应该内部存储目录 对应文件目录\data\date\application包名\files
* 该目录属于应用程序的私有目录 其他应用程序时无法访问的 无需申请权限
*/
public static String getFilePath(Context context){
File file=context.getFilesDir();
path=file.getAbsolutePath();
return path;
}
/**
* 获取应用程序内部存储目录 对应文件目录\data\data\application包名\cache
* 该目录属于应用程序的私有目录 无需权限
*/
public static String getCachePath(Context context){
File cacheFile=context.getCacheDir();
path=cacheFile.getAbsolutePath();
return path;
}
/**
* 获取sd卡外部存储文件目录
该目录下的文件数据是属于外部存储卡存储当前应用的文件目录 该目录需要申请权限
* 对应的目录 /sdCard/Androida/data/applicaion包名/files
* 对于外部存储的内置TF卡,应用一旦被卸载,对应sdCard中该目录的文件将全部被清除
* 对于外部存储的外置TF卡,应用一旦被卸载,数据任然不会被删除
*/
public static String getExtralFilePath(Context context){
File extralFile=context.getExternalFilesDir(null);
path=extralFile.getAbsolutePath();
return path;
}
/***
* 获取sd卡外部存储文件目录
* 该目录下的文件数据是属于外部存储卡存储当前应用的文件目录 该目录需要申请权限
* 对应的目录 /sdCard/Androida/data/applicaion包名/files
* 对于大部分手机现在智能机 外部存储sdCard都是内嵌在手机中不可插拔 所以对于这类手机来说 应用一断被卸载 对应sdCard中该目录的文件将全部被清除
* 对于外部存储是物理存储 外部插拔式sdCard来说 应用被卸载数据任然不会被删除
*/
public static String getExtralCachePath(Context context){
File externalCacheDir=context.getExternalCacheDir();
path=externalCacheDir.getAbsolutePath();
return path;
}
/**
* 通用方法用于保存应用文件数据
*/
public static String getCommenFilePath(Context context){
if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())||!Environment.isExternalStorageRemovable()){
path=getExtralFilePath(context);
}else {
path=getFilePath(context);
}
return path;
}
/**
* 通用方法用于保存应用缓存数据
*/
public static String getCommenCachePath(Context context){
if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())||!Environment.isExternalStorageRemovable()){
path=getExtralCachePath(context);
}else {
path=getCachePath(context);
}
return path;
}
/**
* 获取sd卡根目录方法
*/
public static String getSDCardPath(){
if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())||!Environment.isExternalStorageRemovable()){
File file=Environment.getExternalStorageDirectory();
path=file.getAbsolutePath();
}else {
path="";
}
return path;
}
}
接下来对几种目录打印日志:
String cachePath=FileHelper.getCachePath(this);
String filePath=FileHelper.getFilePath(this);
String extralFilePath=FileHelper.getExtralFilePath(this);
String extralCachePath=FileHelper.getExtralCachePath(this);
String sdCardPath=FileHelper.getSDCardPath();
Log.v("log1","cachePath:"+cachePath);
Log.v("log1","filePath:"+filePath);
Log.v("log1","extralFilePath:"+extralFilePath);
Log.v("log1","extralCachePath:"+extralCachePath);
Log.v("log1","sdCardPath:"+sdCardPath);
以下是对应日志输出结果:
log1:cachePath:/data/data/com.example.administrator.myjavadeamo/cache
log1:filePath:/data/data/com.example.administrator.myjavadeamo/files
log1:extralFilePath:/storage/emulated/0/Android/data/com.example.administrator.myjavadeamo/files
log1:extralCachePath:/storage/emulated/0/Android/data/com.example.administrator.myjavadeamo/cache
log1:sdCardPath:/storage/emulated/0
动态申请权限的过程
package com.test.android.permissionrequest;
import android.Manifest;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
// 要申请的权限
private String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
private AlertDialog dialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 版本判断。当手机系统大于 23 时,才有必要去判断权限是否获取
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// 检查该权限是否已经获取
int i = ContextCompat.checkSelfPermission(this, permissions[0]);
// 权限是否已经 授权 GRANTED---授权 DINIED---拒绝
if (i != PackageManager.PERMISSION_GRANTED) {
// 如果没有授予该权限,就去提示用户请求
showDialogTipUserRequestPermission();
}
}
}
// 提示用户该请求权限的弹出框
private void showDialogTipUserRequestPermission() {
new AlertDialog.Builder(this)
.setTitle("存储权限不可用")
.setMessage("由于支付宝需要获取存储空间,为你存储个人信息;\n否则,您将无法正常使用支付宝")
.setPositiveButton("立即开启", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startRequestPermission();
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
}).setCancelable(false).show();
}
// 开始提交请求权限
private void startRequestPermission() {
ActivityCompat.requestPermissions(this, permissions, 321);
}
// 用户权限 申请 的回调方法
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 321) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
// 判断用户是否 点击了不再提醒。(检测该权限是否还可以申请)
boolean b = shouldShowRequestPermissionRationale(permissions[0]);
if (!b) {
// 用户还是想用我的 APP 的
// 提示用户去应用设置界面手动开启权限
showDialogTipUserGoToAppSettting();
} else
finish();
} else {
Toast.makeText(this, "权限获取成功", Toast.LENGTH_SHORT).show();
}
}
}
}
// 提示用户去应用设置界面手动开启权限
private void showDialogTipUserGoToAppSettting() {
dialog = new AlertDialog.Builder(this)
.setTitle("存储权限不可用")
.setMessage("请在-应用设置-权限-中,允许支付宝使用存储权限来保存用户数据")
.setPositiveButton("立即开启", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 跳转到应用设置界面
goToAppSetting();
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
}).setCancelable(false).show();
}
// 跳转到当前应用的设置界面
private void goToAppSetting() {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivityForResult(intent, 123);
}
//
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 123) {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// 检查该权限是否已经获取
int i = ContextCompat.checkSelfPermission(this, permissions[0]);
// 权限是否已经 授权 GRANTED---授权 DINIED---拒绝
if (i != PackageManager.PERMISSION_GRANTED) {
// 提示用户应该去应用设置界面手动开启权限
showDialogTipUserGoToAppSettting();
} else {
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
}
Toast.makeText(this, "权限获取成功", Toast.LENGTH_SHORT).show();
}
}
}
}
}
--------------------- https://blog.csdn.net/u013394527/article/details/81811762https://www.jianshu.com/p/9528329d4787https://blog.csdn.net/qq_38414907/article/details/76581295https://blog.csdn.net/HuntCode/article/details/48473205 https://blog.csdn.net/zheglei/article/details/79745982https://blog.csdn.net/u010784887/article/details/53560025 https://www.cnblogs.com/xmcx1995/p/5870191.html