在实现这个功能之前可以弄清几个获取系统路径的方法: Context.getCacheDir().getPath() ; 对应路径:/ data /user / 0 / <应用程序包>/ cache(路径不可见) Context.getFilesDir().getPath() ; 对应路径:/ data / user / 0 / <应用程序包> /files(路径不可见) Context.getExternalCacheDir().getPath(); 对应路径:Android/data/<应用程序包>/cache (如果想缓存文件可用这个,内存不足时删除,节约用户空间) Context.getExternalFilesDir("glide").getPath(); 对应路径:Android/data/<应用程序包>/files/glide (如果想缓存文件可用这个,会随应用删除而删除,节约用户空间) context.getDir("config", Context.MODE_PRIVATE); 对应路径:Android/data/包名/app_config (其中包名后面的app_是为调用时,系统自己加上的,Context.MODE_PRIVATE:为默认操作模式,代表该文件是私有数据,只能被应用本身访问) Context.getPackageName() : <应用程序包> Context.getPackageResourcePath() : /data/app/com.zhd-1.apk Context.getPackageResourcePath() :用于获取该程序的安装包路径 Context.getDatabasePath():用于获取/ data / <应用程序包> / databases 目录 Environment.getRootDirectory().getPath() : /system Environment.getDataDirectory().getPath(): /data Environment.getExternalStorageDirectory().getPath() :用于获得内部存储卡路径

如何获取外置SD卡路径,由于不同厂商定制系统不同,没有统一的方法,可以通过运行时解析安装信息,详细可看下文 在开发中可能会遇到,向SD卡存储文件或让用户选择存储路径,你只有知道了路径才能创建文件,如内置存储卡路径innerPath =“/存储/模拟/ 0”,外置存储卡路径externalPath = “/存储/ extSdCard”。内置存储卡的路径好弄,一行代码就行,而外置内存卡路径就比较难搞定,因为还要兼顾不同手机的适配问题,不同手机路径名字不同,因为不同手机系统可能被定制过,你懂的。 第一种方式(推荐): 5.0版本及以上可用ContextCompat.getExternalFilesDirs() 获取到内置和外置存储卡路径 if (android.os.Build.VERSION.SDK_INT >= 19) { File[] files = ContextCompat.getExternalFilesDirs( BaseApplication.mContext, null)
5.0版本以下可以用反射 pathsTmp = (String[]) methodGetPaths.invoke(sManager);
详细代码代码地址:https://github.com/gaoleicoding/StorageCard 第二种方式: 根据安装信息解析SD卡路径。测试通很多型号手机项目中以运用,主要代码如下: <span style="font-size:14px;">public class SDCardUtil { public final String TAG = "SDCardUtils"; public final static String DIR_SINGLE_SDCARD_NAME = "内置存储卡"; public final static String DIR_SDCARD_NAME = "内置存储卡"; public final static String DIR_EXT_SDCARD_NAME = "扩展存储卡"; // public final static String DIR_UDISK_NAME = "USB存储设备"; private static String SDCARD_PATH = getSDCardPath(); // 系统目录链接,key是linkPath,value是realPath public static HashMap<String, String> sysLinkToReal = new HashMap<String, String>(); // 系统目录的反向链接, key是realPath,value是linkPath public static HashMap<String, String> sysRealToLink = new HashMap<String, String>(); public static boolean isMounted() { return Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED); public static String getSDCardPath() { return Environment.getExternalStorageDirectory().getAbsolutePath(); private static boolean testBasicFilter(String str) { String[] keys = new String[] { "sd", "emmc", "hwuserdata", "udisk", "ext_card", "usbotg", "disk1", "disk2", "disk3", "disk4", "usbdrivea", "usbdriveb", "usbdrivec", "usbdrived", "storage", for (int i = 0; i < len; i++) { if (str.contains(keys[i])) { public static boolean sdCardCanWrite(String path) { File SdCardRoot = new File(path); if (SdCardRoot.canWrite() == false) { if (Build.VERSION.SDK_INT >= 19) { // canWrite() 在4.4系统不起作用,只要路径存在总是返回true File testPath = new File(new File(path), ".testwrite" + String.valueOf(System.currentTimeMillis())); public static ArrayList<SDCardStat> getSDCardStats(Context context) { ArrayList<SDCardStat> list = new ArrayList<SDCardStat>(); Process process = Runtime.getRuntime().exec("mount"); BufferedReader br = new BufferedReader(new InputStreamReader( process.getInputStream())); // BufferedReader br = new BufferedReader(new InputStreamReader(new // FileInputStream("/mnt/sdcard/sdcards_info/mount.log"))); while ((str = br.readLine()) != null) { lowerStr = str.toLowerCase(); // Log.d("gaolei", "str---------line-----"+str); if(str.contains("/storage/")){ // MainActivity.filterBuilder.append("\nstorage:\n"); MainActivity.filterBuilder.append("storage----: "+str+"\n\n"); // if(str.contains("/sd/")){ // MainActivity.filterBuilder.append("sd:\n\n"); // MainActivity.filterBuilder.append(str+"\n\n"); // Utils.writeLog("getSDCardStats: " + lowerStr); if (!testBasicFilter(lowerStr)) { String[] cols = str.split("\\s+"); String path = findSDCardPath(cols); Log.d("gaolei", "path--------0-------"+path); if (TextUtils.isEmpty(path)) { // path = findUDiskPath(cols); // if (!TextUtils.isEmpty(path)) { // SDCardStat.Format format = findSDCardFormat(cols); // UDiskStat = new SDCardStat(path, SDCardStat.Format format = findSDCardFormat(cols); int minorIdx = (Format.vfat == format || Format.exfat == format || Format.texfat == format) ? findVoldDevNodeMinorIndex(cols) SDCardStat stat = new SDCardStat(path, format, minorIdx); Log.d("gaolei", "path--------1-------"+path); if (!compareData(list, stat.totalSize)) { // MainActivity.filterBuilder.append("path----: "+path+"\n\n"); // 4.4以上版本修改trootPath路径,因为4.4及以上版本不支持往外置SD卡根目录写权限 if (android.os.Build.VERSION.SDK_INT >= 19) { if (!SDCardUtil.sdCardCanWrite(path)) { File[] filePath = ContextCompat.getExternalFilesDirs( for (File f : filePath) { if (f.getAbsolutePath().startsWith(path)) { stat.rootPath = f.getAbsolutePath(); Log.d("gaolei", "path--------if-------"+path); MainActivity.filterBuilder.append("path--if--: "+path+"\n\n"); Log.d("gaolei", "path--------else-------"+path); MainActivity.filterBuilder.append("path--else--: "+path+"\n\n"); MainActivity.filterBuilder.append("path--other--: "+path+"\n\n"); Log.d("gaolei", "path--------other-------"+path); } catch (IOException e) { list = sortSDCardList(list); for (int idx = 0, size = list.size(); idx < size; idx++) { list.get(0).name = (size == 1) ? DIR_SINGLE_SDCARD_NAME list.get(1).name = DIR_EXT_SDCARD_NAME; list.get(idx).name = DIR_EXT_SDCARD_NAME + idx; // if (UDiskStat != null) { // UDiskStat.name = DIR_UDISK_NAME; private static boolean checkSDCardExistByRootPath(String path, ArrayList<SDCardStat> list) { boolean existFlag = false; for (SDCardStat stat : list) { if (SDCARD_PATH.equals(stat.rootPath)) { private static ArrayList<SDCardStat> sortSDCardList( ArrayList<SDCardStat> list) { ArrayList<SDCardStat> resultList = new ArrayList<SDCardStat>(); for (SDCardStat stat : list) { minIdx = stat.voldMinorIdx; if (stat.voldMinorIdx < minIdx || isInnerSdcard(stat.rootPath, stat.totalSize)) { minIdx = stat.voldMinorIdx; private final static long SD_PHY_SIZE_1G = 1000 * 1000 * 1000; private final static long SD_LOGIC_SIZE_1G = 1024 * 1024 * 1024; private final static double SD_LOGIC_DIFF = SD_LOGIC_SIZE_1G / (double) SD_PHY_SIZE_1G; static boolean nCF3(int n) { String s = Integer.toBinaryString(n); for (int i = 1; i < b.length; i++) { private static boolean isPhySize(long totalSize) { long count = totalSize / SD_PHY_SIZE_1G; if (!nCF3((int) count) || 0 >= totalSize) { double real_diff = SD_LOGIC_SIZE_1G * count / (double) totalSize; // 1.063 <= real_diff <= 1.083 result = real_diff >= SD_LOGIC_DIFF - 0.01 && real_diff <= SD_LOGIC_DIFF + 0.01; private static boolean isInnerSdcard(String path, long totalSize) { if (!path.endsWith("/")) { return !isPhySize(totalSize) && (Environment.getExternalStorageDirectory() .getAbsoluteFile().getCanonicalPath() + "/") } catch (IOException e) { private static String findSDCardPath(String[] mountInfo) { for (String col : mountInfo) { lowerStr = col.toLowerCase(); // Log.d("gaolei", "lowerStr--------------"+lowerStr); // lenovo 部分手机会把扩展卡bind镜像 /mnt/extrasd_bind if ((lowerStr.contains("sd") && !lowerStr.contains("extrasd_bind")) || lowerStr.contains("emmc") || lowerStr.contains("ext_card") || lowerStr.contains("external_sd") || lowerStr.contains("usbstorage")) { String pDir = getParentPath(col); // onda平板 扩展卡 /mnt/sdcard/external_sdcard, 三星note扩展卡 // /mnt/sdcard/external_sd // Sony C6603 扩展卡 /storage/removable/sdcard1 if (pDir.equals(getParentPath(SDCARD_PATH)) || pDir.equals(SDCARD_PATH) || pDir.equals(SDCARD_PATH + "/") || pDir.equals("/storage/") || pDir.equals("/storage/removable/")) { if ((col.contains("/storage/") && !col.contains("self") && !col Log.d("gaolei", "storage--------------"+col); if (col.equals("/mnt/ext_sdcard")) { if (col.equals("/udisk")) { if (col.equals("/HWUserData")) { // 部分 huawei 内置卡 /HWUserData if (col.equals("/storage/external")) { if (col.equals("/Removable/MicroSD")) { * 类似<strong>/storage/emulated/0</strong>不需要显示路径.<br> * 类似<strong>/storage/extSdCard/Android/data/{pageageName}/files</strong> * 只显示从<strong>/Android</strong>开头的路径. public static String getShowSDPath(SDCardStat stat) { String path = stat.rootPath; if (android.os.Build.VERSION.SDK_INT >= 19 && !stat.canWrite) { int index = path.indexOf("Android/data/"); showPath = path.substring(index); showPath = path.substring(path.lastIndexOf(File.separator) + 1); if (showPath.equals("0")) { private static SDCardStat.Format findSDCardFormat(String[] mountInfo) { for (SDCardStat.Format format : SDCardStat.Format.values()) { int len = format.toString().length(); if (len > formatMaxLength) { } else if (len < formatMinLength) { for (String col : mountInfo) { if (col.length() < formatMinLength || col.length() > formatMaxLength) { for (SDCardStat.Format format : SDCardStat.Format.values()) { if (format.toString().equals(col)) { private static String findVoldDevNodeIndex(String[] mountInfo) { if (mountInfo == null || mountInfo.length <= 0) { String voldInfo = mountInfo[0]; if (TextUtils.isEmpty(voldInfo)) { return voldInfo.replaceFirst("/dev/block/vold/", ""); private static int findVoldDevNodeMinorIndex(String[] mountInfo) { String voldInfo = findVoldDevNodeIndex(mountInfo); if (TextUtils.isEmpty(voldInfo)) { String[] infos = voldInfo.split(":"); if (infos == null || infos.length < 2) { return Integer.valueOf(infos[1]); private static DiskStat getDiskCapacity(String path) { File file = new File(path); StatFs stat = new StatFs(path); long blockSize = stat.getBlockSize(); long totalBlockCount = stat.getBlockCount(); long feeBlockCount = stat.getAvailableBlocks(); return new DiskStat(blockSize * feeBlockCount, blockSize public static String getParentPath(String path) { if (path != null && path.length() > 0) { path = path.substring(0, path.length() - 1); // 去掉最后一个字符 , 以兼容以“/” return path.substring(0, path.lastIndexOf(File.separator) + 1); // 1.判断如果总容量小于2G,则排除 2.排除内置或外置重复路径 public static boolean compareData(ArrayList<SDCardStat> list, for (int i = 0; i < list.size(); i++) { if (list.get(i).totalSize==capacity) { Log.d("gaolei", "duplicate-------------------------"); if (capacity/ 1073741824<2) { Log.d("gaolei", "capacity/ 1073741824-------------------------"+capacity/ 1073741824); public static class SDCardStat { public String excludePath; // 排除路径, 某些手机会将扩展卡挂载在sdcard下面 public boolean isCaseSensitive; // voldMinorIdx 小于此值的为sdcard内置设备,大于此值的是u盘 public static int SDCARD_MAX_COUNT = 1024; * Android4.4增加了SD卡读写权限设置,分为内置存储和外置SD卡,对权限见下表:<br> * <table width="60%" border="1" align="center"> * <th align="center">Action</th> * <th align="center">Primary</th> * <th align="center">Secondary</th> * <td>Read Top-Level Directories</td> * <td align="center">R</td> * <td align="center">R</td> * <td>Write Top-Level Directories</td> * <td align="center">W</td> * <td align="center">N</td> * <td>Read My Package’s Android Data Directory</td> * <td align="center">Y</td> * <td align="center">Y</td> * <td>Write My Package’s Android Data Directory</td> * <td align="center">Y</td> * <td align="center">Y</td> * <td>Read Another Package’s Android Data Directory</td> * <td align="center">R</td> * <td align="center">R</td> * <td>Write Another Package’s Android Data Directory</td> * <td align="center">W</td> * <td align="center">N</td> * <p style="text-align: center;"> * <strong>R = With Read Permission, W = With Write Permission, Y = * Always, N = Never </strong> * 根据上面表格判断SD类型,这个属性代表了Write Top-Level Directories的Secondary(外置SD卡).<br> * 由于部分手机厂商没有遵循Google新的SD卡规范,所以在部分Android4.4手机上外置SD卡的根目录仍然有读写 * 权限.所以只有在Android4.4以上手机,并且外置SD卡不可写的情况此属性才为<strong>false</strong>. public boolean canWrite = true; public SDCardStat(String path, Format format, int voldMinorIdx, DiskStat stat = getDiskCapacity(path); this.freeSize = stat.free; this.totalSize = stat.total; this.isCaseSensitive = checkCaseSensitive(format); // this.name = getSDCardName(path); this.voldMinorIdx = voldMinorIdx; this.excludePath = excludePath; public SDCardStat(String path, Format format, int voldMinorIdx) { this(path, format, voldMinorIdx, ""); public static enum Format { vfat, exfat, ext4, fuse, sdcardfs, texfat public boolean checkCaseSensitive(Format format) { return (format == Format.vfat || format == Format.exfat) ? false public void setExcludePath(String excludePath) { DiskStat excludeStat = getDiskCapacity(excludePath); if (excludeStat != null) { this.freeSize -= excludeStat.free; this.totalSize -= excludeStat.total; this.excludePath = excludePath; public void refreshDiskCapacity() { DiskStat stat = getDiskCapacity(this.rootPath); this.freeSize = stat.free; this.totalSize = stat.total; public static class DiskStat { public DiskStat(long free, long total) {
|