android.os下的StatFs类主要用来获取文件系统的状态,能够获取sd卡的大小和剩余空间,例如通过实例化一个StatFs对象 new StatFs(path.getPath())把SD卡根目录路径传进去可以获取SD卡根目录下的内存存储状态。调用系统API:Environment.getExternalStorageDirectory().getPath();得到的是SDcard路径为内置的SDcard路径。当然,现在很多手机厂商处理SDcard的路径也都不相同。
SD卡的状态通常有一下这几种状态,显然,只有Environment.MEDIA_MOUNTED才是我们想要的。
Environment.MEDIA_MOUNTED // sd卡在手机上正常使用状态 Environment.MEDIA_UNMOUNTED // 用户手工到手机设置中卸载sd卡之后的状态 Environment.MEDIA_REMOVED // 用户手动卸载,然后将sd卡从手机取出之后的状态 Environment.MEDIA_BAD_REMOVAL // 用户未到手机设置中手动卸载sd卡,直接拨出之后的状态 Environment.MEDIA_SHARED // 手机直接连接到电脑作为u盘使用之后的状态 Environment.MEDIA_CHECKINGS // 手机正在扫描sd卡过程中的状态
不废话了,创建个项目,在写代码前先给项目配置权限:
第三个权限加不加根据自己的需求,但是前两个权限我们必须配置
<!-- 支持SD卡的写入权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <!-- 支持SD卡的读取权限 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <!-- 支持SD卡的创建,删除文件或者文件夹权限 --> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
1、首先,我们先封装一个SDCard操作类SDCardUtils.java,在这个工具类中,我们封装一些操作方法,方便我们后面使用。
第一步就是先判断你的SD卡是否可用,当return返回的结果为true,代表SD卡正常可用。
后面就是我们封装的一些方法了,有获取SD卡的总内存大小、获取SD卡的可用内存大小、获取SD卡根目录的路径、读取指定文件中数据、向指定路径中写入数据、复制文件等方法,代码中注释写的很详细,就不再一一赘述。SD卡可操作的方法有很多,不止这些。你可以根据自己的需求,去添加一些操作方法。
public class SDCardUtils { // 判断sd卡是否可用 public boolean isSdSafe() { /** * 当equals结果为true时,代表sd卡可正常使用 */ return Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED); } // 获取sd卡总大小 public String getTotalSize(Context context) { // 获取SD卡根目录 File path = Environment.getExternalStorageDirectory(); // 获取指定目录下的内存存储状态 StatFs stat = new StatFs(path.getPath()); // 获取单个扇区的大小 long blockSize = stat.getBlockSize(); // 获取扇区的数量 long totalBlocks = stat.getBlockCount(); // 总空间 = 扇区的总数 * 扇区的大小 long totalSize = blockSize * totalBlocks; // 格式化文件大小的格式 Log.i("lyb", "总空间 = " + Formatter.formatFileSize(context, totalSize)); return Formatter.formatFileSize(context, totalSize); } // 获取sd卡的可用大小 public String getAvailableSize(Context context) { // 获取SD卡根目录 File path = Environment.getExternalStorageDirectory(); // 获取指定目录下的内存存储状态 StatFs stat = new StatFs(path.getPath()); // 获取单个扇区的大小 long blockSize = stat.getBlockSize(); // 获取可以使用的扇区数量 long availableBlocks = stat.getAvailableBlocks(); // 可用空间 = 扇区的大小 + 可用的扇区 long availableSize = blockSize * availableBlocks; // 格式化文件大小的格式 Log.i("lyb", "可用空间 = " + Formatter.formatFileSize(context, availableSize)); return Formatter.formatFileSize(context, availableSize); } // 获取sd卡根目录字符串的路径 public String getSdPath() { return Environment.getExternalStorageDirectory().getAbsolutePath(); } public File getSdFile() { return Environment.getExternalStorageDirectory(); } /** * 读取指定文件中的数据,将数据读取为byte[]类型 * 参数:要读取数据的文件路径 */ public byte[] getDataFromFile(String path){ FileInputStream fis = null; ByteArrayOutputStream bos = null; try { fis = new FileInputStream(path); byte[] b = new byte[1024]; int num =-1; bos = new ByteArrayOutputStream(); while ((num = fis.read(b)) != -1) { bos.write(b,0,num); } return bos.toByteArray(); } catch (Exception e) { e.printStackTrace(); } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } if (bos != null) { try { bos.close(); } catch (IOException e) { e.printStackTrace(); } } } return null; } /** * 向指定路径中写入指定数据 * 1. 设置写人数据要存储到的文件夹的路径 * 2. 要写入的数据 * 3. 文件名称 */ public void saveFile (String path,byte[] b,String fileName) { File file = new File(path); if (!file.exists()) { file.mkdirs(); } try { FileOutputStream fos = new FileOutputStream(path+File.separator+fileName); fos.write(b); fos.close(); } catch (Exception e) { e.printStackTrace(); } } /* * 复制指定文件 * 1. 要复制的原文件 * 2. 复制文件的存储路径 * 3. 复制文件的文件名称 * */ public void copyFile(File source,String path,String fileName){ try { FileInputStream fis = new FileInputStream(source); FileOutputStream fos = new FileOutputStream(path+File.separator+fileName); byte[] b = new byte[1024]; int num = -1; while((num = fis.read(b)) != -1) { fos.write(b,0,num); } fos.close(); fis.close(); } catch (Exception e) { e.printStackTrace(); } } }
2、通过封装好的工具类提供的操作方法可以获取到我们想要的数据,然后利用ListView控件自定义一个适配器去展示这些数据。
获取的数据可能是子文件夹或者文件,所以我们用两个图片标识,这样列表中的数据就能一目了然了。
另外,当用户点击某个子文件夹进入该子文件夹的目录下又会得到新的数据。给ListView注册监听事件,currentFile是定义的File变量,用来标记当前用户选中的文件或者子文件夹,如果currentFile是子文件夹就会再去调用initData()方法获取子文件夹目录下的文件,记得在获取数据前先调用clear()方法把当前列表中的数据清空,最后调用适配器的notifyDataSetChanged()方法刷新列表。
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { currentFile = files[position]; if (currentFile.isDirectory()) { initData(currentFile); adapter.notifyDataSetChanged(); } } });
private void initData(File file) { //获取 files = file.listFiles(); list.clear(); for (File f: files) { if (f.isDirectory()) { // 判断其是否为一个文件夹 list.add(f); } else { list.add(f); } } }
@Override public void onBackPressed() { if (!currentFile.getAbsolutePath().equals(utils.getSdPath())) { //获取上一层目录 currentFile = currentFile.getParentFile(); initData(currentFile); adapter.notifyDataSetChanged(); } else { finish(); } }
另外,APP里用到了一个ProgressBar控件,用来展示SD卡内存使用的情况。
前面我们获取的SD卡内存总大小(Formatter.formatFileSize(context, totalSize))和可用内存空间数据(Formatter.formatFileSize(context, availableSize))是格式化的,3.80GB、4.84GB。所以必须转成数字。这里用了字符串一些操作方法得到的返回值是可用内存所占总内存的百分数,然后给progress.setMax(100)就OK了。当然你也可以不这样做。
long availableSize = blockSize * availableBlocks;
long totalSize = blockSize * totalBlocks;
直接拿到long类型的数据。
private int getProgress(String str1, String str2){ String [] totalArr = str1.split("G"); String [] avaArr = str2.split("G"); double total = Double.parseDouble(totalArr[0]); double ava = Double.parseDouble(avaArr[0]); return (int) (ava/total*100); }
这里附上完整的代码,有兴趣的点击下载代码:项目源码
补充:
Context.getFilesDir(); //得到的是当前应用程序默认在内部的数据存储目录 Context.getCacheDir(); //得到的是当前应用程序默认在内部的缓存文件的存储目录 Context.getExternalCacheDir(); //得到的是当前应用程序默认在外部的缓存文件的存储目录 Context.getgetExternalFilesDir(null); //得到的是当前应用程序默认在外部的数据文件的存储目录 Environment.getExternalStorageDirectory(); //得到的是系统外部数据存储目录 Environment.getDownloadCacheDirectory(); //得到的是系统内部缓存存储目录 Environment.getDataDirectory(); //得到的是系统内部数据存储目录
番外篇:
谷歌推出了Android Studio 2.2正式版,你更新了吗。
本次更新的内容,包括speed, smarts, and Android platform support,也就是速度、智能和Android平台三个方面。
约束布局(ConstraintLayout),它是今年 Google 推出的一种全新的布局,变得更强大,而且属性也特别多。Constraint Layout 可以实现以前我们开发中各种需要嵌套才能实现的效果,考虑到实际操作,Google 提供了一种完全可视化的操作界面。
下面给大家推荐两篇博客,一篇stormzhangV的,一篇徐宜生的。
stormzhangV的Android Studio 2.2 来啦
医生的新博客详细的介绍了ConstraintLayout基本界面,有兴趣的点击可阅:
自律给你自由——设计布局的新姿势