Android存储使用参考

可能遇到的问题

android系统自身自带有存储,另外也可以通过sd卡来扩充存储空间。前者好比pc中的硬盘,后者好移动硬盘。 前者空间较小,后者空间大,但后者不一定可用。 开发应用,处理本地数据存取时,可能会遇到这些问题:

  1. 需要判断sd卡是否可用: 占用过多机身内部存储,容易招致用户反感,优先将数据存放于sd卡;
  • 应用数据存放路径,同其他应用应该保持一致,应用卸载时,清除数据:

    • 标新立异在sd卡根目录建一个目录,招致用户反感

    • 用户卸载应用后,残留目录或者数据在用户机器上,招致用户反感

  • 需要判断两者的可用空间: sd卡存在时,可用空间反而小于机身内部存储,这时应该选用机身存储;

  • 数据安全性,本应用数据不愿意被其他应用读写;

  • 图片缓存等,不应该被扫描加入到用户相册等媒体库中去。

基本操作

  1. 使用外部存储,需要的权限,在AndoridManifest.xml中:
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
> 从API 19 / Andorid 4.4 / KITKAT开始,不再需要显式声明这两个权限,除非要读写其他应用的应用数据($appDataDir)
  • 判断sd卡可用:
/**
 * Check if the primary "external" storage device is available.
 * 
 * @return
 */
public static boolean hasSDCardMounted() {
    String state = Environment.getExternalStorageState();
    if (state != null && state.equals(Environment.MEDIA_MOUNTED)) {
        return true;
    } else {
        return false;
    }
}

存储的用量情况

  • 根据系统用户不同,所能占用的存储空间大小也有不同

    在API level 9及其以上时,File对象的getFreeSpace()方法获取系统root用户可用空间;
    getUsableSpace()取非root用户可用空间

  • 当有多个存储可用时获取磁盘用量,根据当前系统情况选用合适的存储。

  • 根据系统存储用量,合理设定app所用的空间大小;运行时,也可做动态调整。

  • 在API level 9及其以上的系统,可直接调用File对象的相关方法,以下需自行计算:

@TargetApi(VERSION_CODES.GINGERBREAD)
public static long getUsableSpace(File path) {
    if (path == null) {
        return -1;
    }
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
        return path.getUsableSpace();
    } else {
        if (!path.exists()) {
            return 0;
        } else {
            final StatFs stats = new StatFs(path.getPath());
            return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
        }
    }
}

路径的规律

一般地,通过Context 和Environment`相关的方法获取文件存取的路径。

通过这两个类可获取各种路径,如图:

    ($rootDir)
+- /data                -> Environment.getDataDirectory()
|   |
|   |   ($appDataDir)
|   +- data/com.srain.cube.sample
|       |
|       |   ($filesDir)
|       +- files            -> Context.getFilesDir() / Context.getFileStreamPath("")
|       |       |
|       |       +- file1    -> Context.getFileStreamPath("file1")
|       |   ($cacheDir)
|       +- cache            -> Context.getCacheDir()
|       |
|       +- app_$name        ->(Context.getDir(String name, int mode)
|
|   ($rootDir)
+- /storage/sdcard0     -> Environment.getExternalStorageDirectory()
    |                       / Environment.getExternalStoragePublicDirectory("")
    |
    +- dir1             -> Environment.getExternalStoragePublicDirectory("dir1")
    |
    |   ($appDataDir)
    +- Andorid/data/com.srain.cube.sample
        |
        |   ($filesDir)
        +- files        -> Context.getExternalFilesDir("")
        |   |
        |   +- file1    -> Context.getExternalFilesDir("file1")
        |   +- Music    -> Context.getExternalFilesDir(Environment.Music);
        |   +- Picture  -> ... Environment.Picture
        |   +- ...
        |
        |   ($cacheDir)
        +- cache        -> Context.getExternalCacheDir()
        |
        +- ???

各个路径的特性

下面介绍这些路径的特性以及使用中需要注意的细节:

  1. 根目录($rootDir):

    内部存储路径: /data, 通过Environment.getDataDirectory() 获取
    外部存储路径: /storage/sdcard0 (也有类似 /mnt/ 这样的),通过Environment.getExternalStorageDirectory()获取

    示例:

    Environment.getDataDirectory(): 
            /data
    
    Environment.getExternalStorageDirectory(): 
            /storage/sdcard0
    
  • 应用数据目录($appDataDir)

    • 内部储存: $appDataDir = $rootDir/data/$packageName,
    • 外部存储: $appDataDir = $rootDir/Andorid/data/$packageName
      在这些目录下的数据,在app卸载之后,会被系统删除,我们应将应用的数据放于这两个目录中。
  • 外部存储中,公开的数据目录。 这些目录将不会随着应用的删除而被系统删除,请斟酌使用:

    Environment.getExternalStorageDirectory(): 
        /storage/sdcard0
    
    // 同 $rootDir
    Environment.getExternalStoragePublicDirectory(""): 
        /storage/sdcard0
    
    Environment.getExternalStoragePublicDirectory("folder1"): 
        /storage/sdcard0/folder1
    
  • 应用数据目录下的目录

    一般的在$appDataDir下,会有两个目录:

    1. 数据缓存:$cacheDir = $appDataDir/cache:

      • 内部存储:Context.getCacheDir(), 机身内存不足时,文件会被删除
      • 外部存储:Context.getExternalCacheDir()

      外部存储没有实时监控,当空间不足时,文件不会实时被删除,可能返回空对象

      示例:

      Context.getCacheDir(): 
              /data/data/com.srain.cube.sample/cache
      
      Context.getExternalCacheDir(): 
              /storage/sdcard0/Android/data/com.srain.cube.sample/cache
      
    • 文件目录 $filesDir = $appDataDir/files:

      • 内部存储:通过Context.getFilesDir() 获取

      Context.getFileStreamPath(String name)返回以name为文件名的文件对象,name为空,则返回 $filesDir 本身

      示例:

      Context.getFilesDir(): 
              /data/data/com.srain.cube.sample/files
      
      Context.getFileStreamPath(""):
              /data/data/com.srain.cube.sample/files
      
      Context.getFileStreamPath("file1"):
              /data/data/com.srain.cube.sample/files/file1
      
      • 外部存储:通过Context.getExternalFilesDir(String type), type为空字符串时获取.

      type系统指定了几种类型:

      Environment.DIRECTORY_MUSIC
      Environment.DIRECTORY_PICTURES
      ...
      

      示例:

      Context.getExternalFilesDir(""): 
              /storage/sdcard0/Android/data/com.srain.cube.sample/files
      
      Context.getExternalFilesDir(Environment.DIRECTORY_MUSIC)
              /storage/sdcard0/Android/data/com.srain.cube.sample/files/Music
      
  • $cacheDir / $filesDir 安全性

在内部存储中,$cacheDir, $filesDir是app安全的,其他应用无法读取本应用的数据,而外部存储则不是。

在外部存储中,这两个文件夹其他应用程序也可访问。

在外部存储中,$filesDir中的媒体文件,不会被当做媒体扫描出来,加到媒体库中。

  • $cacheDir / $filesDir 同级目录

在内部存储中:通过 Context.getDir(String name, int mode)可获取和 $filesDir / $cacheDir 同级的目录

目录的命名规则为 app_ + name, 通过mode可控制此目录为app私有还是其他app可读写。

示例:

Context.getDir("dir1", MODE_PRIVATE):
        Context.getDir: /data/data/com.srain.cube.sample/app_dir1
  • 特别注意, 对于外部存储,获取$cacheDir 或者 $filesDir及其下的路径

在API level 8 以下,或者空间不足,相关的方法获路径为空时,需要自己构造。

@TargetApi(VERSION_CODES.FROYO)
public static File getExternalCacheDir(Context context) {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO)) {
        File path = context.getExternalCacheDir();

        // In some case, even the sd card is mounted,
        // getExternalCacheDir will return null
        // may be it is nearly full.
        if (path != null) {
            return path;
        }
    }

    // Before Froyo or the path is null,
    // we need to construct the external cache folder ourselves
    final String cacheDir = "/Android/data/" + context.getPackageName() + "/cache/";
    return new File(Environment.getExternalStorageDirectory().getPath() + cacheDir);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,076评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,658评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,732评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,493评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,591评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,598评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,601评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,348评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,797评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,114评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,278评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,953评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,585评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,202评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,180评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,139评论 2 352

推荐阅读更多精彩内容