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

269

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


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

3
将所有文件和目录从资产复制到您的文件夹!
为了更好地复制,请使用Apache Commons IO。
public void doCopyAssets() throws IOException {
    File externalFilesDir = context.getExternalFilesDir(null);

    doCopy("", externalFilesDir.getPath());

}

//这是复制的主要方法

private void doCopy(String dirName, String outPath) throws IOException {

    String[] srcFiles = assets.list(dirName);//for directory
    for (String srcFileName : srcFiles) {
        String outFileName = outPath + File.separator + srcFileName;
        String inFileName = dirName + File.separator + srcFileName;
        if (dirName.equals("")) {// for first time
            inFileName = srcFileName;
        }
        try {
            InputStream inputStream = assets.open(inFileName);
            copyAndClose(inputStream, new FileOutputStream(outFileName));
        } catch (IOException e) {//if directory fails exception
            new File(outFileName).mkdir();
            doCopy(inFileName, outFileName);
        }

    }
}

public static void closeQuietly(AutoCloseable autoCloseable) {
    try {
        if(autoCloseable != null) {
            autoCloseable.close();
        }
    } catch(IOException ioe) {
        //skip
    }
}

public static void copyAndClose(InputStream input, OutputStream output) throws IOException {
    copy(input, output);
    closeQuietly(input);
    closeQuietly(output);
}

public static void copy(InputStream input, OutputStream output) throws IOException {
    byte[] buffer = new byte[1024];
    int n = 0;
    while(-1 != (n = input.read(buffer))) {
        output.write(buffer, 0, n);
    }
}

3

使用Kotlin可以轻松完成,这里我只复制了一些文件而不是全部从assets目录到我的应用程序文件目录。

private fun copyRelatedAssets() {
    val assets = arrayOf("myhome.html", "support.css", "myscript.js", "style.css")
    assets.forEach {
        val inputStream = requireContext().assets.open(it)
        val nameSplit = it.split(".")
        val name = nameSplit[0]
        val extension = nameSplit[1]
        val path = inputStream.getFilePath(requireContext().filesDir, name, extension)
        Log.v(TAG, path)
    }
}

以下是扩展函数:

fun InputStream.getFilePath(dir: File, name: String, extension: String): String {
    val file = File(dir, "$name.$extension")
    val outputStream = FileOutputStream(file)
    this.copyTo(outputStream, 4096)
    return file.absolutePath
}

LOGCAT

/data/user/0/com.***.***/files/myhome.html
/data/user/0/com.***.***/files/support.css
/data/user/0/com.***.***/files/myscript.js
/data/user/0/com.***.***/files/style.css

2

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

2

这可以通过两种方式完成。

首先,您可以使用AssetManager.open方法,并像Rohith Nandakumar所描述的那样迭代输入流。

其次,您可以使用AssetManager.openFd方法,它允许您使用FileChannel(具有transferTotransferFrom方法),因此您不必自己循环输入流。

我将在这里介绍openFd方法。

压缩

首先,您需要确保文件未经压缩存储。封装系统可能会选择压缩任何未标记为“noCompress”的扩展名的文件,而压缩文件无法映射到内存,因此您必须在这种情况下依靠AssetManager.open

您可以将'.mp3'扩展名添加到文件中以防止对其进行压缩,但适当的解决方案是修改您的app/build.gradle文件并添加以下行(以禁用PDF文件的压缩)。

aaptOptions {
    noCompress 'pdf'
}

文件打包

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

查找打包文件的正确部分

一旦您确保文件存储为未压缩状态,就可以使用AssetManager.openFd方法获取AssetFileDescriptor,该对象可用于获取包含FileChannelFileInputStream(与返回InputStreamAssetManager.open不同)。它还包含起始偏移量(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){}
    }
}

这个答案基于 JPM的回答


1
import android.app.Activity;
import android.content.Intent;
import android.content.res.AssetManager;
import android.net.Uri;
import android.os.Environment;
import android.os.Bundle;
import android.util.Log;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;


public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        copyReadAssets();
    }


    private void copyReadAssets()
    {
        AssetManager assetManager = getAssets();

        InputStream in = null;
        OutputStream out = null;

        String strDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)+ File.separator + "Pdfs";
        File fileDir = new File(strDir);
        fileDir.mkdirs();   // crear la ruta si no existe
        File file = new File(fileDir, "example2.pdf");



        try
        {

            in = assetManager.open("example.pdf");  //leer el archivo de assets
            out = new BufferedOutputStream(new FileOutputStream(file)); //crear el archivo


            copyFile(in, out);
            in.close();
            in = null;
            out.flush();
            out.close();
            out = null;
        } catch (Exception e)
        {
            Log.e("tag", e.getMessage());
        }

        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setDataAndType(Uri.parse("file://" + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + File.separator + "Pdfs" + "/example2.pdf"), "application/pdf");
        startActivity(intent);
    }

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

更改类似以下代码的部分:

out = new BufferedOutputStream(new FileOutputStream(file));

前面的例子是针对PDF文件的,而后面的例子则是针对.txt文件。

FileOutputStream fos = new FileOutputStream(file);

1
稍作修改以上答案,以递归方式复制文件夹并适应自定义目标。
public void copyFileOrDir(String path, String destinationDir) {
    AssetManager assetManager = this.getAssets();
    String assets[] = null;
    try {
        assets = assetManager.list(path);
        if (assets.length == 0) {
            copyFile(path,destinationDir);
        } else {
            String fullPath = destinationDir + "/" + path;
            File dir = new File(fullPath);
            if (!dir.exists())
                dir.mkdir();
            for (int i = 0; i < assets.length; ++i) {
                copyFileOrDir(path + "/" + assets[i], destinationDir + path + "/" + assets[i]);
            }
        }
    } catch (IOException ex) {
        Log.e("tag", "I/O Exception", ex);
    }
}

private void copyFile(String filename, String destinationDir) {
    AssetManager assetManager = this.getAssets();
    String newFileName = destinationDir + "/" + filename;

    InputStream in = null;
    OutputStream out = null;
    try {
        in = assetManager.open(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());
    }
    new File(newFileName).setExecutable(true, false);
}

1
在Kotlin中,这可以用一行代码完成!
将扩展函数添加到InputStream。
fun InputStream.toFile(to: File){
    this.use { input->
        to.outputStream().use { out->
            input.copyTo(out)
        }
    }
}

然后使用它

MainActivity.kt

assets.open("test.zip").toFile(File(filesDir,"test.zip"))

1
嗨,大家好。我做了这样的事情。 为了复制第N层深度的文件夹和文件进行复制。 这使你能够从Android AssetManager中复制所有目录结构 :)
    private void manageAssetFolderToSDcard()
    {

        try
        {
            String arg_assetDir = getApplicationContext().getPackageName();
            String arg_destinationDir = FRConstants.ANDROID_DATA + arg_assetDir;
            File FolderInCache = new File(arg_destinationDir);
            if (!FolderInCache.exists())
            {
                copyDirorfileFromAssetManager(arg_assetDir, arg_destinationDir);
            }
        } catch (IOException e1)
        {

            e1.printStackTrace();
        }

    }


    public String copyDirorfileFromAssetManager(String arg_assetDir, String arg_destinationDir) throws IOException
    {
        File sd_path = Environment.getExternalStorageDirectory(); 
        String dest_dir_path = sd_path + addLeadingSlash(arg_destinationDir);
        File dest_dir = new File(dest_dir_path);

        createDir(dest_dir);

        AssetManager asset_manager = getApplicationContext().getAssets();
        String[] files = asset_manager.list(arg_assetDir);

        for (int i = 0; i < files.length; i++)
        {

            String abs_asset_file_path = addTrailingSlash(arg_assetDir) + files[i];
            String sub_files[] = asset_manager.list(abs_asset_file_path);

            if (sub_files.length == 0)
            {
                // It is a file
                String dest_file_path = addTrailingSlash(dest_dir_path) + files[i];
                copyAssetFile(abs_asset_file_path, dest_file_path);
            } else
            {
                // It is a sub directory
                copyDirorfileFromAssetManager(abs_asset_file_path, addTrailingSlash(arg_destinationDir) + files[i]);
            }
        }

        return dest_dir_path;
    }


    public void copyAssetFile(String assetFilePath, String destinationFilePath) throws IOException
    {
        InputStream in = getApplicationContext().getAssets().open(assetFilePath);
        OutputStream out = new FileOutputStream(destinationFilePath);

        byte[] buf = new byte[1024];
        int len;
        while ((len = in.read(buf)) > 0)
            out.write(buf, 0, len);
        in.close();
        out.close();
    }

    public String addTrailingSlash(String path)
    {
        if (path.charAt(path.length() - 1) != '/')
        {
            path += "/";
        }
        return path;
    }

    public String addLeadingSlash(String path)
    {
        if (path.charAt(0) != '/')
        {
            path = "/" + path;
        }
        return path;
    }

    public void createDir(File dir) throws IOException
    {
        if (dir.exists())
        {
            if (!dir.isDirectory())
            {
                throw new IOException("Can't create directory, a file is in the way");
            }
        } else
        {
            dir.mkdirs();
            if (!dir.isDirectory())
            {
                throw new IOException("Unable to create directory");
            }
        }
    }

最终创建一个异步任务:

    private class ManageAssetFolders extends AsyncTask<Void, Void, Void>
    {

        @Override
        protected Void doInBackground(Void... arg0)
        {
            manageAssetFolderToSDcard();
            return null;
        }

    }

从您的活动中调用它:

    new ManageAssetFolders().execute();

1

对于那些正在更新到Kotlin的人:

按照this步骤避免FileUriExposedExceptions,假设用户已经授予WRITE_EXTERNAL_STORAGE权限并且您的文件位于assets/pdfs/mypdf.pdf中。

private fun openFile() {
    var inputStream: InputStream? = null
    var outputStream: OutputStream? = null
    try {
        val file = File("${activity.getExternalFilesDir(null)}/$PDF_FILE_NAME")
        if (!file.exists()) {
            inputStream = activity.assets.open("$PDF_ASSETS_PATH/$PDF_FILE_NAME")
            outputStream = FileOutputStream(file)
            copyFile(inputStream, outputStream)
        }

        val uri = FileProvider.getUriForFile(
            activity,
            "${BuildConfig.APPLICATION_ID}.provider.GenericFileProvider",
            file
        )
        val intent = Intent(Intent.ACTION_VIEW).apply {
            setDataAndType(uri, "application/pdf")
            addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
        }
        activity.startActivity(intent)
    } catch (ex: IOException) {
        ex.printStackTrace()
    } catch (ex: ActivityNotFoundException) {
        ex.printStackTrace()
    } finally {
        inputStream?.close()
        outputStream?.flush()
        outputStream?.close()
    }
}

@Throws(IOException::class)
private fun copyFile(input: InputStream, output: OutputStream) {
    val buffer = ByteArray(1024)
    var read: Int = input.read(buffer)
    while (read != -1) {
        output.write(buffer, 0, read)
        read = input.read(buffer)
    }
}

companion object {
    private const val PDF_ASSETS_PATH = "pdfs"
    private const val PDF_FILE_NAME = "mypdf.pdf"
}

当使用FileOutputStream时,flush()是无用的,因为OutputStreamflush()的实现为空。 而且你也可以使用Stream.use{}来省略对close的调用。 - Zhou Hongbo

1
那是我的个性化文本提取器类,希望它有用。
package lorenzo.morelli.platedetector;

import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;

import com.googlecode.tesseract.android.TessBaseAPI;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class TextExtractor {

    private final Context context;
    private final String dirName;
    private final String language;

    public TextExtractor(final Context context, final String dirName, final String language) {
        this.context = context;
        this.dirName = dirName;
        this.language = language;
    }

    public String extractText(final Bitmap bitmap) {
        final TessBaseAPI tessBaseApi = new TessBaseAPI();
        final String datapath = this.context.getFilesDir()+ "/tesseract/";
        checkFile(new File(datapath + this.dirName + "/"), datapath, this.dirName, this.language);

        tessBaseApi.init(datapath, this.language);
        tessBaseApi.setImage(bitmap);
        final String extractedText = tessBaseApi.getUTF8Text();
        tessBaseApi.end();
        return extractedText;
    }

    private void checkFile(final File dir, final String datapath, final String dirName, final String language) {
        //directory does not exist, but we can successfully create it
        if (!dir.exists()&& dir.mkdirs()) {
            copyFiles(datapath, dirName, language);
        } //The directory exists, but there is no data file in it
        if(dir.exists()) {
            final String datafilepath = datapath + "/" + dirName + "/" + language + ".traineddata";
            final File datafile = new File(datafilepath);
            if (!datafile.exists()) {
                copyFiles(datapath, dirName, language);
            }
        }
    }

    private void copyFiles(final String datapath, final String dirName, final String language) {
        try {
            //location we want the file to be at
            final String filepath = datapath + "/" + dirName + "/" + language + ".traineddata";

            //get access to AssetManager
            final AssetManager assetManager = this.context.getAssets();

            //open byte streams for reading/writing
            final InputStream instream = assetManager.open(dirName + "/" + language + ".traineddata");
            final OutputStream outstream = new FileOutputStream(filepath);

            //copy the file to the location specified by filepath
            byte[] buffer = new byte[1024];
            int read;
            while ((read = instream.read(buffer)) != -1) {
                outstream.write(buffer, 0, read);
            }
            outstream.flush();
            outstream.close();
            instream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

要使用它,您需要训练数据文件。您可以从此链接下载trainddata文件。

一旦您下载了所需的训练数据文件,您需要在Android项目中创建一个名为“assets”的Android资源目录。在新创建的资产文件夹中,您需要创建一个名为“tessdata”的常规目录,您可以在其中放置训练数据文件。 最后,您需要在MainActivity中初始化“TextExtractor”类。

final TextExtractor textExtractor = new TextExtractor(this, "tessdata", "eng");

第一个参数是上下文,第二个参数是刚创建的目录名称,最后一个参数是刚下载的traineddata的语言。

要提取文本,您需要调用“extractText”方法:

final String text = textExtractor.extractText(imageWithText);

请注意,extractText需要一个位图图像才能工作!您可以使用以下代码从drawable文件创建一个位图图像:
final BitMap image = BitmapFactory.decodeResource(getResources(), R.drawable.test_image);

如果您需要更多支持,我建议您遵循这个有用的指南:https://github.com/SamVanRoy/Android_OCR_App

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