我在资产文件夹里有几个文件。我需要把它们都复制到一个文件夹,比如/sdcard/folder。我想从一个线程中做这件事。我该怎么做?


当前回答

基本上有两种方法。

首先,您可以使用AssetManager。打开和,如Rohith Nandakumar所述,并在输入流上迭代。

其次,您可以使用AssetManager。openFd,它允许你使用一个FileChannel(它有transferTo和transferFrom方法),所以你不必自己循环输入流。

我将在这里描述openFd方法。

压缩

首先,您需要确保文件以未压缩的方式存储。打包系统可能会选择压缩任何扩展名未标记为noCompress的文件,并且压缩后的文件不能映射到内存中,因此您必须依赖于AssetManager。在那个箱子里打开。

你可以给你的文件添加一个“。mp3”扩展名来阻止它被压缩,但正确的解决方案是修改你的app/build。gradle文件并添加以下行(禁用PDF文件压缩)

aaptOptions {
    noCompress 'pdf'
}

包装文件

请注意,打包器仍然可以将多个文件打包成一个文件,因此您不能只读取AssetManager提供的整个文件。您需要询问AssetFileDescriptor需要哪些部分。

找到已打包文件的正确部分

一旦您确保您的文件存储为未压缩的,您就可以使用AssetManager。openFd方法来获取一个AssetFileDescriptor,它可以用来获取FileInputStream(不像AssetManager. openFd方法)。open,返回一个InputStream),其中包含一个FileChannel。它还包含起始偏移量(getStartOffset)和大小(getLength),用于获取文件的正确部分。

实现

下面给出了一个实现示例:

private void copyFileFromAssets(String in_filename, File out_file){
    Log.d("copyFileFromAssets", "Copying file '"+in_filename+"' to '"+out_file.toString()+"'");
    AssetManager assetManager = getApplicationContext().getAssets();
    FileChannel in_chan = null, out_chan = null;
    try {
        AssetFileDescriptor in_afd = assetManager.openFd(in_filename);
        FileInputStream in_stream = in_afd.createInputStream();
        in_chan = in_stream.getChannel();
        Log.d("copyFileFromAssets", "Asset space in file: start = "+in_afd.getStartOffset()+", length = "+in_afd.getLength());
        FileOutputStream out_stream = new FileOutputStream(out_file);
        out_chan = out_stream.getChannel();
        in_chan.transferTo(in_afd.getStartOffset(), in_afd.getLength(), out_chan);
    } catch (IOException ioe){
        Log.w("copyFileFromAssets", "Failed to copy file '"+in_filename+"' to external storage:"+ioe.toString());
    } finally {
        try {
            if (in_chan != null) {
                in_chan.close();
            }
            if (out_chan != null) {
                out_chan.close();
            }
        } catch (IOException ioe){}
    }
}

这个答案是基于摩根大通的答案。

其他回答

由于一些错误,上述解决方案无法工作:

目录创建失败 Android返回的资产也包含三个文件夹:图像,声音和webkit 增加了处理大文件的方法:在项目的资产文件夹中添加扩展名.mp3到文件,在复制目标文件时将没有.mp3扩展名

下面是代码(我留下了Log语句,但你现在可以删除它们):

final static String TARGET_BASE_PATH = "/sdcard/appname/voices/";

private void copyFilesToSdCard() {
    copyFileOrDir(""); // copy all files in assets folder in my project
}

private void copyFileOrDir(String path) {
    AssetManager assetManager = this.getAssets();
    String assets[] = null;
    try {
        Log.i("tag", "copyFileOrDir() "+path);
        assets = assetManager.list(path);
        if (assets.length == 0) {
            copyFile(path);
        } else {
            String fullPath =  TARGET_BASE_PATH + path;
            Log.i("tag", "path="+fullPath);
            File dir = new File(fullPath);
            if (!dir.exists() && !path.startsWith("images") && !path.startsWith("sounds") && !path.startsWith("webkit"))
                if (!dir.mkdirs())
                    Log.i("tag", "could not create dir "+fullPath);
            for (int i = 0; i < assets.length; ++i) {
                String p;
                if (path.equals(""))
                    p = "";
                else 
                    p = path + "/";

                if (!path.startsWith("images") && !path.startsWith("sounds") && !path.startsWith("webkit"))
                    copyFileOrDir( p + assets[i]);
            }
        }
    } catch (IOException ex) {
        Log.e("tag", "I/O Exception", ex);
    }
}

private void copyFile(String filename) {
    AssetManager assetManager = this.getAssets();

    InputStream in = null;
    OutputStream out = null;
    String newFileName = null;
    try {
        Log.i("tag", "copyFile() "+filename);
        in = assetManager.open(filename);
        if (filename.endsWith(".jpg")) // extension was added to avoid compression on APK file
            newFileName = TARGET_BASE_PATH + filename.substring(0, filename.length()-4);
        else
            newFileName = TARGET_BASE_PATH + filename;
        out = new FileOutputStream(newFileName);

        byte[] buffer = new byte[1024];
        int read;
        while ((read = in.read(buffer)) != -1) {
            out.write(buffer, 0, read);
        }
        in.close();
        in = null;
        out.flush();
        out.close();
        out = null;
    } catch (Exception e) {
        Log.e("tag", "Exception in copyFile() of "+newFileName);
        Log.e("tag", "Exception in copyFile() "+e.toString());
    }

}

编辑:更正了一个错位的“;”,这是抛出一个系统的“无法创建目录”错误。

您还可以使用Guava的ByteStream将文件从资产文件夹复制到SD卡。这是我最终得到的解决方案,将文件递归地从资产文件夹复制到SD卡:

/**
 * Copies all assets in an assets directory to the SD file system.
 */
public class CopyAssetsToSDHelper {

    public static void copyAssets(String assetDir, String targetDir, Context context) 
        throws IOException {
        AssetManager assets = context.getAssets();
        String[] list = assets.list(assetDir);
        for (String f : Objects.requireNonNull(list)) {
            if (f.indexOf(".") > 1) { // check, if this is a file
                File outFile = new File(context.getExternalFilesDir(null), 
                    String.format("%s/%s", targetDir, f));
                File parentFile = outFile.getParentFile();
                if (!Objects.requireNonNull(parentFile).exists()) {
                    if (!parentFile.mkdirs()) {
                        throw new IOException(String.format("Could not create directory %s.", 
                            parentFile));
                    }
                }
                try (InputStream fin = assets.open(String.format("%s/%s", assetDir, f));
                     OutputStream fout = new FileOutputStream(outFile)) {
                    ByteStreams.copy(fin, fout);
                }
            } else { // This is a directory
                copyAssets(String.format("%s/%s", assetDir, f), String.format("%s/%s", targetDir, f), 
                    context);
            }
        }
    }

}

基于Yoram Cohen的回答,这里有一个支持非静态目标目录的版本。

使用copyFileOrDir(getDataDir(), "")来写入应用程序内部存储文件夹/data/data/pkg_name/

Supports subfolders. Supports custom and non-static target directory Avoids copying "images" etc fake asset folders like private void copyFileOrDir(String TARGET_BASE_PATH, String path) { AssetManager assetManager = this.getAssets(); String assets[] = null; try { Log.i("tag", "copyFileOrDir() "+path); assets = assetManager.list(path); if (assets.length == 0) { copyFile(TARGET_BASE_PATH, path); } else { String fullPath = TARGET_BASE_PATH + "/" + path; Log.i("tag", "path="+fullPath); File dir = new File(fullPath); if (!dir.exists() && !path.startsWith("images") && !path.startsWith("sounds") && !path.startsWith("webkit")) if (!dir.mkdirs()) Log.i("tag", "could not create dir "+fullPath); for (int i = 0; i < assets.length; ++i) { String p; if (path.equals("")) p = ""; else p = path + "/"; if (!path.startsWith("images") && !path.startsWith("sounds") && !path.startsWith("webkit")) copyFileOrDir(TARGET_BASE_PATH, p + assets[i]); } } } catch (IOException ex) { Log.e("tag", "I/O Exception", ex); } } private void copyFile(String TARGET_BASE_PATH, String filename) { AssetManager assetManager = this.getAssets(); InputStream in = null; OutputStream out = null; String newFileName = null; try { Log.i("tag", "copyFile() "+filename); in = assetManager.open(filename); if (filename.endsWith(".jpg")) // extension was added to avoid compression on APK file newFileName = TARGET_BASE_PATH + "/" + filename.substring(0, filename.length()-4); else newFileName = TARGET_BASE_PATH + "/" + filename; out = new FileOutputStream(newFileName); byte[] buffer = new byte[1024]; int read; while ((read = in.read(buffer)) != -1) { out.write(buffer, 0, read); } in.close(); in = null; out.flush(); out.close(); out = null; } catch (Exception e) { Log.e("tag", "Exception in copyFile() of "+newFileName); Log.e("tag", "Exception in copyFile() "+e.toString()); } }

我知道这已经回答,但我有一个稍微更优雅的方式从资产目录复制到sdcard上的文件。它不需要“for”循环,而是使用文件流和通道来完成工作。

(注)如果使用任何类型的压缩文件,APK, PDF,…你可能想重命名文件扩展名之前插入到资产,然后重命名一旦你复制到SDcard)

AssetManager am = context.getAssets();
AssetFileDescriptor afd = null;
try {
    afd = am.openFd( "MyFile.dat");

    // Create new file to copy into.
    File file = new File(Environment.getExternalStorageDirectory() + java.io.File.separator + "NewFile.dat");
    file.createNewFile();

    copyFdToFile(afd.getFileDescriptor(), file);

} catch (IOException e) {
    e.printStackTrace();
}

一种复制文件而不必循环遍历它的方法。

public static void copyFdToFile(FileDescriptor src, File dst) throws IOException {
    FileChannel inChannel = new FileInputStream(src).getChannel();
    FileChannel outChannel = new FileOutputStream(dst).getChannel();
    try {
        inChannel.transferTo(0, inChannel.size(), outChannel);
    } finally {
        if (inChannel != null)
            inChannel.close();
        if (outChannel != null)
            outChannel.close();
    }
}

基于您的解决方案,我自己做了一些事情来允许子文件夹。有些人可能会觉得这很有用:

...

copyFileOrDir("myrootdir");

...

private void copyFileOrDir(String path) {
    AssetManager assetManager = this.getAssets();
    String assets[] = null;
    try {
        assets = assetManager.list(path);
        if (assets.length == 0) {
            copyFile(path);
        } else {
            String fullPath = "/data/data/" + this.getPackageName() + "/" + path;
            File dir = new File(fullPath);
            if (!dir.exists())
                dir.mkdir();
            for (int i = 0; i < assets.length; ++i) {
                copyFileOrDir(path + "/" + assets[i]);
            }
        }
    } catch (IOException ex) {
        Log.e("tag", "I/O Exception", ex);
    }
}

private void copyFile(String filename) {
    AssetManager assetManager = this.getAssets();

    InputStream in = null;
    OutputStream out = null;
    try {
        in = assetManager.open(filename);
        String newFileName = "/data/data/" + this.getPackageName() + "/" + filename;
        out = new FileOutputStream(newFileName);

        byte[] buffer = new byte[1024];
        int read;
        while ((read = in.read(buffer)) != -1) {
            out.write(buffer, 0, read);
        }
        in.close();
        in = null;
        out.flush();
        out.close();
        out = null;
    } catch (Exception e) {
        Log.e("tag", e.getMessage());
    }

}