How to give deadline to okio via okHttp - android

From looking at okHttp source code, when call.execute() is called the body being transferred from server to the client.
It doesn't make sense because it makes impossible to set deadline to okio which means i cannot give timeout to the whole request but only readTimeout and connectTimeout which have effect only until the first byte is ready for read.
Am i missing something here?

There’s no way to give a deadline to the entire request. You should open a feature request on this! OkHttp’s use of Okio is one of it’s differentiating features, and exposing more Okio functionality through OkHttp’s API is a great way to put more power in OkHttp’s users.

This is on the schedule for the next version of okhttp (https://github.com/square/okhttp/issues/2840), but for now we successfully implemented a deadline for both the request and response body reading by subclassing Call in our application in production:
package com.pushd.util;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.io.IOException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.internal.http2.StreamResetException;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
/**
* An okhttp3.Call with a deadline timeout from the start of isExecuted until ResponseBody.source() is closed or unused.
*/
public class DeadlineCall implements Call {
private final static Logger LOGGER = Logger.getLogger(DeadlineCall.class.getName());
private static AtomicInteger sFutures = new AtomicInteger();
private static final ScheduledExecutorService sHTTPCancelExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
#Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "DeadlineCallCancel");
t.setDaemon(true);
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
});
private final Call mUnderlying;
private final int mDeadlineTimeout;
private volatile ScheduledFuture mDeadline;
private volatile boolean mDeadlineHit;
private volatile boolean mCancelled;
private volatile BufferedSource mBodySource;
DeadlineCall(Call underlying, int deadlineTimeout) {
mUnderlying = underlying;
mDeadlineTimeout = deadlineTimeout;
}
/**
* Factory wrapper for OkHttpClient.newCall(request) to create a new DeadlineCall scheduled to cancel its underlying Call after the deadline.
* #param client
* #param request
* #param deadlineTimeout in ms
* #return Call
*/
public static DeadlineCall newDeadlineCall(#NonNull OkHttpClient client, #NonNull Request request, int deadlineTimeout) {
final Call underlying = client.newCall(request);
return new DeadlineCall(underlying, deadlineTimeout);
}
/**
* Shuts down thread that cancels calls when their deadline is hit.
*/
public static void shutdownNow() {
sHTTPCancelExecutorService.shutdownNow();
}
#Override
public Request request() {
return mUnderlying.request();
}
/**
* Response MUST be closed to clean up deadline even if body is not read, e.g. on !isSuccessful
* #return
* #throws IOException
*/
#Override
public Response execute() throws IOException {
startDeadline();
try {
return wrapResponse(mUnderlying.execute());
} catch (IOException e) {
cancelDeadline();
throw wrapIfDeadline(e);
}
}
/**
* Deadline is removed when onResponse returns unless response.body().source() or a method using
* it is called synchronously from onResponse to indicate caller's committment to close it themselves.
* This includes peekBody so prefer DeadlineResponseBody.peek unless you explicitly close after peekBody.
* #param responseCallback
*/
#Override
public void enqueue(final Callback responseCallback) {
startDeadline();
mUnderlying.enqueue(new Callback() {
#Override
public void onFailure(Call underlying, IOException e) {
cancelDeadline(); // there is no body to read so no need for deadline anymore
responseCallback.onFailure(DeadlineCall.this, wrapIfDeadline(e));
}
#Override
public void onResponse(Call underlying, Response response) throws IOException {
try {
responseCallback.onResponse(DeadlineCall.this, wrapResponse(response));
if (mBodySource == null) {
cancelDeadline(); // remove deadline if body was never opened
}
} catch (IOException e) {
cancelDeadline();
throw wrapIfDeadline(e);
}
}
});
}
private IOException wrapIfDeadline(IOException e) {
if (mDeadlineHit && isCancellationException(e)) {
return new DeadlineException(e);
}
return e;
}
public class DeadlineException extends IOException {
public DeadlineException(Throwable cause) {
super(cause);
}
}
/**
* Wraps response to cancelDeadline when response closed and throw correct DeadlineException when deadline happens during response reading.
* #param response
* #return
*/
private Response wrapResponse(final Response response) {
return response.newBuilder().body(new DeadlineResponseBody(response)).build();
}
public class DeadlineResponseBody extends ResponseBody {
private final Response mResponse;
DeadlineResponseBody(final Response response) {
mResponse = response;
}
#Override
public MediaType contentType() {
return mResponse.body().contentType();
}
#Override
public long contentLength() {
return mResponse.body().contentLength();
}
/**
* #return the body source indicating it will be closed later by the caller to cancel the deadline
*/
#Override
public BufferedSource source() {
if (mBodySource == null) {
mBodySource = Okio.buffer(new ForwardingSource(mResponse.body().source()) {
#Override
public long read(Buffer sink, long byteCount) throws IOException {
try {
return super.read(sink, byteCount);
} catch (IOException e) {
throw wrapIfDeadline(e);
}
}
#Override
public void close() throws IOException {
cancelDeadline();
super.close();
}
});
}
return mBodySource;
}
/**
* #return the body source without indicating it will be closed later by caller, e.g. to peekBody on unsucessful requests
*/
public BufferedSource peekSource() {
return mResponse.body().source();
}
/**
* Copy of https://square.github.io/okhttp/3.x/okhttp/okhttp3/Response.html#peekBody-long- that uses peekSource() since Response class is final
* #param byteCount
* #return
* #throws IOException
*/
public ResponseBody peek(long byteCount) throws IOException {
BufferedSource source = peekSource();
source.request(byteCount);
Buffer copy = source.buffer().clone();
// There may be more than byteCount bytes in source.buffer(). If there is, return a prefix.
Buffer result;
if (copy.size() > byteCount) {
result = new Buffer();
result.write(copy, byteCount);
copy.clear();
} else {
result = copy;
}
return ResponseBody.create(mResponse.body().contentType(), result.size(), result);
}
}
private void startDeadline() {
mDeadline = sHTTPCancelExecutorService.schedule(new Runnable() {
#Override
public void run() {
mDeadlineHit = true;
mUnderlying.cancel(); // calls onFailure or causes body read to throw
LOGGER.fine("Deadline hit for " + request()); // should trigger a subsequent wrapIfDeadline but if we see this log line without that it means the caller orphaned us without closing
}
}, mDeadlineTimeout, TimeUnit.MILLISECONDS);
LOGGER.fine("started deadline for " + request());
if (sFutures.incrementAndGet() == 1000) {
LOGGER.warning("1000 pending DeadlineCalls, may be leaking due to not calling close()");
}
}
private void cancelDeadline() {
if (mDeadline != null) {
mDeadline.cancel(false);
mDeadline = null;
sFutures.decrementAndGet();
LOGGER.fine("canceled deadline for " + request());
} else {
LOGGER.info("deadline already canceled for " + request());
}
}
#Override
public void cancel() {
mCancelled = true;
// should trigger onFailure or raise from execute or responseCallback.onResponse which will cancelDeadline
mUnderlying.cancel();
}
#Override
public boolean isExecuted() {
return mUnderlying.isExecuted();
}
#Override
public boolean isCanceled() {
return mCancelled;
}
#Override
public Call clone() {
return new DeadlineCall(mUnderlying.clone(), mDeadlineTimeout);
}
private static boolean isCancellationException(IOException e) {
// okhttp cancel from HTTP/2 calls
if (e instanceof StreamResetException) {
switch (((StreamResetException) e).errorCode) {
case CANCEL:
return true;
}
}
// https://android.googlesource.com/platform/external/okhttp/+/master/okhttp/src/main/java/com/squareup/okhttp/Call.java#281
if (e instanceof IOException &&
e.getMessage() != null && e.getMessage().equals("Canceled")) {
return true;
}
return false;
}
}
Note that we also have a separate interceptor to timeout DNS since even our deadline doesn't cover that:
/**
* Based on http://stackoverflow.com/questions/693997/how-to-set-httpresponse-timeout-for-android-in-java/31643186#31643186
* as per https://github.com/square/okhttp/issues/95
*/
private static class DNSTimeoutInterceptor implements Interceptor {
long mTimeoutMillis;
public DNSTimeoutInterceptor(long timeoutMillis) {
mTimeoutMillis = timeoutMillis;
}
#Override
public Response intercept(final Chain chain) throws IOException {
Request request = chain.request();
Log.SplitTimer timer = (request.tag() instanceof RequestTag ? ((RequestTag) request.tag()).getTimer() : null);
// underlying call should timeout after 2 tries of 5s: https://android.googlesource.com/platform/bionic/+/android-5.1.1_r38/libc/dns/include/resolv_private.h#137
// could use our own Dns implementation that falls back to public DNS servers: https://garage.easytaxi.com/tag/dns-android-okhttp/
if (!DNSResolver.isDNSReachable(request.url().host(), mTimeoutMillis)) {
throw new UnknownHostException("DNS timeout");
}
return chain.proceed(request);
}
private static class DNSResolver implements Runnable {
private String mDomain;
private InetAddress mAddress;
public static boolean isDNSReachable(String domain, long timeoutMillis) {
try {
DNSResolver dnsRes = new DNSResolver(domain);
Thread t = new Thread(dnsRes, "DNSResolver");
t.start();
t.join(timeoutMillis);
return dnsRes.get() != null;
} catch(Exception e) {
return false;
}
}
public DNSResolver(String domain) {
this.mDomain = domain;
}
public void run() {
try {
InetAddress addr = InetAddress.getByName(mDomain);
set(addr);
} catch (UnknownHostException e) {
}
}
public synchronized void set(InetAddress inetAddr) {
this.mAddress = inetAddr;
}
public synchronized InetAddress get() {
return mAddress;
}
}
}

Related

Android deprecated Tasks.call - replacement

In my android app I have an option to backup the database to Google Drive. For that I am using DriveServiceHelper class, but I just noticed, in Android 11 the Task.call is deprecated.
public Task<FileList> queryFiles() {
return Tasks.call(mExecutor, () ->
mDriveService.files().list().setSpaces("drive").execute());
}
From my BackupActivity then I call queryFiles from backup method:
public void backup(View v) {
driveServiceHelper.queryFiles()
.addOnSuccessListener(fileList -> {
// another code
})
.addOnFailureListener(e -> showMsgSnack(getString(R.string.uploaderror)));
I did not find any solution how to deal with this to avoid complete rework of that class.
What I tried:
I tried to replace with runnable, also callable, but it doesn't work as Task is expected to be returned, not Filelist.
also I tried to use TaskCompletionSource:
public Task<FileList> queryFiles(int delay) throws IOException, ExecutionException, InterruptedException {
new Thread(
new Runnable() {
#Override
public void run() {
TaskCompletionSource<FileList> taskCompletionSource = new TaskCompletionSource<>();
FileList result = null;
try {
result = mDriveService.files().list().setSpaces("drive").execute();
} catch (IOException e) {
e.printStackTrace();
}
FileList finalResult = result;
new Handler().postDelayed(() -> taskCompletionSource.setResult(finalResult), delay);
return taskCompletionSource.getTask();
}
}).start();
}
but the return works not from a method of void type.
Ok, after hours of testing I tried this solution and this seems working for now: (using executorService, and in Handler a Looper is needed.)
public Task<FileList> queryFiles() {
final TaskCompletionSource<FileList> tcs = new TaskCompletionSource<FileList>();
ExecutorService service = Executors.newFixedThreadPool(1);
service.execute(
new Runnable() {
#Override
public void run() {
FileList result = null;
try {
result = mDriveService.files().list().setSpaces("drive").execute();
} catch (IOException e) {
e.printStackTrace();
}
FileList finalResult = result;
new Handler(Looper.getMainLooper()).postDelayed(() -> tcs.setResult(finalResult), 1000);
}
});
return tcs.getTask();
}
I meant something like this:
public Task<FileList> queryFiles(int delay) throws IOException {
Task<FileList> retVal;
final FutureValue<Task<FileList>> future = new FutureValue<>();
// now run this bit in a runnable
/*
TaskCompletionSource<FileList> taskCompletionSource = new TaskCompletionSource<>();
FileList result = mDriveService.files().list().setSpaces("drive").execute();
new Handler().postDelayed(() -> taskCompletionSource.setResult(result), delay);
return taskCompletionSource.getTask();
*/
new Thread(
new Runnable() {
#Override
public void run() {
TaskCompletionSource<FileList> taskCompletionSource = new TaskCompletionSource<>();
FileList result = mDriveService.files().list().setSpaces("drive").execute();
new Handler().postDelayed(() -> taskCompletionSource.setResult(result), delay);
// and we replace the return statement with something else
// return taskCompletionSource.getTask();
future.set(taskCompletionSource.getTask());
}
}).start();
// And block (wait) for future to finish so we can return it, deadlocking the main thread...
// return future.get();
//FIXME do either this
// retVal = future.get();
// For bonus points, we'll do a timed wait instead -- OR THIS
try {
retVal = future.get(30, TimeUnit.SECONDS);
} catch (TimeoutException e) {
future.cancel(true);
Log.d(LOG_TAG, "Exception "+e+" happened!", e);
} catch (InterruptedException | ExecutionException e) {
Log.d(LOG_TAG, "Exception "+e+" happened!", e);
}
return retVal;
}
and that should set you on some path to solving the problem.
However, if the only reason for using the Task<> is just so you can add success/fail listeners to these methods - i strongly suggest you come up with something better that actually runs on background threads instead of the thread you're calling them on.
The FutureValue class:
/**
* Implementation of {#link Future}, allowing waiting for value to be set (from another thread).
* Use {#link #set(Object)} to set value, {#link #get()} or {#link #get(long, TimeUnit)} to retrieve
* value.
* TODO: tests
*
* #param <T> type of awaited value
*/
public class FutureValue<T> implements Future<T> {
private static final String LOGTAG = "FutureValue";
private static final long NANOS_IN_MILLI = TimeUnit.MILLISECONDS.toNanos(1);
private volatile T value;
private volatile boolean isDone = false;
private volatile boolean isCanceled = false;
/**
* Sets value awaited by this future.
*
* #param value value
*/
public synchronized void set(T value) {
this.value = value;
isDone = true;
notifyAll();
}
/** {#inheritDoc} */
#Override
public synchronized boolean cancel(boolean mayInterruptIfRunning) {
isCanceled = true;
notifyAll();
return !isDone;
}
/** {#inheritDoc} */
#Override
public boolean isCancelled() {
return isCanceled;
}
/** {#inheritDoc} */
#Override
public boolean isDone() {
return isDone;
}
/** {#inheritDoc} */
#Override
public synchronized T get() {
while (!isDone) {
if (isCanceled) {
return value;
}
try {
wait();
} catch (InterruptedException ignored) {
Log.w(LOGTAG, "We're just gonna ignore this exception: " + ignored, ignored);
}
}
return value;
}
/** {#inheritDoc} */
#Override
public synchronized T get(long timeout, #NonNull TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
final long targetTime = System.nanoTime() + unit.toNanos(timeout);
while (!isDone && !isCanceled) {
try {
final long waitTimeNanos = targetTime - System.nanoTime();
if (waitTimeNanos <= 0) {
throw new TimeoutException();
}
wait(waitTimeNanos / NANOS_IN_MILLI, (int) (waitTimeNanos % NANOS_IN_MILLI));
} catch (InterruptedException ignored) {
Log.w(LOGTAG, "We're just gonna ignore this exception: " + ignored, ignored);
}
}
return value;
}
}

RxJava2 PublishSubject does not have observers when doOnSubscribe called

I am using RxJava2.
i have some observable, and few subscribers that can be subscribed for it.
each time when new subscribers arrive, some job should be done and each of subscribers should be notified.
for this i decide to use PublishSubject. but when doOnSubscribe received from firs subscriber, myPublishSubject.hasObservers() return false...
any idea why it happens and how can i fix this?
private val myPublishSubject = PublishSubject.create<Boolean>()
fun getPublishObservable():Observable<Boolean> {
return myPublishSubject.doOnSubscribe {
//do some job when new subscriber arrived and notify all subscribers
//via
myPublishSubject.onNext(true)
}
}
Do I understand it correct, that when doOnSubscribe called it mean that there is at least one subscribers already present?
i did not find ready answer, so i create my own version of subject and call it RefreshSubject.
it based on PublishSubject but with one difference: if you would like to return observable and be notified when new subscriber arrives and ready to receive some data you should use method getSubscriberReady.
here a small example:
private RefreshSubject<Boolean> refreshSubject = RefreshSubject.create();
//ordinary publish behavior
public Observable<Boolean> getObservable(){
return refreshSubject;
}
//refreshSubject behaviour
public Observable<Boolean> getRefreshObserver(){
return refreshSubject.getSubscriberReady(new Action() {
#Override
public void run() throws Exception {
//new subscriber arrives and ready to receive some data
//so you can make some data request and all your subscribers (with new one just arrived)
//will receive new content
}
});
}
and here is full class:
public class RefreshSubject<T> extends Subject<T> {
/** The terminated indicator for the subscribers array. */
#SuppressWarnings("rawtypes")
private static final RefreshSubject.RefreshDisposable[] TERMINATED = new RefreshSubject.RefreshDisposable[0];
/** An empty subscribers array to avoid allocating it all the time. */
#SuppressWarnings("rawtypes")
private static final RefreshSubject.RefreshDisposable[] EMPTY = new RefreshSubject.RefreshDisposable[0];
/** The array of currently subscribed subscribers. */
private final AtomicReference<RefreshDisposable<T>[]> subscribers;
/** The error, write before terminating and read after checking subscribers. */
Throwable error;
/**
* Constructs a RefreshSubject.
* #param <T> the value type
* #return the new RefreshSubject
*/
#CheckReturnValue
public static <T> RefreshSubject<T> create() {
return new RefreshSubject<T>();
}
/**
* Constructs a RefreshSubject.
* #since 2.0
*/
#SuppressWarnings("unchecked")
private RefreshSubject() {
subscribers = new AtomicReference<RefreshSubject.RefreshDisposable<T>[]>(EMPTY);
}
#Override
public void subscribeActual(Observer<? super T> t) {
RefreshSubject.RefreshDisposable<T> ps = new RefreshSubject.RefreshDisposable<T>(t, RefreshSubject.this);
t.onSubscribe(ps);
if (add(ps)) {
// if cancellation happened while a successful add, the remove() didn't work
// so we need to do it again
if (ps.isDisposed()) {
remove(ps);
}
} else {
Throwable ex = error;
if (ex != null) {
t.onError(ex);
} else {
t.onComplete();
}
}
}
public Observable<T> getSubscriberReady(final Action onReady){
return Observable.create(new ObservableOnSubscribe<T>() {
#Override
public void subscribe(ObservableEmitter<T> e) throws Exception {
add(new RefreshDisposable(e, RefreshSubject.this));
onReady.run();
}
});
}
/**
* Tries to add the given subscriber to the subscribers array atomically
* or returns false if the subject has terminated.
* #param ps the subscriber to add
* #return true if successful, false if the subject has terminated
*/
private boolean add(RefreshSubject.RefreshDisposable<T> ps) {
for (;;) {
RefreshSubject.RefreshDisposable<T>[] a = subscribers.get();
if (a == TERMINATED) {
return false;
}
int n = a.length;
#SuppressWarnings("unchecked")
RefreshSubject.RefreshDisposable<T>[] b = new RefreshSubject.RefreshDisposable[n + 1];
System.arraycopy(a, 0, b, 0, n);
b[n] = ps;
if (subscribers.compareAndSet(a, b)) {
return true;
}
}
}
/**
* Atomically removes the given subscriber if it is subscribed to the subject.
* #param ps the subject to remove
*/
#SuppressWarnings("unchecked")
private void remove(RefreshSubject.RefreshDisposable<T> ps) {
for (;;) {
RefreshSubject.RefreshDisposable<T>[] a = subscribers.get();
if (a == TERMINATED || a == EMPTY) {
return;
}
int n = a.length;
int j = -1;
for (int i = 0; i < n; i++) {
if (a[i] == ps) {
j = i;
break;
}
}
if (j < 0) {
return;
}
RefreshSubject.RefreshDisposable<T>[] b;
if (n == 1) {
b = EMPTY;
} else {
b = new RefreshSubject.RefreshDisposable[n - 1];
System.arraycopy(a, 0, b, 0, j);
System.arraycopy(a, j + 1, b, j, n - j - 1);
}
if (subscribers.compareAndSet(a, b)) {
return;
}
}
}
#Override
public void onSubscribe(Disposable s) {
if (subscribers.get() == TERMINATED) {
s.dispose();
}
}
#Override
public void onNext(T t) {
if (subscribers.get() == TERMINATED) {
return;
}
if (t == null) {
onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."));
return;
}
for (RefreshSubject.RefreshDisposable<T> s : subscribers.get()) {
s.onNext(t);
}
}
#SuppressWarnings("unchecked")
#Override
public void onError(Throwable t) {
if (subscribers.get() == TERMINATED) {
RxJavaPlugins.onError(t);
return;
}
if (t == null) {
t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources.");
}
error = t;
for (RefreshSubject.RefreshDisposable<T> s : subscribers.getAndSet(TERMINATED)) {
s.onError(t);
}
}
#SuppressWarnings("unchecked")
#Override
public void onComplete() {
if (subscribers.get() == TERMINATED) {
return;
}
for (RefreshSubject.RefreshDisposable<T> s : subscribers.getAndSet(TERMINATED)) {
s.onComplete();
}
}
#Override
public boolean hasObservers() {
return subscribers.get().length != 0;
}
#Override
public Throwable getThrowable() {
if (subscribers.get() == TERMINATED) {
return error;
}
return null;
}
#Override
public boolean hasThrowable() {
return subscribers.get() == TERMINATED && error != null;
}
#Override
public boolean hasComplete() {
return subscribers.get() == TERMINATED && error == null;
}
/**
* Wraps the actualEmitter subscriber, tracks its requests and makes cancellation
* to remove itself from the current subscribers array.
*
* #param <T> the value type
*/
private static final class RefreshDisposable<T> extends AtomicBoolean implements Disposable {
private static final long serialVersionUID = 3562861878281475070L;
/** The actualEmitter subscriber. */
final Emitter<? super T> actualEmitter;
/** The actualEmitter subscriber. */
final Observer<? super T> actualObserver;
/** The subject state. */
final RefreshSubject<T> parent;
/**
* Constructs a PublishSubscriber, wraps the actualEmitter subscriber and the state.
* #param actualEmitter the actualEmitter subscriber
* #param parent the parent RefreshProcessor
*/
RefreshDisposable(Emitter<? super T> actualEmitter, RefreshSubject<T> parent) {
this.actualEmitter = actualEmitter;
this.parent = parent;
actualObserver = null;
}
/**
* Constructs a PublishSubscriber, wraps the actualEmitter subscriber and the state.
* #param actualObserver the actualObserver subscriber
* #param parent the parent RefreshProcessor
*/
RefreshDisposable(Observer<? super T> actualObserver, RefreshSubject<T> parent) {
this.actualObserver = actualObserver;
this.parent = parent;
actualEmitter = null;
}
public void onNext(T t) {
if (!get()) {
if (actualEmitter != null)
actualEmitter.onNext(t);
if (actualObserver != null)
actualObserver.onNext(t);
}
}
public void onError(Throwable t) {
if (get()) {
RxJavaPlugins.onError(t);
} else {
if (actualEmitter != null)
actualEmitter.onError(t);
if (actualObserver != null)
actualObserver.onError(t);
}
}
public void onComplete() {
if (!get()) {
if (actualEmitter != null)
actualEmitter.onComplete();
if (actualObserver != null)
actualObserver.onComplete();
}
}
#Override
public void dispose() {
if (compareAndSet(false, true)) {
parent.remove(this);
}
}
#Override
public boolean isDisposed() {
return get();
}
}
}

Android MQTT using Paho Client. Unable to receive messages

I have a AndroidService to listen MQTT messages. Below is the code. For some reason the service is able to connect and subscribe to channel, but is unable is read messages. messageArrived is never called.
public class FollowService extends Service implements MqttCallback{
private final IBinder localBinder = new FollowServiceBinder();
private final String TAG = "Service";
private MqttClient mqClient;
public class FollowServiceBinder extends Binder {
public FollowService getService() {
return FollowService.this;
}
}
public FollowService() {
}
#Override
public IBinder onBind(Intent intent) {
return localBinder;
}
#Override
public void onCreate() {
super.onCreate();
try {
mqClient = new MqttClient("tcp://192.168.1.46:1883", "sadfsfi", new MemoryPersistence());
mqClient.connect();
Log.i(TAG, "Connected to client");
}
catch(MqttException me){
Log.e(TAG, "MqttClient Exception Occured in on create!!!", me);
}
}
#Keep
public void beginFollowing(){
try {
mqClient.subscribe("test");
Log.i(TAG, "Subscribed test");
}
catch (MqttException me){
Log.e(TAG, "MqttClient Exception Occured in following!!!", me);
}
}
#Override
public void connectionLost(Throwable cause) {
Log.i(TAG, "ConnectionLost");
}
#Override
public void deliveryComplete(IMqttDeliveryToken token) {
Log.i(TAG, "Delivered");
}
#Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
Log.i(TAG, "Received update: " + topic + ":" + message.toString());
}
}
There is Eclipse Paho Android Service which is dedicated to Android you can use instead of the regular MqttClient, it may solves your problem (if your are sure the problem is not on your MQTT server side) & some other problems you may have in the future if you want to settle an Android MQTT service :
If you want to give it a try :
in build.gradle :
compile 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.0.3-SNAPSHOT'
compile ('org.eclipse.paho:org.eclipse.paho.android.service:1.0.3-SNAPSHOT'){
exclude module: 'support-v4'
}
compile 'com.android.support:support-v4:22.1.0'
In AndroidManifest.xml :
<uses-permission android:name="android.permission.INTERNET" />
and in your <application></application> :
<service android:name="org.eclipse.paho.android.service.MqttService" />
Here is an example MqttHandler.java :
public class MqttHandler {
protected final static String TAG = DeviceHandler.class.getSimpleName();
/**
* MQTT client
*/
private MqttAndroidClient mClient = null;
/**
* client ID used to authenticate
*/
protected String mClientId = "";
/**
* Android context
*/
private Context mContext = null;
/**
* callback for MQTT events
*/
private MqttCallback mClientCb = null;
/**
* callback for MQTT connection
*/
private IMqttActionListener mConnectionCb = null;
/**
* Sets whether the client and server should remember state across restarts and reconnects
*/
protected boolean mCleanSessionDefault = false;
/**
* Sets the connection timeout value (in seconds)
*/
protected int mTimeoutDefault = 30;
/**
* Sets the "keep alive" interval (in seconds)
*/
protected int mKeepAliveDefault = 60;
/**
* connection state
*/
private boolean connected = false;
/**
* list of message callbacks
*/
private List<IMessageCallback> mMessageCallbacksList = new ArrayList<>();
private final static String SERVER_URI = "192.168.1.46";
private final static int SERVER_PORT = 1883;
public MqttHandler(Context context) {
this.mContext = context;
this.mClientCb = new MqttCallback() {
#Override
public void connectionLost(Throwable cause) {
connected = false;
for (int i = 0; i < mMessageCallbacksList.size(); i++) {
mMessageCallbacksList.get(i).connectionLost(cause);
}
}
#Override
public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception {
for (int i = 0; i < mMessageCallbacksList.size(); i++) {
mMessageCallbacksList.get(i).messageArrived(topic, mqttMessage);
}
}
#Override
public void deliveryComplete(IMqttDeliveryToken token) {
for (int i = 0; i < mMessageCallbacksList.size(); i++) {
mMessageCallbacksList.get(i).deliveryComplete(token);
}
}
};
}
public boolean isConnected() {
if (mClient == null)
return false;
else
return connected;
}
public void connect() {
try {
if (!isConnected()) {
MqttConnectOptions options = new MqttConnectOptions();
String serverURI = "";
options.setCleanSession(mCleanSessionDefault);
options.setConnectionTimeout(mTimeoutDefault);
options.setKeepAliveInterval(mKeepAliveDefault);
mClient = new MqttAndroidClient(mContext, "tcp://" + SERVER_URI + ":" + SERVER_PORT, mClientId);
mClient.setCallback(mClientCb);
mConnectionCb = new IMqttActionListener() {
#Override
public void onSuccess(IMqttToken iMqttToken) {
connected = true;
for (int i = 0; i < mMessageCallbacksList.size(); i++) {
mMessageCallbacksList.get(i).onConnectionSuccess(iMqttToken);
}
}
#Override
public void onFailure(IMqttToken iMqttToken, Throwable throwable) {
connected = false;
for (int i = 0; i < mMessageCallbacksList.size(); i++) {
mMessageCallbacksList.get(i).onConnectionFailure(iMqttToken, throwable);
}
}
};
try {
mClient.connect(options, mContext, mConnectionCb);
} catch (MqttException e) {
e.printStackTrace();
}
} else {
Log.v(TAG, "cant connect - already connected");
}
} catch (IllegalArgumentException e) {
Log.v(TAG, "parameters error. cant connect");
}
}
public void disconnect() {
if (isConnected()) {
try {
mClient.disconnect(mContext, mConnectionCb);
} catch (MqttException e) {
e.printStackTrace();
}
} else {
Log.v(TAG, "cant disconnect - already disconnected");
}
}
/**
* Publish a message to MQTT server
*
* #param topic message topic
* #param message message body
* #param isRetained define if message should be retained on MQTT server
* #param listener completion listener (null allowed)
* #return
*/
public IMqttDeliveryToken publishMessage(String topic, String message, boolean isRetained, IMqttActionListener listener) {
if (isConnected()) {
MqttMessage mqttMessage = new MqttMessage(message.getBytes());
mqttMessage.setRetained(isRetained);
mqttMessage.setQos(0);
try {
return mClient.publish(topic, mqttMessage, mContext, listener);
} catch (MqttPersistenceException e) {
e.printStackTrace();
} catch (MqttException e) {
e.printStackTrace();
}
} else {
Log.e(TAG, "cant publish message. Not connected");
}
return null;
}
/**
* Subscribe to topic
*
* #param topic topic to subscribe
* #param listener completion listener (null allowed)
* #return
*/
public void subscribe(String topic, IMqttActionListener listener) {
if (isConnected()) {
try {
mClient.subscribe(topic, 0, mContext, listener);
} catch (MqttException e) {
e.printStackTrace();
}
} else {
Log.e(TAG, "cant publish message. Not connected");
}
}
/**
* Unsubscribe a topic
*
* #param topic topic to unsubscribe
* #param listener completion listener (null allowed)
*/
public void unsubscribe(String topic, IMqttActionListener listener) {
if (isConnected()) {
try {
mClient.unsubscribe(topic, mContext, listener);
} catch (MqttException e) {
e.printStackTrace();
}
} else {
Log.e(TAG, "cant publish message. Not connected");
}
}
public void addCallback(IMessageCallback callback) {
mMessageCallbacksList.add(callback);
}
}
With this listener IMessageCallback.java :
public interface IMessageCallback {
/**
* This method is called when the connection to the server is lost.
*
* #param cause the reason behind the loss of connection.
*/
void connectionLost(Throwable cause);
/**
* This method is called when a message arrives from the server.
*
* #param topic name of the topic on the message was published to
* #param mqttMessage the actual message
* #throws Exception
*/
void messageArrived(String topic, MqttMessage mqttMessage) throws Exception;
/**
* Called when delivery for a message has been completed, and all acknowledgments have been received.
*
* #param messageToken he delivery token associated with the message.
*/
void deliveryComplete(IMqttDeliveryToken messageToken);
/**
* Called when connection is established
*
* #param iMqttToken token for this connection
*/
void onConnectionSuccess(IMqttToken iMqttToken);
/**
* Called when connection has failed
*
* #param iMqttToken token when failure occured
* #param throwable exception
*/
void onConnectionFailure(IMqttToken iMqttToken, Throwable throwable);
/**
* Called when disconnection is successfull
*
* #param iMqttToken token for this connection
*/
void onDisconnectionSuccess(IMqttToken iMqttToken);
/**
* Called when disconnection failed
*
* #param iMqttToken token when failure occured
* #param throwable exception
*/
void onDisconnectionFailure(IMqttToken iMqttToken, Throwable throwable);
}
You can call it like that :
final MqttHandler mqttHandler = new MqttHandler(mContext);
mqttHandler.addCallback(new IMessageCallback() {
#Override
public void connectionLost(Throwable cause) {
Log.v(TAG, "connectionLost");
}
#Override
public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception {
Log.v(TAG, "messageArrived : " + topic + " : " + new String(mqttMessage.getPayload()));
}
#Override
public void deliveryComplete(IMqttDeliveryToken messageToken) {
try {
Log.v(TAG, "deliveryComplete : " + new String(messageToken.getMessage().getPayload()));
} catch (MqttException e) {
e.printStackTrace();
}
}
#Override
public void onConnectionSuccess(IMqttToken iMqttToken) {
Log.v(TAG, "connection success");
mqttHandler.subscribe("test", new IMqttActionListener() {
#Override
public void onSuccess(IMqttToken asyncActionToken) {
Log.v(TAG, "subscribe success");
}
#Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
Log.e(TAG, "subscribe failure");
}
});
}
#Override
public void onConnectionFailure(IMqttToken iMqttToken, Throwable throwable) {
Log.v(TAG, "connection failure");
}
#Override
public void onDisconnectionSuccess(IMqttToken iMqttToken) {
Log.v(TAG, "disconnection success");
}
#Override
public void onDisconnectionFailure(IMqttToken iMqttToken, Throwable throwable) {
Log.v(TAG, "disconnection failure");
}
});
mqttHandler.connect();
You can find a complete working usecase with Paho Mqtt Android client here

How to send Progress from library on Android?

I have a project: myApp
these files...
- myFragment.java.
- myDialogFragment.java.
- myAsyncTask.java
I have a project: myLibrary
This project "is Library" of "myApp"
I have...
- myMethodsToUpload.java
One of these methods, have a While bucle for write the file on php server.
Everything works like magic! :)
and the reason for the file structure is to make the library reusable.
but...
How can I send the increments of a value inside of this While bucle, to myAsyncTask.java?
Considering that...
what I want to do... is to make "myMethodsToUpload.java", reusable.
Some code...
myFragment.java
myDialogFragment df = new myDialogFragment();
df.setMyThings(new myAsynctask(), myParameters);
df.setTargetFragment(this, 0);
df.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
.
myDialogFragment.java
public class myDialogFragment extends DialogFragment {
myAsyncTask async;
public void setMyThings(myAsynctask inAsynctask, String[] inArray){
async = inAsynctask;
async.sendFragment(this);
parameters = inArray;
}
//...
//Only called from "myAsyncTask.java"
public void updateFromAsyncTask(Integer porcent){
progressbar.setProgress(porcent);
}
//...
}
.
myAsyncTask.java
public class myAsynctask extends AsyncTask<String, Integer, String> {
void sendFragment(myDialogFrament inFragment){
myDialogFrament = inFragment;
}
//...
#Override
protected String doInBackground(String... inArray) {
String urlPHP = inArray[0];
String pathImg = inArray[1];
String paramValue = inArray[2];
String msj = "";
try {
methodsToUpload up = new methodsToUpload(urlPHP);
up.connectNow();
up.insertFile(pathImg);
up.insertParams("pName", paramValue);
up.insertFinish();
msj = up.coonectClose();
} catch (Exception e) {
e.printStackTrace();
}
return msj;
}
//Called from "myMethods.java"
public void updateFromAsyncTask(int porcent){
publishProgress(porcent);
}
#Override
protected void onProgressUpdate(Integer... inPorcent) {
if(myDialogFragment == null){
return;
}
myDialogFragment.updateFromAsyncTask(inPorcent[0]);
}
}
.
myMethodsToUpload.java
public class myMethodsToUpload {
//...
public myMethodsToUpload(String url_in){
this.url = url_in;
}
public void insertFile(String path) throws Exception {
//...
//...
while (bytesRead > 0) {
salidaStream.write(buffer, 0, bufferSize);
sendedPorcent += bytesRead;
completedPorcent = (int) (sendedPorcent * 100 / fileSize);
//This line doesn't work...
//because myAsyncTask.java, is in another project.
myAsyncTask.updateFromAsyncTask(completedPorcent);
bytesAvailable = archivoStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
bytesRead = archivoStream.read(buffer, 0, bufferSize);
}
//...
//...
}
}
.
I've already tried...
"MyLibrary" -> propeties -> java build path -> projects -> add -> Project(myApp)
but...
throws me errors:
W/System.err(32469): at java.util.concurrent.FutureTask.run(FutureTask.java:237)...
ThreadPoolExecutor.runworker...
etc.
And, in the status bar of eclipse appears every moment "Building Workspace (X%)"
I'm a newbie, but I think the error happens because "MyLibrary" is Library of "MyApp", and I'm trying use "java build path".
So... how can I resolve this?, I'm lost!!!
sorry by my english... thanks in advance! :)
Here is a simple exemple :
Your AsyncTask class :
private CallBack mCallback;
public static interface CallBack {
public void updateValue(int value);
}
public void setCallBack(CallBack callBack){
this.mCallBack = callBack;
}
#Override
protected void onProgressUpdate(Integer... inPorcent) {
mCallback.updateValue(inPorcent[0].intValue());
}
Your fragment class :
public class Fragment extends Fragment implements Callback {
private AsyncTask yourAsyncTask;
...
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
yourAsyncTask = new AsyncTask();
yourAsyncTask.setCallBack(this);
yourAsyncTask.excecute();
}
#Override
public void updateValue(int value){
Log.e(TAG,"Value : " + value);
}
}
EDIT 1 :
public class AdsHttpRequest {
private static final String TAG = AdsHttpRequest.class.getSimpleName(); // log
private GetHttpTask mGetAsyncTask;
private static AdsHttpRequest mInstance;
private OnGetRequestListener mCallBack;
private static final String SUCCESS = "success";
private static final String SUCCES = "succes";
private static final String FAILED = "fail";
/**
* #return a singleton instance of {#link AdsHttpRequest}
*/
public static AdsHttpRequest getInstance() {
if (mInstance == null) {
synchronized (AdsHttpRequest.class) {
if (mInstance == null) {
mInstance = new AdsHttpRequest();
}
}
}
return mInstance;
}
/**
* Initialize the {#link AsyncTask}, set the callback, execute the task
*
* #param url
* url for the request
* #param callback
* {#link OnGetRequestListener} for feed back
*/
public void post(String url, OnGetRequestListener callback) {
mCallBack = callback;
if (mGetAsyncTask == null) {
mGetAsyncTask = new GetHttpTask();
} else {
cancelGetTask();
mGetAsyncTask = new GetHttpTask();
}
mGetAsyncTask.execute(url);
}
/**
* cancel the {#link AsyncTask} if it's still alive <br>
* <b>see </b> {#link Status}
*/
public void cancelGetTask() {
if (mGetAsyncTask != null && mGetAsyncTask.getStatus().equals(Status.RUNNING)) {
mGetAsyncTask.cancel(true);
}
mGetAsyncTask = null;
}
private AdsHttpRequest() {
super();
}
/**
* Actually construct and launch the HTTP request
*
* #param url
* url of the request
* #return response of the server
*/
private String getResponseFromUrl(String url) {
String xml = null;
try {
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
xml = EntityUtils.toString(httpEntity);
} catch (Exception e) {
Log.e(TAG, "", e);
}
return xml;
}
/**
* Manage the http request in background
*
* #param String
* url for the request
*/
private class GetHttpTask extends AsyncTask<String, String, String> {
#Override
protected String doInBackground(String... params) {
if (params[0] != null) {
return getResponseFromUrl(params[0]); // return the response of the server
}
return null;
}
#Override
protected void onPostExecute(String result) {
if (result != null) {
if (result.contains(SUCCES) || result.contains(SUCCESS)) {
mCallBack.onGetRequestResult(SUCCESS);
} else {
mCallBack.onGetRequestResult(FAILED);
}
}
}
}
}
The way that I'm doing this consume more memory, time, threads? (I'm guessing)

Servlet WebSocket Acessed by Android

I'm trying learn how to use the websocket and make a simple servlet for being connected with Android but I don't get it.
The index.jsp :
var ws = new WebSocket("ws://" + document.location.host + "/myws/ServletWS");
ws.onopen = function() { };
ws.onclose = function() { };
ws.onerror = function() { log("ERROR"); };
ws.onmessage = function(data) { var message = data.data; };
function sendMessage(msg) { ws.send(msg); }
How or where I receive the data from client?
Now on the servlet:
#Override protected StreamInbound createWebSocketInbound(String subProtocol, HttpServletRequest request) {
return new ConnectionWS();
}
class ConnectionWS extends MessageInbound {
private WsOutbound outbound;
#Override protected void onOpen(WsOutbound outbound) {
this.outbound = outbound;
}
#Override protected void onTextMessage(CharBuffer msg) throws IOException {
String message = msg.toString();
ServletWS.processData(message);
}
public void sendMessage(String message) {
CharBuffer cb = CharBuffer.wrap(message);
try {
outbound.writeTextMessage(cb);
} catch (IOException e) {}
}
}
public void processData(String message){
here I have to call the sendMessage with the answer to the client
}
I have saw a lot of examples on web but all of then about chat.
Thanks a lot for any help.
I understand that, you have a basic knowledge about tomcat configuration as well as java Servlet programming. As WekSocket is newly introduced in Tomcat, you may need to use latest tomcat version to implement WebSocket over it. I have used Apache Tomcat 7.0.42 for it.
So here we go. First, create a Servlet which will just create a new WebSocket for the request. You may need to modify it, if you want to go by session rather than request. Here is sample code.
import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WebSocketServlet;
public class WsChatServlet extends WebSocketServlet {
private static final long serialVersionUID = 1456546233L;
#Override
protected StreamInbound createWebSocketInbound(String protocol,
HttpServletRequest request) {
return new IncomingMessageHandler();
}
}
Now, create a Message Handler class which will handle each WebSocket stream independently. and that's it !
public class IncomingMessageHandler extends MessageInbound {
private WsOutbound myoutbound;
public IncomingMessageHandler() {
}
#Override
public void onOpen(WsOutbound outbound) {
logger.info("Open Client.");
this.myoutbound = outbound;
}
#Override
public void onClose(int status) {
logger.info("Close Client.");
}
/**
* Called when received plain Text Message
*/
#Override
public void onTextMessage(CharBuffer cb) throws IOException {
}
/**
* We can use this method to pass image binary data, eventually !
*/
#Override
public void onBinaryMessage(ByteBuffer bb) throws IOException {
}
public synchronized void sendTextMessage(String message) {
try {
CharBuffer buffer = CharBuffer.wrap(message);
this.getMyoutbound().writeTextMessage(buffer);
this.getMyoutbound().flush();
} catch (IOException e) {
}
}
/**
* Set websocket connection timeout in milliseconds,
* -1 means never
*/
#Override
public int getReadTimeout() {
return -1;
}
public WsOutbound getMyoutbound() {
return myoutbound;
}
public void setMyoutbound(WsOutbound myoutbound) {
this.myoutbound = myoutbound;
}
}
If not misunderstood and you want to use web sockets on Android then recommended API for you is jWebSocket.
Get it here, hopefully it already provides you APIs for a lot of the work that you need to do or even more.
http://jwebsocket.org/

Categories

Resources