{"record":{"id":"d84dad2621acb927","repo":"apache/cordova-android","slug":"do-not-perform-io-operations-on-the-ui-thread-use","errorCode":null,"errorMessage":"Do not perform IO operations on the UI thread. Use CordovaInterface.getThreadPool() instead.","messagePattern":"Do not perform IO operations on the UI thread\\. Use CordovaInterface\\.getThreadPool\\(\\) instead\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"framework/src/org/apache/cordova/CordovaResourceApi.java","lineNumber":422,"sourceCode":"                outputStream.close();\n            }\n        }\n    }\n\n    public void copyResource(Uri sourceUri, OutputStream outputStream) throws IOException {\n        copyResource(openForRead(sourceUri), outputStream);\n    }\n\n    // Added in 3.5.0.\n    public void copyResource(Uri sourceUri, Uri dstUri) throws IOException {\n        copyResource(openForRead(sourceUri), openOutputStream(dstUri));\n    }\n\n    private void assertBackgroundThread() {\n        if (threadCheckingEnabled) {\n            Thread curThread = Thread.currentThread();\n            if (curThread == Looper.getMainLooper().getThread()) {\n                throw new IllegalStateException(\"Do not perform IO operations on the UI thread. Use CordovaInterface.getThreadPool() instead.\");\n            }\n            if (curThread == jsThread) {\n                throw new IllegalStateException(\"Tried to perform an IO operation on the WebCore thread. Use CordovaInterface.getThreadPool() instead.\");\n            }\n        }\n    }\n\n    private String getDataUriMimeType(Uri uri) {\n        String uriAsString = uri.getSchemeSpecificPart();\n        int commaPos = uriAsString.indexOf(',');\n        if (commaPos == -1) {\n            return null;\n        }\n        String[] mimeParts = uriAsString.substring(0, commaPos).split(\";\");\n        if (mimeParts.length > 0) {\n            return mimeParts[0];\n        }\n        return null;","sourceCodeStart":404,"sourceCodeEnd":440,"githubUrl":"https://github.com/apache/cordova-android/blob/7c1e190064e349ffa4bbc6ac37b77cd773e4dbd3/framework/src/org/apache/cordova/CordovaResourceApi.java#L404-L440","documentation":"CordovaResourceApi.assertBackgroundThread guards all IO entry points: when thread checking is enabled (the default) and the calling thread is the Android main/UI thread (Looper.getMainLooper().getThread()), it throws IllegalStateException. File and network IO on the UI thread jams the app and can trigger ANRs, so cordova enforces background execution.","triggerScenarios":"Invoking openForRead, openOutputStream, copyResource, createHttpConnection, etc. directly from onCreate/onPostExecute/UI-button handlers or any code running on the main looper — e.g. a plugin doing synchronous file IO inside execute() when called on the UI thread.","commonSituations":"Plugin execute() assumed to be on a background thread but invoked on UI; refactor moving IO into a click listener; Native code calling the resource API from onResume; occasional crashes only when threadCheckingEnabled is on (default).","solutions":["Move the IO call onto Cordova's thread pool: cordova.getThreadPool().execute(() -> resourceApi.copyResource(...))","For UI-initiated work, wrap the whole read/write in the thread pool and hop back with runOnUiThread for UI updates only","As a last-resort workaround during migration, webView.getResourceApi().setThreadCheckingEnabled(false) — only for debugging, since it removes the ANR protection"],"exampleFix":"// before: crashes on UI thread\ncopyButton.setOnClickListener(v ->\n    resourceApi.copyResource(srcUri, dstUri));\n\n// after\ncopyButton.setOnClickListener(v ->\n    cordova.getThreadPool().execute(() ->\n        resourceApi.copyResource(srcUri, dstUri)));","handlingStrategy":"validation","validationCode":"// guard before IO\nif (Looper.myLooper() == Looper.getMainLooper()) {\n    throw new IllegalStateException(\"refuse IO on UI thread\");\n}\nresourceApi.copyResource(src, dst);","typeGuard":"static boolean isSafeIoThread() {\n    Thread t = Thread.currentThread();\n    return t != Looper.getMainLooper().getThread() && t != CordovaResourceApi.jsThread;\n}","tryCatchPattern":"if (isSafeIoThread()) { resourceApi.copyResource(src, dst); }\nelse { cordova.getThreadPool().execute(() -> resourceApi.copyResource(src, dst)); }","preventionTips":["Route every resourceApi call through cordova.getThreadPool()","Hop back with runOnUiThread only for UI updates","Keep setThreadCheckingEnabled(true) in production as a safety net"],"tags":["cordova","cordova-android","java","threading","ui-thread","resource-api"],"backgroundTag":"io-on-main-thread","analyzedSha":"7c1e190064e349ffa4bbc6ac37b77cd773e4dbd3","analyzedAt":"2026-08-22T04:57:58.868Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}