【问题标题】:Can't read socket InputStream on Jelly Bean无法读取 Jelly Bean 上的套接字 InputStream
【发布时间】:2013-02-15 17:46:24
【问题描述】:

我有一个 TCP 套接字连接,它在 Android 2.3 上运行良好,但现在在 Android 4.1 上遇到了一些问题。 问题是 InputStream.read() 方法总是返回 -1(没有阻塞),就像连接关闭一样。

创建套接字:

SocketFactory socketFactory = SocketFactory.getDefault();
Socket socket = socketFactory.createSocket("c.whatsapp.net", 5222);
socket.setSoTimeout(3*60*1000);
socket.setTcpNoDelay(true);

检索输入和输出流并写入一些初始数据:

InputStream inputStream = new BufferedInputStream(socket.getInputStream());
OutputStream outputStream = new BufferedOutputStream(socket.getOutputStream());

outputStream.write(87);
outputStream.write(65);
outputStream.write(1);
outputStream.write(2);
outputStream.flush();

那么,这个条件总是通过而不阻塞:

int c = inputStream.read();
if (c < 0) {
    Log.d(TAG, "End of stream");
}

此代码在后台线程中运行。它正在开发 Gingerbread。

尝试使用 InputStreamReader 和 OutputStreamWriter 代替直接流 - 没有效果。

【问题讨论】:

  • 你检查过 logcat 的输出以及套接字是否被绑定?您是否在清单uses-permission android:name="android.permission.INTERNET 中设置了正确的权限。
  • 是的,没有例外。权限存在。
  • 我认为自 ICS 以来网络或流逻辑发生了一些变化,但无法猜测到底是什么。以前与XmlPullParser 有类似的问题,它停止与InputStream 合作,但请接受InputStreamReader 代替:stackoverflow.com/questions/11190494/…
  • 好的,所以你的代码在后台线程中运行,你没有得到NetworkOnMainThreadException。抱歉,帮不了你
  • 你找到解决这个问题的方法了吗?我也有同样的问题

标签: java android sockets inputstream android-4.2-jelly-bean


【解决方案1】:

我之前也看到过同样的错误,虽然这个答案可能看起来离题,但如果它有效,请给我一个机会,让我知道它是否有效,由于某种原因,即使它们在低级 android 中完全正常工作,套接字在 jellybean 上也有奇怪的行为版本,我解决这个问题的方法是将targetSdkVersion移动到jelly bean以及项目的Android属性下的Project Build Target,没有修改一行代码,只是这样,出于某种原因它确实诡计……

希望这会有所帮助。

问候!

【讨论】:

    【解决方案2】:

    我遇到了一些类似的问题,inputStream.read() 返回 -1 并且我没有收到任何异常。事实上,服务器已关闭,连接中断。我没有用不同的版本测试它,只有4.0。

    这是关于此行为的Google Bug Report

    不幸的是,该错误的状态似乎是“关闭”,因为不可复制。

    我的解决方法是将 -1 解释为套接字关闭和无法访问的服务器。当您尝试重新连接时,您会得到正确的错误。

    【讨论】:

    • 没关系,我知道当服务器没有数据要发送时可能会发生这种情况,但如果是这种情况,姜饼和 iPhone 也不能收到它
    • 对我来说,这看起来像是一个时间问题。所以很难复制。事实上,有时它可以工作(当服务器连接丢失时抛出异常),有时不能。那是我停止调查并围绕我的工作建立的那一刻。可能值得检查的是,如果较新的 Android 更频繁地失去服务器连接,并且在这种情况下可能更频繁地出现这种情况。现在我会解决这个问题:-1 强制重新连接到服务器。
    【解决方案3】:

    我遇到了类似的问题,并用这样的解决方法解决了它

    private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
    
    private static class WatchDog implements Runnable{
        private Thread thread = Thread.currentThread();
    
        public void run() {
            Log.d(LOG_TAG, "Interrupting read due to timeout");
            thread.interrupt();
        }
    }
    
    private void read(InputStream in, ByteBuffer bb, long waitTime) throws IOException {
        int startingPos = bb.position();
        long timeout = System.currentTimeMillis() + RESPONSE_TIMEOUT;
    
    
        ScheduledFuture<?> watchdogFuture = executor.schedule(new WatchDog(), RESPONSE_TIMEOUT, TimeUnit.MILLISECONDS);
        try {
            while(System.currentTimeMillis() < timeout && bb.hasRemaining()){ //workaround fixing timeout after 1ms
                try{
                    int read = in.read(bb.array(), bb.position(), bb.remaining());
                    if(read > 0){
                        bb.position(bb.position()+read);
                    }
                } catch(SocketTimeoutException e){}
                if(bb.hasRemaining()){
                    Thread.sleep(5);
                }
            }
            watchdogFuture.cancel(true);
        } catch (InterruptedException e) {}
    
    
        if(bb.hasRemaining()){
            throw new SocketTimeoutException("Unable to read requested bytes: " 
                    + (bb.position()-startingPos) + "/" +  (bb.limit()-startingPos)
                    + " after " + (System.currentTimeMillis() - timeout + RESPONSE_TIMEOUT) + "ms");
        }
    }
    

    【讨论】:

      【解决方案4】:

      使用 BufferedReader 和 PrintWriter 对我来说适用于所有版本,并且对于通过任何通信协议发送和接收您想要的任何内容(甚至是 JSON 字符串)都非常方便。在像这样启动后台线程时尝试将它们保存为成员变量:

      mInput = new BufferedReader(new InputStreamReader(
                  socket.getInputStream()));
      mOutput = new PrintWriter(new BufferedWriter(
                  new OutputStreamWriter(socket.getOutputStream())), true);
      

      对于异步通信,您的后台线程可能如下所示:

      @Override
      public final void run() {
          while (!Thread.currentThread().isInterrupted()) {
              if (mInput == null) {
                  break;
              }
              String message = null;
              try {
                  message = mInput.readLine();
              } catch (IOException e) {
                  // handle the exception as you like
                  break;
              }
              if (Thread.currentThread().isInterrupted()) {
                  // thread was interrupted while reading
                  break;
              } else if (message != null) {
                  // handle the message as you like
              }
          }
      }
      

      使用另一个后台线程发送消息:

      @Override
      public void run() {
          if (mOutput != null) {
              mOutput.println(<message to be );
              if (mOutput == null) {
                  // the above thread was interrupted while writing
              } else if (!mOutput.checkError()) {
                  // everything went fine
              } else {
                  // handle the exception
              }
          }
      }
      

      此外,您必须从外部关闭流以确保 readLine 不会永远阻塞:

      try {
          mOutput.close();
          mInput.close();
          mOutput = null;
          mInput = null;
      } catch (IOException e) {
          // log the exception
      }
      

      现在,由于您使用的是 TCP 套接字,因此可能会发生套接字实际上已失效且 readLine 仍处于阻塞状态的情况。您必须像上面一样检测到并关闭流。为此,您必须添加另一个定期发送保持活动消息的线程(哦,好吧)。如果 X 秒内没有收到来自远程设备的消息,它必须关闭流。

      整个方法确保套接字关闭并且所有线程在任何情况下都完成。当然,如果您需要,您可以通过删除发送者线程并在阅读器线程中包含 println() 来使通信同步。希望对您有所帮助(即使答案晚了 8 个月)。

      【讨论】:

        【解决方案5】:

        朋友,

        尝试inputStream.readLine();(即)DataInputStream.readLine();(不推荐使用的方法)

        这对我有用...

        【讨论】:

        • 不存在的方法。如果你的意思是 DataInputStream.readLine() 你应该这么说。
        【解决方案6】:

        试试这个代码 -

        Runnable runnable = new Runnable() {
        
            @Override
            public void run() {
        
                synchronized (this) {
                    Socket s = null;
                    String inMsg = null, msg2 = null;
                    try {
                        try {
                            s = new Socket(server, port);
                        } catch (Exception e) {
                            return;
                        }
                        BufferedReader in = new BufferedReader(
                                new InputStreamReader(s.getInputStream()));
                        BufferedWriter out = new BufferedWriter(
                                new OutputStreamWriter(s.getOutputStream()));
                        try {
                            inMsg = in.readLine()
                                    + System.getProperty("line.separator");
                        } catch (Exception e) {
                            return;
                        }
        
                        out.write(message + "\n\r");
                        out.flush();
                        try {
                            msg2 = in.readLine();
                            if (msg2 == null) {
                                return;
                            }
                        } catch (Exception e) {
                            return;
                        }
                        out.close();
                        s.close();
                    } catch (Exception e) {
                        return;
                    }
                }
        
            }
        
        };
        

        它对我有用。

        【讨论】:

          【解决方案7】:

          【讨论】:

          • 向 Android 应用程序添加像 Commons IO 这样的大型库会使应用程序膨胀,并将您的应用程序暴露给库中低效的代码。除非您使用大部分库功能,否则我建议您不要引入它。
          猜你喜欢
          • 2015-05-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-02-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多