Descripción general de Wi-Fi directo (entre pares o P2P)

Wi-Fi directo (P2P) permite que los dispositivos con el hardware adecuado se conecten directamente entre sí a través de Wi-Fi sin un punto de acceso intermedio. Con estas APIs, puedes detectar otros dispositivos y conectarte a ellos cuando cada uno admita Wi-Fi P2P. Luego, podrás comunicarte mediante una conexión rápida a distancias mucho más largas que con una conexión Bluetooth. Esto es útil para aplicaciones que comparten datos entre usuarios, como un juego multijugador o una aplicación para compartir fotos.

Las API P2P Wi-Fi constan de las siguientes partes principales:

  • Métodos que te permiten descubrir pares, solicitarlos y conectarte a ellos, los cuales se definen en la clase WifiP2pManager.
  • Los objetos de escucha que te permiten recibir notificaciones sobre el éxito o el fracaso de las llamadas al método WifiP2pManager Cuando se llama a los métodos WifiP2pManager, cada método puede recibir un objeto de escucha específico que se pasa como parámetro.
  • Intents que te notifican de eventos específicos que detecta el framework de P2P Wi-Fi, como una conexión perdida o un par recién detectado

Por lo general, usarás estos tres componentes principales de las APIs juntos. Por ejemplo, puedes proporcionar un WifiP2pManager.ActionListener a una llamada a discoverPeers() para que los métodos ActionListener.onSuccess() y ActionListener.onFailure() puedan enviarte notificaciones. También se transmite un intent WIFI_P2P_PEERS_CHANGED_ACTION si el método discoverPeers() detecta que la lista de pares cambió.

Descripción general de la API

La clase WifiP2pManager proporciona métodos que te permiten interactuar con el hardware Wi-Fi de tu dispositivo para realizar acciones como detectar pares y conectarte a ellos. Las siguientes acciones están disponibles:

Tabla 1: Métodos P2P Wi-Fi

Método Description
initialize() Registra la aplicación con el marco de trabajo Wi-Fi. Realiza esa llamada antes de llamar a cualquier otro método Wi-Fi P2P.
connect() Inicia una conexión entre pares con un dispositivo que tiene la configuración especificada.
cancelConnect() Cancela cualquier negociación de grupo entre pares que haya en curso.
requestConnectInfo() Solicita la información de conexión de un dispositivo.
createGroup() Crea un grupo entre pares con el dispositivo actual como propietario del grupo.
removeGroup() Elimina el grupo entre pares actual.
requestGroupInfo() Solicita información del grupo entre pares.
discoverPeers() Inicia la detección de pares.
requestPeers() Solicita la lista actual de pares detectados.

Los métodos WifiP2pManager te permiten pasar un objeto de escucha, de modo que el framework Wi-Fi P2P pueda notificar a tu actividad sobre el estado de una llamada. Las interfaces de objetos de escucha disponibles y las llamadas al método WifiP2pManager correspondientes que usan los objetos de escucha se describen en la tabla 2.

Tabla 2: Objetos de escucha de P2P Wi-Fi

Interfaz de objeto de escucha Acciones asociadas
WifiP2pManager.ActionListener connect(), cancelConnect(), createGroup(), removeGroup() y discoverPeers()
WifiP2pManager.ChannelListener initialize()
WifiP2pManager.ConnectionInfoListener requestConnectInfo()
WifiP2pManager.GroupInfoListener requestGroupInfo()
WifiP2pManager.PeerListListener requestPeers()

Las APIs de P2P Wi-Fi definen los intents que se transmiten cuando ocurren ciertos eventos de P2P Wi-Fi, como cuando se detecta un par nuevo o cuando cambia el estado de Wi-Fi de un dispositivo. Si quieres registrarte para recibir estos intents en tu aplicación, crea un receptor de emisión que controle estos intents:

Tabla 3: Intents de P2P Wi-Fi

Intent Description
WIFI_P2P_CONNECTION_CHANGED_ACTION Transmite cuando cambia el estado de la conexión Wi-Fi del dispositivo.
WIFI_P2P_PEERS_CHANGED_ACTION Transmite cuando llamas a discoverPeers(). Por lo general, llamarás a requestPeers() para obtener una lista actualizada de pares si controlas este intent en tu aplicación.
WIFI_P2P_STATE_CHANGED_ACTION Transmite cuando P2P Wi-Fi se habilita o se inhabilita en el dispositivo.
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION Transmite cuando cambian los detalles de un dispositivo, como el nombre.

Crear un receptor de emisión para intents P2P Wi-Fi

Un receptor de emisión te permite recibir intents que transmite el sistema Android, de modo que tu aplicación pueda responder a eventos que te interesen. Los pasos básicos para crear un receptor de emisión que controle los intents P2P de Wi-Fi son los siguientes:

  1. Crea una clase que extienda la clase BroadcastReceiver. Para el constructor de la clase, usarás parámetros para WifiP2pManager, WifiP2pManager.Channel y la actividad en la que se registrará este receptor de emisión. Esto permite que el receptor de emisión envíe actualizaciones a la actividad, además de tener acceso al hardware de Wi-Fi y a un canal de comunicación si es necesario.

  2. En el receptor de emisión, busca los intents que te interesan del método onReceive(). Realiza las acciones necesarias según el intent que se reciba. Por ejemplo, si el receptor de emisión recibe un intent WIFI_P2P_PEERS_CHANGED_ACTION, puedes llamar al método requestPeers() para obtener una lista de los pares detectados actualmente.

El siguiente código muestra cómo crear un receptor de emisión típico. El receptor de emisión toma un objeto WifiP2pManager y una actividad como argumentos, y usa estas dos clases para realizar las acciones necesarias cuando el receptor de emisión recibe un intent:

Kotlin

/**
* A BroadcastReceiver that notifies of important Wi-Fi p2p events.
*/
class WiFiDirectBroadcastReceiver(
       private val manager: WifiP2pManager,
       private val channel: WifiP2pManager.Channel,
       private val activity: MyWifiActivity
) : BroadcastReceiver() {

   override fun onReceive(context: Context, intent: Intent) {
       val action: String = intent.action
       when (action) {
           WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> {
               // Check to see if Wi-Fi is enabled and notify appropriate activity
           }
           WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {
               // Call WifiP2pManager.requestPeers() to get a list of current peers
           }
           WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {
               // Respond to new connection or disconnections
           }
           WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> {
               // Respond to this device's wifi state changing
           }
       }
   }
}

Java

/**
* A BroadcastReceiver that notifies of important Wi-Fi p2p events.
*/
public class WiFiDirectBroadcastReceiver extends BroadcastReceiver {

   private WifiP2pManager manager;
   private Channel channel;
   private MyWiFiActivity activity;

   public WiFiDirectBroadcastReceiver(WifiP2pManager manager, Channel channel,
           MyWifiActivity activity) {
       super();
       this.manager = manager;
       this.channel = channel;
       this.activity = activity;
   }

   @Override
   public void onReceive(Context context, Intent intent) {
       String action = intent.getAction();

       if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
           // Check to see if Wi-Fi is enabled and notify appropriate activity
       } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
           // Call WifiP2pManager.requestPeers() to get a list of current peers
       } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
           // Respond to new connection or disconnections
       } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
           // Respond to this device's wifi state changing
       }
   }
}

En dispositivos que ejecutan Android 10 y versiones posteriores, los siguientes intents de transmisión no son persistentes:

WIFI_P2P_CONNECTION_CHANGED_ACTION
Las aplicaciones pueden usar requestConnectionInfo(), requestNetworkInfo() o requestGroupInfo() para recuperar la información de conexión actual.
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
Las aplicaciones pueden usar requestDeviceInfo() para recuperar la información de conexión actual.

Crear una aplicación P2P Wi-Fi

La creación de una aplicación P2P Wi-Fi implica la creación y el registro de un receptor de emisión para tu aplicación, el descubrimiento de pares, la conexión con un par y la transferencia de datos a un par. En las siguientes secciones, se describe cómo hacerlo.

Configuración inicial

Antes de usar las APIs de P2P Wi-Fi, debes asegurarte de que tu aplicación pueda acceder al hardware y de que el dispositivo sea compatible con el protocolo Wi-Fi P2P. Si se admite Wi-Fi P2P, puedes obtener una instancia de WifiP2pManager, crear y registrar tu receptor de emisión, y comenzar a usar las APIs de P2P Wi-Fi.

  1. Solicita permiso para usar el hardware Wi-Fi en el dispositivo y declara que tu aplicación tenga la versión mínima del SDK correcta en el manifiesto de Android:

    <uses-sdk android:minSdkVersion="14" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!-- If your app targets Android 13 (API level 33)
         or higher, you must declare the NEARBY_WIFI_DEVICES permission. -->
    <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
                     <!-- If your app derives location information from
                          Wi-Fi APIs, don't include the "usesPermissionFlags"
                          attribute. -->
                     android:usesPermissionFlags="neverForLocation" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
                     <!-- If any feature in your app relies on precise location
                          information, don't include the "maxSdkVersion"
                          attribute. -->
                     android:maxSdkVersion="32" />
    

    Además de los permisos anteriores, las siguientes APIs también requieren que se habilite el modo de ubicación:

  2. Verifica que P2P Wi-Fi esté activado y sea compatible. Un buen lugar para verificar esto es en el receptor de emisión cuando recibe el intent WIFI_P2P_STATE_CHANGED_ACTION. Notifica a tu actividad sobre el estado de P2P Wi-Fi y reacciona según corresponda:

    Kotlin

    override fun onReceive(context: Context, intent: Intent) {
    ...
    val action: String = intent.action
    when (action) {
       WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> {
           val state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1)
           when (state) {
               WifiP2pManager.WIFI_P2P_STATE_ENABLED -> {
                   // Wifi P2P is enabled
               }
               else -> {
                   // Wi-Fi P2P is not enabled
               }
           }
       }
    }
    ...
    }
    

    Java

    @Override
    public void onReceive(Context context, Intent intent) {
    ...
    String action = intent.getAction();
    if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
       int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
       if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
           // Wifi P2P is enabled
       } else {
           // Wi-Fi P2P is not enabled
       }
    }
    ...
    }
    
  3. En el método onCreate() de tu actividad, obtén una instancia de WifiP2pManager y registra tu aplicación con el framework P2P Wi-Fi llamando a initialize(). Este método muestra un WifiP2pManager.Channel, que se usa para conectar tu aplicación al framework Wi-Fi P2P. También debes crear una instancia del receptor de emisión con los objetos WifiP2pManager y WifiP2pManager.Channel junto con una referencia a tu actividad. Esto permite que el receptor de emisión le notifique a tu actividad sobre eventos interesantes y la actualice según corresponda. También te permite manipular el estado de Wi-Fi del dispositivo si es necesario:

    Kotlin

    val manager: WifiP2pManager? by lazy(LazyThreadSafetyMode.NONE) {
       getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager?
    }
    
    var channel: WifiP2pManager.Channel? = null
    var receiver: BroadcastReceiver? = null
    
    override fun onCreate(savedInstanceState: Bundle?) {
       ...
    
       channel = manager?.initialize(this, mainLooper, null)
       channel?.also { channel ->
           receiver = WiFiDirectBroadcastReceiver(manager, channel, this)
       }
    }
    

    Java

    WifiP2pManager manager;
    Channel channel;
    BroadcastReceiver receiver;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState){
       ...
       manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
       channel = manager.initialize(this, getMainLooper(), null);
       receiver = new WiFiDirectBroadcastReceiver(manager, channel, this);
       ...
    }
    
  4. Crea un filtro de intents y agrega los mismos intents que busca tu receptor de emisión:

    Kotlin

    val intentFilter = IntentFilter().apply {
       addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)
       addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)
       addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)
       addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)
    }
    

    Java

    IntentFilter intentFilter;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState){
       ...
       intentFilter = new IntentFilter();
       intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
       intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
       intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
       intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
       ...
    }
    
  5. Registra el receptor de emisión en el método onResume() de tu actividad y cancela su registro en el método onPause() de tu actividad:

    Kotlin

    /* register the broadcast receiver with the intent values to be matched */
    override fun onResume() {
       super.onResume()
       receiver?.also { receiver ->
           registerReceiver(receiver, intentFilter)
       }
    }
    
    /* unregister the broadcast receiver */
    override fun onPause() {
       super.onPause()
       receiver?.also { receiver ->
           unregisterReceiver(receiver)
       }
    }
    

    Java

    /* register the broadcast receiver with the intent values to be matched */
    @Override
    protected void onResume() {
       super.onResume();
       registerReceiver(receiver, intentFilter);
    }
    /* unregister the broadcast receiver */
    @Override
    protected void onPause() {
       super.onPause();
       unregisterReceiver(receiver);
    }
    
  6. Cuando obtuviste un WifiP2pManager.Channel y configuraste un receptor de emisión, tu aplicación puede realizar llamadas al método P2P Wi-Fi y recibir intents P2P Wi-Fi.

  7. Implementa tu aplicación con las funciones de P2P Wi-Fi llamando a los métodos en WifiP2pManager.

En las siguientes secciones, se describe cómo realizar acciones comunes, como descubrir pares y conectarse a ellos.

Cómo detectar pares

Llama a discoverPeers() para detectar los pares disponibles que estén dentro del rango y disponibles para la conexión. La llamada a esta función es asíncrona, y se comunica una operación correcta o un error a tu aplicación con onSuccess() y onFailure() si creaste un WifiP2pManager.ActionListener. El método onSuccess() solo te notifica que el proceso de descubrimiento se realizó correctamente y no proporciona información sobre los pares reales que descubrió, si los hubiera. En la siguiente muestra de código, se indica cómo configurarlo.

Kotlin

manager?.discoverPeers(channel, object : WifiP2pManager.ActionListener {

   override fun onSuccess() {
       ...
   }

   override fun onFailure(reasonCode: Int) {
       ...
   }
})

Java

manager.discoverPeers(channel, new WifiP2pManager.ActionListener() {
   @Override
   public void onSuccess() {
       ...
   }

   @Override
   public void onFailure(int reasonCode) {
       ...
   }
});

Si el proceso de descubrimiento se realiza correctamente y detecta pares, el sistema transmite el intent WIFI_P2P_PEERS_CHANGED_ACTION, que puedes escuchar en un receptor de emisión para obtener una lista de pares. Cuando tu aplicación recibe el intent WIFI_P2P_PEERS_CHANGED_ACTION, puedes solicitar una lista de los pares detectados con requestPeers(). En el siguiente código, se muestra cómo configurarlo.

Kotlin

override fun onReceive(context: Context, intent: Intent) {
   val action: String = intent.action
   when (action) {
       ...
       WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {
           manager?.requestPeers(channel) { peers: WifiP2pDeviceList? ->
               // Handle peers list
           }
       }
       ...
   }
}

Java

PeerListListener myPeerListListener;
...
if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {

   // request available peers from the wifi p2p manager. This is an
   // asynchronous call and the calling activity is notified with a
   // callback on PeerListListener.onPeersAvailable()
   if (manager != null) {
       manager.requestPeers(channel, myPeerListListener);
   }
}

El método requestPeers() también es asíncrono y puede notificar a tu actividad cuando hay una lista de apps similares disponible con onPeersAvailable(), que se define en la interfaz de WifiP2pManager.PeerListListener. El método onPeersAvailable() te proporciona un WifiP2pDeviceList, que puedes iterar para encontrar el intercambio de tráfico al que deseas conectarte.

Cómo conectarse a pares

Una vez que obtengas una lista de posibles pares y hayas seleccionado un dispositivo al cual conectarte, llama al método connect() para conectarte al dispositivo. Esta llamada de método requiere un objeto WifiP2pConfig que contenga información sobre el dispositivo para conectarse. WifiP2pManager.ActionListener puede notificarte sobre una conexión exitosa o una falla. El siguiente código muestra cómo crear una conexión a un dispositivo.

Kotlin

val device: WifiP2pDevice = ...
val config = WifiP2pConfig()
config.deviceAddress = device.deviceAddress
channel?.also { channel ->
   manager?.connect(channel, config, object : WifiP2pManager.ActionListener {

       override fun onSuccess() {
           //success logic
       }

       override fun onFailure(reason: Int) {
           //failure logic
       }
   }
)}

Java

//obtain a peer from the WifiP2pDeviceList
WifiP2pDevice device;
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = device.deviceAddress;
manager.connect(channel, config, new ActionListener() {

   @Override
   public void onSuccess() {
       //success logic
   }

   @Override
   public void onFailure(int reason) {
       //failure logic
   }
});

Cómo transferir datos

Una vez que se establece la conexión, puedes transferir datos entre los dispositivos con sockets. A continuación, se muestran los pasos básicos para transferir datos:

  1. Crea un elemento ServerSocket. Este socket espera la conexión de un cliente en un puerto específico y se bloquea hasta que ocurra, así que haz esto en un subproceso en segundo plano.
  2. Crea un cliente Socket. El cliente usa la dirección IP y el puerto del socket de servidor para conectarse al dispositivo de servidor.
  3. Envía datos desde el cliente al servidor. Cuando el socket del cliente se conecta de forma correcta al socket de servidor, puedes enviar datos del cliente al servidor con flujos de bytes.
  4. El socket de servidor espera una conexión del cliente (con el método accept()). Esta llamada se bloquea hasta que se conecte un cliente, así que llámala en otro subproceso. Cuando se produce una conexión, el dispositivo del servidor puede recibir los datos del cliente.

En el siguiente ejemplo, modificado de la Demostración de P2P Wi-Fi, se muestra cómo crear esta comunicación de socket cliente-servidor y transferir imágenes JPEG de un cliente a un servidor con un servicio. Para obtener un ejemplo funcional completo, compila y ejecuta la demostración.

Kotlin

class FileServerAsyncTask(
       private val context: Context,
       private var statusText: TextView
) : AsyncTask<Void, Void, String?>() {

   override fun doInBackground(vararg params: Void): String? {
       /**
        * Create a server socket.
        */
       val serverSocket = ServerSocket(8888)
       return serverSocket.use {
           /**
            * Wait for client connections. This call blocks until a
            * connection is accepted from a client.
            */
           val client = serverSocket.accept()
           /**
            * If this code is reached, a client has connected and transferred data
            * Save the input stream from the client as a JPEG file
            */
           val f = File(Environment.getExternalStorageDirectory().absolutePath +
                   "/${context.packageName}/wifip2pshared-${System.currentTimeMillis()}.jpg")
           val dirs = File(f.parent)

           dirs.takeIf { it.doesNotExist() }?.apply {
               mkdirs()
           }
           f.createNewFile()
           val inputstream = client.getInputStream()
           copyFile(inputstream, FileOutputStream(f))
           serverSocket.close()
           f.absolutePath
       }
   }

   private fun File.doesNotExist(): Boolean = !exists()

   /**
    * Start activity that can handle the JPEG image
    */
   override fun onPostExecute(result: String?) {
       result?.run {
           statusText.text = "File copied - $result"
           val intent = Intent(android.content.Intent.ACTION_VIEW).apply {
               setDataAndType(Uri.parse("file://$result"), "image/*")
           }
           context.startActivity(intent)
       }
   }
}

Java

public static class FileServerAsyncTask extends AsyncTask {

   private Context context;
   private TextView statusText;

   public FileServerAsyncTask(Context context, View statusText) {
       this.context = context;
       this.statusText = (TextView) statusText;
   }

   @Override
   protected String doInBackground(Void... params) {
       try {

           /**
            * Create a server socket and wait for client connections. This
            * call blocks until a connection is accepted from a client
            */
           ServerSocket serverSocket = new ServerSocket(8888);
           Socket client = serverSocket.accept();

           /**
            * If this code is reached, a client has connected and transferred data
            * Save the input stream from the client as a JPEG file
            */
           final File f = new File(Environment.getExternalStorageDirectory() + "/"
                   + context.getPackageName() + "/wifip2pshared-" + System.currentTimeMillis()
                   + ".jpg");

           File dirs = new File(f.getParent());
           if (!dirs.exists())
               dirs.mkdirs();
           f.createNewFile();
           InputStream inputstream = client.getInputStream();
           copyFile(inputstream, new FileOutputStream(f));
           serverSocket.close();
           return f.getAbsolutePath();
       } catch (IOException e) {
           Log.e(WiFiDirectActivity.TAG, e.getMessage());
           return null;
       }
   }

   /**
    * Start activity that can handle the JPEG image
    */
   @Override
   protected void onPostExecute(String result) {
       if (result != null) {
           statusText.setText("File copied - " + result);
           Intent intent = new Intent();
           intent.setAction(android.content.Intent.ACTION_VIEW);
           intent.setDataAndType(Uri.parse("file://" + result), "image/*");
           context.startActivity(intent);
       }
   }
}

En el cliente, conéctate al socket de servidor con un socket de cliente y transfiere los datos. En este ejemplo, se transfiere un archivo JPEG en el sistema de archivos del dispositivo cliente.

Kotlin

val context = applicationContext
val host: String
val port: Int
val len: Int
val socket = Socket()
val buf = ByteArray(1024)
...
try {
   /**
    * Create a client socket with the host,
    * port, and timeout information.
    */
   socket.bind(null)
   socket.connect((InetSocketAddress(host, port)), 500)

   /**
    * Create a byte stream from a JPEG file and pipe it to the output stream
    * of the socket. This data is retrieved by the server device.
    */
   val outputStream = socket.getOutputStream()
   val cr = context.contentResolver
   val inputStream: InputStream = cr.openInputStream(Uri.parse("path/to/picture.jpg"))
   while (inputStream.read(buf).also { len = it } != -1) {
       outputStream.write(buf, 0, len)
   }
   outputStream.close()
   inputStream.close()
} catch (e: FileNotFoundException) {
   //catch logic
} catch (e: IOException) {
   //catch logic
} finally {
   /**
    * Clean up any open sockets when done
    * transferring or if an exception occurred.
    */
   socket.takeIf { it.isConnected }?.apply {
       close()
   }
}

Java

Context context = this.getApplicationContext();
String host;
int port;
int len;
Socket socket = new Socket();
byte buf[]  = new byte[1024];
...
try {
   /**
    * Create a client socket with the host,
    * port, and timeout information.
    */
   socket.bind(null);
   socket.connect((new InetSocketAddress(host, port)), 500);

   /**
    * Create a byte stream from a JPEG file and pipe it to the output stream
    * of the socket. This data is retrieved by the server device.
    */
   OutputStream outputStream = socket.getOutputStream();
   ContentResolver cr = context.getContentResolver();
   InputStream inputStream = null;
   inputStream = cr.openInputStream(Uri.parse("path/to/picture.jpg"));
   while ((len = inputStream.read(buf)) != -1) {
       outputStream.write(buf, 0, len);
   }
   outputStream.close();
   inputStream.close();
} catch (FileNotFoundException e) {
   //catch logic
} catch (IOException e) {
   //catch logic
}

/**
* Clean up any open sockets when done
* transferring or if an exception occurred.
*/
finally {
   if (socket != null) {
       if (socket.isConnected()) {
           try {
               socket.close();
           } catch (IOException e) {
               //catch logic
           }
       }
   }
}