wasabeef/android-gpuimage · error · IllegalStateException
Do not call this method from the UI thread!
Error message
Do not call this method from the UI thread!
What it means
GPUImageView.capture(width, height) performs a slow blocking capture (semaphore wait for the GL thread) and deliberately forbids being called from the main/UI thread. Calling it from the UI thread throws IllegalStateException("Do not call this method from the UI thread!").
Solutions
- Run capture() on a background thread (ExecutorService, Thread, or coroutine on Dispatchers.IO/Default).
- Wrap in a suspend function that moves off the main dispatcher before calling capture().
- Post the result back to the UI thread after capture completes instead of calling on the UI thread.
Example fix
// before
Bitmap bmp = gpuImageView.capture(w, h); // on UI thread
// after
executor.execute(() -> {
try {
Bitmap bmp = gpuImageView.capture(w, h);
runOnUiThread(() -> useBitmap(bmp));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}); Defensive patterns
Strategy: try-catch
Validate before calling
boolean onMainThread = Looper.myLooper() == Looper.getMainLooper();
if (onMainThread) { /* move capture to background executor first */ } Try / catch
backgroundExecutor.execute(() -> {
try {
Bitmap bmp = gpuImageView.capture(w, h);
mainHandler.post(() -> onCaptured(bmp));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}); Prevention
- Never call capture() from onClick/onResume or other main-thread callbacks
- Use coroutines with Dispatchers.IO around capture()
- Route all image-capture calls through a background-thread helper
When it happens
Trigger: Calling gpuImageView.capture(w, h) directly from an Activity's UI thread, inside onClick()/onCreate(), or from any callback that runs on the main looper (Looper.myLooper() == Looper.getMainLooper()).
Common situations: Taking a snapshot in a button click handler; capturing during onResume; developers used to synchronous capture APIs on other libraries; coroutine/dispatcher misconfiguration leaving the call on Main.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- eglChooseConfig failed
- renderMode
- r must not be null
- setRenderer has already been called for this instance.
- OpenGL ES 2.0 is not supported on this phone.
AI-assisted analysis of wasabeef/android-gpuimage@ceea576ec9 (2026-09-11).
Data as JSON: /api/errors/299a197a234adba4.
Report an issue: GitHub.
Appendix: source
Thrown at library/src/main/java/jp/co/cyberagent/android/gpuimage/GPUImageView.java:327
*/
public void saveToPictures(final String folderName, final String fileName,
int width, int height,
final OnPictureSavedListener listener) {
new SaveTask(folderName, fileName, width, height, listener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
/**
* Retrieve current image with filter applied and given size as Bitmap.
*
* @param width requested Bitmap width
* @param height requested Bitmap height
* @return Bitmap of picture with given size
* @throws InterruptedException
*/
public Bitmap capture(final int width, final int height) throws InterruptedException {
// This method needs to run on a background thread because it will take a longer time
if (Looper.myLooper() == Looper.getMainLooper()) {
throw new IllegalStateException("Do not call this method from the UI thread!");
}
forceSize = new Size(width, height);
final Semaphore waiter = new Semaphore(0);
// Layout with new size
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
waiter.release();
}
});View on GitHub (pinned to ceea576ec9)