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

  1. Run capture() on a background thread (ExecutorService, Thread, or coroutine on Dispatchers.IO/Default).
  2. Wrap in a suspend function that moves off the main dispatcher before calling capture().
  3. 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

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


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)