【问题标题】:Load files bigger than 1M from assets folder从 assets 文件夹加载大于 1M 的文件
【发布时间】:2011-02-21 01:20:09
【问题描述】:

我快疯了,我创建了一个文件对象,所以它可以用 ObjectInputStream 读取,我放置了 assets 文件夹。 该方法适用于小于 1M 的文件,但较大的文件会出错。 我读到这是 Android 平台的限制,但我也知道可以“轻松”避免。 例如,下载了雷霆雷霆游戏的人可以很容易地看到他们的资产文件夹中有一个 18.9M 大的文件。 这是我从 ObjecInputStream 中读取 1 个对象的代码

File f = File.createTempFile("mytempfile", "dat");
FileOutputStream fos = new FileOutputStream(f);

InputStream is = mc.getAssets().open(path,3);

ObjectInputStream ois=new ObjectInputStream(is);
byte[] data = (byte[]) ois.readObject();
fos.write(data);

fos.flush();
fos.close();
ois.close();
is.close();

现在我有一个未压缩的文件,我可以使用它而不必担心“此文件无法作为文件描述符打开;它可能已压缩”的错误

此函数适用于小于 1M 的文件,较大的文件返回 java.io.IOException on line "ObjectInputStream ois=new ObjectInputStream(is);"

为什么??

【问题讨论】:

    标签: android load inputstream assets objectinputstream


    【解决方案1】:

    我将大文件放在原始文件夹中,而不是资产文件夹。这个对我有用。

    【讨论】:

    【解决方案2】:

    限制是压缩资产。如果资产未压缩,系统可以对文件数据进行内存映射,并使用 Linux 虚拟内存分页系统酌情拉入或丢弃 4K 块。 (“zipalign”工具可确保未压缩的资产在文件中是字对齐的,这意味着它们在直接映射时也会在内存中对齐。)

    如果资产被压缩,系统必须将整个内容解压缩到内存中。如果您有 20MB 的资产,这意味着您的应用程序占用了 20MB 的物理内存。

    理想情况下,系统会采用某种窗口压缩,因此只需要存在部分,但这需要资产 API 中的一些花哨的东西以及适用于随机访问的压缩方案。现在 APK == Zip 带有“deflate”压缩,所以这不实用。

    您可以通过为资产指定未压缩文件类型的后缀(例如“.png”或“.mp3”)来保持资产未压缩。您也可以在构建过程中使用“zip -0”手动添加它们,而不是通过 aapt 捆绑它们。这可能会增加您的 APK 的大小。

    【讨论】:

    • 我今天刚遇到这个问题,可以通过按照建议将文件名后缀更改为 .mp3 来使其正常工作。 .png 没有工作。 AssetManager 说找不到文件 :( 非常感谢后缀提示!
    • 这个post有一个这样的扩展列表。
    【解决方案3】:

    我使用 NetBeans 构建包,但我没有找到如何更改 AAPT 的设置。 我没有尝试过png,但mp3是压缩的。 我可以编译包,然后用参数-0进入assets文件夹吗? 正确的命令是什么?

    【讨论】:

      【解决方案4】:

      面临同样的问题。我已经将我的 4MB 文件切成 1MB 块,在第一次运行时,我将这些块加入到手机上的数据文件夹中。作为额外的奖励,APK 被正确压缩。块文件称为 1.db、2.db 等。代码如下:

      File Path = Ctxt.getDir("Data", 0);
      File DBFile = new File(Path, "database.db");
      
      if(!DBFile.exists() || DatabaseNeedsUpgrade)  //Need to copy...
          CopyDatabase(Ctxt, DBFile);
      
      
      static private void CopyDatabase(Context Ctxt, File DBFile) throws IOException
      {
          AssetManager assets = Ctxt.getAssets();
          OutputStream outstream = new FileOutputStream(DBFile);
          DBFile.createNewFile();
          byte []b = new byte[1024];
          int i, r;
          String []assetfiles = assets.list("");
          Arrays.sort(assetfiles);
          for(i=1;i<10;i++) //I have definitely less than 10 files; you might have more
          {
              String partname = String.format("%d.db", i);
              if(Arrays.binarySearch(assetfiles, partname) < 0) //No such file in assets - time to quit the loop
                  break;
              InputStream instream = assets.open(partname);
              while((r = instream.read(b)) != -1)
                  outstream.write(b, 0, r);
              instream.close();
          }
          outstream.close();
      }
      

      【讨论】:

      • 感谢您的回答。但是您使用哪种工具将大型数据库切割成每个较小的块?
      • 尝试了几个现成的免费软件,然后又写了一个。 :) 这是一个 20 行的直 C 曲。
      • 谢谢 Seva.. Loop 对我不起作用,但我手动完成了。我有 5.3MB 的数据库。它节省了我的时间。再次感谢。对于拆分数据库,我使用了 HJSplit 工具。
      • 拆分数据库后,在 android 2.2 中仍然出现 IOException。任何人都可以帮忙。我有 6MB 的数据库,但找不到使用它的方法。谢谢
      • 2djk:问一个单独的问题。这不是论坛,OP的问题早就回答了。
      【解决方案5】:

      将文件扩展名更改为 .mp3

      【讨论】:

      • 翻译:将文件扩展名改为.mp3
      • 不错的工作,但是使用此解决方案,playStore 上没有问题(当我在 playStore 上上传 apk 时)
      【解决方案6】:

      就像 Seva 建议的那样,您可以将文件分成几块。我用它来分割我的 4MB 文件

      public static void main(String[] args) throws Exception {  
          String base = "tracks";  
          String ext = ".dat";  
          int split = 1024 * 1024;  
          byte[] buf = new byte[1024];  
          int chunkNo = 1;  
          File inFile = new File(base + ext);  
          FileInputStream fis = new FileInputStream(inFile);  
          while (true) {  
            FileOutputStream fos = new FileOutputStream(new File(base + chunkNo + ext));  
            for (int i = 0; i < split / buf.length; i++) {  
              int read = fis.read(buf);  
              fos.write(buf, 0, read);  
              if (read < buf.length) {  
                fis.close();  
                fos.close();  
                return;  
              }  
            }  
            fos.close();  
            chunkNo++;  
          }  
        }  
      

      如果您不需要在设备上再次将文件合并为一个文件,只需使用此 InputStream,它将它们即时合并为一个。

      import java.io.IOException;  
      import java.io.InputStream;  
      
      import android.content.res.AssetManager;  
      
      public class SplitFileInputStream extends InputStream {  
      
        private String baseName;  
        private String ext;  
        private AssetManager am;  
        private int numberOfChunks;  
        private int currentChunk = 1;  
        private InputStream currentIs = null;  
      
        public SplitFileInputStream(String baseName, String ext, int numberOfChunks, AssetManager am) throws IOException {  
          this.baseName = baseName;  
          this.am = am;  
          this.numberOfChunks = numberOfChunks;  
          this.ext = ext;  
          currentIs = am.open(baseName + currentChunk + ext, AssetManager.ACCESS_STREAMING);  
        }  
      
        @Override  
        public int read() throws IOException {  
          int read = currentIs.read();  
          if (read == -1 && currentChunk < numberOfChunks) {  
            currentIs.close();  
            currentIs = am.open(baseName + ++currentChunk + ext, AssetManager.ACCESS_STREAMING);  
            return read();  
          }  
          return read;  
        }  
      
        @Override  
        public int available() throws IOException {  
          return currentIs.available();  
        }  
      
        @Override  
        public void close() throws IOException {  
          currentIs.close();  
        }  
      
        @Override  
        public void mark(int readlimit) {  
          throw new UnsupportedOperationException();  
        }  
      
        @Override  
        public boolean markSupported() {  
          return false;  
        }  
      
        @Override  
        public int read(byte[] b, int offset, int length) throws IOException {  
          int read = currentIs.read(b, offset, length);  
          if (read < length && currentChunk < numberOfChunks) {  
            currentIs.close();  
            currentIs = am.open(baseName + ++currentChunk + ext, AssetManager.ACCESS_STREAMING);  
            read += read(b, offset + read, length - read);  
          }  
          return read;  
        }  
      
        @Override  
        public int read(byte[] b) throws IOException {  
          return read(b, 0, b.length);  
        }  
      
        @Override  
        public synchronized void reset() throws IOException {  
          if (currentChunk == 1) {  
            currentIs.reset();  
          } else {  
            currentIs.close();  
            currentIs = am.open(baseName + currentChunk + ext, AssetManager.ACCESS_STREAMING);  
            currentChunk = 1;  
          }  
        }  
      
        @Override  
        public long skip(long n) throws IOException {  
          long skipped = currentIs.skip(n);  
          if (skipped < n && currentChunk < numberOfChunks) {  
            currentIs.close();  
            currentIs = am.open(baseName + ++currentChunk + ext, AssetManager.ACCESS_STREAMING);  
            skipped += skip(n - skipped);  
          }  
          return skipped;  
        }  
      }
      

      用法:
      ObjectInputStream ois = new ObjectInputStream(new SplitFileInputStream("mytempfile", ".dat", 4, getAssets()));

      【讨论】:

      • 您的方法似乎有效,但在实践中我遇到了两个问题。第一种方法(main - java - splitter 方法)当文件正好为 1024 * n (n>0) 长时,我面临问题。在读取最后一个字节后,int read 的值 -1fos.write 失败。我改变了写机制,比如if (read &gt; -1) { fos.write(buf, 0, read); }'. Next issue belongs to SplitFileInputStream` 类。方法public int read(byte[] b) 效果不佳。所以我像public int read(byte[] b, int offset, int length)方法一样类比地改变了它。
      • 看起来像public int read(byte[] b) throws IOException { int read = currentIs.read(b); if (read &lt; b.length &amp;&amp; currentChunk &lt; numberOfChunks) { currentIs.close(); currentIs = am.open(baseName + ++currentChunk + ext, AssetManager.ACCESS_STREAMING); byte[] buffer = new byte[b.length - read]; read += read(buffer); } return read; }
      【解决方案7】:

      我知道这是一个老问题,但我想到了一个很好的解决方案。 为什么不将已经预压缩的文件存储在资产文件夹中。 然后由于它已经是一个 zip 文件并因此被压缩,因此不需要再次压缩它。因此,如果您希望压缩文件以减小 apk 的大小,但又不想处理拆分文件,我认为这更容易。

      当您需要从设备上读取该文件时,只需将输入流包装在一个 zipinputstream 中 http://developer.android.com/reference/java/util/zip/ZipInputStream.html

      【讨论】:

      【解决方案8】:

      添加文件扩展名为 mp3。我使用 mydb.mp3in assets 文件夹并复制 .this 运行没有错误。显示检查它。

      【讨论】:

        【解决方案9】:

        我找到了另一个解决方案,也许你对此感兴趣。

        在源的根目录中,您有 build.xml 文件,您可以覆盖 -package-resources 文件中的 -package-resources 目标,该文件用于在 ant 中添加/修改目标,而不会破坏标准中的任何内容android 应用构建系统。

        只需创建一个包含此内容的文件:

        <?xml version="1.0" encoding="UTF-8"?>
        <project name="yourAppHere" default="help">
        
            <target name="-package-resources" depends="-crunch">
                <!-- only package resources if *not* a library project -->
                <do-only-if-not-library elseText="Library project: do not package resources..." >
                    <aapt executable="${aapt}"
                            command="package"
                            versioncode="${version.code}"
                            versionname="${version.name}"
                            debug="${build.is.packaging.debug}"
                            manifest="${out.manifest.abs.file}"
                            assets="${asset.absolute.dir}"
                            androidjar="${project.target.android.jar}"
                            apkfolder="${out.absolute.dir}"
                            nocrunch="${build.packaging.nocrunch}"
                            resourcefilename="${resource.package.file.name}"
                            resourcefilter="${aapt.resource.filter}"
                            libraryResFolderPathRefid="project.library.res.folder.path"
                            libraryPackagesRefid="project.library.packages"
                            libraryRFileRefid="project.library.bin.r.file.path"
                            previousBuildType="${build.last.target}"
                            buildType="${build.target}"
                            ignoreAssets="${aapt.ignore.assets}">
                        <res path="${out.res.absolute.dir}" />
                        <res path="${resource.absolute.dir}" />
                        <nocompress /> <!-- forces no compression on any files in assets or res/raw -->
                        <!-- <nocompress extension="xml" /> forces no compression on specific file extensions in assets and res/raw -->
                    </aapt>
                </do-only-if-not-library>
            </target>
        </project>
        

        【讨论】:

          【解决方案10】:

          使用 GZIP 将是另一种方法。你只需要将 InputStream 包裹在 GZIPInputStream 中。

          我将它用于一个大小约为 3.0 MB 且输出压缩文件约为 600KB 的数据库。

          • 为了在第一次运行中复制 DB,我使用 GZIP tool 压缩了我的源 .db 文件。
          • 然后将其重命名为 .jpg 以避免更多压缩 (这些过程在编译 APK 文件之前完成)。
          • 然后用于从资产中读取压缩的 GZIP 文件

          并复制它:

          private void copydatabase() throws IOException {
                  // Open your local db as the input stream
                  InputStream myinput = mContext.getAssets().open(DB_NAME_ASSET);
                  BufferedInputStream buffStream = new BufferedInputStream(myinput);
                  GZIPInputStream zis = new GZIPInputStream(buffStream);
          
                  // Path to the just created empty db
                  String outfilename = DB_PATH + DB_NAME;
          
                  // Open the empty db as the output stream
                  OutputStream myoutput = new FileOutputStream(outfilename);
          
          
                  // transfer byte to inputfile to outputfile
                  byte[] buffer = new byte[1024];
                  int length;
                  while ((length = zis.read(buffer)) > 0) {
                      myoutput.write(buffer, 0, length);
                  }
          
                  // Close the streams
                  myoutput.flush();
                  myoutput.close();
                  zis.close();
                  buffStream.close();
                  myinput.close();
              }
          

          【讨论】:

            【解决方案11】:

            使用程序将数据库设置为多个部分,例如“Win Hex”,您可以从Link下载

            然后继续Load files bigger than 1M from assets folder

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2012-07-28
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-11-21
              • 1970-01-01
              相关资源
              最近更新 更多