从Assets复制目录到本地目录

23

我试图使用位于assets文件夹中的目录并将其作为File访问。是否可以将Assets目录中的内容作为File访问?如果不能,如何将目录从Assets文件夹复制到应用程序的本地目录?

我会像这样复制一个文件:

    try
    {
        InputStream stream = this.getAssets().open("myFile");
        OutputStream output = new BufferedOutputStream(new FileOutputStream(this.getFilesDir() + "/myNewFile"));

        byte data[] = new byte[1024];
        int count;

        while((count = stream.read(data)) != -1)
        {
            output.write(data, 0, count);
        }

        output.flush();
        output.close();
        stream.close();
    }
    catch(IOException e)
    {
        e.printStackTrace();
    }

然而,我不确定如何针对一个目录执行此操作。

我宁愿不要以某些不起作用的东西为基础构建我的基础设施,那么如何将一个目录从Assets复制到本地目录,或者是否可以像使用File一样访问Assets中的目录?

编辑

这是我为自己的项目解决此问题的方法:

InputStream stream = null;
OutputStream output = null;

for(String fileName : this.getAssets().list("demopass"))
{
    stream = this.getAssets().open("directoryName/" + fileName);
    output = new BufferedOutputStream(new FileOutputStream(this.getFilesDir() + "/newDirectory/" + fileName));

    byte data[] = new byte[1024];
    int count;

    while((count = stream.read(data)) != -1)
    {
        output.write(data, 0, count);
    }

    output.flush();
    output.close();
    stream.close();

    stream = null;
    output = null;
}

1
也许这可以帮助:https://dev59.com/7m855IYBdhLWcg3wQx5L - dmaxi
1
@dmaxi,感谢您的建议,但我想复制的不是Assets目录,而是Assets文件夹中的一个目录。 - RileyE
这个命令不会复制子目录,对吧? - Flash Thunder
尝试使用以下链接复制子目录: https://dev59.com/7m855IYBdhLWcg3wQx5L#25988337 - DropAndTrap
尝试使用此链接复制子目录: - DropAndTrap
7个回答

11

如评论区dmaxi所建议,你可以使用他提供的链接,附上以下代码:

    void displayFiles (AssetManager mgr, String path) {
        try {
            String list[] = mgr.list(path);
            if (list != null)
                for (int i=0; i<list.length; ++i)
                {
                    Log.v("Assets:", path +"/"+ list[i]);
                    displayFiles(mgr, path + "/" + list[i]);
                }
        } catch (IOException e) {
             Log.v("List error:", "can't list" + path);
        }
     }

我在这个链接上找到了它。也许你可以将此代码与之前的代码结合使用。 编辑:另请参见AssetManager
private void copyFolder(String name) {
            // "Name" is the name of your folder!
    AssetManager assetManager = getAssets();
    String[] files = null;

    String state = Environment.getExternalStorageState();

    if (Environment.MEDIA_MOUNTED.equals(state)) {
        // We can read and write the media
        // Checking file on assets subfolder
        try {
            files = assetManager.list(name);
        } catch (IOException e) {
            Log.e("ERROR", "Failed to get asset file list.", e);
        }
        // Analyzing all file on assets subfolder
        for(String filename : files) {
            InputStream in = null;
            OutputStream out = null;
            // First: checking if there is already a target folder
            File folder = new File(Environment.getExternalStorageDirectory() + "/yourTargetFolder/" + name);
            boolean success = true;
            if (!folder.exists()) {
                success = folder.mkdir();
            }
            if (success) {
                // Moving all the files on external SD
                try {
                    in = assetManager.open(name + "/" +filename);
                    out = new FileOutputStream(Environment.getExternalStorageDirectory() + "/yourTargetFolder/" + name + "/" + filename);
                    Log.i("WEBVIEW", Environment.getExternalStorageDirectory() + "/yourTargetFolder/" + name + "/" + filename);
                    copyFile(in, out);
                    in.close();
                    in = null;
                    out.flush();
                    out.close();
                    out = null;
                } catch(IOException e) {
                    Log.e("ERROR", "Failed to copy asset file: " + filename, e);
                } finally {
                    // Edit 3 (after MMs comment)
                    in.close();
                    in = null;
                    out.flush();
                    out.close();
                    out = null;
                }
            }
            else {
                // Do something else on failure
            }       
        }
    } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
        // We can only read the media
    } else {
        // Something else is wrong. It may be one of many other states, but all we need
        // is to know is we can neither read nor write
    }
}

// Method used by copyAssets() on purpose to copy a file.
private void copyFile(InputStream in, OutputStream out) throws IOException {
    byte[] buffer = new byte[1024];
    int read;
    while((read = in.read(buffer)) != -1) {
        out.write(buffer, 0, read);
    }
}

编辑2:我已经添加了一个示例:此代码片段仅从assets中复制特定文件夹到SD卡。如果可以,请告诉我!


这对我复制目录没有帮助,因为我只能复制目录中的文件,列出所有文件将复制所有文件。我希望整个目录都能被复制,而不是资产目录中的每个文件。 - RileyE
@RileyE 我太好奇了,想找到一个解决方案,我已经详细阐述了一个解决方案,请看看我的答案!希望它能起作用。 - JJ86
我认为这会起作用。我无法弄清如何让 getAssets(String) 中的字符串参数工作。如果我能让它起作用,我将跟进我的解决方案。 - RileyE
1
如果出现故障,您没有关闭流(请使用finally块!) - Martin Marconcini
我认为out可能为空,如果出现NPE,则不会关闭它,但我不确定。赞一个 ;) - Martin Marconcini

5
这里有一个递归函数可以完成此操作 - copyAssetFolder
public static boolean copyAssetFolder(Context context, String srcName, String dstName) {
    try {
        boolean result = true;
        String fileList[] = context.getAssets().list(srcName);
        if (fileList == null) return false;

        if (fileList.length == 0) {
            result = copyAssetFile(context, srcName, dstName);
        } else {
            File file = new File(dstName);
            result = file.mkdirs();
            for (String filename : fileList) {
                result &= copyAssetFolder(context, srcName + File.separator + filename, dstName + File.separator + filename);
            }
        }
        return result;
    } catch (IOException e) {
        e.printStackTrace();
        return false;
    }
}

public static boolean copyAssetFile(Context context, String srcName, String dstName) {
    try {
        InputStream in = context.getAssets().open(srcName);
        File outFile = new File(dstName);
        OutputStream out = new FileOutputStream(outFile);
        byte[] buffer = new byte[1024];
        int read;
        while ((read = in.read(buffer)) != -1) {
            out.write(buffer, 0, read);
        }
        in.close();
        out.close();
        return true;
    } catch (IOException e) {
        e.printStackTrace();
        return false;
    }
}

或者用 Kotlin 实现相同功能

fun AssetManager.copyAssetFolder(srcName: String, dstName: String): Boolean {
    return try {
        var result = true
        val fileList = this.list(srcName) ?: return false
        if (fileList.isEmpty()) {
            result = copyAssetFile(srcName, dstName)
        } else {
            val file = File(dstName)
            result = file.mkdirs()
            for (filename in fileList) {
                result = result and copyAssetFolder(
                    srcName + separator.toString() + filename,
                    dstName + separator.toString() + filename
                )
            }
        }
        result
    } catch (e: IOException) {
        e.printStackTrace()
        false
    }
}

fun AssetManager.copyAssetFile(srcName: String, dstName: String): Boolean {
    return try {
        val inStream = this.open(srcName)
        val outFile = File(dstName)
        val out: OutputStream = FileOutputStream(outFile)
        val buffer = ByteArray(1024)
        var read: Int
        while (inStream.read(buffer).also { read = it } != -1) {
            out.write(buffer, 0, read)
        }
        inStream.close()
        out.close()
        true
    } catch (e: IOException) {
        e.printStackTrace()
        false
    }
}

2
以下是 OP 答案的简洁版本:
public void copyAssetFolderToFolder(Context activity, String assetsFolder, File destinationFolder) {
  InputStream stream = null;
  OutputStream output = null;
  try {
    for (String fileName : activity.getAssets().list(assetsFolder)) {
      stream = activity.getAssets().open(assetsFolder + ((assetsFolder.endsWith(File.pathSeparator))?"":File.pathSeparator) + fileName);
      output = new BufferedOutputStream(new FileOutputStream(new File(destinationFolder, fileName)));

      byte data[] = new byte[1024];
      int count;

      while ((count = stream.read(data)) != -1) {
        output.write(data, 0, count);
      }

      output.flush();
      output.close();
      stream.close();

      stream = null;
      output = null;
    }
  } catch (/*any*/Exception e){e.printStackTrace();}
}

为了以后参考,请节省大家的麻烦并发布完整的源代码清单。如果您能够发布完整的答案,这个网站可以成为初学者和专家的极佳编码资源。不能假设其他人“理解”随机代码块所属的位置或代码应在其中执行的上下文。
此示例需要活动的上下文,其中包含getAssets()方法。在Android平台中,除了Activity之外,还有其他类可以提供此上下文。一个例子是(通用引用)Service类。

“complete” 意味着在方法/类头中完整地编写代码,而且不包含任何泛化的省略代码,例如:... 做某事 ...。初学者可能无法区分伪代码和实际代码。 - Hypersoft Systems
你需要修复以下代码:
  • ((assetsFolder.endsWith(File.pathSeparator))?"":File.pathSeparator) + 结果是:我将其更改为
  • "/" + 这样代码就可以完美运行了。 请更新代码,因为我花了大约1小时搜索。
- M.Ali El-Sayed

2
您可以使用以下方法将您的资产文件夹复制到SD卡的位置。从调用方法中,只需调用moveAssetToStorageDir("")来移动整个资产文件夹。对于子文件夹,您可以指定在资产文件夹内的相对路径。
public void moveAssetToStorageDir(String path){
    File file = getExternalFilesDir(null);
    String rootPath = file.getPath() + "/" + path;
    try{
        String [] paths = getAssets().list(path);
        for(int i=0; i<paths.length; i++){
            if(paths[i].indexOf(".")==-1){
                File dir = new File(rootPath + paths[i]);
                dir.mkdir();
                moveAssetToStorageDir(paths[i]);
            }else {
                File dest = null;
                InputStream in = null;
                if(path.length() == 0) {
                    dest = new File(rootPath + paths[i]);
                    in = getAssets().open(paths[i]);
                }else{
                    dest = new File(rootPath + "/" + paths[i]);
                    in = getAssets().open(path + "/" + paths[i]);
                }
                dest.createNewFile();
                FileOutputStream out = new FileOutputStream(dest);
                byte [] buff = new byte[in.available()];
                in.read(buff);
                out.write(buff);
                out.close();
                in.close();
            }
        }
    }catch (Exception exp){
        exp.printStackTrace();
    }
}

1
这是一个用Kotlin编写的递归解决方案,可用于文件和目录。
用法 - copyAssetDir(context, "<asset path>", "<dest dir>")
import android.content.Context
import java.io.File
import java.io.FileOutputStream

fun copyAssetDir(context: Context, assetPath: String, destDirPath: String) {
    walkAssetDir(context, assetPath) {
        copyAssetFile(context, it, "$destDirPath/$it")
    }
}

fun walkAssetDir(context: Context, assetPath: String, callback: ((String) -> Unit)) {
    val children = context.assets.list(assetPath) ?: return
    if (children.isEmpty()) {
        callback(assetPath)
    } else {
        for (child in children) {
            walkAssetDir(context, "$assetPath/$child", callback)
        }
    }
}

fun copyAssetFile(context: Context, assetPath: String, destPath: String): File {
    val destFile = File(destPath)
    File(destFile.parent).mkdirs()
    destFile.createNewFile()

    context.assets.open(assetPath).use { src ->
        FileOutputStream(destFile).use { dest ->
            src.copyTo(dest)
        }
    }

    return destFile
}

1

将任意文件夹和文件从Assets移动

问题是... Assets是特殊的。您无法将其包装在File对象中并询问isDirectory(),也无法将这些资产传递到NDK。因此,最好将它们封装起来,将它们移动到缓存目录或SDCard上,这就是为什么您在这里的原因。

我看到了许多SO答案,涉及一些版本的遍历fileOrDirectoryName字符串数组,然后创建目录,跟随递归调用和复制单个文件。这导致您创建一个文件夹或文件,并且您无法从资产中确定您拥有哪个。

将其制作成Zip文件

我的建议是将要运送到SDCard或内部缓存文件夹的每个任意资产集合压缩。该问题结构更符合Assets概念。

AssetManager assetManager = context.getAssets();
String fullAssetPath = fromAssetPath + "/" + zipFilename;
String toPath = "/wherever/I/want";

try {
    InputStream inputStream = assetManager.open(fullAssetPath);
    ZipInputStream zipInputStream = new ZipInputStream(new BufferedInputStream(inputStream));

    ZipEntry zipEntry;
    byte[] buffer = new byte[8192];
    while ((zipEntry = zipInputStream.getNextEntry()) != null) {
        String fileOrDirectory = zipEntry.getName();

        Uri.Builder builder = new Uri.Builder();
        builder.scheme("file");
        builder.appendPath(toPath);
        builder.appendPath(fileOrDirectory);
        String fullToPath = builder.build().getPath();

        if (zipEntry.isDirectory()) {
            File directory = new File(fullToPath);
            directory.mkdirs();
            continue;
        }   

        FileOutputStream fileOutputStream = new FileOutputStream(fullToPath);
        while ((count = zipInputStream.read(buffer)) != -1) {
            fileOutputStream.write(buffer, 0, count);
        }
        fileOutputStream.close();
        zipInputStream.closeEntry();
    }

    zipInputStream.close();

} catch (IOException e) {
    Log.e(TAG, e.getLocalizedMessage());
}

缓冲区大小的一小笔记

我见过很多使用非常小的缓冲区大小的例子,比如1024。除非你只想浪费时间,否则可以尝试更大的字节缓冲区大小。即使我选择的8192在现代硬件上也可能很小。

避免使用字符串路径

请注意使用Uri.Builder构建路径的方式。我更喜欢这种方式的路径构建,而不是directory + "/" + file。然后你就可以为了一致性而避免分配String d = "myDirectory/"String f = "/file.txt"等这样的字符串操作无聊。


1
谢谢,我一直在寻找一个能执行这种功能的模型。 - Hypersoft Systems
@Cameron Lowell Palmer,将您的示例代码放入AsyncTask或ExecutorService中(使用Executors.newSingleThreadExecutor())会有什么不利影响吗? - AJW

0

这是一个复制资产文件夹及其目录和文件到SD卡文件夹的代码...对我来说,这个代码完美地工作...

public 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());
}

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接