Skip to content

Commit

Permalink
Reland "Avoid vsync scheduling delay" (flutter#36197)
Browse files Browse the repository at this point in the history
  • Loading branch information
jiahaog authored Sep 16, 2022
1 parent 6c26a62 commit 8f76f17
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 17 deletions.
1 change: 1 addition & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -1648,6 +1648,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/Platfor
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java
FILE: ../../../flutter/shell/platform/android/io/flutter/util/HandlerCompat.java
FILE: ../../../flutter/shell/platform/android/io/flutter/util/PathUtils.java
FILE: ../../../flutter/shell/platform/android/io/flutter/util/Preconditions.java
FILE: ../../../flutter/shell/platform/android/io/flutter/util/Predicate.java
Expand Down
1 change: 1 addition & 0 deletions shell/platform/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ android_java_sources = [
"io/flutter/plugin/platform/PlatformViewsController.java",
"io/flutter/plugin/platform/SingleViewPresentation.java",
"io/flutter/plugin/platform/VirtualDisplayController.java",
"io/flutter/util/HandlerCompat.java",
"io/flutter/util/PathUtils.java",
"io/flutter/util/Preconditions.java",
"io/flutter/util/Predicate.java",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
import io.flutter.util.HandlerCompat;

/** A BinaryMessenger.TaskQueue that posts to the platform thread (aka main thread). */
public class PlatformTaskQueue implements DartMessenger.DartMessengerTaskQueue {
@NonNull private final Handler handler = new Handler(Looper.getMainLooper());
// Use an async handler because the default is subject to vsync synchronization and can result
// in delays when dispatching tasks.
@NonNull private final Handler handler = HandlerCompat.createAsyncHandler(Looper.getMainLooper());

@Override
public void dispatch(@NonNull Runnable runnable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.flutter.FlutterInjector;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.util.HandlerCompat;
import io.flutter.util.PathUtils;
import io.flutter.util.TraceSection;
import io.flutter.view.VsyncWaiter;
Expand Down Expand Up @@ -385,7 +386,7 @@ public void ensureInitializationCompleteAsync(
Log.e(TAG, "Flutter initialization failed.", e);
throw new RuntimeException(e);
}
new Handler(Looper.getMainLooper())
HandlerCompat.createAsyncHandler(Looper.getMainLooper())
.post(
() -> {
ensureInitializationComplete(applicationContext.getApplicationContext(), args);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,19 +224,29 @@ private void setSystemChromeChangeListener() {
new View.OnSystemUiVisibilityChangeListener() {
@Override
public void onSystemUiVisibilityChange(int visibility) {
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
// The system bars are visible. Make any desired adjustments to
// your UI, such as showing the action bar or other navigational
// controls. Another common action is to set a timer to dismiss
// the system bars and restore the fullscreen mode that was
// previously enabled.
platformChannel.systemChromeChanged(false);
} else {
// The system bars are NOT visible. Make any desired adjustments
// to your UI, such as hiding the action bar or other
// navigational controls.
platformChannel.systemChromeChanged(true);
}
// `platformChannel.systemChromeChanged` may trigger a callback that eventually results
// in a call to `setSystemUiVisibility`.
// `setSystemUiVisibility` must not be called in the same frame as when
// `onSystemUiVisibilityChange` is received though.
//
// As such, post `platformChannel.systemChromeChanged` to the view handler to ensure
// that downstream callbacks are trigged on the next frame.
decorView.post(
() -> {
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
// The system bars are visible. Make any desired adjustments to
// your UI, such as showing the action bar or other navigational
// controls. Another common action is to set a timer to dismiss
// the system bars and restore the fullscreen mode that was
// previously enabled.
platformChannel.systemChromeChanged(false);
} else {
// The system bars are NOT visible. Make any desired adjustments
// to your UI, such as hiding the action bar or other
// navigational controls.
platformChannel.systemChromeChanged(true);
}
});
}
});
}
Expand Down
35 changes: 35 additions & 0 deletions shell/platform/android/io/flutter/util/HandlerCompat.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.util;

import android.os.Build;
import android.os.Handler;
import android.os.Looper;

/** Compatability wrapper over {@link Handler}. */
public final class HandlerCompat {
/**
* Create a new Handler whose posted messages and runnables are not subject to synchronization
* barriers such as display vsync.
*
* <p>Messages sent to an async handler are guaranteed to be ordered with respect to one another,
* but not necessarily with respect to messages from other Handlers. Compatibility behavior:
*
* <ul>
* <li>SDK 28 and above, this method matches platform behavior.
* <li>Otherwise, returns a synchronous handler instance.
* </ul>
*
* @param looper the Looper that the new Handler should be bound to
* @return a new async Handler instance
* @see Handler#createAsync(Looper)
*/
public static Handler createAsyncHandler(Looper looper) {
if (Build.VERSION.SDK_INT >= 28) {
return Handler.createAsync(looper);
}
return new Handler(looper);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
Expand All @@ -27,7 +28,6 @@
import androidx.activity.OnBackPressedCallback;
import androidx.fragment.app.FragmentActivity;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
import io.flutter.embedding.engine.systemchannels.PlatformChannel.Brightness;
import io.flutter.embedding.engine.systemchannels.PlatformChannel.ClipboardContentFormat;
Expand All @@ -38,10 +38,13 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLooper;

@Config(manifest = Config.NONE)
@RunWith(AndroidJUnit4.class)
@RunWith(RobolectricTestRunner.class)
public class PlatformPluginTest {
private final Context ctx = ApplicationProvider.getApplicationContext();

Expand Down Expand Up @@ -368,6 +371,32 @@ public void setSystemUiMode() {
}
}

@Test
public void setSystemUiModeListener() {
ActivityController<Activity> controller = Robolectric.buildActivity(Activity.class);
controller.setup();
Activity fakeActivity = controller.get();

PlatformChannel fakePlatformChannel = mock(PlatformChannel.class);
PlatformPlugin platformPlugin = new PlatformPlugin(fakeActivity, fakePlatformChannel);

// Subscribe to system Ui visibility events.
platformPlugin.mPlatformMessageHandler.setSystemUiChangeListener();

// Simulate changing of the system ui visibility.
fakeActivity.getWindow().getDecorView().dispatchSystemUiVisibilityChanged(0);

// No events should have been sent to the platform channel yet. They are scheduled for
// the next frame.
verify(fakePlatformChannel, never()).systemChromeChanged(anyBoolean());

// Simulate the next frame.
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();

// Now the platform channel should receive the event.
verify(fakePlatformChannel).systemChromeChanged(anyBoolean());
}

@Config(sdk = 28)
@Test
public void doNotEnableEdgeToEdgeOnOlderSdk() {
Expand Down
29 changes: 29 additions & 0 deletions shell/platform/android/test/io/flutter/util/HandlerCompatTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.util;

import static org.junit.Assert.assertTrue;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

@RunWith(RobolectricTestRunner.class)
public class HandlerCompatTest {
@Test
@Config(sdk = 28)
public void createAsync_createsAnAsyncHandler() {
Handler handler = Handler.createAsync(Looper.getMainLooper());

Message message = Message.obtain();
handler.sendMessageAtTime(message, 0);

assertTrue(message.isAsynchronous());
}
}
1 change: 1 addition & 0 deletions tools/android_lint/project.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<src file="../../../flutter/shell/platform/android/io/flutter/util/Predicate.java" />
<src file="../../../flutter/shell/platform/android/io/flutter/util/TraceSection.java" />
<src file="../../../flutter/shell/platform/android/io/flutter/util/PathUtils.java" />
<src file="../../../flutter/shell/platform/android/io/flutter/util/HandlerCompat.java" />
<src file="../../../flutter/shell/platform/android/io/flutter/embedding/android/KeyboardMap.java" />
<src file="../../../flutter/shell/platform/android/io/flutter/embedding/android/KeyboardManager.java" />
<src file="../../../flutter/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java" />
Expand Down

0 comments on commit 8f76f17

Please sign in to comment.