AppInventor2 TCP客户端扩展使应用程序崩溃

时间:2019-01-10 13:54:46

标签: java android sockets tcp app-inventor

我一直在使用Jean-Rodolphe Letertre的tcp客户端扩展程序为应用程序发明者2,它可以完美地工作,直到您调用断开连接方法,然后崩溃了该应用程序。在查看了扩展的代码后,我发现Disconnect仅关闭输出,输入,然后关闭套接字,这不会导致任何崩溃,所以我怀疑是因为运行线程的connect方法会不断循环读取数据,因为从tcp套接字中调用,当我们调用断开连接时,由于输入已关闭并且未处理异常,因此未完成导致应用程序崩溃的线程。

注意:该代码不是我的,我不要求自己修复它,我只想知道iv是否发现了导致崩溃的问题,如果可以,我将对其进行修复。我。预先感谢您的帮助!

代码:

// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2009-2011 Google, All Rights reserved
// Copyright 2011-2012 MIT, All rights reserved
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0

package com.gmail.at.moicjarod;

import com.google.appinventor.components.runtime.*;
import com.google.appinventor.components.runtime.util.RuntimeErrorAlert;
import com.google.appinventor.components.annotations.DesignerComponent;
import com.google.appinventor.components.annotations.DesignerProperty;
import com.google.appinventor.components.annotations.PropertyCategory;
import com.google.appinventor.components.annotations.SimpleEvent;
import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.annotations.SimpleObject;
import com.google.appinventor.components.annotations.SimpleProperty;
import com.google.appinventor.components.annotations.UsesLibraries;
import com.google.appinventor.components.annotations.UsesPermissions;
import com.google.appinventor.components.common.ComponentCategory;
import com.google.appinventor.components.common.PropertyTypeConstants;
import com.google.appinventor.components.runtime.util.AsynchUtil;
import com.google.appinventor.components.runtime.util.ErrorMessages;
import com.google.appinventor.components.runtime.util.YailList;
import com.google.appinventor.components.runtime.util.SdkLevel;

import com.google.appinventor.components.runtime.errors.YailRuntimeError;

import android.app.Activity;
import android.text.TextUtils;
import android.util.Log;
import android.os.StrictMode;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.InetSocketAddress;
import java.net.SocketException;

/**
 * Simple Client Socket
 * @author moicjarod@gmail.com (Jean-Rodolphe Letertre)
 * with the help of the work of lizlooney @ google.com (Liz Looney) and josmasflores @ gmail.com (Jose Dominguez)
 * the help of Alexey Brylevskiy for debugging
 * and the help of Hossein Amerkashi from AppyBuilder for compatibility with AppyBuilder
 */
@DesignerComponent(version = 4,
  description = "Non-visible component that provides client socket connectivity.",
  category = ComponentCategory.EXTENSION,
  nonVisible = true,
  iconName = "http://jr.letertre.free.fr/Projets/AIClientSocket/clientsocket.png")
  @SimpleObject(external = true)
  @UsesPermissions(permissionNames = "android.permission.INTERNET")


public class ClientSocketAI2Ext extends AndroidNonvisibleComponent implements Component
{
  private static final String LOG_TAG = "ClientSocketAI2Ext";

  private final Activity activity;

  // the socket object
  private Socket clientSocket = null;
  // the address to connect to
  private String serverAddress = "";
  // the port to connect to
  private String serverPort = "";
  // boolean that indicates the state of the connection, true = connected, false = not connected
  private boolean connectionState = false;
  // boolean that indicates the mode used, false = string sent as is, true = String is considered as hexadecimal data and will be converted before sending
  // same behavior is used when receiving data
  private boolean hexaStringMode = false;

  InputStream inputStream = null;

  /**
   * Creates a new Client Socket component.
   *
   * @param container the Form that this component is contained in.
   */
  public ClientSocketAI2Ext(ComponentContainer container)
  {
    super(container.$form());
    activity = container.$context();
    // compatibility with AppyBuilder (thx Hossein Amerkashi <kkashi01 [at] gmail [dot] com>)
    StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
    StrictMode.setThreadPolicy(policy);
  }

  /**
   * Method that returns the server's address.
   */
  @SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "The address of the server the client will connect to.")
  public String ServerAddress()
  {
    return serverAddress;
  }

  /**
   * Method to specify the server's address
   */
  @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING)
  @SimpleProperty
  public void ServerAddress(String address)
  {
    serverAddress = address;
  }

  /**
   * Method that returns the server's port.
   */
  @SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "The port of the server the client will connect to.")
  public String ServerPort()
  {
    return serverPort;
  }

  /**
   * Method to specify the server's port
   */
  @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING)
  @SimpleProperty
  public void ServerPort(String port)
  {
    serverPort = port;
  }

  /**
   * Method that returns the connection state
   */
  @SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "The state of the connection - true = connected, false = disconnected")
  public boolean ConnectionState()
  {
    return connectionState;
  }

  /**
   * Method that returns the mode (string or hexastring)
   */
  @SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "The mode of sending and receiving data.")
  public boolean HexaStringMode()
  {
    return hexaStringMode;
  }

  /**
   * Method to specify the mode (string or hexastring)
   */
  @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN)
  @SimpleProperty
  public void HexaStringMode(boolean mode)
  {
    hexaStringMode = mode;
  }

  /**
   * Creates the socket, connect to the server and launches the thread to receive data from server
   */
  @SimpleFunction(description = "Tries to connect to the server and launches the thread for receiving data (blocking until connected or failed)")
  public void Connect()
  {
    if (connectionState == true)
    {
      throw new YailRuntimeError("Connect error, socket connected yet, please disconnect before reconnect !", "Error");
    }
    try
    {
      // connecting the socket
      clientSocket = new Socket();
      clientSocket.connect(new InetSocketAddress(serverAddress, Integer.parseInt(serverPort)), 5000);
      connectionState = true;
      // begin the receive loop in a new thread
      AsynchUtil.runAsynchronously(new Runnable()
      {
        @Override
        public void run()
        {
          ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(1024);
          byte[] buffer = new byte[1024];
          int bytesRead;

          try
          {
            // get the input stream and save the data
            inputStream = clientSocket.getInputStream();
            while (true)
            {
              // test if there is a server problem then close socket properly (thx Axeley :-))
              try
              {
                bytesRead = inputStream.read(buffer);
                if(bytesRead == -1)
                  break;
              }
              catch(SocketException e)
              {
                if(e.getMessage().indexOf("ETIMEDOUT") >= 0)
                  break;
                throw e;
              }
              byteArrayOutputStream.write(buffer, 0, bytesRead);
              final String dataReceived;
              // hexaStringMode is false, so we don't transform the string received
              if (hexaStringMode == false)
              {
                dataReceived = byteArrayOutputStream.toString("UTF-8");
              }
              // hexaStringMode is true, so we make a string with each character as an hexa symbol representing the received message
              else
              {
                int i;
                char hexaSymbol1, hexaSymbol2;
                String tempData = "";
                byte[] byteArray = byteArrayOutputStream.toByteArray();
                for (i = 0; i < byteArrayOutputStream.size(); i++)
                {
                  if (((byteArray[i] & 0xF0) >> 4) < 0xA)
                  // 0 to 9 symbol
                  hexaSymbol1 = (char)(((byteArray[i] & 0xF0) >> 4) + 0x30);
                  else
                    // A to F symbol
                    hexaSymbol1 = (char)(((byteArray[i] & 0xF0) >> 4) + 0x37);
                  if ((byteArray[i] & 0x0F) < 0xA)
                  hexaSymbol2 = (char)((byteArray[i] & 0x0F) + 0x30);
                  else
                    hexaSymbol2 = (char)((byteArray[i] & 0x0F) + 0x37);
                  tempData = tempData + hexaSymbol1 + hexaSymbol2;
                }

                dataReceived = tempData;
              }
              // reset of the byteArrayOutputStream to flush the content
              byteArrayOutputStream.reset();
              // then we send the data to the user using an event
              // events must be sent by the main thread (UI)
              activity.runOnUiThread(new Runnable()
              {
                @Override
                public void run()
                {
                  DataReceived(dataReceived);
                }
              } );
            }
            // When we go there, either we have
            // - server shutdown
            // - disconnection asked (inputstream closed => -1 returned)
            // - connection problem
            // so, if it is not disconnected yet, we disconnect the socket and inform the user of it.
            if (connectionState == true)
            {
              Disconnect();
              // events must be sent by the main thread (UI)
              activity.runOnUiThread(new Runnable()
              {
                @Override
                public void run()
                {
                  RemoteConnectionClosed();
                }
              } );
            }
          }
          catch (SocketException e)
          {
            Log.e(LOG_TAG, "ERROR_READ", e);
            throw new YailRuntimeError("Connect error (read)" + e.getMessage(), "Error");
          }
          catch (IOException e)
          {
            Log.e(LOG_TAG, "ERROR_READ", e);
            throw new YailRuntimeError("Connect error (read)", "Error");
          }
          catch (Exception e)
          {
            connectionState = false;
            Log.e(LOG_TAG, "ERROR_READ", e);
            throw new YailRuntimeError("Connect error (read)" + e.getMessage(), "Error");
          }
        }
      } );
    }
    catch (SocketException e)
    {
      Log.e(LOG_TAG, "ERROR_CONNECT", e);
      throw new YailRuntimeError("Connect error" + e.getMessage(), "Error");
    }
    catch (Exception e)
    {
      connectionState = false;
      Log.e(LOG_TAG, "ERROR_CONNECT", e);
      throw new YailRuntimeError("Connect error (Socket Creation)" + e.getMessage(), "Error");
    }
  }

  /**
   * Send data through the socket to the server
   */
  @SimpleFunction(description = "Send data to the server")
  public void SendData(final String data)
  {
    final byte [] dataToSend;
    byte [] dataCopy = data.getBytes();
    if (connectionState == false)
    {
      throw new YailRuntimeError("Send error, socket not connected.", "Error");
    }

    if (hexaStringMode == false)
    {
      //dataSend = new byte [data.length()];
      // if hexaStringMode is false, we send data as is
      dataToSend = data.getBytes();
    }
    else
    {
      // if hexaStringMode is true, we begin to verify we can transcode the symbols
      // verify if the data we want to send contains only hexa symbols
      int i;
      for (i = 0; i < data.length(); i++)
      {
        if (((dataCopy[i] < 0x30) || (dataCopy[i] > 0x39)) && ((dataCopy[i] < 0x41) || (dataCopy[i] > 0x46)) && ((dataCopy[i] < 0x61) || (dataCopy[i] > 0x66)))
          throw new YailRuntimeError("Send data : hexaStringMode is selected and non hexa symbol found in send String.", "Error");
      }
      // verify that the number of symbols is even
      if ((data.length() %2) == 1)
      {
        throw new YailRuntimeError("Send data : hexaStringMode is selected and send String length is odd. Even number of characters needed.", "Error");
      }
      // if all tests pass, we transcode the data :
      dataToSend=new byte[data.length()/2+1];
      for (i = 0; i < data.length(); i=i+2)
      {
        byte [] temp1 = new byte [2];
        temp1 [0] = dataCopy[i];
        temp1 [1] = dataCopy[i+1];
        String temp2 = new String (temp1);
        dataToSend[i/2]=(byte)Integer.parseInt(temp2, 16);
      }
      // end of c-type string character
      dataToSend[i/2] = (byte)0x00;
    }

    // we then send asynchonously the data
    AsynchUtil.runAsynchronously(new Runnable()
    {
      @Override
      public void run()
      {
        try
        {
          OutputStream out;
          out = clientSocket.getOutputStream();
          out.write(dataToSend);
        }
        catch (SocketException e)
        {
          Log.e(LOG_TAG, "ERROR_SEND", e);
          throw new YailRuntimeError("Send data" + e.getMessage(), "Error");
        }
        catch (Exception e)
        {
          Log.e(LOG_TAG, "ERROR_UNABLE_TO_SEND_DATA", e);
          throw new YailRuntimeError("Send Data", "Error");
        }
      }
    } );
  }

  /**
   * Close the socket
   */
  @SimpleFunction(description = "Disconnect to the server")
  public void Disconnect()
  {
    if (connectionState == true)
    {
      connectionState = false;
      try
      {
        // shutdown the input socket,
        clientSocket.shutdownInput();
        clientSocket.shutdownOutput();
        clientSocket.close();
      }
      catch (SocketException e)
      {
        // modifications by axeley too :-)
        if(e.getMessage().indexOf("ENOTCONN") == -1)
        {
          Log.e(LOG_TAG, "ERROR_CONNECT", e);
          throw new YailRuntimeError("Disconnect" + e.getMessage(), "Error");
         }
         // if not connected, then just ignore the exception
      }
      catch (IOException e)
      {
        Log.e(LOG_TAG, "ERROR_CONNECT", e);
        throw new YailRuntimeError("Disconnect" + e.getMessage(), "Error");
      }
      catch (Exception e)
      {
        Log.e(LOG_TAG, "ERROR_CONNECT", e);
        throw new YailRuntimeError("Disconnect" + e.getMessage(), "Error");
      }
      finally
      {
        clientSocket=null;
      }

    }
    else
      throw new YailRuntimeError("Socket not connected, can't disconnect.", "Error");
  }

  /**
   * Event indicating that a message has been received
   *
   * @param data the data sent by the server
   */
  @SimpleEvent
  public void DataReceived(String data)
  {
    // invoke the application's "DataReceived" event handler.
    EventDispatcher.dispatchEvent(this, "DataReceived", data);
  }

  /**
   * Event indicating that the remote socket closed the connection
   *
   */
  @SimpleEvent
  public void RemoteConnectionClosed()
  {
    // invoke the application's "RemoteConnectionClosed" event handler.
    EventDispatcher.dispatchEvent(this, "RemoteConnectionClosed");
  }
}

1 个答案:

答案 0 :(得分:1)

通过评论以下几行,可以实现:

throw new YailRuntimeError("...

此方法的问题是,我们将无法知道断开连接的原因,但是我们必须同意,这些消息在应用程序工作流程的意义上是信息性的,而不是功能性的,因此,另一种选择是添加能够在运行时禁用这些调用。