为什么在我使用 BuffferedReader 时 InputStream 会关闭?

Why InputStream is closed when I use a BuffferedReader?

我在读取 InputStream 并尝试再次使用它时遇到此错误。

07-20 17:36:24.762  11253-11277/? W/System.err﹕ java.io.IOException
07-20 17:36:24.762  11253-11277/? W/System.err﹕ at java.io.InputStream.reset(InputStream.java:208)
07-20 17:36:24.762  11253-11277/? W/System.err﹕ at demo31.com.maps.GMapDirections$DownloadDocumentTask.doInBackground(GMapDirections.java:134)
07-20 17:36:24.762  11253-11277/? W/System.err﹕ at demo31.com.maps.GMapDirections$DownloadDocumentTask.doInBackground(GMapDirections.java:86)
07-20 17:36:24.762  11253-11277/? W/System.err﹕ at android.os.AsyncTask.call(AsyncTask.java:292)
07-20 17:36:24.762  11253-11277/? W/System.err﹕ at javautil.concurrent.FutureTask.run(FutureTask.java:237)
07-20 17:36:24.762  11253-11277/? W/System.err﹕ at android.os.AsyncTask$SerialExecutor.run(AsyncTask.java:231)
07-20 17:36:24.762  11253-11277/? W/System.err﹕ at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
07-20 17:36:24.762  11253-11277/? W/System.err﹕ at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
07-20 17:36:24.762  11253-11277/? W/System.err﹕ at java.lang.Thread.run(Thread.java:818)
07-20 17:36:24.762  11253-11277/? D/GMapDirections﹕ Exception while downloading data: java.io.IOException

我使用的代码是...

@Override
protected Document doInBackground(ArrayList... latLngs) {
  Log.d(TAG, "DownloadDocumentTask(doInBackground): Estoy dentro del background!!!");
  try {
    HttpsURLConnection urlConnection;
    FileService file = new FileService();
    InputStream iStream;
    Document result;

    file.writeLog(TAG, GMapDirections.class.getName(), getUrlConnection());

    URL url = new URL(getUrlConnection());

    // Creating an http connection to communicate with url
    urlConnection = (HttpsURLConnection) url.openConnection();

    // Connecting to url
    urlConnection.setRequestMethod("POST");
    urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
    urlConnection.setRequestProperty("charset", "utf-8");
    urlConnection.setRequestProperty("Accept", "application/xml");
    urlConnection.setDoOutput(true);
    urlConnection.setDoInput(true);
    urlConnection.setUseCaches(false);
    urlConnection.connect();

    //Return data
    iStream = urlConnection.getInputStream();

    //Display what returns POST request
    BufferedReader br = new BufferedReader(new InputStreamReader(iStream));
    StringBuilder sb = new StringBuilder();

    String line;
    while ((line = br.readLine()) != null) {
      sb.append(line + "\n");
    }
    br.close();
    file.writeLog(GMapDirections.TAG, "doInBackground", sb.toString());

    // Parse the data to a Document Object
    DocumentBuilder builder =  DocumentBuilderFactory.newInstance().newDocumentBuilder();
    result = builder.parse(iStream);
    urlConnection.disconnect();

    return result;
  } catch (Exception e) {
     e.printStackTrace();
     Log.d(TAG, "Exception while downloading data: " + e.toString());
  }
  return null;
}

当我尝试解析 iStream 的内容时出现异常,因为之前我使用 BufferedReader 读取它。

DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
result = builder.parse(iStream);

那么,我如何才能用 BufferedReader 读取 iStream 然后用它来解析内容而不出错?

如果您想再次从流中读取,您必须通过调用 iStream.reset()

来重置它

来自 InputStream 文档:http://developer.android.com/reference/java/io/InputStream.html

public synchronized void reset ()

Added in API level 1

Rests this stream to the last marked location. Throws anIOException if the number of bytes read since the mark has been set is greater than the limit provided to mark, or if no mark has been set.

This implementation always throws an IOException and concrete subclasses should provide the proper implementation.

Throws

IOExceptionif this stream is closed or another IOException occurs.

因为你打电话给BufferedReader.close()。来自 Javadocs,

Closes the stream and releases any system resources associated with it.

我刚才看了这个,因为我担心像这样的序列:

new BufferedReader(new FileReader(...))

会泄露 FileReader 的 Reader 句柄。事实证明,BufferedReader.close 也关闭了基础 reader。

您的堆栈跟踪和代码似乎不匹配。从堆栈跟踪来看,doInBackground 似乎正在递归调用自身,然后调用 InputStream.reset()。但是我看不到您的代码中的任何一个调用。

关于您重新阅读流的实际问题: 由于您显然已经尝试过(但失败了)InputStream.reset(),该流中的数据量可能太大(或者您忘记调用 InputStream.mark())。简单的方法是创建另一个连接+流并读取它。这确实意味着您实际上会低效地从 URL.

传输两次该数据

更有效的方法:

  1. 将流的全部内容读入内存/或临时文件(取决于大小),然后将其用作数据源。

  2. 使用 TeeInputStream (https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/input/TeeInputStream.html) 复制流数据。

添加 finally 关闭所有流的部分。将流的声明移到 try 块之外,即:

BufferedReader br;
InputStream iStream;
try {
    // your regular code, but remove this line as you'll close later
    // br.close();
} catch (Exception whatever) {
    //...
} finally {
    if (iStream != null) {
        iStream.close(); // TODO you need to catch exception here too
    }
}

根据@emu的回答(见第1点)

More efficient ways to do this:
1. Read the entire contents of the stream into memory / or into a temporary file (depending on size) and then use that as your data source.

最好的解决方案是...

        @Override
    protected Document doInBackground(Void... voids) {
        try {
            HttpsURLConnection urlConnection;
            FileService file = new FileService();
            InputStream iStream;
            Document result;

            file.writeLog(TAG, GMapDirections.class.getName(), getUrlConnection());


            URL url = new URL(getUrlConnection());

            // Creating an http connection to communicate with url
            urlConnection = (HttpsURLConnection) url.openConnection();

            // Connecting to url
            urlConnection.setRequestMethod("POST");
            urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            urlConnection.setRequestProperty("charset", "utf-8");
            urlConnection.setRequestProperty("Accept", "application/xml");
            urlConnection.setDoOutput(true);
            urlConnection.setDoInput(true);
            urlConnection.setUseCaches(false);
            urlConnection.connect();

            //Return data
            iStream = urlConnection.getInputStream();

            // Parse the data to a Document Object
            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            result = builder.parse(iStream);
            urlConnection.disconnect();

            //Write data get it from POST request
            //Transform Document 2 InputStream
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            Source xmlSource = new DOMSource(result);
            Result outputTarget = new StreamResult(outputStream);
            TransformerFactory.newInstance().newTransformer().transform(xmlSource, outputTarget);
            iStream = new ByteArrayInputStream(outputStream.toByteArray());

            //Write document in a file
            BufferedReader br = new BufferedReader(new InputStreamReader(iStream));
            StringBuilder sb = new StringBuilder();

            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line + "\n");
            }
            br.close();
            file.writeLog(GMapDirections.TAG, "doInBackground", sb.toString());

            //Return Document with data
            return result;

        } catch (Exception e) {
            e.printStackTrace();
            Log.d(TAG, "Exception while downloading data: " + e.toString());
        }

        return null;
    }