App Inventor 2 TCP客户端扩展程序崩溃了应用程序

问题描述 投票:1回答:1

我一直在使用Jean-Rodolphe Letertre的tcp客户端扩展程序给app发明者2,它可以完美地工作,直到你调用disconnect方法而不是崩溃应用程序。在查看扩展的代码之后,我发现disconnect只关闭了输出,输入并关闭了不应该导致任何崩溃的套接字,所以我的怀疑落在了运行线程的connect方法上,因为它一直在循环中读取数据从tcp套接字和我们调用disconnect时,我们没有完成导致应用程序崩溃的线程,因为输入已关闭且异常未处理。

注意:代码不是我的,我不要求为我修复它我只想知道iv'e是否发现导致崩溃的问题,如果是的话我会自己解决它。在此先感谢您的帮助!

代码:

// -*- 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 [email protected] (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");
  }
}
java android sockets tcp app-inventor
1个回答
1
投票

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

throw new YailRuntimeError("...

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

© www.soinside.com 2019 - 2024. All rights reserved.