Our company develops several games for mobile platforms, including Android. We use OpenGL for all visual items, including UI (more technical details are below).
We have received some weird warnings from Google Play Console in Pre-launch report, like “Your app took 20764 ms to launch”. On the video provided with this report, the game took about a second to start.
After some investigation we found that Android Systrace cannot detect OpenGL draws made from another thread. So Pre-launch tests think (wrongly) that our game is super-slow.
Is there some method to notify the system that a frame is drawn? It’s seems that eglSwapBuffers() is not enough.
There’s a link to the same problem with Cocos2d: https://discuss.cocos2d-x.org/t/frozen-frames-warnings-by-google-play-pre-launch-report-for-3-17-cocos-demo-app/42894
Some details
When a new build is published to Google Play Console, some automated tests are performed on different devices. Results of these tests are available in Pre-launch report section of the Google Play Console.
Starting from beginning of April we receive strange performance warnings on some of devices (always the same ones). Two examples:
Startup time: Your app took 20764 ms to launch…
Frozen frames: 33.33% of the frames took longer than 700ms to render
Both problems sound dreadful--would have they be true. But when we examined videos of testing, we could not see any problems. All games started pretty fast and ran without visual stuttering.
Systrace report
This is the picture of systrace showing 5 seconds of our game being started (rectangles were drawn by me).
systrace
As you can see, the systrace have found only 4 frames rendered (the pink rect), which were drawn from the RenderThread. But by some reason Android cannot detect our GL draw calls which are performed in another thread (blue rects).
Pre-launch reports also displays only 3 to 4 frames, each 300-400 ms long.
Initialization code
Our game engine runs all game logic and render code in a separate thread. This is simplified initialization code.
The worker thread is created from our Activity’s onStart() overriden method.
public class MyActivity extends Activity
{
protected Thread worker = null;
private native void Run();
#Override
protected void onStart()
{
super.onStart();
if(worker == null)
{
worker = new Thread()
{
public void run()
{
Run();
}
};
worker.start();
}
}
}
The only thing the thread does is the Run() native function. This function may be resolved into something like this:
void MyActivity::Run()
{
initApp();
while(!destroyRequested())
{
// Process the game logic.
if (activated && window != NULL)
{
time->process();
input->process();
sound->process();
logic->process();
graphics->draw();
}
}
clearApp();
}
As you can see, the worker thread constantly spins the update-and-draw loop. Vsync protects the loop from overperforming. Heavy operations like resource loading are done asynchronously to avoid freezes.
From the user side this approach works just fine. Games are loading fast and go smoothly.
Related
Inside my overridden analyze() I need to add some kind of throttle before performing an IO operation. Without the throttle, this operation gets executed immediately at each call of analyze() and it actually completes quickly, but apparently the calls are too fast and after a while the camera preview freezes for eternity (the app is still running because Logcat keeps displaying new messages).
I'm currently investigating if it has something to do with my code, like forgetting to call imageProxy.close(). So far everything seems fine and I'm afraid the device that performs the IO operation is raising too many interrupts for the CPU to handle, or something along the lines.
I've tried the good old Thread.sleep() but obviously it blocks the main thread and freezes the UI; I've seen some examples with Handler#postDelayed() but I don't think it does what I want; I've tried wrapping the IO call in a coroutine with a delay() at the beginning but again I don't think it does what I want. Basically I'd like to call some form of sleep() on the Executor thread itself, from within the code executed by it.
after a while the camera preview freezes for eternity
I've seen this issue occur many times, and it's usually due to an image that the Analyzer doesn't close. Are you seeing the issue even when the image analysis use case isn't used?
I've tried the good old Thread.sleep() but obviously it blocks the main thread and freezes the UI
Why's that? This shouldn't be the case if you're adding the call to Thread.sleep() inside Analyzer.analyze(), since it'll block the thread of the Executor you provided when calling ImageAnalysis.setAnalyzer(), which shouldn't be tied to the main thread.
One option to perform analysis fewer times is to drop images inside the Analyzer, something like the following:
private static final int ANALYSIS_DELAY_MS = 1_000;
private static final int INVALID_TIME = -1;
private long lastAnalysisTime = INVALID_TIME;
public void analyze (ImageProxy image) {
final long now = SystemClock.uptimeMillis();
// Drop frame if an image has been analyzed less than ANALYSIS_DELAY_MS ms ago
if (lastAnalysisTime != INVALID_TIME && (now - lastAnalysisTime < ANALYSIS_DELAY_MS)) {
image.close();
}
lastAnalysisTime = now;
// Analyze image
image.close();
}
There is one more way, This is how I am doing it in a local project. So in my Image Analyzer, I am rebinding the camera using Handler#PostDelayed and this gives a small black screen in preview, but it won't process the next image that quickly.
My use case was that I had to continuously scan barcodes, but the scanning was too fast and it kept on scanning one code many times. So, I just needed 100ms wait. So this works out for me.
try {
cameraProvider.unbindAll()
Handler(Looper.getMainLooper()).postDelayed({
cameraProvider.bindToLifecycle(
this, CameraSelector.DEFAULT_BACK_CAMERA, useCaseGroup.build())
}, 100)
}
catch(exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
I want to make 8 squares change colors between red/black periodically.
I acomplish this using timer.schedule with period time in milliseconds and it work
BUT then I realized that I need to use small time between this transitions (example nanoseconds).
To accomplish that I wrote this code:
timerTask = new TimerTask() {
public void run() {
handler.post(new Runnable() {
public void run(){
//CODE OF THE TASK.
}
});
}
};
//To make schedule this task in 5 nanoseconds I use this!
exec = new ScheduledThreadPoolExecutor(1);
exec.scheduleAtFixedRate(timerTask, 0, 5, TimeUnit.NANOSECONDS);
But when I run this, the UI is not updating (seems to be stuck), but in logcat, all the logs are printing very fast. How can I achieve to make a task periodically x nanoseconds?
The entire Android UI runs at 60Hz- 60 updates per second. This means the minimum time between redraws is 16 ms. You cannot run it at a higher framerate. Nor are human eyes capable of seeing changes at a much higher frequency than that.
iOS and most video game consoles also work on a 60 Hz refresh rate. You'd find very few to no systems that go faster.
I'm not sure what exactly you're trying to accomplish, but I'm fairly certain you're trying to do it the wrong way.
ALSO: I notice your timer task posts to a handler. That means your timer task is going to tell the main thread to run something, and the timer task is running in nanoseconds. YOu're basically going to choke your main thread full of "run this task" messages, then eventually crash with an OOM error when the event queue becomes so massive it can't add any more (which may take several minutes), because there's no way you're processing them fast enough with the thread switching overhead.
After doing a lot of research, I realized that in order to get the view to refresh so quickly, I needed the use of SurfaceView and a Thread to make the UI redraw very fast, I really had no knowledge of this. Thanks for the help
In the new Android Vitals section in the console I'm getting warnings about more than 60% of sessions being affected by slow UI render times (missed Vsync: 1.02%, slow UI thread: 14.29%, slow draw commands: 96.84%). I've turned on GPU profiling on my test device (using the production version of the app) and I'm seeing the following TextView update causing render times well over 16ms (around 24-30ms):
updateTimer = new Timer();
updateTimer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
timeLeftView.setText(timeLeftString);
}
});
}
}, 100, 500);
When I comment out the textView update, nothing is being changed on the screen and profiler doesn't create any new bars.
One clue is that when opening the activity with the timer, the first 3-4 updates of the timer have rendering at about 8ms but then they rise to around 24-30ms.
Another clue is when I touch any part of the screen, the render times drop back to around 8ms for a few seconds before they shoot up again to 24-30ms. When I stop touch, the render times drop back again for a few seconds before they shoot up again.
So what I'd like to know is:
Is this normal for such a simple TextView update to cause high render times?
Is this what's messing up my Android vitals? Because it runs at only twice a second. Could the problem be elsewhere? The above code is the only thing that's creating high bars in GPU profiling, the other elements of the app work fine, long listviews with multiple textviews and images have rendering times of around 8ms.
What can I do to reduce these draw times? I've tried removing the centering and gravity in the layout for the TextView, as well as wrap_content (as suggested in another answer) but neither have any effect. Apart from that, I'm unsure what to do.
If you put a lot of layers in your xml it will force android to render multiple times ( if you have a lot of layers, refact your code!! ).
I strongly recommend this reading : https://developer.android.com/training/improving-layouts/index.html
About render the TextView multiple times, the speed of the rendering depends of the device you are running your application!
Tried pretty much every suggestion.
Finally solved it by increasing the frequency of the runnable from 500ms to 50ms or shorter. The problem was that the low frequency of the runnable let the CPU/GPU go to a low power state so draws took longer. By increasing the frequency of the runnable and the draws, the CPU/GPU doesn't go into low power state and frames are drawn much faster. Yes, it's more taxing on the battery but not as much as the screen being on in the first place. No users have complained either way and Android vitals are happy now.
Besides, looking at how default/official apps from device manufacturers work (including from Google itself), this is exactly how they handle TextView updates. Google's clock app for example (countdown timer, not stopwatch) updates the TextView ~60 times a second even though once a second would be all that's needed and most frugal.
I'm encountering a strange problem when trying to implement low-latency streaming audio playback on a Nexus 6 running Android 6.0.1 using OpenSL ES.
My initial attempt seemed to be suffering from starvation issues, so I added some basic timing benchmarks in the buffer completion callback function. What I've found is that audio plays back fine if I continually tap the screen while my app is open, but if I leave it alone for a few seconds, the callback starts to take much longer. I'm able to reproduce this behavior consistently. A couple of things to note:
"a few seconds" ~= 3-5 seconds, not long enough to trigger a screen change
My application's activity sets FLAG_KEEP_SCREEN_ON, so no screen changes should occur anyway
I have taken no action to try to increase the audio callback thread's priority, since I was under the impression that Android reserves high priority for these threads already
The behavior occurs on my Nexus 6 (Android 6.0.1), but not on a Galaxy S6 I also have available (Android 5.1.1).
The symptoms I'm seeing really seem like the OS kicks down the audio thread priority after a few seconds of non-interaction with the phone. Is this right? Is there any way I can avoid this behavior?
While watching the latest Google I/O 2016 audio presentation, I finally found the cause and the (ugly) solution for this problem.
Just watch the around one minute of this you tube clip (starting at 8m56s):
https://youtu.be/F2ZDp-eNrh4?t=8m56s
It explains why this is happening and how you can get rid of it.
In fact, Android slows the CPU down after a few seconds of touch inactivity to reduce the battery usage. The guy in the video promises a proper solution for this soon, but for now the only way to get rid of it is to send fake touches (that's the official recommendation).
Instrumentation instr = new Instrumentation();
instr.sendKeyDownUpSync(KeyEvent.KEYCODE_BACKSLASH); // or whatever event you prefer
Repeat this with a timer every 1.5 seconds and the problem will vanish.
I know, this is an ugly hack, and it might have ugly side effects which must be handled. But for now, it is simply the only solution.
Update:
Regarding your latest comment ... here's my solution.
I'm using a regular MotionEvent.ACTION_DOWN at a location outside of the screen bounds. Everything else interfered in an unwanted way with the UI. To avoid the SecurityException, initialize the timer in the onStart() handler of the main activity and terminate it in the onStop() handler. There are still situations when the app goes to the background (depending on the CPU load) in which you might run into a SecurityException, therefore you must surround the fake touch call with a try catch block.
Please note, that I'm using my own timer framework, so you have to transform the code to use whatever timer you want to use.
Also, I cannot ensure yet that the code is 100% bulletproof. My apps have that hack applied, but are currently in beta state, therefore I cannot give you any guarantee if this is working correctly on all devices and Android versions.
Timer fakeTouchTimer = null;
Instrumentation instr;
void initFakeTouchTimer()
{
if (this.fakeTouchTimer != null)
{
if (this.instr == null)
{
this.instr = new Instrumentation();
}
this.fakeTouchTimer.restart();
}
else
{
if (this.instr == null)
{
this.instr = new Instrumentation();
}
this.fakeTouchTimer = new Timer(1500, Thread.MIN_PRIORITY, new TimerTask()
{
#Override
public void execute()
{
if (instr != null && fakeTouchTimer != null && hasWindowFocus())
{
try
{
long downTime = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, -100, -100, 0);
instr.sendPointerSync(event);
event.recycle();
}
catch (Exception e)
{
}
}
}
}, true/*isInfinite*/);
}
}
void killFakeTouchTimer()
{
if (this.fakeTouchTimer != null)
{
this.fakeTouchTimer.interupt();
this.fakeTouchTimer = null;
this.instr = null;
}
}
#Override
protected void onStop()
{
killFakeTouchTimer();
super.onStop();
.....
}
#Override
protected void onStart()
{
initFakeTouchTimer();
super.onStart();
.....
}
It is well known that the audio pipeline in Android 6 has been completely rewritten. While this improved latency-related issues in most cases, it is not impossible that it generated a number of undesirable side-effects, as is usually the case with such large-scale changes.
While your issue does not seem to be a common one, there are a few things you might be able to try:
Increase the audio thread priority. The default priority for audio threads in Android is -16, with the maximum being -20, usually only available to system services. While you can't assign this value to you audio thread, you can assign the next best thing: -19 by using the ANDROID_PRIORITY_URGENT_AUDIO flag when setting the thread's priority.
Increase the number of buffers to prevent any kind of jitter or latency (you can even go up to 16). However on some devices the callback to fill a new buffer isn’t always called when it should.
This SO post has several suggestions to improve audio latency on Anrdoid. Of particular interest are points 3, 4 and 5 in the accepted answer.
Check whether the current Android system is low-latency-enabled by querying whether hasSystemFeature(FEATURE_AUDIO_LOW_LATENCY) or hasSystemFeature(FEATURE_AUDIO_PRO).
Additionally, this academic paper discusses strategies for improving audio latency-related issues in Android/OpenSL, including buffer- and callback interval-related approaches.
Force resampling to native device sample rate on Android 6.
Use the device's native sample rate of 48000. For example:
SLDataFormat_PCM dataFormat;
dataFormat.samplesPerSec = 48000;
I've been developing a live wallpaper using GLWallpaperService, and have gotten good results overall. It runs rock-solid in the emulator and looks good. I've dealt with OpenGL many times before so have a solid command of how to do things... unfortunately I'm having a hell of a time getting this to actually be stable on the actual hardware.
The basic symption occurs when you slide the physical keyboard on a Motorola Droid in and out a few times. This causes the wallpaper to get destroyed/recreated several times in quick succession -- which would be fine, as I have my assets clearing in onDestroy and reloading in onSurfaceChanged. The problem is after a few iterations of this, (four or five, maybe) the calls to onSurfaceChanged completely stop, and i get an endless string of this printed to the log:
04-02 00:53:18.088: WARN/SharedBufferStack(1032): waitForCondition(ReallocateCondition) timed out (identity=337, status=0). CPU may be pegged. trying again.
Is there something I should be implementing here aside from the Android-typical onSurfaceCreated/onSurfaceChanged/onSurfaceDestroyed triumvirate? Browsing through the WallpaperService and WallpaperRenderer classes doesn't pop up anything obvious to me.
I had a similar problem. The error was that I needed to call to "unlockCanvasAndPost":
Canvas c = null;
**try {**
c = holder.lockCanvas(null);
synchronized (holder) {
instance.doDraw(c);
}
**} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
holder.unlockCanvasAndPost(c);
}
}**