1

I'm new to android and I'm trying to develop file explorer which includes search function. I'm using a recursive search function that works fine in folders with a few subfolders and files, but for some reason it's EXTREMELY SLOW and could "Force Close" in folders with lots of subfolders and files, because there's not enough memory. I do the search by creating ArrayList where the results will be placed, and then calling the recursive function that will fill the list. The "path" argument is the file where the search will start from, and "query" is the search query.

ArrayList<File> result = new ArrayList<File>();
fileSearch(path, query, result);

this is what the recursive function looks like:

private void fileSearch(File dir, String query, ArrayList<File> res) {
    if (dir.getName().toLowerCase().contains(query.toLowerCase()))
        res.add(dir);
    if (dir.isDirectory() && !dir.isHidden()) {
        if (dir.list() != null) {
            for (File item : dir.listFiles()) {
                fileSearch(item, query, res);
            }
        }
    }
}

If someone could point me to a way of performing a faster and/or more efficient file search, I would really appreciate that.

EDIT:

This is how I tried to do the job with AsyncTask:

private class Search extends AsyncTask<File, Integer, Void> {

    String query;
    ArrayList<File> result = new ArrayList<File>();

    public Search(String query){
        this.query = query;
        setTitle("Searching");
    }

    @Override
    protected Void doInBackground(File... item) {
        int count = item.length;
        for (int i = 0; i < count; i++) {
            fileSearch(item[i], query, result);
            publishProgress((int) ((i / (float) count) * 100));
            // Escape early if cancel() is called
            if (isCancelled()) break;
        }
        return null;
    }

    protected void onProgressUpdate(Integer... progress) {
        setProgress(progress[0]);
    }

    protected void onPostExecute() {
        searchResults = new ListItemDetails[result.size()];
        for (int i = 0; i < result.size(); i++) {
            File temp = result.get(i);
            if (temp.isDirectory())
                searchResults[i] = new ListItemDetails(temp.getAbsolutePath(),
                        R.drawable.folder, temp.lastModified(), temp.length());
            else {
                String ext;
                if (temp.getName().lastIndexOf('.') == -1)
                    ext = "";
                else
                    ext = temp.getName().substring(
                            temp.getName().lastIndexOf('.'));
                searchResults[i] = new ListItemDetails(temp.getAbsolutePath(),
                        getIcon(ext), temp.lastModified(), temp.length());
            }
        }
        finishSearch();
    }

}

public void finishSearch() {
    Intent intent = new Intent(this, SearchResults.class);
    startActivity(intent);
}

The call to finishSearch() is just so I can create the Intent to show the results in other Activity. Any ideas, suggestions, tips? Thanks in advance

kiko283
  • 490
  • 6
  • 15

4 Answers4

4

It is possible that you are hitting symbolic links and going into an infinitive loop with your search function and depleting available memory to your application.

I would suggest you to keep a separate list containing canonical paths (File.getCanonicalPath()) of directories you've visited and avoid visiting them over and over again.

auselen
  • 27,577
  • 7
  • 73
  • 114
  • this is a really good advice, although I gave up on the explorer app (as you see there is no accepted answer), I really liked your advice, upvoted and commented so other people may find it useful too. – kiko283 Jan 06 '14 at 06:15
  • @kiko283 thanks however I think you still should accept the answer if it covers your problem regardless of what you did with your app. Cheers. – auselen Jan 06 '14 at 11:26
  • I haven't tested if it'll work, so I don't want to mislead people. Hope you understand. – kiko283 Jan 07 '14 at 01:17
2

Why don't you use Apache Commons IO? It has some functions to deal with searching.

I also suggest using the method FileUtils.listFiles, which takes a folder, a search query and a directory filter as parameters.

The following example returns you a list of all file's paths that matched according to a regex. Try adding it in doInBackground of your AsyncTask:

Collection files = FileUtils.listFiles(new File(yourRootPath), 
                   new RegexFileFilter(searchQuery), 
                   DirectoryFileFilter.DIRECTORY);
Thiago M Rocha
  • 1,416
  • 1
  • 20
  • 26
0

Have you looked into Lucene?

It is especially designed to index and query large numbers of free-text documents, so many of the I/O streaming and indexing tasks have already been solved for you. If you remove the recursion and do the document indexing using a Lucene index in a purely iterative fashion, memory issues may be mitigated.

Look into this thread:

Lucene in Android

Community
  • 1
  • 1
João Rocha da Silva
  • 4,259
  • 4
  • 26
  • 35
0

Do it in the background, and starting from Android O (API 26) , you can use Files.find API. Exmaple:

Files.find(
    Paths.get(startPath), Integer.MAX_VALUE,
    { path, _ -> path.fileName.toString() == file.name }
).forEach { foundPath ->
   Log.d("AppLog", "found file on:${foundPath.toFile().absolutePath}")
}
android developer
  • 114,585
  • 152
  • 739
  • 1,270