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;
}
}
Related
I wanted to create a wrapper for api calls in retrofit so I can display ProgressDialog at common place & handle common response.
I achieved this by creating wrapper like this
public static <T> Observable<T> callApiWrapper(final Context context,
final boolean shouldShowProgress,
final String message,
final Observable<T> source) {
final ProgressDialog progressDialog = new ProgressDialog(context);
if (shouldShowProgress) {
if (!TextUtils.isEmpty(message))
progressDialog.setMessage(message);
else
progressDialog.setMessage(context.getString(R.string.please_wait));
}
return source.lift(new Observable.Operator<T, T>() {
#Override
public Subscriber<? super T> call(final Subscriber<? super T> child) {
return new Subscriber<T>() {
#Override
public void onStart() {
super.onStart();
if (shouldShowProgress) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
progressDialog.show();
}
});
}
child.onStart();
}
#Override
public void onCompleted() {
if (shouldShowProgress && progressDialog.isShowing())
progressDialog.dismiss();
child.onCompleted();
}
#Override
public void onError(Throwable e) {
if (shouldShowProgress && progressDialog.isShowing())
progressDialog.dismiss();
child.onError(e);
}
#Override
public void onNext(T t) {
/*
Handle Invalid API response
*/
if (((BaseResponse) t).getStatus() == RestParams.Codes.INVALID_API_KEY) {
mCommonDataModel.setApiKey("");
getCommonApiService().getApiKey()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<ResponseBody>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(ResponseBody responseBody) {
try {
String response = responseBody.string();
JSONObject jsonObject = new JSONObject(response);
String key = jsonObject.optString("KEY");
if (!TextUtils.isEmpty(key))
mCommonDataModel.setApiKey(key);
callApiWrapper(context, shouldShowProgress,
message, source)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
} catch (Exception e) {
e.printStackTrace();
}
}
});
} else {
if (shouldShowProgress && progressDialog.isShowing())
progressDialog.dismiss();
child.onNext(t);
}
}
};
}
});
}
In the above code, I check that if I get specific status code like Invalid API KEY, then I am calling an API to get the new API key instead of giving the status directly to original subscriber.
Once I get the new API key successfully, I call the wrapper recursively & try to give the response to original subscriber. But the problem is Original Subscriber is not getting onNext callback
What am I missing here? Is there any other way of achieving what I am trying to do?
You need to add some retry logic in case you get an invalid key failure so something like
source.flatMap(
t ->
{
if (((BaseResponse) t).getStatus() == RestParams.Codes.INVALID_API_KEY) {
return Observable.error(new InvalidKeyException("The key is not valid"));
}
else {
return Observable.just(t);
}
}
)
.retryWhen(
errors ->
errors.flatMap(error -> {
if (error instanceof InvalidKeyException()) {
return getCommonApiService().getApiKey()
.flatMap(
responseBody -> {
String response = responseBody.string();
JSONObject jsonObject = new JSONObject(response);
String key = jsonObject.optString("KEY");
if (TextUtils.isEmpty(key))
return Observable.error();
else {
return Observable.just(key);
}})
.doOnNext( key -> mCommonDataModel.setApiKey(key));
}
// For anything else, don't retry
return Observable.error(error);
}))
.subscribe(/* do what you need to do with the results*/)
In order to add the side effects i.e. enable progress bar when you start the subscription and dismiss it when you've finished something like
modifiedSource.doOnSubscribe(/* progress bar show logic */)
.doOnTerminate(/* progress bar dismiss logic */)
Finally I managed to create a wrapper which handles for me the common Progressbar & retry logic in case of Invalid Key API Response. This kind of wrapper might be useful if in many cases. Thanks to #JohnWowUs for his answer which helped me to understand things & implement this wrapper.
Here's the working code
private static final int MAX_RETRIES = 2;
private static int sCurrentRetryAttempt = 0;
/**
* Common Wrapper for calling API.
*
* #param context context for showing progress dialog
* #param shouldShowProgress boolean which indicated if progress dialog should be shown or not
* #param message message to be shown in progress dialog. if null passed, then "Please wait..." will be shown
* #param source original observable
* #return observable to which observer can subscribe
*/
public static <T> Observable<T> callApiWrapper(final Context context,
final boolean shouldShowProgress,
final String message,
final Observable<T> source) {
// Progress Dialog
final ProgressDialog progressDialog = setupProgressDialog(context, shouldShowProgress, message);
if (progressDialog != null) progressDialog.show();
return source
.flatMap(new Func1<T, Observable<T>>() {
#Override
public Observable<T> call(T t) {
/*
* Check if the response contains invalid key status code.
*/
if (t instanceof BaseResponse) {
if (((BaseResponse) t).getStatus() == RestParams.Codes.INVALID_API_KEY) {
return Observable.error(new InvalidKeyException("Invalid key"));
}
}
/*
* We are here, that means, there wasn't invalid key status code.
* So we wouldn't like to handle it so just return to original subscriber
*/
if (progressDialog != null && progressDialog.isShowing())
progressDialog.dismiss();
return Observable.just(t);
}
}).retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
#Override
public Observable<?> call(Observable<? extends Throwable> observable) {
return observable.flatMap(new Func1<Throwable, Observable<?>>() {
#Override
public Observable<?> call(final Throwable throwable) {
if (throwable instanceof InvalidKeyException) {
/*
* Check for retry limit. if we already have retried enough, then
* we should tell the original subscriber about the error as it
* doesn't seems recoverable.
*/
if (sCurrentRetryAttempt >= MAX_RETRIES) {
if (progressDialog != null && progressDialog.isShowing())
progressDialog.dismiss();
//Resetting the attempts to 0
sCurrentRetryAttempt = 0;
return Observable.error(throwable);
}
//Increase the attempt counter
sCurrentRetryAttempt += 1;
return getCommonApiService().getApiKey()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<ResponseBody, Observable<?>>() {
#Override
public Observable<?> call(ResponseBody responseBody) {
try {
/*
* Check if we succeed in our attempt to handle
* invalid key
*/
if (processApiKey(responseBody)) {
/*
* We succeeded in our attempts to handle
* invalid api key, so we will return the
* original subscriber what it wanted.
*/
return callApiWrapper(context,
shouldShowProgress, message, source);
} else
return Observable.just(throwable);
} catch (Exception e) {
/*
* We are here that means something went wrong,
* so we will retry silently.
*/
return Observable.just(throwable);
}
}
});
} else {
/*
* For any other error, we are not going to handle right now,
* so just return
*/
return Observable.error(throwable);
}
}
});
}
});
}
& using this is same as normal like:
RestClient.callApiWrapper(mContext, true, null,
RestClient.getAuthenticationApiService().socialLogIn(name, email, singInVia, mobile, "android", deviceToken))
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<BaseResponse<RegistrationResponse>>() {
//...
}
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;
}
}
}
Is there some way to interrupt AsyncTaskLoader's loadInBackground() thread when I call cancelLoad()? I believe that AsyncTask.cancel() does this but the task variables are private and cannot be accessed.
Use stopLoading() or abandon() or reset() for that
Article: https://plus.google.com/117981280628062796190/posts/8b9RmQvxudb
It's relatively simple to add that functionality, this is the Gist of it:
public abstract class InterruptibleAsyncTaskLoader<D> extends AsyncTaskLoader<D> {
private volatile Thread thread;
public InterruptibleAsyncTaskLoader(Context context) {
super(context);
}
public boolean isLoadInBackgroundRunning() {
return thread != null;
}
#Override
public void cancelLoadInBackground() {
Thread t = thread;
if (t != null) t.interrupt();
}
#Override
public final D loadInBackground() {
try {
thread = Thread.currentThread();
return doLoadInBackground();
} catch (InterruptedException e) {
OperationCanceledException oce = new OperationCanceledException(e.toString());
oce.initCause(e);
throw oce;
} finally {
Thread.interrupted();
thread = null;
}
}
public abstract D doLoadInBackground() throws InterruptedException;
}
I've checked the available methods, and it seems none really interrupts the thread that runs in the background.
Looking at the code, it seems that AsyncTask is being used under the hood. I've tried finding a "hole" that I could use to interrupt the asyncTask, but I couldn't find (unless you are fine with reflection).
I think you could make your own loader that will have this functionality.
I think you can modify the code to your needs. Maybe add "forceCancel", which will check the value of the asyncTask, and if it's not null, call "cancel(true)" on it. Here's the code I've looked at:
AsyncTaskLoader.java
package android.support.v4.content;
import android.content.Context;
import android.os.Handler;
import android.os.SystemClock;
import android.support.v4.util.TimeUtils;
import android.util.Log;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.concurrent.CountDownLatch;
/**
* Static library support version of the framework's {#link android.content.AsyncTaskLoader}.
* Used to write apps that run on platforms prior to Android 3.0. When running
* on Android 3.0 or above, this implementation is still used; it does not try
* to switch to the framework's implementation. See the framework SDK
* documentation for a class overview.
*/
public abstract class AsyncTaskLoader<D> extends Loader<D> {
static final String TAG = "AsyncTaskLoader";
static final boolean DEBUG = false;
final class LoadTask extends ModernAsyncTask<Void, Void, D> implements Runnable {
D result;
boolean waiting;
private CountDownLatch done = new CountDownLatch(1);
/* Runs on a worker thread */
#Override
protected D doInBackground(Void... params) {
if (DEBUG) Log.v(TAG, this + " >>> doInBackground");
result = AsyncTaskLoader.this.onLoadInBackground();
if (DEBUG) Log.v(TAG, this + " <<< doInBackground");
return result;
}
/* Runs on the UI thread */
#Override
protected void onPostExecute(D data) {
if (DEBUG) Log.v(TAG, this + " onPostExecute");
try {
AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
} finally {
done.countDown();
}
}
#Override
protected void onCancelled() {
if (DEBUG) Log.v(TAG, this + " onCancelled");
try {
AsyncTaskLoader.this.dispatchOnCancelled(this, result);
} finally {
done.countDown();
}
}
#Override
public void run() {
waiting = false;
AsyncTaskLoader.this.executePendingTask();
}
}
volatile LoadTask mTask;
volatile LoadTask mCancellingTask;
long mUpdateThrottle;
long mLastLoadCompleteTime = -10000;
Handler mHandler;
public AsyncTaskLoader(Context context) {
super(context);
}
/**
* Set amount to throttle updates by. This is the minimum time from
* when the last {#link #onLoadInBackground()} call has completed until
* a new load is scheduled.
*
* #param delayMS Amount of delay, in milliseconds.
*/
public void setUpdateThrottle(long delayMS) {
mUpdateThrottle = delayMS;
if (delayMS != 0) {
mHandler = new Handler();
}
}
#Override
protected void onForceLoad() {
super.onForceLoad();
cancelLoad();
mTask = new LoadTask();
if (DEBUG) Log.v(TAG, "Preparing load: mTask=" + mTask);
executePendingTask();
}
/**
* Attempt to cancel the current load task. See {#link android.os.AsyncTask#cancel(boolean)}
* for more info. Must be called on the main thread of the process.
*
* <p>Cancelling is not an immediate operation, since the load is performed
* in a background thread. If there is currently a load in progress, this
* method requests that the load be cancelled, and notes this is the case;
* once the background thread has completed its work its remaining state
* will be cleared. If another load request comes in during this time,
* it will be held until the cancelled load is complete.
*
* #return Returns <tt>false</tt> if the task could not be cancelled,
* typically because it has already completed normally, or
* because {#link #startLoading()} hasn't been called; returns
* <tt>true</tt> otherwise.
*/
public boolean cancelLoad() {
if (DEBUG) Log.v(TAG, "cancelLoad: mTask=" + mTask);
if (mTask != null) {
if (mCancellingTask != null) {
// There was a pending task already waiting for a previous
// one being canceled; just drop it.
if (DEBUG) Log.v(TAG,
"cancelLoad: still waiting for cancelled task; dropping next");
if (mTask.waiting) {
mTask.waiting = false;
mHandler.removeCallbacks(mTask);
}
mTask = null;
return false;
} else if (mTask.waiting) {
// There is a task, but it is waiting for the time it should
// execute. We can just toss it.
if (DEBUG) Log.v(TAG, "cancelLoad: task is waiting, dropping it");
mTask.waiting = false;
mHandler.removeCallbacks(mTask);
mTask = null;
return false;
} else {
boolean cancelled = mTask.cancel(false);
if (DEBUG) Log.v(TAG, "cancelLoad: cancelled=" + cancelled);
if (cancelled) {
mCancellingTask = mTask;
}
mTask = null;
return cancelled;
}
}
return false;
}
/**
* Called if the task was canceled before it was completed. Gives the class a chance
* to properly dispose of the result.
*/
public void onCanceled(D data) {
}
void executePendingTask() {
if (mCancellingTask == null && mTask != null) {
if (mTask.waiting) {
mTask.waiting = false;
mHandler.removeCallbacks(mTask);
}
if (mUpdateThrottle > 0) {
long now = SystemClock.uptimeMillis();
if (now < (mLastLoadCompleteTime+mUpdateThrottle)) {
// Not yet time to do another load.
if (DEBUG) Log.v(TAG, "Waiting until "
+ (mLastLoadCompleteTime+mUpdateThrottle)
+ " to execute: " + mTask);
mTask.waiting = true;
mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle);
return;
}
}
if (DEBUG) Log.v(TAG, "Executing: " + mTask);
mTask.executeOnExecutor(ModernAsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
}
}
void dispatchOnCancelled(LoadTask task, D data) {
onCanceled(data);
if (mCancellingTask == task) {
if (DEBUG) Log.v(TAG, "Cancelled task is now canceled!");
rollbackContentChanged();
mLastLoadCompleteTime = SystemClock.uptimeMillis();
mCancellingTask = null;
executePendingTask();
}
}
void dispatchOnLoadComplete(LoadTask task, D data) {
if (mTask != task) {
if (DEBUG) Log.v(TAG, "Load complete of old task, trying to cancel");
dispatchOnCancelled(task, data);
} else {
if (isAbandoned()) {
// This cursor has been abandoned; just cancel the new data.
onCanceled(data);
} else {
commitContentChanged();
mLastLoadCompleteTime = SystemClock.uptimeMillis();
mTask = null;
if (DEBUG) Log.v(TAG, "Delivering result");
deliverResult(data);
}
}
}
/**
*/
public abstract D loadInBackground();
/**
* Called on a worker thread to perform the actual load. Implementations should not deliver the
* result directly, but should return them from this method, which will eventually end up
* calling {#link #deliverResult} on the UI thread. If implementations need to process
* the results on the UI thread they may override {#link #deliverResult} and do so
* there.
*
* #return Implementations must return the result of their load operation.
*/
protected D onLoadInBackground() {
return loadInBackground();
}
/**
* Locks the current thread until the loader completes the current load
* operation. Returns immediately if there is no load operation running.
* Should not be called from the UI thread: calling it from the UI
* thread would cause a deadlock.
* <p>
* Use for testing only. <b>Never</b> call this from a UI thread.
*
* #hide
*/
public void waitForLoader() {
LoadTask task = mTask;
if (task != null) {
try {
task.done.await();
} catch (InterruptedException e) {
// Ignore
}
}
}
#Override
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
super.dump(prefix, fd, writer, args);
if (mTask != null) {
writer.print(prefix); writer.print("mTask="); writer.print(mTask);
writer.print(" waiting="); writer.println(mTask.waiting);
}
if (mCancellingTask != null) {
writer.print(prefix); writer.print("mCancellingTask="); writer.print(mCancellingTask);
writer.print(" waiting="); writer.println(mCancellingTask.waiting);
}
if (mUpdateThrottle != 0) {
writer.print(prefix); writer.print("mUpdateThrottle=");
TimeUtils.formatDuration(mUpdateThrottle, writer);
writer.print(" mLastLoadCompleteTime=");
TimeUtils.formatDuration(mLastLoadCompleteTime,
SystemClock.uptimeMillis(), writer);
writer.println();
}
}
}
EDIT: after 3 years, I've decided to post my solution for this:
/**
* makes it a bit easier to use AsyncTaskLoader. based on https://github.com/alexjlockwood/AppListLoader
*/
abstract class AsyncTaskLoaderEx<T>(context: Context) : AsyncTaskLoader<T>(context) {
#JvmField
var hasResult = false
#Suppress("MemberVisibilityCanBePrivate")
#JvmField
var isCanceled = false
var result: T? = null
private set
private var _currentThread: Thread? = null
init {
onContentChanged()
}
override fun onStartLoading() {
if (takeContentChanged())
forceLoad()
}
override fun deliverResult(data: T?) {
result = data
hasResult = true
super.deliverResult(data)
}
override fun onLoadInBackground(): T? {
_currentThread = Thread.currentThread()
return super.onLoadInBackground()
}
open fun interrupt() {
isCanceled = true
_currentThread?.interrupt()
}
override fun onReset() {
super.onReset()
onStopLoading()
if (hasResult) {
onReleaseResources(result)
result = null
hasResult = false
}
}
protected open fun onReleaseResources(data: T?) {
//nothing to do.
}
companion object {
private val sCurrentUniqueId = AtomicInteger(0)
#JvmStatic
val newUniqueLoaderId: Int
get() = sCurrentUniqueId.getAndIncrement()
}
}
And usage sample:
class MainActivity : AppCompatActivity() {
companion object {
val TASK_LOADER_ID = AsyncTaskLoaderEx.newUniqueLoaderId
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val loaderManager = LoaderManager.getInstance(this)
loaderManager.initLoader(TASK_LOADER_ID, null, object : LoaderManager.LoaderCallbacks<Boolean?> {
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Boolean?> {
return ImageLoadingTask(this#MainActivity)
}
override fun onLoadFinished(loader: Loader<Boolean?>, result: Boolean?) {
Log.d("AppLog", "finished without being interrupted?:$result")
if (result == null)
return
//TODO use result
}
override fun onLoaderReset(loader: Loader<Boolean?>) {
}
})
val runnable = Runnable {
Log.d("AppLog", "trying to stop loader")
(loaderManager.getLoader<Loader<Boolean?>>(TASK_LOADER_ID) as AsyncTaskLoaderEx?)?.interrupt()
}
val handler = Handler()
handler.postDelayed(runnable, 2000L)
Log.d("AppLog", "will try to interrupt in 2 seconds")
lifecycle.addObserver(object : LifecycleObserver {
#Suppress("unused")
#OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
handler.removeCallbacks(runnable)
}
})
}
private class ImageLoadingTask(context: Context) : AsyncTaskLoaderEx<Boolean?>(context) {
override fun loadInBackground(): Boolean? {
try {
for (i in 0..10) {
Log.d("AppLog", "loadInBackground: $i")
Thread.sleep(1000L)
}
return true
} catch (e: InterruptedException) {
Log.d("AppLog", "INTERRUPTED!!!")
}
return false
}
}
}
My goal is to have an AsyncTask that
can execute multiple times (one task at a time of course)
its current task can be cancelled
can be used by any activity
can execute many different tasks
does not have any problem with screen rotation (or phonecalls etc)
To achieve that i have created the classes shown below. But my experience with (and understanding of) threads is very limited. And since i don't know of any way to debug multiple threads, there is no way (for me) of knowing if this is going to work or not. So what i'm really asking is: Is this code ok?
And since there is no code that it is currently using this, here's an example use for it:
Data2Get d2g = new Data2Get(this, Data2Get.OpCountNumbers);
d2g.setParam("up2Num", String.valueOf(800));
LongOpsRunner.getLongOpsRunner().runOp(d2g);
So, here we go. This is the interface that every activity that wants to execute a long task (operation - op) should implement:
public interface LongOpsActivity {
public void onTaskCompleted(OpResult result);
}
This is a class to enclose any result of any task:
public class OpResult {
public LongOpsActivity forActivity;
public int opType;
public Object result;
public OpResult(LongOpsActivity forActivity, int opType, Object result){
this.forActivity = forActivity;
this.opType = opType;
this.result = result;
}
}
And finally the big part, the singleton async task class:
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import android.os.AsyncTask;
public class LongOpsRunner extends AsyncTask<Void, OpResult, Void> {
public class Data2Get implements Cloneable {
// one id for each operation
public static final int OpCountNumbers = 1;
public static final int OpCountLetters = 2;
public LongOpsActivity forActivity;
public int opType;
private HashMap<String, String> params = new HashMap<String, String>();
public Data2Get(LongOpsActivity forActivity, int opType) {
this.forActivity = forActivity;
this.opType = opType;
}
public void setParam(String key, String value) {
params.put(key, value);
}
public String getParam(String key) {
return params.get(key);
}
public void clearParams() {
params.clear();
}
#Override
protected Object clone() throws CloneNotSupportedException {
// deep clone
Data2Get myClone = (Data2Get) super.clone();
myClone.clearParams();
for (Entry<String, String> entry : params.entrySet()) {
myClone.setParam(new String(entry.getKey()), new String(entry.getValue()));
}
return myClone;
}
}
private class IntermediateResult extends OpResult {
public IntermediateResult(LongOpsActivity forActivity, int opType, Object result) {
super(forActivity, opType, result);
}
}
// not really needed
private class FinalResult extends OpResult {
public FinalResult(LongOpsActivity forActivity, int opType, Object result) {
super(forActivity, opType, result);
}
}
private final ReentrantLock lock = new ReentrantLock();
private final Condition executeOp = lock.newCondition();
private volatile boolean finished = false;
private volatile boolean waiting = true;
private volatile boolean shouldCancel = false;
private volatile boolean activityHasBeenNotified = true;
private Data2Get startingOpParams = null;
private Data2Get currentOpParams = null;
private FinalResult currentOpResult;
protected Void doInBackground(Void... nothing) {
try {
lock.lockInterruptibly();
do {
waiting = true;
while (waiting) {
executeOp.await();
}
shouldCancel = false;
activityHasBeenNotified = false;
boolean opCancelled = false;
try {
currentOpParams = (Data2Get) startingOpParams.clone();
} catch (CloneNotSupportedException cns) {
// do nothing
}
switch (currentOpParams.opType) {
case Data2Get.OpCountNumbers:
int numberCounter = 0;
int numLoopCount = 0;
while ((!opCancelled) & (numLoopCount <= 5000000)) {
if (!shouldCancel) {
numberCounter = (numberCounter + 1)
% Integer.parseInt(currentOpParams.getParam("up2Num"));
if (numberCounter == 0) {
numLoopCount++;
publishProgress(new IntermediateResult(
currentOpParams.forActivity,
currentOpParams.opType,
"Numbers loop count:" + numLoopCount));
}
} else {
opCancelled = true;
activityHasBeenNotified = true;
}
if (!opCancelled) {
currentOpResult = new FinalResult(
currentOpParams.forActivity,
currentOpParams.opType,
"Numbers loop completed.");
publishProgress(currentOpResult);
}
}
break;
case Data2Get.OpCountLetters:
int letterLoopCount = 0;
char ch = 'a';
while (!opCancelled & (letterLoopCount <= 5000000)) {
if (!shouldCancel) {
ch++;
if (Character.toString(ch).equals(currentOpParams.getParam("up2Letter"))) {
ch = 'a';
letterLoopCount++;
publishProgress(new IntermediateResult(
currentOpParams.forActivity,
currentOpParams.opType,
"Letters loop count:" + letterLoopCount));
}
} else {
opCancelled = true;
activityHasBeenNotified = true;
}
if (!opCancelled) {
currentOpResult = new FinalResult(
currentOpParams.forActivity,
currentOpParams.opType,
"Letters loop completed.");
publishProgress(currentOpResult);
}
}
break;
default:
}
} while (!finished);
lock.unlock();
} catch (InterruptedException e) {
// do nothing
}
return null;
}
public void cancelCurrentOp() {
shouldCancel = true;
}
#Override
protected void onProgressUpdate(OpResult... res) {
OpResult result = res[0];
if (result instanceof IntermediateResult) {
// normal progress update
// use result.forActivity to show something in the activity
} else {
notifyActivityOpCompleted(result);
}
}
public boolean currentOpIsFinished() {
return waiting;
}
public void runOp(Data2Get d2g) {
// Call this to run an operation
// Should check first currentOpIsFinished() most of the times
startingOpParams = d2g;
waiting = false;
executeOp.signal();
}
public void terminateAsyncTask() {
// The task will only finish when we call this method
finished = true;
lock.unlock(); // won't this throw an exception?
}
protected void onCancelled() {
// Make sure we clean up if the task is killed
terminateAsyncTask();
}
// if phone is rotated, use setActivity(null) inside
// onRetainNonConfigurationInstance()
// and setActivity(this) inside the constructor
// and all that only if there is an operation still running
public void setActivity(LongOpsActivity activity) {
currentOpParams.forActivity = activity;
if (currentOpIsFinished() & (!activityHasBeenNotified)) {
notifyActivityOpCompleted(currentOpResult);
}
}
private void notifyActivityOpCompleted(OpResult result) {
if (currentOpParams.forActivity != null) {
currentOpParams.forActivity.onTaskCompleted(result);
activityHasBeenNotified = true;
}
}
private static LongOpsRunner ref;
private LongOpsRunner() {
this.execute();
}
public static synchronized LongOpsRunner getLongOpsRunner() {
if (ref == null)
ref = new LongOpsRunner();
return ref;
}
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}
I hope someone helps with making this work, as it would be very useful not only for me, but many other people out there. Thank you.
Try Loaders. I switched from simple AsyncTasks to AsyncTaskLoaders and they solve lots of problems. If you implement a Loader as a standalone class, it would meet all of your requirements, especially when it comes to rotation which is the biggest issue with old AsyncTask.
I'm having an issue with db4o on Android 3.0+ because it turns out that on the creation of the db4o database, it uses some of the network apis by default. (I stumbled upon this post: http://mavistechchannel.wordpress.com/2011/11/18/db4o-at-honeycomb-and-ice-cream-sandwich/ about it)
However, I've attempted to make the db creation requests async, but I think I'm running into an issue of calling the db before it's fully created as it locks the DB. (And I now get a locking error) Is there any way I can do this synchronous? or, at a minimum wait until it's been finished? Here's my db4o helper:
public class Db4oHelperAsync implements Constants{
private static final String USE_INTERNAL_MEMORY_FOR_DATABASE = "USE_INTERNAL_MEMORY_FOR_DATABASE";
private static ObjectContainer oc = null;
private Context context;
/**
* #param ctx
*/
public Db4oHelperAsync(Context ctx) {
context = ctx;
}
/**
* Create, open and close the database
*/
public ObjectContainer db() {
if (oc == null || oc.ext().isClosed()) {
if (Utilities.getPreferences(context).getBoolean(USE_INTERNAL_MEMORY_FOR_DATABASE, true)) {
new GetDbFromInternalMemory().execute();
} else {
new GetDbFromSDCard().execute();
}
return oc;
} else {
return oc;
}
}
/**
* Configure the behavior of the database
*/
private EmbeddedConfiguration dbConfig() throws IOException {
EmbeddedConfiguration configuration = Db4oEmbedded.newConfiguration();
configuration.common().objectClass(PersistentObjectWithCascadeOnDelete.class).objectField("name").indexed(true);
configuration.common().objectClass(PersistentObjectWithCascadeOnDelete.class).cascadeOnUpdate(true);
configuration.common().objectClass(PersistentObjectWithCascadeOnDelete.class).cascadeOnActivate(true);
configuration.common().objectClass(PersistentObjectWithCascadeOnDelete.class).cascadeOnDelete(true);
configuration.common().objectClass(PersistentObjectWithoutCascadeOnDelete.class).objectField("name").indexed(true);
configuration.common().objectClass(PersistentObjectWithoutCascadeOnDelete.class).cascadeOnUpdate(true);
configuration.common().objectClass(PersistentObjectWithoutCascadeOnDelete.class).cascadeOnActivate(true);
return configuration;
}
/**
* Returns the path for the database location
*/
private String db4oDBFullPathInternal(Context ctx) {
return ctx.getDir("data", 0) + "/" + "testapp.db4o";
}
private String db4oDBFullPathSdCard(Context ctx) {
File path = new File(Environment.getExternalStorageDirectory(), ".testapp");
if (!path.exists()) {
path.mkdir();
}
return path + "/" + "testapp.db4o";
}
/**
* Closes the database
*/
public void close() {
if (oc != null)
oc.close();
}
private class GetDbFromInternalMemory extends AsyncTask<Void, Void, ObjectContainer>{
#Override
protected ObjectContainer doInBackground(Void... params) {
try {
ObjectContainer obj = Db4oEmbedded.openFile(dbConfig(), db4oDBFullPathInternal(context));
CLog.v("USING INTERNAL MEMORY FOR DATABASE");
return obj;
} catch (Exception ie) {
ie.printStackTrace();
CLog.e(Db4oHelper.class.getName(), ie.toString());
return null;
}
}
#Override
protected void onPostExecute(ObjectContainer result)
{
oc = result;
}
}
private class GetDbFromSDCard extends AsyncTask<Void, Void, ObjectContainer>{
#Override
protected ObjectContainer doInBackground(Void... params) {
try {
ObjectContainer obj = Db4oEmbedded.openFile(dbConfig(), db4oDBFullPathSdCard(context));
CLog.v("USING SDCARD FOR DATABASE");
SharedPreferences.Editor edit = Utilities.getPreferencesEditor(context);
edit.putBoolean(USE_INTERNAL_MEMORY_FOR_DATABASE, true);
edit.commit();
return obj;
} catch (Exception ie) {
ie.printStackTrace();
CLog.e(Db4oHelper.class.getName(), ie.toString());
return null;
}
}
#Override
protected void onPostExecute(ObjectContainer result)
{
oc = result;
}
}
}
Update: This db4o bug has been fixed. If you get the newest 8.1 bits the error should not occur and the workaround is obsolute:
You get a file-locked exception when trying to get the database? Right.
Well the issue is that you are not waiting for the async task to finish and just start a new one in case the Db4oHelperAsync.oc is null. You basically have to wait until the initialization has finished and only then use the Db4oHelperAsync.oc variable. So your in Java synchronization land.
For example you can do this: Synchronize the Db4oHelperAsync.oc access. When requesting the database wait until the variable is set. Now unfortunately I don't know the exact behavior of the async task. My guess is that it will run the .onPostExecute() method back on the main activity. That also means that you cannot just wait for it, because it would mean that you block the Activity-Thread and .onPostExecute() will never be executed.
Here's my draft of what I would try to do. I never executed nor compiled it. And it probably has synchronization issues. For example when the initialization fail it will just hang your applicaition on the .db() call, because it waits forever. So be very careful and try to improve it:
public class Db4oHelperAsync implements Constants{
private static final String USE_INTERNAL_MEMORY_FOR_DATABASE = "USE_INTERNAL_MEMORY_FOR_DATABASE";
private static ObjectContainer oc = null;
private static final Object lock = new Object();
private Context context;
/**
* #param ctx
*/
public Db4oHelperAsync(Context ctx) {
context = ctx;
}
/**
* Create, open and close the database
*/
public ObjectContainer db() {
synchronized(lock){
if (oc == null || oc.ext().isClosed()) {
if (Utilities.getPreferences(context).getBoolean(USE_INTERNAL_MEMORY_FOR_DATABASE, true)) {
new GetDbFromInternalMemory().start();
} else {
new GetDbFromSDCard().start();
}
while(oc==null){
this.wait()
}
return oc;
} else {
return oc;
}
}
}
/**
* Configure the behavior of the database
*/
private EmbeddedConfiguration dbConfig() throws IOException {
EmbeddedConfiguration configuration = Db4oEmbedded.newConfiguration();
configuration.common().objectClass(PersistentObjectWithCascadeOnDelete.class).objectField("name").indexed(true);
configuration.common().objectClass(PersistentObjectWithCascadeOnDelete.class).cascadeOnUpdate(true);
configuration.common().objectClass(PersistentObjectWithCascadeOnDelete.class).cascadeOnActivate(true);
configuration.common().objectClass(PersistentObjectWithCascadeOnDelete.class).cascadeOnDelete(true);
configuration.common().objectClass(PersistentObjectWithoutCascadeOnDelete.class).objectField("name").indexed(true);
configuration.common().objectClass(PersistentObjectWithoutCascadeOnDelete.class).cascadeOnUpdate(true);
configuration.common().objectClass(PersistentObjectWithoutCascadeOnDelete.class).cascadeOnActivate(true);
return configuration;
}
/**
* Returns the path for the database location
*/
private String db4oDBFullPathInternal(Context ctx) {
return ctx.getDir("data", 0) + "/" + "testapp.db4o";
}
private String db4oDBFullPathSdCard(Context ctx) {
File path = new File(Environment.getExternalStorageDirectory(), ".testapp");
if (!path.exists()) {
path.mkdir();
}
return path + "/" + "testapp.db4o";
}
/**
* Closes the database
*/
public void close() {
synchronized(lock){
if (oc != null)
oc.close();
}
}
private class GetDbFromInternalMemory extends Thread{
#Override
protected void run() {
try {
ObjectContainer obj = Db4oEmbedded.openFile(dbConfig(), db4oDBFullPathInternal(context));
CLog.v("USING INTERNAL MEMORY FOR DATABASE");
synchronized(Db4oHelperAsync.lock){
Db4oHelperAsync.oc = obj;
Db4oHelperAsync.lock.notifyAll()
}
} catch (Exception ie) {
ie.printStackTrace();
CLog.e(Db4oHelper.class.getName(), ie.toString());
}
}
}
private class GetDbFromSDCard extends Thread{
#Override
protected void run() {
try {
ObjectContainer obj = Db4oEmbedded.openFile(dbConfig(), db4oDBFullPathSdCard(context));
CLog.v("USING SDCARD FOR DATABASE");
SharedPreferences.Editor edit = Utilities.getPreferencesEditor(context);
edit.putBoolean(USE_INTERNAL_MEMORY_FOR_DATABASE, true);
edit.commit();
synchronized(Db4oHelperAsync.lock){
Db4oHelperAsync.oc = obj;
Db4oHelperAsync.lock.notifyAll()
}
} catch (Exception ie) {
ie.printStackTrace();
CLog.e(Db4oHelper.class.getName(), ie.toString());
}
}
}
}
P.S. Added this problem as a bug to db4o: http://tracker.db4o.com/browse/COR-2269
Thanks for posting this issue, this is a serious fun-spoiler on Android.
When a new db4o database file is created, db4o generates it's unique internal signature by calling java.net.InetAddress.getLocalHost().getHostName(). Exceptions are not caught in this call. We will find a workaround for Android and post back here and to our forums when this is fixed.
Update Feb 9 2012:
The issue has been fixed and new builds are online.
http://community.versant.com/Blogs/db4o/tabid/197/entryid/1057/Default.aspx