13

Hej,

I have some data shipped out with the app which shall be copied on the external storage. It's nested in a couple of subfolders and I'd like to copy the whole structure.

I'm having a hard time getting a File object for any ressource in /assets. But I think I'm depended on that 'cause I need something like File.isDirectory() to determine if I have to start copying or dive deeper into the system.

My first approach was using Assets Manager but it seems that class is not providing the information I need. The most promising why was to obtain an AssetFileDescriptorand go down to a [FileDescriptor][2]. However non of them seems to have a isDirectory-method.

So my other approach is straight forward: Creating a File Object and be happy. However it seems like I'm running in this problem of lacking a proper path to instance the file object. I'm aware of file://android_asset but it doesn't seem to work for the fileconstructor.

My last idea would to utilise the InputStream (which I need for copying anyway) and somehow filter the byte for a significant bit which indicates this resource to be a directory. That's a pretty hacky solution and probably right in the hell of ineffectiveness but I don't see another way to get around that.

nuala
  • 2,681
  • 4
  • 30
  • 50
  • You can also check https://stackoverflow.com/a/46827944/2179872 which makes a combination of both ***AssetManager.list()*** and ***AssetManager.open()*** methods – Cosmin Oct 19 '17 at 10:42

3 Answers3

13

I had the same problem. At some point I realized that list() is really slow (50ms on every call), so i'm using a different approach now:

I have an (eclipse) ant-builder which creates an index-file everytime my asset-folder changes. The file just contains one file-name per line, so directories are listed implicitely (if they are not empty).

The Builder:

<?xml version="1.0"?>
<project default="createAssetIndex">
    <target name="createAssetIndex">
        <fileset id="assets.fileset" dir="assets/" includes="**"
            excludes="asset.index" />
        <pathconvert pathsep="${line.separator}" property="assets"
            refid="assets.fileset">
            <mapper>
                <globmapper from="${basedir}/assets/*" to="*"
                    handledirsep="yes" />
            </mapper>
        </pathconvert>
        <echo file="assets/asset.index">${assets}</echo>
    </target>
</project>

The class which loads asset.index into a List of Strings, so you can do arbitrary stuff with it, fast:

import android.content.ContextWrapper;

import com.google.common.collect.ImmutableList;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

import java.util.Scanner;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * uses asset.index file (which is pregenerated) since asset-list()s take very long
 *
 */
public final class AssetIndex {

    //~ Static fields/initializers -------------------------------------------------------------------------------------

    private static final Logger L = LoggerFactory.getLogger(AssetIndex.class);

    //~ Instance fields ------------------------------------------------------------------------------------------------

    private final ImmutableList<String> files;

    //~ Constructors ---------------------------------------------------------------------------------------------------

    public AssetIndex(final ContextWrapper contextWrapper) {

        ImmutableList.Builder<String> ib = ImmutableList.builder();

        L.debug("creating index from assets");

        InputStream in  = null;
        Scanner scanner = null;
        try {
            in          = contextWrapper.getAssets().open("asset.index");
            scanner     = new Scanner(new BufferedInputStream(in));

            while (scanner.hasNextLine()) {
                ib.add(scanner.nextLine());
            }

            scanner.close();
            in.close();

        } catch (final IOException e) {
            L.error(e.getMessage(), e);
        } finally {
            if (scanner != null) {
                scanner.close();
            }
            if (in != null) {
                try {
                    in.close();
                } catch (final IOException e) {
                    L.error(e.getMessage(), e);
                }
            }
        }

        this.files = ib.build();
    }

    //~ Methods --------------------------------------------------------------------------------------------------------

    /* returns the number of files in a directory */
    public int numFiles(final String dir) {

        String directory = dir;
        if (directory.endsWith(File.separator)) {
            directory = directory.substring(0, directory.length() - 1);
        }

        int num = 0;
        for (final String file : this.files) {
            if (file.startsWith(directory)) {

                String rest = file.substring(directory.length());
                if (rest.charAt(0) == File.separatorChar) {
                    if (rest.indexOf(File.separator, 1) == -1) {
                        num = num + 1;
                    }
                }
            }
        }

        return num;
    }
}
Fabian Zeindl
  • 5,860
  • 8
  • 54
  • 78
  • 1
    This is very good and useful, but I needed to add 'dirsep="/"' to pathconvert. Otherwise on Windoze Java says the path separator is '\', which doesn't work in Android asset manager – Flynny75 Feb 02 '14 at 22:21
  • 1
    For those struggling with this and using Gradle, see http://stackoverflow.com/questions/18569983/androidgradle-list-directories-into-a-file – Petrus Repo May 30 '14 at 06:21
  • I'm using gradle, is there a solution by using gradle? – wukong Apr 17 '19 at 03:10
4

In my specific case, regular files have a name like filename.ext, while directories only have a name, without extension, and their name never contains the "." (dot) character. So a regular file can be distinguished from a directory by testing its name as follows:

filename.contains(".")

If this your case too, the same solution should work for you.

Giorgio Barchiesi
  • 6,109
  • 3
  • 32
  • 36
4

list() on AssetManager will probably give a null / zero length array / IOException if you try to get a list on a file, but a valid response on a directory.

But otherwise it should be file:///android_asset (with 3 /)

FunkTheMonk
  • 10,908
  • 1
  • 31
  • 37
  • I'll have a look for the list-hack. yeah but using new File(file:///android_asset/foo.bar) doesn't seem to work. I don't really know what type of object I get back but it fails for both test: isDirectory() AND (!) isFile(). – nuala Dec 06 '11 at 15:13
  • 1
    I wish I could double vote for this answer. `String[] s = am.list(filename)` will throw an `IOException` in case filename is a file. I really like this solution, thanks a lot! – nuala Dec 06 '11 at 17:38
  • However, list() is really slow (see the other answer) – Petrus Repo May 30 '14 at 06:20