如何将文件从“assets”文件夹复制到SD卡?

269

我有一些文件在assets文件夹中。我需要将它们全部复制到一个名为/sdcard/folder的文件夹中。我想要在一个线程内完成此操作。如何做到这一点?


你正在寻找这个吗?https://dev59.com/7m855IYBdhLWcg3wQx5L#25988337 - DropAndTrap
2
在你复制/粘贴下面(很棒!)的解决方案之前,考虑使用这个库在一行代码中完成它:https://dev59.com/7m855IYBdhLWcg3wQx5L#41970539 - JohnnyLambada
23个回答

360

如果其他人也遇到了同样的问题,这是我是如何解决的

private void copyAssets() {
    AssetManager assetManager = getAssets();
    String[] files = null;
    try {
        files = assetManager.list("");
    } catch (IOException e) {
        Log.e("tag", "Failed to get asset file list.", e);
    }
    if (files != null) for (String filename : files) {
        InputStream in = null;
        OutputStream out = null;
        try {
          in = assetManager.open(filename);
          File outFile = new File(getExternalFilesDir(null), filename);
          out = new FileOutputStream(outFile);
          copyFile(in, out);
        } catch(IOException e) {
            Log.e("tag", "Failed to copy asset file: " + filename, e);
        }     
        finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    // NOOP
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    // NOOP
                }
            }
        }  
    }
}
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);
    }
}

参考:使用Java移动文件


28
要在sd卡中写入文件,您需要在清单文件中授予权限,例如:<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />。 - IronBlossom
22
我也不会依赖于sdcard位于/sdcard,而是使用Environment.getExternalStorageDirectory()来检索路径。 - Axarydax
2
应该使用:16*1024(16kb)。我倾向于选择16K或32K作为内存使用和性能之间的良好平衡。 - Nam Vu
3
@rciovati 在运行时遇到了以下错误:无法复制资产文件:myfile.txt java.io.FileNotFoundException: myfile.txt at android.content.res.AssetManager.openAsset (Native Method) - likejudo
7
只有在我添加以下内容时,这段代码才能正常运行:in = assetManager.open("images-wall/"+filename); 其中 "images-wall" 是我 assets 文件夹中的一个子文件夹。 - Ultimo_m
显示剩余13条评论

67

根据您的解决方案,我自己做了一些工作来允许子文件夹。可能会有人觉得这很有帮助:

...

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

}

1
在设备上,assetManager.list(path) 可能会很慢,为了事先创建资产路径列表,可以从 assets 目录使用以下片段:find . -name "*" -type f -exec ls -l {} \; | awk '{print substr($9,3)}' >> assets.list - alexkasko
3
好的解决方案!唯一需要修复的是在copyFileOrDir()函数开头修剪前导分隔符:path = path.startsWith("/") ? path.substring(1) : path;以使其可用。 - Cross_
这个stackoverflow在某些设备上出现,例如S5。 - behelit
当发生异常时,流不需要关闭吗? - AndreKR
3
请将 "/data/data/" + this.getPackageName() 替换为 this.getFilesDir().getAbsolutePath()。 - ibrahimyilmaz
1
finally 块中关闭流。 - Mixaz

50

上面的解决方案由于一些错误而无法正常工作:

  • 目录创建未能成功
  • Android 返回的资源还包含三个文件夹:images、sounds 和 webkit
  • 新增了处理大文件的方式:在项目中的 assets 文件夹中给文件添加扩展名 .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());
    }

}

编辑:更正了一个放错位置的 ";",导致出现系统性的“无法创建目录”错误。


4
必须让这成为解决方案! - Massimo Variolo
1
注意:Log.i("tag", "could not create dir "+fullPath);总是发生在if语句中分号放错位置的情况下。 - RoundSparrow hilltx
太棒了!非常感谢!但是为什么要检查JPG文件? - Phuong

33

我知道这个问题已经有了答案,但我有一种更加优雅的方法将文件从资产目录复制到SD卡上的文件。它不需要“for”循环,而是使用文件流和通道来完成工作。

(注意)如果使用任何类型的压缩文件,例如APK、PDF等,则可能需要在插入资产之前重命名文件扩展名,并在复制到SD卡后重新命名。

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

3
这种方法对我来说行不通, "This file can not be opened as a file descriptor; it is probably compressed" -- 这是一个PDF文件。你知道怎么解决吗? - Gaʀʀʏ
Eclipse提示:资源泄漏:'<未分配Closeable值>'从未关闭。 - behelit
1
假定inChannel.size()返回文件的大小。它没有这样的保证。我得到了两个文件的2.5 MiB,每个文件都是450 KiB。 - AI0867
1
我刚刚发现AssetFileDescriptor.getLength()会返回正确的文件大小。 - AI0867
1
除此之外,资源可能不会从文件描述符的位置0开始。AssetFileDescriptor.getStartOffset()将返回起始偏移量。 - AI0867
显示剩余11条评论

11

这将是Kotlin中简洁的方法。

    fun AssetManager.copyRecursively(assetPath: String, targetFile: File) {
        val list = list(assetPath)
        if (list.isEmpty()) { // assetPath is file
            open(assetPath).use { input ->
                FileOutputStream(targetFile.absolutePath).use { output ->
                    input.copyTo(output)
                    output.flush()
                }
            }

        } else { // assetPath is folder
            targetFile.delete()
            targetFile.mkdir()

            list.forEach {
                copyRecursively("$assetPath/$it", File(targetFile, it))
            }
        }
    }

list(assetPath)?.let { ... },实际上是可空的。 - Gábor
当使用FileOutputStream时,flush()是无用的,因为OutputStreamflush()的实现为空。 - Zhou Hongbo

6

试试这个,它更简单,这将帮助你:

// Open your local db as the input stream
    InputStream myInput = _context.getAssets().open(YOUR FILE NAME);

    // Path to the just created empty db
    String outFileName =SDCARD PATH + YOUR FILE NAME;

    // Open the empty db as the output stream
    OutputStream myOutput = new FileOutputStream(outFileName);

    // transfer bytes from the inputfile to the outputfile
    byte[] buffer = new byte[1024];
    int length;
    while ((length = myInput.read(buffer)) > 0) {
        myOutput.write(buffer, 0, length);
    }
    // Close the streams
    myOutput.flush();
    myOutput.close();
    myInput.close();

4
这是适用于当前Android设备的清理过的版本,功能性方法设计,可以将其复制到AssetsHelper类中,例如;)
/**
 * 
 * Info: prior to Android 2.3, any compressed asset file with an
 * uncompressed size of over 1 MB cannot be read from the APK. So this
 * should only be used if the device has android 2.3 or later running!
 * 
 * @param c
 * @param targetFolder
 *            e.g. {@link Environment#getExternalStorageDirectory()}
 * @throws Exception
 */
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public static boolean copyAssets(AssetManager assetManager,
        File targetFolder) throws Exception {
    Log.i(LOG_TAG, "Copying files from assets to folder " + targetFolder);
    return copyAssets(assetManager, "", targetFolder);
}

/**
 * The files will be copied at the location targetFolder+path so if you
 * enter path="abc" and targetfolder="sdcard" the files will be located in
 * "sdcard/abc"
 * 
 * @param assetManager
 * @param path
 * @param targetFolder
 * @return
 * @throws Exception
 */
public static boolean copyAssets(AssetManager assetManager, String path,
        File targetFolder) throws Exception {
    Log.i(LOG_TAG, "Copying " + path + " to " + targetFolder);
    String sources[] = assetManager.list(path);
    if (sources.length == 0) { // its not a folder, so its a file:
        copyAssetFileToFolder(assetManager, path, targetFolder);
    } else { // its a folder:
        if (path.startsWith("images") || path.startsWith("sounds")
                || path.startsWith("webkit")) {
            Log.i(LOG_TAG, "  > Skipping " + path);
            return false;
        }
        File targetDir = new File(targetFolder, path);
        targetDir.mkdirs();
        for (String source : sources) {
            String fullSourcePath = path.equals("") ? source : (path
                    + File.separator + source);
            copyAssets(assetManager, fullSourcePath, targetFolder);
        }
    }
    return true;
}

private static void copyAssetFileToFolder(AssetManager assetManager,
        String fullAssetPath, File targetBasePath) throws IOException {
    InputStream in = assetManager.open(fullAssetPath);
    OutputStream out = new FileOutputStream(new File(targetBasePath,
            fullAssetPath));
    byte[] buffer = new byte[16 * 1024];
    int read;
    while ((read = in.read(buffer)) != -1) {
        out.write(buffer, 0, read);
    }
    in.close();
    out.flush();
    out.close();
}

4

根据@DannyA在SO的回答,对此进行了修改。

private void copyAssets(String path, String outPath) {
    AssetManager assetManager = this.getAssets();
    String assets[];
    try {
        assets = assetManager.list(path);
        if (assets.length == 0) {
            copyFile(path, outPath);
        } else {
            String fullPath = outPath + "/" + path;
            File dir = new File(fullPath);
            if (!dir.exists())
                if (!dir.mkdir()) Log.e(TAG, "No create external directory: " + dir );
            for (String asset : assets) {
                copyAssets(path + "/" + asset, outPath);
            }
        }
    } catch (IOException ex) {
        Log.e(TAG, "I/O Exception", ex);
    }
}

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

    InputStream in;
    OutputStream out;
    try {
        in = assetManager.open(filename);
        String newFileName = outPath + "/" + 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();
        out.flush();
        out.close();
    } catch (Exception e) {
        Log.e(TAG, e.getMessage());
    }

}

准备工作

src/main/assets 中添加名为 fold 的文件夹。

用法

File outDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString());
copyAssets("fold",outDir.toString());

在外部目录中找到所有在assets文件夹内的文件和目录


3

在Rohith Nandakumar的方案基础上,我做了一些自己的东西来从assets子文件夹(即"assets/MyFolder")复制文件。同时,在尝试再次复制之前,我会检查文件是否已存在于sdcard中。

private void copyAssets() {
    AssetManager assetManager = getAssets();
    String[] files = null;
    try {
        files = assetManager.list("MyFolder");
    } catch (IOException e) {
        Log.e("tag", "Failed to get asset file list.", e);
    }
    if (files != null) for (String filename : files) {
        InputStream in = null;
        OutputStream out = null;
        try {
          in = assetManager.open("MyFolder/"+filename);
          File outFile = new File(getExternalFilesDir(null), filename);
          if (!(outFile.exists())) {// File does not exist...
                out = new FileOutputStream(outFile);
                copyFile(in, out);
          }
        } catch(IOException e) {
            Log.e("tag", "Failed to copy asset file: " + filename, e);
        }     
        finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    // NOOP
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    // NOOP
                }
            }
        }  
    }
}
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);
    }
}

3

利用这个问题中的一些概念,我编写了一个名为AssetCopier的类,使得复制/assets/变得简单。它可以在GitHub上获得,并且可以通过jitpack.io进行访问:

new AssetCopier(MainActivity.this)
        .withFileScanning()
        .copy("tocopy", destDir);

请查看https://github.com/flipagram/android-assetcopier,了解更多详情。

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