I am working with the AndroidX Work Dependency to try and run some background service operations. I am currently running the most recent stable version, 2.2.0 as of posting this question.
The actual operation I am running in the background is a fairly heavy-CPU operation as it is using some compression code in one of my libraries (here) and can take anywhere from 3-30 minutes depending on the size and length of the video in question.
Here is the code I have that builds and runs the work request:
public static void startService(){
//Create the Work Request
String uniqueTag = "Tag_" + new Date().getTime() + "_ChainVids";
OneTimeWorkRequest.Builder builder = new OneTimeWorkRequest.Builder(CompleteVideoBackgroundService.class);
Constraints.Builder constraints = new Constraints.Builder();
constraints.setRequiredNetworkType(NetworkType.CONNECTED);
builder.setConstraints(constraints.build());
builder.setInitialDelay(1, TimeUnit.MILLISECONDS);
builder.addTag(uniqueTag);
Data inputData = new Data.Builder().putString("some_key", mySerializedJSONString).build();
builder.setInputData(inputData);
OneTimeWorkRequest compressRequest = builder.build();
//Set the Work Request to run
WorkManager.getInstance(MyApplication.getContext())
.beginWith(compressRequest)
.enqueue();
}
It then fires off this class which runs all of the background service operations:
public class MyServiceSampleForStackoverflow extends Worker {
private Context context;
private WorkerParameters params;
public MyServiceSampleForStackoverflow(#NonNull Context context, #NonNull WorkerParameters params){
super(context, params);
this.context = context;
this.params = params;
}
/**
* Trimming this code down considerably, but the gist is still here
*/
#NonNull
#Override
public Result doWork() {
try {
//Using using a hard-coded 50% for this SO sample
float percentToBringDownTo = 0.5F;
Uri videoUriToCompress = MyCustomCode.getVideoUriToCompress();
VideoConversionProgressListener listener = (progressPercentage, estimatedNumberOfMillisecondsLeft) -> {
float percentComplete = (100 * progressPercentage);
//Using this value to update the Notification Bar as well as printing in the logcat. Erroneous code removed
};
String newFilePath = MyCustomCode.generateNewFilePath();
//The line below this is the one that takes a while as it is running a long operation
String compressedFilePath = SiliCompressor.with(MyApplication.getContext()).compressVideo(
listener, videoUriToCompress.getPath(), newFilePath, percentToBringDownTo);
//Do stuff here with the compressedFilePath as it is now done
return Result.success();
} catch (Exception e){
e.printStackTrace();
return Result.failure();
}
}
}
Once in a while, without any rhyme or reason to it, the worker randomly stops without me telling it to do so. When that happens, I am seeing this error:
Work [ id=254ae962-114e-4088-86ec-93a3484f948d, tags={ Tag_1571246190190_ChainVids, myapp.packagename.services.MyServiceSampleForStackoverflow } ] was cancelled
java.util.concurrent.CancellationException: Task was cancelled.
at androidx.work.impl.utils.futures.AbstractFuture.cancellationExceptionWithCause(AbstractFuture.java:1184)
at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:514)
at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475)
at androidx.work.impl.WorkerWrapper$2.run(WorkerWrapper.java:284)
at androidx.work.impl.utils.SerialExecutor$Task.run(SerialExecutor.java:91)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)
I am quite literally just staring at my phone with and not interacting with it at all when this randomly happens. I am not trying to run other apps, nor am I trying to hog the CPU for something else. I never see any stacktrace of my own and no immediate problem or cause is visible.
The question therefore is, what is happening here that is randomly stopping the Worker service without any provocation? Why is it randomly stopping operations?
Thanks all.
EDIT 1
I did test removing the network constraints requirement line thinking that may be causing the issue and I did see it occur even after that, so I don't believe that is the problem.
I am testing on a Google Pixel 3, API 28, Android 9, but any other device I have tested regardless of API level (minimum supported is 21) has shown the same issue.
Edit 2
I tried rewriting the class to work on the Asynchronous approach by having the class extend ListenableWorker instead of Worker and that did not resolve the issue.
you are most likely facing one of 2 issues.
First, assuming your background service runs more than 10 minutes can take anywhere from 3-30 minutes depending on the size and length of the video in question, you may be running into a hard time limit which is imposed by the WorkManager code.
In the docs here they say: The system instructed your app to stop your work for some reason. This can happen if you exceed the execution deadline of 10 minutes.
That seems the most likely, but the other issue could be related to Android background limitations outlined here in which it details changes related to Apps that target Android 8.0 or higher. As you mentioned in an edit, you are testing on a Google Pixel 3, API 28, Android 9 and that may be directly related.
As far as solutions go, the simplest, albeit a frustrating solution, would be to tell the user that they need to keep the app in the foreground. This would at least prevent that 10 minute gap.
Another option would be to utilize the new Bubble API that was introduced in API 29. The docs are here and the section of docs that might interest you is where it says, When a bubble is expanded, the content activity goes through the normal process lifecycle, resulting in the application becoming a foreground process. Making a miniaturized 'expanded' view and having that be expanded by users when the app closes my be a good alternative solution to bypassing that 10 minute timer.
We can now run longer than 10 mins, at least according to work manger version Support for long-running workers :
WorkManager 2.3.0-alpha02 adds first-class support for long running
workers. In such cases, WorkManager can provide a signal to the OS
that the process should be kept alive if possible while this work is
executing. These Workers can run longer than 10 minutes. Example
use-cases for this new feature include bulk uploads or downloads (that
cannot be chunked), crunching on an ML model locally, or a task that's
important to the user of the app.
An example is given in the link shared. Please check it out.
Related
I'm using Espresso to write some automated tests for an Android app that I've developed. All the tests are automated and are passing/failing according to what happens with the UI. I've ran the code through SonarQube to detect bad coding practices and it's informed me that Thread.Sleep() should not be used.
I'm mainly using Thread.sleep() in instances where I'm typing out a form and need to hide the keyboard to scroll down to tap the next form field etc. From my understanding, using something like awaitility is for big async functions like fetching data etc. but what should I use in my case where something is not being fetched but more so for just interacting with the UI?
Here is an example of a log in test that I have created that uses Thread.Sleep():
onView(withId(R.id.fieldEmail)).perform(typeText("shelley#gmail.com"));
Thread.sleep(SHORT_WAIT);
onView(withId(R.id.fieldPassword)).perform(click());
onView(withId(R.id.fieldPassword)).perform(typeText("password"));
Thread.sleep(SHORT_WAIT);
onView(isRoot()).perform(pressBack());
Thread.sleep(SHORT_WAIT);
onView(withId(R.id.signIn)).perform(click());
Thread.sleep(LONG_WAIT);
There are several options:
Repeated retries
You can use Awaitility to repeatedly retry an assertion/check, up to a specified time allowance:
app/build.gradle
dependencies {
// Note: Awaitility version 4 has dependency conflicts with JUnit's
// Hamcrest. See: https://github.com/awaitility/awaitility/issues/194
androidTestImplementation 'org.awaitility:awaitility:3.1.6'
}
// Set the retry time to 0.5 seconds, instead of the default 0.1 seconds
Awaitility.setDefaultPollInterval(500, TimeUnit.MILLISECONDS);
Kotlin:
Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAsserted {
onView(withId(R.id.fieldPassword)).perform(click())
}
Java 7:
Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAsserted(new ThrowingRunnable() {
#Override
public void run() throws Throwable {
onView(withId(R.id.fieldPassword)).perform(click());
}
});
This means that if the assertion fails the first time, it will retry for up to 2 seconds, until it's true or until a timeout happens (fail).
Repeated retries with an initial time delay
You can also set an initial time delay before making the first assertion:
Awaitility.await().pollDelay(1, TimeUnit.SECONDS).atMost(3, TimeUnit.SECONDS).untilAsserted {
onView(withId(R.id.fieldPassword)).perform(click())
}
Simple time delay
Alternatively, you can add simple time delays in between statements, just like Thread.sleep(), but in a more verbose way:
Kotlin:
Awaitility.await().pollDelay(2, TimeUnit.SECONDS).until { true }
Java 7:
Awaitility.await().pollDelay(2, TimeUnit.SECONDS).until(new Callable<Boolean>() {
#Override
public Boolean call() throws Exception {
return true;
}
});
Use Espresso or Barista functions to create the timed waits:
Espresso: Thread.sleep( )
Use Espresso Idling Resources
https://dev.to/keyopstech/robust-ui-tests-with-espresso-and-idling-resources-157f
How to use Espresso Idling Resource for network calls
https://tudip.com/blog-post/espresso-idling-resources/
https://medium.com/androiddevelopers/android-testing-with-espressos-idling-resources-and-testing-fidelity-8b8647ed57f4
https://www.repeato.app/the-problem-of-espresso-idling-resources/ (some disadvantages)
General note: Although using Thread.sleep() or some other time delay should normally be avoided in unit tests, there will be times where you need to use it, and there is no alternative. An example is using IntentsTestRule to click on a web link in your app to launch the external web browser. You don't know how long the browser will take to launch the web page, so you need to add a time delay.
More info about Awaitility:
https://medium.com/stepstone-tech/taming-the-ui-test-monster-26c017848ae0
https://www.jfokus.se/jfokus12/preso/jf12_StopSleepingStartAwaiting.pdf
https://github.com/awaitility/awaitility/wiki/Usage
I have been trying to fix this for weeks and have no clue what is causing the issue. In my project I am utilizing the Android Webview's evaluateJavascript() method like this:
this.runOnUiThread(new Runnable() {
#Override
public void run() {
webView.evaluateJavascript(command, new ValueCallback<String>() {
#Override
public void onReceiveValue(String value) {
//Parsing and taking action here
Log.d("Response", value);
}
});
}
});
A sample string of the javascript I am sending would be:
document.getElementById("message").value="Stuff worked!";
or
document.getElementById("some-awesome-button").click();
While 9 times out of 10 these calls return a value in the onReceiveValue() method, once in a while I just straight never receive a response at all.
This lack of a response has made chaining events a nightmare and I have no clue why this would be happening.
Some more data to head off any additional questions:
The minimum SDK for this project is 21 and I am targeting 28.
I am not utilizing any javascriptInterfaces at this point in time and don't intend to for this project for a few business-related reasons.
When I do get a response fro the webview in the onReceiveValue() method it is usually the value I had just set or 'null' if it was a click event. Regardless, the issue isn't that I am sometimes receiving nulls or other values, but the distinct lack of a response sometimes.
As shown in the code sample, I am definitely running this on the UI Thread as per the documentation.
I had the server dev add in some code to console log whenever I am hitting the button and when I get back a successful value in the onReceiveValue() method the console logs are working and responding but when I am in the situation where I do not get back a response, the web console logs never fire and it never detects an interaction with the button.
I have also tried adding in a timer with logic that listens for a response from the webview and if it does not receive it, attempts to make the same call again up to 10x. When the first call fails, none of the subsequent attempts work.
These are the settings for my webview:
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setDomStorageEnabled(true);
webView.getSettings().setAllowFileAccess(true);
It almost seems like the issue is with the Javascript Bridge but I cannot be certain.
The summary question I have is, what could cause the Android WebView's evaluateJavascript() call to not trigger a callback nor return a value in the onReceiveValue() method?
Happened to me because I was trying to call webView.evaluateJavascript(...) from a background thread.
I solved it by wrapping it using View#post() :
webView.post(() -> webView.evaluateJavascript(...));
I did eventually figure out what happened here.
The shorter version is, this can happen when you have a memory leak and an object that is utilizing a semi-destroyed webview will fire off commands, but the newly-initialized webview will not receive a response because it is going to a dead listener.
If you want to prevent this from occurring, just make sure that all objects that maintain an application Context are properly disposed of or correctly reset if the activity restarts, else you have all objects that use the same Application-level context, but are using new views and callbacks entirely.
I am working on a third-party library for Android and need to be able to tell if I am running as a privileged process with my desired permissions, or if I am living in an isolated process with restricted permissions as defined in the AndroidManifest.xml file:
<service android:name="mySandboxedService" android:permission="com.android.chrome.permission.CHILD_SERVICE" android:exported="false" android:process=":sandboxed_process0" android:isolatedProcess="true" />
The reason being that certain things I'm trying to do, such as get the number of running application processes (and various other things), will throw a RuntimeException if they are isolated. This code will run successfully if not run as an isolated process, but will throw RTE if the process is isolated:
ActivityManager aM = (ActivityManager) context.getSystemService(android.content.Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> l = null;
try {
l = aM.getRunningAppProcesses();
} catch (RuntimeException e) {
Log.w(LOGTAG, "Isolated process not allowed allowed to call getRunningAppProcesses, cannot get number of running apps.");
}
From my logcat:
java.lang.SecurityException: Isolated process not allowed to call getRunningAppProcesses
Does anyone know of a way I can check my current process to see if it is isolated or privileged? I've checked the Android Service doc here, and it does not provide much information.
My end goal is to initialize my app once from the main, privileged thread, and then ignore all of the startup calls from the various sandboxed processes that may get created. I don't want to run in those, but my hook is in Application.onCreate and gets called for every process, sandboxed or not.
I've considered the idea of adding one of these checks to my initialization and catching the RTE if it's thrown. But if there is a public API for it, I'd rather use that.
One can check the uid of the running process to see if they fall in the range of isolated process.
int AID_ISOLATED_START = 99000;
int AID_ISOLATED_END = 99999;
int uid = Process.myUid();
if (uid >= AID_ISOLATED_START && uid <= AID_ISOLATED_END) {
Log.i(TAG, "This is from an isolated process");
}
Process range info source: https://android.googlesource.com/platform/system/sepolicy/+/master/public/isolated_app.te
EDIT: The above has been found to be unreliable on Android 8.1 and below.
Another approach is to try accessing privileged APIs and see if an exception is thrown.
try {
ActivityManager activityManager = (ActivityManager) mContext.getSystemService(ACTIVITY_SERVICE);
activityManager.getRunningAppProcesses();
} catch (SecurityException e) {
Log.i(TAG, "This is from an isolated process");
}
As pointed out in an other answer, Android Pie/9 (API 28) introduced a new API for this. See https://developer.android.com/reference/android/os/Process#isIsolated().
This seems to be present in versions as low as Android 4.3. See https://cs.android.com/android/platform/superproject/+/android-4.3.1_r1:frameworks/base/core/java/android/os/Process.java;l=680
API 28 (Android Pie / 9) introduced new method for checking if current process is an isolated one: Process#isIsolated.
Note that this method was added to Process class in API 16, but was hidden till API 28. Therefore the following method can be used to check if the process is isolated or not (this is actually how Chrome app performs this check):
#SuppressLint("NewApi")
public boolean isIsolated() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
return false;
}
return android.os.Process.isIsolated();
}
Checking if the UID is in the {99000, 99999} range (as demonstrated in the accepted answer) will produce incorrect results in multi-user/profile environment for all users/profiles which are not the main one - since their UIDs will be prefixed with matching user id (example UID for user 10's isolated process will be 1099013).
I have Android app with GAE backend. I'm encountering java.net.SocketTimeoutException, probably due to fetch time limitations of GAE.
However, operations I'm doing there is writing pretty simple object into datastore and returning it to the user. Debug time, that eclipse generates makes it too long I guess...
What would be the way to increase timeout time in such usage:
Gameendpoint.Builder builder = new Gameendpoint.Builder(AndroidHttp.newCompatibleTransport(), new JacksonFactory(), null);
builder = CloudEndpointUtils.updateBuilder(builder);
Gameendpoint endpoint = builder.build();
try {
Game game = endpoint.createGame().execute();;
} catch (Exception e) {
e.printStackTrace();
}
Well, it was a silly mistake. The limit of such operation is 30 seconds, which should be enough. However, inside createGame() there was an infinite loop. I have a feeling that GAE framework recognizes such situation and causes SocketTimeoutException before 30 seconds actually passes.
Sockets on endpoints have a 2000 ms timeout. This is ample time if you are running short processes: a quick query(continuous queries handled differently), or a write operation. If you overload the process and try to do too much (My issue) then you will get this error. what you need to do it run a lot of different endpoint operations and not try to handle too much at one time. You can override the timeout with the HTTP transport if needed but it is not advised.
I am writing a Java applet that downloads images from a web server and displays them to the user. It works fine in Java 1.6.0_3 and later, but on older versions it will completely crash the process about once every 20 page views. There are no error messages in the Java console, because the process is completely frozen. I've waited for almost 15 minutes sometimes, but it never un-freezes.
I added a debug message after every line of code, and determined that the line that is causing the crash is this: InputStream data = urlConn.getInputStream().
urlConn is a URLConnection object that is pointed at the image I want to load. I've tried every combination of options that I can think of, but nothing helps. I haven't been able to find anything in the Java bug database or the release notes for 1.6.0_3.
Has anyone encountered this problem before? Any idea how to fix it?
To determine if it really is the whole JVM process that's frozen, or something else:
(1) get a java stack dump (sigquit/ctrl-break/jstack)
(2) have another background thread doing something you can observe; does it stop?
(3) check if another process (browser/etc) can contact server during freeze? (There's a chance the real problem is server connection depletion)
Is it randomly once-in-every-20-fetches (for example, 5% of the time, sometimes the first fetch in the JVM run), or always after about 20 fetches? If the latter, it sounds like something isn't being closed properly.
If on Linux you can use 'netstat -t' or 'lsof' (with certain options or grepped to show only some lines) to see open sockets; if after each fetch, one more is open, and the count never goes down, you're not closing things properly.
If so, calling close() on the stream you get back and/or disconnect() on the HttpUrlConnection after each try may help. (There may also be more severe limits on the number of connections an applet can leave open, so you're hitting this more quickly than you would in a standalone app.)
The fact that it 'works' in later Javas is also suggestive that some sort of automatic cleanup might be happening more effectively/regularly by finalization/GC. It's best to close things up cleanly yourself but you could also try forcing a GC/runFinalization in the earlier Javas showing the problem.
I'm unsure the cause of the problem you are facing, but I use the following code successfully for synchronously loading images from within applets (loads from either jar file or the server):
public Image loadImage(String imageName) {
// get the image
Image image = getImage(getCodeBase(), imageName);
// wait for it to fully load
MediaTracker tracker = new MediaTracker(this);
tracker.addImage(image, 0);
boolean interrupted = false;
try {
tracker.waitForID(0);
} catch (InterruptedException e) {
interrupted = true;
}
int status = tracker.statusID(thisImageTrackerID, false);
if (status != MediaTracker.COMPLETE) {
throw new RuntimeException("Failed to load " + imageName + ", interrupted:" + interrupted + ", status:" + status);
}
return image;
}