如何android扫描SD卡列出大文件

闲来无事,正好以前老碰到这个磁盘空间满的问题

背景

安卓手机用了一段时间之后经常就报磁盘空间不够了,当然可以用手机管家,或者缓存/应用清理工具释放空间,但是还是会有莫名其妙的空间不够;这是由安卓的属性导致的,即使把APP卸载,他占用的空间SD卡空间也不会被释放。


image.png

导致的结果是SD空间会越来越紧张,最后只能恢复出厂设置解决问题。

谁用了我的空间

我们自然会想到底是谁用了我的SD卡空间呢;现在各个厂家安卓系统都会提供一定功能,检测大文件,检测应用占用大空间;但是到目前我还没有发现有基于目录罗列的,列出每一个目录,每一个文件占用多大的空间,因为有了这个,我们就比较清楚,到底是谁在占用我们的空间。

然后我们可以自己判断,是否可以删除这个文件或者目录。手动把文件删除,以释放空间。

我的SD卡扫描程序

基于上述目标,反正闲着也是闲着,写了一个很简单的SD卡扫描程序,列出所有的大文件。

程序主题页面如下:


image.png

第一个输入框指示扫描多大的文件(M),点击"Scan"按钮,在下面的列表框中,显示出SD卡所有的大于指定大小的文件,在这个例子中一共22个大于5M的大文件。

这里我们可以看到Baidu_music和baiduTTS下面有很多大文件,这些文件是可以放心的删除的,而用手机管家(华为家的)就扫不出这个问题,他会认为这些是真实的APP文件,不能删除。

贴代码,供参考

在Android 6.0上测试通过。

  1. AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.mydiskscan">

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

这个地方,注意一点,因为其他都是缺省生成的:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

我们需要SD卡的读取权限,因为要扫描SD卡啊。

  1. 两个layout文件
  • activity_main.xml
    这是主activity,也就是APP的主界面。
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:focusable="true"
    android:focusableInTouchMode="true"
    tools:context="com.example.mydiskscan.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="76dip"
        android:orientation="horizontal" >

        <EditText
            android:layout_width="80dip"
            android:layout_height="40dip"
            android:inputType="numberDecimal"
            android:text="5"
            android:id="@+id/edit_size"
            android:contentDescription="Scan file greater than" />

        <TextView
            android:layout_width="40dip"
            android:layout_height="40dip"
            android:text="M"
            android:id="@+id/textView" />

        <Button
            android:text="Scan"
            android:layout_width="120dip"
            android:layout_height="40dip"
            android:id="@+id/button_scan" />
    </LinearLayout>

    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/list_file" />
</LinearLayout>

注意在头上的这两行:

android:focusable="true"
android:focusableInTouchMode="true"

这两行是用来使得APP刚打开的时候焦点不在EditText框里面,否则每回一打开,焦点定位输入框,输入法窗口就显示出来,极不美观大方。

  • filelistitem.xml
    这是文件的列表项的view,每一个大于指定大小的大文件,包含一项;内容包含序号,大小,和文件路径。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <TextView
        android:id="@+id/filelistitem_id"
        android:layout_width="20dp"
        android:layout_height="20dp" />

    <TextView
        android:id="@+id/filelistitem_size"
        android:layout_width="40dp"
        android:layout_height="20dp" />

    <TextView
        android:id="@+id/filelistitem_path"
        android:layout_width="wrap_content"
        android:layout_height="20dp" />
</LinearLayout>
  1. activity源代码文件

作为简易程序,我把所有的代码都放在了一个源文件里面;当然这不是规范的做法,不符合软件工程的追求啊。

package com.example.mydiskscan;

import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.StatFs;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends Activity {
    private static final String tag = MainActivity.class.getSimpleName();
    private List<ViewFile> fileList = new ArrayList<ViewFile>();

    private FileListAdapter fileListAdapter = new FileListAdapter();
    private ScanListener scanListener = new ScanListener();

    private EditText edtThreshold;
    private ListView listFileView;
    private Button btnScan;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        requestStoragePermission();

        edtThreshold = (EditText)findViewById(R.id.edit_size);
        listFileView = (ListView)findViewById(R.id.list_file);
        btnScan = (Button) findViewById(R.id.button_scan);

        btnScan.setOnClickListener(scanListener);
        listFileView.setAdapter(fileListAdapter );
    }

    private void requestStoragePermission() {
        String[] permissions = {"android.permission.READ_EXTERNAL_STORAGE"};
        int requestCode = 200;
        int permission = checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
        if (permission != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(permissions, requestCode);
        }
    }

    private long getFileSize(String prefix, File file, int threshold) {
        long size = file.length();
        if (file.isDirectory()) {
            File[] subs = file.listFiles();
            if (subs != null) {
                for (File sub : subs) {
                    size += getFileSize(prefix, sub, threshold);
                }
            }
        }

        float sizem = (float) (size * 1.0 / 1024 / 1024);
        if (sizem > threshold && !file.isDirectory()) {
            String path = file.getPath();
            if (path.startsWith(prefix)) {
                path = path.substring(prefix.length());
            }

            Log.i(tag, "File: path=" + path + ", size= " + sizem);
            fileList.add(new ViewFile(path, sizem));
        }
        return size;
    }

    class ViewFile {
        ViewFile(String p, float s) {
            path = p;
            size = s;
        }
        private String path;
        private float size;
    }

    class ScanListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            StatFs sf = null;
            long blockSize = 0, total = 0, free = 0, available = 0;
                /*
                File root = Environment.getRootDirectory();
                sf = new StatFs(root.getPath());
                blockSize = sf.getBlockSize();
                total      = sf.getBlockCount() * blockSize/1024/1024;
                free       = sf.getFreeBlocks() * blockSize/1024/1024;
                available  = sf.getAvailableBlocks() * blockSize/1024/1024;
                Log.i(tag, "Root Storage: path = " + root.getAbsolutePath() + ", total=" + total + ", free=" + free + ", available=" + available);
                */

            String state = Environment.getExternalStorageState();
            if(Environment.MEDIA_MOUNTED.equals(state)) {
                File sdcardDir = Environment.getExternalStorageDirectory();
                //sf = new StatFs(sdcardDir.getPath());
                //blockSize = sf.getBlockSize();
                //total      = sf.getBlockCount() * blockSize/1024/1024;
                //free       = sf.getFreeBlocks() * blockSize/1024/1024;
                //available  = sf.getAvailableBlocks() * blockSize/1024/1024;
                //Log.i(tag, "External Storage: path=" + sdcardDir.getAbsolutePath() + ", total=" + total + ", free=" + free + ", available=" + available);
                if (sdcardDir.isDirectory()) {
                    File[] subs = sdcardDir.listFiles();
                    if (subs != null) {
                        fileList.clear();
                        int threshold = 5;
                        try {
                            threshold = Integer.parseInt(edtThreshold.getText().toString());
                        } catch (NumberFormatException e) {
                            edtThreshold.setText("5");
                        }
                        for (int i = 0; i < subs.length; i++) {
                            getFileSize(sdcardDir.getPath(), subs[i], threshold);
                        }
                        fileListAdapter.notifyDataSetChanged();
                    }
                }
            }
        }
    }

    class FileListAdapter extends BaseAdapter {
        @Override
        public int getCount() {
            return fileList.size();
        }
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view = null;
            if(convertView == null){
                LayoutInflater inflater = MainActivity.this.getLayoutInflater();
                view = inflater.inflate(R.layout.filelistitem, null);
            }else{
                view = convertView;
            }

            ViewFile m = fileList.get(position);
            TextView v = (TextView)view.findViewById(R.id.filelistitem_id);
            v.setText(  String.valueOf(position + 1)  );

            v = (TextView)view.findViewById(R.id.filelistitem_size);
            v.setText(  String.valueOf(m.size)  );

            v = (TextView)view.findViewById(R.id.filelistitem_path);
            v.setText(  m.path  );
            return view;
        }

        @Override
        public Object getItem(int position) {
            // TODO Auto-generated method stub
            return null;
        }

        @Override
        public long getItemId(int position) {
            // TODO Auto-generated method stub
            return 0;
        }
    }
}

几个注意的地方:

  1. requestStoragePermission();
    虽然在manifest文件里面指定了需要SD卡的读权限,但是后来的版本都需要APP显式的请求用户确认,以获取相应的权限,否则即使在manifest指定了权限,APP还是无法访问SD卡。


    image.png
  2. 后续还可以增强一下
    增加等待提示符,也就是在扫描的同时,提示用户我正在扫描,请稍后。

  3. 界面和代码可以继续美化改进
    谢谢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容