In Android, the UI thread, also known as the main thread, is the thread responsible for handling user interface interactions, such as drawing the UI elements on the screen and responding to user input events like button clicks or touch gestures. It is crucial to understand the concept of the UI thread because it directly affects the responsiveness and smoothness of your app's user interface.
The Android framework follows a single-threaded model for UI interactions, meaning that all UI-related operations and updates should be performed on the UI thread. If you attempt to perform lengthy operations or blocking tasks on the UI thread, it will result in an unresponsive UI, and your app may even be terminated by the system if it becomes unresponsive for too long.
To ensure a smooth and responsive UI, you should offload any time-consuming or potentially blocking tasks to background threads or asynchronous mechanisms, such as AsyncTask, Handlers, or Kotlin coroutines. These mechanisms allow you to perform operations in the background without blocking the UI thread.
Now we can’t directly change UI elements by Frida. Because we need to attach to UI thread and then change it. If you didn’t do that, you get this error. For example consider this code:
public class MainActivity extends AppCompatActivity {
public static TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.my_tv);
textView.setText("Can you update me from frida?");
}
public static void changeText(String text){
textView.setText(text);
}
}
If we intent to use changeText method, we get this error:
Java.perform(function(){
setTimeout(() => {
let MainActivity = Java.use("lab.seczone64.uithread.MainActivity")
MainActivity.changeText("Yeh I can.")
}, 1000);
})
Result:

To solve this issue we should attach to main thread or Ui thread. Fortunately Frida do this automatically by using Java.ScheduleOnMainThread(fn). It should be used within Java.perform.
setTimeout(function(){
Java.scheduleOnMainThread(() => {
let MainActivity = Java.use("lab.seczone64.uithread.MainActivity")
MainActivity.changeText("Yeh I can.")
})
}, 100)
Consider this code:
private static TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.my_tv);
textView.setText("Can you update me from frida?");
}
To change text view value:
Java.perform( () => {
setTimeout(function(){
Java.scheduleOnMainThread(() => {
Java.choose("lab.seczone64.uithread.MainActivity", {
onMatch: (instance) => {
// Create a String object which reffer to my message.
let StringClass = Java.use("java.lang.String")
let myMessageStr = StringClass.$new("Yes I can :)")
// Get referance to CharSequence class to convert String to CharSequence.
let charSeq = Java.use("java.lang.CharSequence")
// Convert String to CharSequance
let charSeqObj = Java.cast(
myMessageStr,
charSeq
)
// Change teview Text
instance.textView.value.setText(charSeqObj)
},
onComplete: () => {
console.log("[+] Done")
}
})
})
}, 10)
}
)
Toasting an message:
Java.perform(() => {
// Getting Toast class
let toastClass = Java.use("android.widget.Toast")
// Getting Application Context
let context = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext()
console.log(context)
// We must run toast on UI thread
Java.scheduleOnMainThread(
() => {
// Create String object to pass it to the toast.makeText
let toastMessage = Java.use("java.lang.String").$new("I'm Frida. I'm the God. :)")
// Calling makeText
toastClass.makeText(context, toastMessage, 1).show()
}
)
})