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,与前面分析流程基本一致。

目录
相关文章
|
3月前
|
人工智能 搜索推荐 物联网
Android系统版本演进与未来展望####
本文深入探讨了Android操作系统从诞生至今的发展历程,详细阐述了其关键版本迭代带来的创新特性、用户体验提升及对全球移动生态系统的影响。通过对Android历史版本的回顾与分析,本文旨在揭示其成功背后的驱动力,并展望未来Android可能的发展趋势与面临的挑战,为读者呈现一个既全面又具深度的技术视角。 ####
|
3月前
|
IDE Java 开发工具
移动应用与系统:探索Android开发之旅
在这篇文章中,我们将深入探讨Android开发的各个方面,从基础知识到高级技术。我们将通过代码示例和案例分析,帮助读者更好地理解和掌握Android开发。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和技巧。让我们一起开启Android开发的旅程吧!
|
2月前
|
监控 Java Android开发
深入探索Android系统的内存管理机制
本文旨在全面解析Android系统的内存管理机制,包括其工作原理、常见问题及其解决方案。通过对Android内存模型的深入分析,本文将帮助开发者更好地理解内存分配、回收以及优化策略,从而提高应用性能和用户体验。
|
2月前
|
存储 安全 Android开发
探索Android系统的最新安全特性
在数字时代,智能手机已成为我们生活中不可或缺的一部分。随着技术的不断进步,手机操作系统的安全性也越来越受到重视。本文将深入探讨Android系统最新的安全特性,包括其设计理念、实施方式以及对用户的影响。通过分析这些安全措施如何保护用户免受恶意软件和网络攻击的威胁,我们希望为读者提供对Android安全性的全面了解。
|
3月前
|
监控 Java Android开发
深入探讨Android系统的内存管理机制
本文将深入分析Android系统的内存管理机制,包括其内存分配、回收策略以及常见的内存泄漏问题。通过对这些方面的详细讨论,读者可以更好地理解Android系统如何高效地管理内存资源,从而提高应用程序的性能和稳定性。
118 16
|
3月前
|
安全 Android开发 iOS开发
深入探讨Android与iOS系统的差异及未来发展趋势
本文旨在深入分析Android和iOS两大移动操作系统的核心技术差异、用户体验以及各自的市场表现,进一步探讨它们在未来技术革新中可能的发展方向。通过对比两者的开放性、安全性、生态系统等方面,本文揭示了两大系统在移动设备市场中的竞争态势和潜在变革。
|
3月前
|
算法 JavaScript Android开发
|
Java 调度 Android开发
android体系课-系统启动流程-之zygote进程启动过程源码分析
笔者刚开始学习Android的时候也和大部分同学一样,只会使用一些应用层面的知识,对于一些比较常见的开源框架如<mark>RxJava</mark>,<mark>OkHttp</mark>,<mark>Retrofit</mark>,以及后来谷歌推出的<mark>协程</mark>等,都只在使用层面,对于他们<mark>内部原理</mark>,基本没有去了解觉得够用就可以了,又比如Activity,Service等四大组件的使用原理,系统开机过程,Launcher启动过程等知之甚少,知其然而不知其所以然,结果就是出现某些问题,不知道从哪里找原因,只能依赖万能的百度,但是百度看多了,你会发现自己
|
Java 调度 Android开发
android体系课-系统启动流程-之SystemServer启动过程源码分析
笔者刚开始学习Android的时候也和大部分同学一样,只会使用一些应用层面的知识,对于一些比较常见的开源框架如<mark>RxJava</mark>,<mark>OkHttp</mark>,<mark>Retrofit</mark>,以及后来谷歌推出的<mark>协程</mark>等,都只在使用层面,对于他们<mark>内部原理</mark>,基本没有去了解觉得够用就可以了,又比如Activity,Service等四大组件的使用原理,系统开机过程,Launcher启动过程等知之甚少,知其然而不知其所以然,结果就是出现某些问题,不知道从哪里找原因,只能依赖万能的百度,但是百度看多了,你会发现自己

热门文章

最新文章

  • 1
    如何修复 Android 和 Windows 不支持视频编解码器的问题?
  • 2
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 3
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
  • 4
    【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
  • 5
    APP-国内主流安卓商店-应用市场-鸿蒙商店上架之必备前提·全国公安安全信息评估报告如何申请-需要安全评估报告的资料是哪些-优雅草卓伊凡全程操作
  • 6
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 7
    Android经典面试题之Kotlin中Lambda表达式和匿名函数的区别
  • 8
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
  • 9
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
  • 10
    Android 13 SystemUI 启动流程
  • 1
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    23
  • 2
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
    21
  • 3
    APP-国内主流安卓商店-应用市场-鸿蒙商店上架之必备前提·全国公安安全信息评估报告如何申请-需要安全评估报告的资料是哪些-优雅草卓伊凡全程操作
    52
  • 4
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    35
  • 5
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
    70
  • 6
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    111
  • 7
    Android经典面试题之Kotlin中Lambda表达式和匿名函数的区别
    29
  • 8
    如何修复 Android 和 Windows 不支持视频编解码器的问题?
    262
  • 9
    【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
    75
  • 10
    【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
    36