{"record":{"id":"299a197a234adba4","repo":"wasabeef/android-gpuimage","slug":"do-not-call-this-method-from-the-ui-thread","errorCode":null,"errorMessage":"Do not call this method from the UI thread!","messagePattern":"Do not call this method from the UI thread!","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"library/src/main/java/jp/co/cyberagent/android/gpuimage/GPUImageView.java","lineNumber":327,"sourceCode":"     */\n    public void saveToPictures(final String folderName, final String fileName,\n                               int width, int height,\n                               final OnPictureSavedListener listener) {\n        new SaveTask(folderName, fileName, width, height, listener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);\n    }\n\n    /**\n     * Retrieve current image with filter applied and given size as Bitmap.\n     *\n     * @param width  requested Bitmap width\n     * @param height requested Bitmap height\n     * @return Bitmap of picture with given size\n     * @throws InterruptedException\n     */\n    public Bitmap capture(final int width, final int height) throws InterruptedException {\n        // This method needs to run on a background thread because it will take a longer time\n        if (Looper.myLooper() == Looper.getMainLooper()) {\n            throw new IllegalStateException(\"Do not call this method from the UI thread!\");\n        }\n\n        forceSize = new Size(width, height);\n\n        final Semaphore waiter = new Semaphore(0);\n\n        // Layout with new size\n        getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {\n            @Override\n            public void onGlobalLayout() {\n                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {\n                    getViewTreeObserver().removeGlobalOnLayoutListener(this);\n                } else {\n                    getViewTreeObserver().removeOnGlobalLayoutListener(this);\n                }\n                waiter.release();\n            }\n        });","sourceCodeStart":309,"sourceCodeEnd":345,"githubUrl":"https://github.com/wasabeef/android-gpuimage/blob/ceea576ec931c2968431ad46f1fb2e6d68a542e2/library/src/main/java/jp/co/cyberagent/android/gpuimage/GPUImageView.java#L309-L345","documentation":"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!\").","triggerScenarios":"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()).","commonSituations":"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.","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."],"exampleFix":"// before\n Bitmap bmp = gpuImageView.capture(w, h); // on UI thread\n// after\n executor.execute(() -> {\n     try {\n         Bitmap bmp = gpuImageView.capture(w, h);\n         runOnUiThread(() -> useBitmap(bmp));\n     } catch (InterruptedException e) {\n         Thread.currentThread().interrupt();\n     }\n });","handlingStrategy":"try-catch","validationCode":"boolean onMainThread = Looper.myLooper() == Looper.getMainLooper();\nif (onMainThread) { /* move capture to background executor first */ }","typeGuard":null,"tryCatchPattern":"backgroundExecutor.execute(() -> {\n    try {\n        Bitmap bmp = gpuImageView.capture(w, h);\n        mainHandler.post(() -> onCaptured(bmp));\n    } catch (InterruptedException e) {\n        Thread.currentThread().interrupt();\n    }\n});","preventionTips":["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"],"tags":["android","gpuimage","threading","ui-thread"],"backgroundTag":"invalid-state-transition","analyzedSha":"ceea576ec931c2968431ad46f1fb2e6d68a542e2","analyzedAt":"2026-09-11T16:56:13.742Z","contentChangedAt":"2026-09-11T16:56:13.742Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}