simplified the android code

UdpSocketClient holds onto the only DatagramSocket/MulticastSocket reference.
UdpSocketClient throws IlllegalStateException if send/addMembership are called on unbound sockets.
UdpReceiverTask takes it's values as a Pair parameter to execute on.
This commit is contained in:
Andy Prock 2015-11-19 09:59:37 -08:00
parent 398935bb67
commit 79d6f346b6
3 changed files with 83 additions and 92 deletions

View File

@ -5,59 +5,44 @@
* Created by Andy Prock on 9/24/15. * Created by Andy Prock on 9/24/15.
*/ */
package com.tradle.react; package com.tradle.react;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.util.Base64; import android.util.Base64;
import android.util.Pair;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.WeakReference; import java.net.DatagramPacket;
import java.net.DatagramPacket; import java.net.DatagramSocket;
import java.net.DatagramSocket; import java.net.InetAddress;
import java.net.InetAddress;
/** /**
* This is a specialized AsyncTask that receives data from a socket in the background, and * This is a specialized AsyncTask that receives data from a socket in the background, and
* notifies it's listener when data is received. This is not threadsafe, the listener * notifies it's listener when data is received. This is not threadsafe, the listener
* should handle synchronicity. * should handle synchronicity.
*/ */
public class UdpReceiverTask extends AsyncTask<Void, Void, Void> { public class UdpReceiverTask extends AsyncTask<Pair<DatagramSocket, UdpReceiverTask.OnDataReceivedListener>, Void, Void> {
private static final String TAG = "UdpReceiverTask"; private static final String TAG = "UdpReceiverTask";
private static final int MAX_UDP_DATAGRAM_LEN = 1024; private static final int MAX_UDP_DATAGRAM_LEN = 1024;
private DatagramSocket mSocket;
private WeakReference<OnDataReceivedListener> mReceiverListener;
/**
* An {@link AsyncTask} that blocks to receive data from a socket.
* Received data is sent via the {@link OnDataReceivedListener}
*/
public UdpReceiverTask(DatagramSocket socket, UdpReceiverTask.OnDataReceivedListener
receiverListener) {
this.mSocket = socket;
this.mReceiverListener = new WeakReference<>(receiverListener);
}
/**
* Returns the UdpReceiverTask's DatagramChannel.
*/
public DatagramSocket getSocket() {
return mSocket;
}
/** /**
* An infinite loop to block and read data from the socket. * An infinite loop to block and read data from the socket.
*/ */
@Override @Override
protected Void doInBackground(Void... a) { protected Void doInBackground(Pair<DatagramSocket, UdpReceiverTask.OnDataReceivedListener>... params) {
OnDataReceivedListener receiverListener = mReceiverListener.get(); if (params.length > 1) {
throw new IllegalArgumentException("This task is only for a single socket/listener pair.");
}
DatagramSocket socket = params[0].first;
OnDataReceivedListener receiverListener = params[0].second;
final byte[] buffer = new byte[MAX_UDP_DATAGRAM_LEN]; final byte[] buffer = new byte[MAX_UDP_DATAGRAM_LEN];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length); DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
while (!isCancelled()) { while (!isCancelled()) {
try { try {
mSocket.receive(packet); socket.receive(packet);
final InetAddress address = packet.getAddress(); final InetAddress address = packet.getAddress();
final String base64Data = Base64.encodeToString(packet.getData(), packet.getOffset(), final String base64Data = Base64.encodeToString(packet.getData(), packet.getOffset(),
@ -79,14 +64,6 @@ public class UdpReceiverTask extends AsyncTask<Void, Void, Void> {
return null; return null;
} }
/**
* Close if cancelled.
*/
@Override
protected void onCancelled() {
// mSocket.close();
}
/** /**
* Listener interface for receive events. * Listener interface for receive events.
*/ */

View File

@ -10,6 +10,7 @@ package com.tradle.react;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Base64; import android.util.Base64;
import android.util.Pair;
import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.Callback;
@ -47,11 +48,11 @@ public final class UdpSocketClient implements UdpReceiverTask.OnDataReceivedList
} }
/** /**
* Checks to see if client is receiving multicast packets. * Checks to see if client part of a multi-cast group.
* @return boolean true if receiving multicast packets, else false. * @return boolean true IF the socket is part of a multi-cast group.
*/ */
public boolean isMulticast() { public boolean isMulticast() {
return (mReceiverTask != null && mReceiverTask.getSocket() instanceof MulticastSocket); return (mSocket != null && mSocket instanceof MulticastSocket);
} }
/** /**
@ -69,7 +70,7 @@ public final class UdpSocketClient implements UdpReceiverTask.OnDataReceivedList
public void bind(Integer port, @Nullable String address) throws IOException { public void bind(Integer port, @Nullable String address) throws IOException {
mSocket = new DatagramSocket(null); mSocket = new DatagramSocket(null);
mReceiverTask = new UdpReceiverTask(mSocket, this); mReceiverTask = new UdpReceiverTask();
SocketAddress socketAddress; SocketAddress socketAddress;
if (address != null) { if (address != null) {
@ -82,7 +83,8 @@ public final class UdpSocketClient implements UdpReceiverTask.OnDataReceivedList
mSocket.bind(socketAddress); mSocket.bind(socketAddress);
// begin listening for data in the background // begin listening for data in the background
mReceiverTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); mReceiverTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
new Pair<DatagramSocket, UdpReceiverTask.OnDataReceivedListener>(mSocket, this));
} }
/** /**
@ -92,18 +94,30 @@ public final class UdpSocketClient implements UdpReceiverTask.OnDataReceivedList
* @param address the multicast group to join * @param address the multicast group to join
* @throws UnknownHostException * @throws UnknownHostException
* @throws IOException * @throws IOException
* @throws IllegalStateException if socket is not bound.
*/ */
public void addMembership(String address) throws UnknownHostException, IOException { public void addMembership(String address) throws UnknownHostException, IOException, IllegalStateException {
final int port = mReceiverTask.getSocket().getLocalPort(); if (null == mSocket || !mSocket.isBound()) {
throw new IllegalStateException("Socket is not bound.");
}
if (!(mSocket instanceof MulticastSocket)) {
// cancel the current receiver task
if (mReceiverTask != null) {
mReceiverTask.cancel(true); mReceiverTask.cancel(true);
}
final MulticastSocket socket = new MulticastSocket(port); // tear down the DatagramSocket, and rebuild as a MulticastSocket
socket.joinGroup(InetAddress.getByName(address)); final int port = mSocket.getLocalPort();
mSocket.close();
mReceiverTask = new UdpReceiverTask(socket, this); mSocket = new MulticastSocket(port);
// begin listening for data in the background // begin listening for data in the background
mReceiverTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); mReceiverTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
new Pair<DatagramSocket, UdpReceiverTask.OnDataReceivedListener>(mSocket, this));
}
((MulticastSocket) mSocket).joinGroup(InetAddress.getByName(address));
} }
/** /**
@ -114,13 +128,8 @@ public final class UdpSocketClient implements UdpReceiverTask.OnDataReceivedList
* @throws IOException * @throws IOException
*/ */
public void dropMembership(String address) throws UnknownHostException, IOException { public void dropMembership(String address) throws UnknownHostException, IOException {
if (mReceiverTask != null && mReceiverTask.getSocket() instanceof MulticastSocket) { if (mSocket instanceof MulticastSocket) {
if (!mReceiverTask.isCancelled()) { ((MulticastSocket) mSocket).leaveGroup(InetAddress.getByName(address));
mReceiverTask.cancel(true);
}
final MulticastSocket socket = (MulticastSocket) mReceiverTask.getSocket();
socket.leaveGroup(InetAddress.getByName(address));
} }
} }
@ -133,10 +142,12 @@ public final class UdpSocketClient implements UdpReceiverTask.OnDataReceivedList
* @param callback callback for results * @param callback callback for results
* @throws UnknownHostException * @throws UnknownHostException
* @throws IOException * @throws IOException
* @throws IllegalStateException if socket is not bound.
*/ */
public void send(String base64String, Integer port, String address, @Nullable Callback callback) throws UnknownHostException, IOException { public void send(String base64String, Integer port, String address, @Nullable Callback callback)
throws UnknownHostException, IllegalStateException, IOException {
if (null == mSocket || !mSocket.isBound()) { if (null == mSocket || !mSocket.isBound()) {
return; throw new IllegalStateException("Socket is not bound.");
} }
byte[] data = Base64.decode(base64String, Base64.NO_WRAP); byte[] data = Base64.decode(base64String, Base64.NO_WRAP);
@ -159,11 +170,8 @@ public final class UdpSocketClient implements UdpReceiverTask.OnDataReceivedList
* Sets the socket to enable broadcasts. * Sets the socket to enable broadcasts.
*/ */
public void setBroadcast(boolean flag) throws SocketException { public void setBroadcast(boolean flag) throws SocketException {
if (mReceiverTask != null) { if (mSocket != null) {
DatagramSocket socket = mReceiverTask.getSocket(); mSocket.setBroadcast(flag);
if (socket != null) {
socket.setBroadcast(flag);
}
} }
} }
@ -172,13 +180,11 @@ public final class UdpSocketClient implements UdpReceiverTask.OnDataReceivedList
*/ */
public void close() throws IOException { public void close() throws IOException {
if (mReceiverTask != null && !mReceiverTask.isCancelled()) { if (mReceiverTask != null && !mReceiverTask.isCancelled()) {
// stop the receiving task, and close the channel // stop the receiving task
mReceiverTask.cancel(true); mReceiverTask.cancel(true);
if (!mReceiverTask.getSocket().isClosed()) {
mReceiverTask.getSocket().close();
}
} }
// close the socket
if (mSocket != null && !mSocket.isClosed()) { if (mSocket != null && !mSocket.isClosed()) {
mSocket.close(); mSocket.close();
mSocket = null; mSocket = null;

View File

@ -5,29 +5,29 @@
* Created by Andy Prock on 9/24/15. * Created by Andy Prock on 9/24/15.
*/ */
package com.tradle.react; package com.tradle.react;
import android.content.Context; import android.content.Context;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.SparseArray; import android.util.SparseArray;
import com.facebook.common.logging.FLog; import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.GuardedAsyncTask; import com.facebook.react.bridge.GuardedAsyncTask;
import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule; import com.facebook.react.modules.core.DeviceEventManagerModule;
import java.io.IOException; import java.io.IOException;
import java.net.SocketException; import java.net.SocketException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
/** /**
* The NativeModule in charge of storing active {@link UdpSocketClient}s, and acting as an api layer. * The NativeModule in charge of storing active {@link UdpSocketClient}s, and acting as an api layer.
@ -185,6 +185,12 @@ public final class UdpSockets extends ReactContextBaseJavaModule
try { try {
client.addMembership(multicastAddress); client.addMembership(multicastAddress);
} catch (IllegalStateException ise) {
// an exception occurred
FLog.e(TAG, "addMembership", ise);
} catch (UnknownHostException uhe) {
// an exception occurred
FLog.e(TAG, "addMembership", uhe);
} catch (IOException ioe) { } catch (IOException ioe) {
// an exception occurred // an exception occurred
FLog.e(TAG, "addMembership", ioe); FLog.e(TAG, "addMembership", ioe);
@ -232,7 +238,9 @@ public final class UdpSockets extends ReactContextBaseJavaModule
try { try {
client.send(base64String, port, address, callback); client.send(base64String, port, address, callback);
} catch (UnknownHostException uhe) { } catch (IllegalStateException ise) {
callback.invoke(UdpErrorUtil.getError(null, ise.getMessage()));
}catch (UnknownHostException uhe) {
callback.invoke(UdpErrorUtil.getError(null, uhe.getMessage())); callback.invoke(UdpErrorUtil.getError(null, uhe.getMessage()));
} catch (IOException ioe) { } catch (IOException ioe) {
// an exception occurred // an exception occurred