Android之存储目录

    xiaoxiao2022-07-12  158

     安卓存储目录分为 内部存储外部存储

    内部存储目录:即 /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

    最新回复(0)