Run a main looper

Instead of blocking the main thread until completion, run a looper.

This will allow the main thread to process any event posted to the main
looper.

Refs #6009 comment <https://github.com/Genymobile/scrcpy/pull/6009#issuecomment-2940810736>
PR #6129 <https://github.com/Genymobile/scrcpy/pull/6129>
This commit is contained in:
Romain Vimont 2025-06-05 20:33:15 +02:00
commit 283326b2f6
2 changed files with 19 additions and 27 deletions

View file

@ -25,9 +25,11 @@ import com.genymobile.scrcpy.video.SurfaceEncoder;
import com.genymobile.scrcpy.video.VideoSource; import com.genymobile.scrcpy.video.VideoSource;
import android.os.Build; import android.os.Build;
import android.os.Looper;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -55,17 +57,7 @@ public final class Server {
this.fatalError = true; this.fatalError = true;
} }
if (running == 0 || this.fatalError) { if (running == 0 || this.fatalError) {
notify(); Looper.getMainLooper().quitSafely();
}
}
synchronized void await() {
try {
while (running > 0 && !fatalError) {
wait();
}
} catch (InterruptedException e) {
// ignore
} }
} }
} }
@ -104,6 +96,7 @@ public final class Server {
boolean audio = options.getAudio(); boolean audio = options.getAudio();
boolean sendDummyByte = options.getSendDummyByte(); boolean sendDummyByte = options.getSendDummyByte();
prepareMainLooper();
Workarounds.apply(); Workarounds.apply();
List<AsyncProcessor> asyncProcessors = new ArrayList<>(); List<AsyncProcessor> asyncProcessors = new ArrayList<>();
@ -172,7 +165,7 @@ public final class Server {
}); });
} }
completion.await(); Looper.loop(); // interrupted by the Completion implementation
} finally { } finally {
if (cleanUp != null) { if (cleanUp != null) {
cleanUp.interrupt(); cleanUp.interrupt();
@ -201,6 +194,20 @@ public final class Server {
} }
} }
private static void prepareMainLooper() {
// Like Looper.prepareMainLooper(), but with quitAllowed set to true
Looper.prepare();
synchronized (Looper.class) {
try {
Field field = Looper.class.getDeclaredField("sMainLooper");
field.setAccessible(true);
field.set(null, Looper.myLooper());
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
}
}
public static void main(String... args) { public static void main(String... args) {
int status = 0; int status = 0;
try { try {

View file

@ -29,8 +29,6 @@ public final class Workarounds {
private static final Object ACTIVITY_THREAD; private static final Object ACTIVITY_THREAD;
static { static {
prepareMainLooper();
try { try {
// ActivityThread activityThread = new ActivityThread(); // ActivityThread activityThread = new ActivityThread();
ACTIVITY_THREAD_CLASS = Class.forName("android.app.ActivityThread"); ACTIVITY_THREAD_CLASS = Class.forName("android.app.ActivityThread");
@ -77,19 +75,6 @@ public final class Workarounds {
fillAppContext(); fillAppContext();
} }
@SuppressWarnings("deprecation")
private static void prepareMainLooper() {
// Some devices internally create a Handler when creating an input Surface, causing an exception:
// "Can't create handler inside thread that has not called Looper.prepare()"
// <https://github.com/Genymobile/scrcpy/issues/240>
//
// Use Looper.prepareMainLooper() instead of Looper.prepare() to avoid a NullPointerException:
// "Attempt to read from field 'android.os.MessageQueue android.os.Looper.mQueue'
// on a null object reference"
// <https://github.com/Genymobile/scrcpy/issues/921>
Looper.prepareMainLooper();
}
private static void fillAppInfo() { private static void fillAppInfo() {
try { try {
// ActivityThread.AppBindData appBindData = new ActivityThread.AppBindData(); // ActivityThread.AppBindData appBindData = new ActivityThread.AppBindData();