Android10-系统存储空间显示源码分析

1、系统设置显示

包括总空间,已用空间,可用空间
源码位置:packages/apps/Settings/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceController.java

   /**
    * Updates the state of the donut preference for the next update using volume to summarize.
    *
    * @param volume VolumeInfo to use to populate the informayion.
    */
   public void updateSizes(StorageVolumeProvider svp, VolumeInfo volume) {
       final long sharedDataSize = volume.getPath().getTotalSpace();
       long totalSize = svp.getPrimaryStorageSize();
  
       if (totalSize <= 0) {
           totalSize = sharedDataSize;
       }
        //已用空间=总空间-可用空间
       final long usedBytes = totalSize - volume.getPath().getFreeSpace();
       updateBytes(usedBytes, totalSize);
   }

2.StorageVolumeProvider

源码位置frameworks/base/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageVolumeProvider.java
它是一个接口,实现类为frameworks/base/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageManagerVolumeProvider.java

/**
 * StorageVolumeProvider provides access to the storage volumes on a device for free space
 * calculations.
StorageVolumeProvider 提供设备上各个分卷可用空间的计算方法
 */
public interface StorageVolumeProvider {
    /** 
     * Returns the number of bytes of total storage on the primary storage.
        计算主分区的总大小 
     */
    long getPrimaryStorageSize();

    /** 
     * Returns a list of VolumeInfos for the device.
        返回分卷列表
     */
    List<VolumeInfo> getVolumes();

    /** 
     * Returns the emulated volume for a given private volume.
      返回指定私有卷对应的模拟卷
     */
    VolumeInfo findEmulatedForPrivate(VolumeInfo privateVolume);

    /** 
     * Returns the total bytes for a given storage volume.
     *返回指定分卷的大小
     * @pre The volume is a private volume and is readable.
     */
    long getTotalBytes(StorageStatsManager stats, VolumeInfo volume) throws IOException;

    /**
     * Returns the free bytes for a given storage volume.
     *返回指定分卷的可用空间大小
     * @pre The volume is a private volume and is readable.
     */
    long getFreeBytes(StorageStatsManager stats, VolumeInfo volume) throws IOException;
}

3.StorageManagerVolumeProvider

源码位置frameworks/base/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageManagerVolumeProvider.java

public class StorageManagerVolumeProvider implements StorageVolumeProvider {
    private StorageManager mStorageManager;
    //传入StorageManager作为参数,它是重点
    public StorageManagerVolumeProvider(StorageManager sm) {
        mStorageManager = sm; 
    }   

    @Override
    public long getPrimaryStorageSize() {
        return mStorageManager.getPrimaryStorageSize();
    }   

    @Override
    public List<VolumeInfo> getVolumes() {
        return mStorageManager.getVolumes();
    }   

    @Override
    public VolumeInfo findEmulatedForPrivate(VolumeInfo privateVolume) {
        return mStorageManager.findEmulatedForPrivate(privateVolume);
    }   
    @Override
    public long getTotalBytes(StorageStatsManager stats, VolumeInfo volume) throws IOException {
        return stats.getTotalBytes(volume.getFsUuid());
    }

    @Override
    public long getFreeBytes(StorageStatsManager stats, VolumeInfo volume) throws IOException {
        return stats.getFreeBytes(volume.getFsUuid());
    }
}

4.重点来了StorageManager

源码位置frameworks/base/core/java/android/os/storage/StorageManager.java
先分析下面三个方法,另外两个后面分析

private final IStorageManager mStorageManager;

   //获取总大小 
    public long getPrimaryStorageSize() {
      //总大小是用户空间+系统空间,并进行二次方化
        return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()
                + Environment.getRootDirectory().getTotalSpace());
    }
//获取所有卷标
   public @NonNull List<VolumeInfo> getVolumes() {
       try {
           return Arrays.asList(mStorageManager.getVolumes(0));
       } catch (RemoteException e) {
           throw e.rethrowFromSystemServer();
       }
   }
//查找私有卷的模拟卷
  public @Nullable VolumeInfo findEmulatedForPrivate(VolumeInfo privateVol) {
      if (privateVol != null) {
          return findVolumeById(privateVol.getId().replace("private", "emulated"));
      } else {
          return null;
      }
  }
//根据卷ID查找卷
 public @Nullable VolumeInfo findVolumeById(String id) {
     Preconditions.checkNotNull(id);
     // TODO; go directly to service to make this faster
     for (VolumeInfo vol : getVolumes()) {
         if (Objects.equals(vol.id, id)) {
             return vol;
         }
     }
     return null;
 }

5.getPrimaryStorageSize方法

源码位置:frameworks/base/core/java/android/os/FileUtils.java

  /**
   * Round the given size of a storage device to a nice round power-of-two
   * value, such as 256MB or 32GB. This avoids showing weird values like
   * "29.5GB" in UI.
将存储设备的给定大小四舍五入为漂亮的 2 次方
    值,例如 256MB 或 32GB。这样可以避免显示奇怪的值,例如
    用户界面中的“29.5GB”。
   *
   * @hide
   */
  public static long roundStorageSize(long size) {
      long val = 1;
      long pow = 1;
      while ((val * pow) < size) {
          val <<= 1;
          if (val > 512) {
              val = 1;
              pow *= 1000;
          }
      }
      return val * pow;
  }

frameworks/base/core/java/android/os/Environment.java

 private static final String ENV_ANDROID_ROOT = "ANDROID_ROOT";
 private static final String ENV_ANDROID_DATA = "ANDROID_DATA";

 public static final String DIR_ANDROID = "Android";
 private static final String DIR_DATA = "data";

//系统分区对应根目录/system
 private static final File DIR_ANDROID_ROOT = getDirectory(ENV_ANDROID_ROOT, "/system");
//用户分区对应根目录/data
 private static final File DIR_ANDROID_DATA = getDirectory(ENV_ANDROID_DATA, "/data");

  static File getDirectory(String variableName, String defaultPath) {
      String path = System.getenv(variableName); //获取是否有定义属性,见下面定义,一般是没有的
      return path == null ? new File(defaultPath) : new File(path);
  }

  /**
   * Return the user data directory.
返回用户空间
   */
  public static File getDataDirectory() {
      return DIR_ANDROID_DATA;
  }

 /**
  * Return root of the "system" partition holding the core Android OS.
  * Always present and mounted read-only.
  返回支行Android OS的system分区,该分区始终以只读的方式存在和挂载
  */
 public static @NonNull File getRootDirectory() {
     return DIR_ANDROID_ROOT;
 }

java.lang.System

    /**
     * Returns the value of the environment variable with the given name, or null if no such
     * variable exists.
     */
    public static String getenv(String name) {
        if (name == null) {
            throw new NullPointerException("name == null");
        }
        return Libcore.os.getenv(name);
    }

/data分区和/system分区配置
源码位置:device/sprd/sharkle/sl8541e_1h10_32b/sl8541e_1h10_32b.xml

   <Partitions>
       <!-- size unit is MBytes -->
       <Partition id="prodnv" size="10"/>
       <Partition id="miscdata" size="1"/>
       <Partition id="recovery" size="35"/>
       <Partition id="misc" size="1"/>
       <Partition id="trustos" size="6"/>
       <Partition id="trustos_bak" size="6"/>
       <Partition id="sml" size="1"/>
       <Partition id="sml_bak" size="1"/>
       <Partition id="uboot" size="1"/>
       <Partition id="uboot_bak" size="1"/>
       <Partition id="uboot_log" size="4"/>
       <Partition id="logo" size="4"/>
       <Partition id="fbootlogo" size="4"/>
       <Partition id="l_fixnv1" size="1"/>
       <Partition id="l_fixnv2" size="1"/>
       <Partition id="l_runtimenv1" size="1"/>
       <Partition id="l_runtimenv2" size="1"/>
       <Partition id="gpsgl" size="1"/>
       <Partition id="gpsbd" size="1"/>
       <Partition id="wcnmodem" size="10"/>
     <Partition id="persist"   size="2"/>
     <Partition id="l_modem" size="25"/>
     <Partition id="l_deltanv" size="1"/>
     <Partition id="l_gdsp" size="10"/>
     <Partition id="l_ldsp" size="20"/>
     <Partition id="pm_sys" size="1"/>
     <Partition id="boot" size="35"/>
     <Partition id="dtbo" size="8"/>
     <Partition id="super" size="3100"/> <!--system product vendor三个镜像+预留空间-->
     <Partition id="cache" size="150"/>
     <Partition id="socko" size="75"/>
     <Partition id="odmko" size="25"/>
     <Partition id="vbmeta" size="1"/>
     <Partition id="vbmeta_bak" size="1"/>
     <Partition id="sysdumpdb" size="10"/>
     <Partition id="metadata" size="16"/>
     <Partition id="vbmeta_system" size="1"/>
     <Partition id="vbmeta_vendor" size="1"/>
     <Partition id="userdata" size="0xFFFFFFFF"/>

device/sprd/sharkle/sl8541e_1h10_32b/BoardConfig.mk

sprdiskexist := $(shell if [ -f $(TOPDIR)sprdisk/Makefile -a "$(TARGET_BUILD_VARIANT)" = "userdebug" ]; then echo "exist"; else echo "notexist"; fi;)
ifneq ($(sprdiskexist), exist)
TARGET_NO_SPRDISK := true
else
TARGET_NO_SPRDISK := false
endif
SPRDISK_BUILD_PATH := sprdisk/

BOARD_SUPER_PARTITION_SIZE := 3250585600 #system product vendor三个镜像+预留空间
BOARD_GROUP_UNISOC_SIZE := 3250585600

# ext4 partition layout
#BOARD_VENDORIMAGE_PARTITION_SIZE := 314572800
BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE := ext4
TARGET_COPY_OUT_VENDOR=vendor
TARGET_USERIMAGES_USE_EXT4 := true
BOARD_BOOTIMAGE_PARTITION_SIZE := 36700160
BOARD_RECOVERYIMAGE_PARTITION_SIZE := 36700160
#BOARD_SYSTEMIMAGE_PARTITION_SIZE := 1625292800
BOARD_CACHEIMAGE_PARTITION_SIZE := 150000000
BOARD_PRODNVIMAGE_PARTITION_SIZE := 10485760
BOARD_USERDATAIMAGE_PARTITION_SIZE := 134217728
BOARD_DTBIMG_PARTITION_SIZE := 8388608
BOARD_DTBOIMG_PARTITION_SIZE := 8388608

BOARD_FLASH_BLOCK_SIZE := 4096
BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE := ext4
BOARD_PRODNVIMAGE_FILE_SYSTEM_TYPE := ext4

TARGET_SYSTEMIMAGES_SPARSE_EXT_DISABLED := true
TARGET_USERIMAGES_SPARSE_EXT_DISABLED := false
BOARD_PERSISTIMAGE_PARTITION_SIZE := 2097152
TARGET_PRODNVIMAGES_SPARSE_EXT_DISABLED := true
TARGET_CACHEIMAGES_SPARSE_EXT_DISABLED := false
USE_SPRD_SENSOR_HUB := false
#BOARD_PRODUCTIMAGE_PARTITION_SIZE :=419430400
BOARD_PRODUCTIMAGE_FILE_SYSTEM_TYPE := ext4
TARGET_COPY_OUT_PRODUCT=product

BOARD_SOCKOIMAGE_FILE_SYSTEM_TYPE := ext4
BOARD_ODMKOIMAGE_FILE_SYSTEM_TYPE := ext4
BOARD_SOCKOIMAGE_PARTITION_SIZE := 78643200 # 75M
BOARD_ODMKOIMAGE_PARTITION_SIZE := 26214400 # 25M
#creates the metadata directory
BOARD_USES_METADATA_PARTITION := true

总结:修改除 system、cache、prodnv、data 之外的分区只需要修改工具中 project.xml 文件即可,修
改好 xml 文件之后请重新制作 pac 包,以确保修改成功

6.getVolumes方法

从5中看到,调用了AIDL接口,源码位置frameworks/base/services/core/java/com/android/server/StorageManagerService.java

 /** Map from volume ID to disk */
//该结构体保存卷信息<VolumeInfo.id,VolumeInfo>
 private final ArrayMap<String, VolumeInfo> mVolumes = new ArrayMap<>();

//返回所有卷标
 @Override
 public VolumeInfo[] getVolumes(int flags) {
     synchronized (mLock) {
         final VolumeInfo[] res = new VolumeInfo[mVolumes.size()];
         for (int i = 0; i < mVolumes.size(); i++) {
             res[i] = mVolumes.valueAt(i);
         }
         return res;
     }
 }

frameworks/base/core/java/android/os/storage/VolumeInfo.java

 /** Stub volume representing internal private storage */
 public static final String ID_PRIVATE_INTERNAL = "private";
 /** Real volume representing internal emulated storage */
 public static final String ID_EMULATED_INTERNAL = "emulated";

//类型,PUBLIC,PRIVATE ASEC,OBB,STUB
public static final int TYPE_PUBLIC = IVold.VOLUME_TYPE_PUBLIC;
public static final int TYPE_PRIVATE = IVold.VOLUME_TYPE_PRIVATE;
public static final int TYPE_EMULATED = IVold.VOLUME_TYPE_EMULATED;
public static final int TYPE_ASEC = IVold.VOLUME_TYPE_ASEC;
public static final int TYPE_OBB = IVold.VOLUME_TYPE_OBB;
public static final int TYPE_STUB = IVold.VOLUME_TYPE_STUB;


//主要有以下成员变量
  public final String id; //id
  public final int type; //权限类型
  public final DiskInfo disk; //磁盘信息
  public final String partGuid; //分区组ID
  public int mountFlags = 0; //挂载标志位
  public int mountUserId = UserHandle.USER_NULL; //用户
  public int state = STATE_UNMOUNTED; //挂载状态
  public String fsType; //文件系统类型
  public String fsUuid; //文件系统id
  public String fsLabel;//文件系统标签
  public String path; //路径
  public String internalPath;//内部内径
  /* SPRD: add for storage manage */
  public String linkName;
  /* @SPRD: add for UMS */
  public int stateBeforeUMS = STATE_UNMOUNTED;

7.接第3节,另外两个没有分析的方法

源码位置:frameworks/base/core/java/android/app/usage/StorageStatsManager.java

 private final IStorageStatsManager mService; 

//获取指定文件系统的大小 stats.getTotalBytes(volume.getFsUuid());
 public @BytesLong long getTotalBytes(@NonNull UUID storageUuid) throws IOException {
     try {
         return mService.getTotalBytes(convert(storageUuid), mContext.getOpPackageName());
     } catch (ParcelableException e) {
         e.maybeRethrow(IOException.class);
         throw new RuntimeException(e);
     } catch (RemoteException e) {
         throw e.rethrowFromSystemServer();
     }
 }

//获取指定文件系统的可用空间大小 stats.getFreeBytes(volume.getFsUuid());
 public @BytesLong long getFreeBytes(@NonNull UUID storageUuid) throws IOException {
      try {
          return mService.getFreeBytes(convert(storageUuid), mContext.getOpPackageName());
      } catch (ParcelableException e) {
          e.maybeRethrow(IOException.class);
          throw new RuntimeException(e);
      } catch (RemoteException e) {
          throw e.rethrowFromSystemServer();
      }
  }

frameworks/base/services/usage/java/com/android/server/usage/StorageStatsService.java

  private final StorageManager mStorage;

  @Override
  public long getTotalBytes(String volumeUuid, String callingPackage) {
      // NOTE: No permissions required

      if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
          return FileUtils.roundStorageSize(mStorage.getPrimaryStorageSize());
      } else {
          final VolumeInfo vol = mStorage.findVolumeByUuid(volumeUuid);
          if (vol == null) {
              throw new ParcelableException(
                      new IOException("Failed to find storage device for UUID " + volumeUuid));
          }
          return FileUtils.roundStorageSize(vol.disk.size);
      }
  }


  @Override
  public long getFreeBytes(String volumeUuid, String callingPackage) {
      // NOTE: No permissions required

      final long token = Binder.clearCallingIdentity();
      try {
          final File path;
          try {
              path = mStorage.findPathForUuid(volumeUuid);
          } catch (FileNotFoundException e) {
              throw new ParcelableException(e);
          }

          // Free space is usable bytes plus any cached data that we're
          // willing to automatically clear. To avoid user confusion, this
          // logic should be kept in sync with getAllocatableBytes().
          if (isQuotaSupported(volumeUuid, PLATFORM_PACKAGE_NAME)) {
              final long cacheTotal = getCacheBytes(volumeUuid, PLATFORM_PACKAGE_NAME);
              final long cacheReserved = mStorage.getStorageCacheBytes(path, 0);
              final long cacheClearable = Math.max(0, cacheTotal - cacheReserved);

              return path.getUsableSpace() + cacheClearable;
          } else {
              return path.getUsableSpace();
          }
      } finally {
          Binder.restoreCallingIdentity(token);
      }
  }

实际上还是调用的StorageManager进行操作,StorageManager又调用到了StorageManagerService,与前面分析流程基本一致。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容