Skip to content

Commit

Permalink
Create platform API for first frame callback. Use for defer hiding sp…
Browse files Browse the repository at this point in the history
…lash screens on Android and iOS (flutter#3956)

* Add back launch screen view until first frame on iOS

* improvements

* Move callback plumbing from ios surfaces to the gpu rasterizer. Didn’t wire java JNI yet.

* Android JNI

* Fix ios reference count and let android engine manage a view on top with launch screen

* Hook up Android activity and view

* review notes

* review notes

* Move thread switching upstream. Use weak references for callbacks.

* Some clean up
  • Loading branch information
xster authored Aug 14, 2017
1 parent fef7d82 commit f187a5c
Show file tree
Hide file tree
Showing 13 changed files with 249 additions and 8 deletions.
4 changes: 4 additions & 0 deletions shell/common/null_rasterizer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,8 @@ void NullRasterizer::Draw(
}));
}

void NullRasterizer::AddNextFrameCallback(ftl::Closure nextFrameCallback) {
// Null rasterizer. Nothing to do.
}

} // namespace shell
2 changes: 2 additions & 0 deletions shell/common/null_rasterizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class NullRasterizer : public Rasterizer {

void Draw(ftl::RefPtr<flutter::Pipeline<flow::LayerTree>> pipeline) override;

void AddNextFrameCallback(ftl::Closure nextFrameCallback) override;

private:
std::unique_ptr<Surface> surface_;
ftl::WeakPtrFactory<NullRasterizer> weak_factory_;
Expand Down
3 changes: 3 additions & 0 deletions shell/common/rasterizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ class Rasterizer {

virtual void Draw(
ftl::RefPtr<flutter::Pipeline<flow::LayerTree>> pipeline) = 0;

// Set a callback to be called once when the next frame is drawn.
virtual void AddNextFrameCallback(ftl::Closure nextFrameCallback) = 0;
};

} // namespace shell
Expand Down
19 changes: 19 additions & 0 deletions shell/gpu/gpu_rasterizer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ void GPURasterizer::DoDraw(std::unique_ptr<flow::LayerTree> layer_tree) {

DrawToSurface(*layer_tree);

NotifyNextFrameOnce();

last_layer_tree_ = std::move(layer_tree);
}

Expand All @@ -140,4 +142,21 @@ void GPURasterizer::DrawToSurface(flow::LayerTree& layer_tree) {
frame->Submit();
}

void GPURasterizer::AddNextFrameCallback(ftl::Closure nextFrameCallback) {
nextFrameCallback_ = nextFrameCallback;
}

void GPURasterizer::NotifyNextFrameOnce() {
if (nextFrameCallback_) {
blink::Threads::Platform()->PostTask([weak_this = weak_factory_.GetWeakPtr()] {
TRACE_EVENT0("flutter", "GPURasterizer::NotifyNextFrameOnce");
if (weak_this) {
ftl::Closure callback = weak_this->nextFrameCallback_;
callback();
weak_this->nextFrameCallback_ = nullptr;
}
});
}
}

} // namespace shell
9 changes: 9 additions & 0 deletions shell/gpu/gpu_rasterizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,25 @@ class GPURasterizer : public Rasterizer {

void Draw(ftl::RefPtr<flutter::Pipeline<flow::LayerTree>> pipeline) override;

// Set a callback to be called once when the next frame is drawn.
void AddNextFrameCallback(ftl::Closure nextFrameCallback) override;

private:
std::unique_ptr<Surface> surface_;
flow::CompositorContext compositor_context_;
std::unique_ptr<flow::LayerTree> last_layer_tree_;
// A closure to be called when the underlaying surface presents a frame the
// next time. NULL if there is no callback or the callback was set back to
// NULL after being called.
ftl::Closure nextFrameCallback_;
ftl::WeakPtrFactory<GPURasterizer> weak_factory_;

void DoDraw(std::unique_ptr<flow::LayerTree> layer_tree);

void DrawToSurface(flow::LayerTree& layer_tree);

void NotifyNextFrameOnce();

FTL_DISALLOW_COPY_AND_ASSIGN(GPURasterizer);
};

Expand Down
110 changes: 107 additions & 3 deletions shell/platform/android/io/flutter/app/FlutterActivityDelegate.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,20 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.content.res.Resources.NotFoundException;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.widget.FrameLayout;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.PluginRegistry.ActivityResultListener;
Expand Down Expand Up @@ -50,6 +58,11 @@ public final class FlutterActivityDelegate
implements FlutterActivityEvents,
FlutterView.Provider,
PluginRegistry {
private static final String LAUNCH_DRAWABLE_META_DATA_KEY = "io.flutter.app.LaunchScreen";
private static final String TAG = "FlutterActivityDelegate";
private static final LayoutParams matchParent =
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);

/**
* Specifies the mechanism by which Flutter views are created during the
* operation of a {@code FlutterActivityDelegate}.
Expand All @@ -71,6 +84,7 @@ public interface ViewFactory {
private final List<UserLeaveHintListener> userLeaveHintListeners = new ArrayList<>(0);

private FlutterView flutterView;
private View launchView;

public FlutterActivityDelegate(Activity activity, ViewFactory viewFactory) {
this.activity = Preconditions.checkNotNull(activity);
Expand Down Expand Up @@ -138,9 +152,9 @@ public void onCreate(Bundle savedInstanceState) {
flutterView = viewFactory.createFlutterView(activity);
if (flutterView == null) {
flutterView = new FlutterView(activity);
flutterView.setLayoutParams(
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
activity.setContentView(flutterView);
flutterView.setLayoutParams(matchParent);
launchView = createLaunchView();
setContentView();
}

if (loadIntent(activity.getIntent())) {
Expand Down Expand Up @@ -272,6 +286,96 @@ private boolean loadIntent(Intent intent) {
return false;
}

/**
* Creates a {@link View} containing the same {@link Drawable} as the one set as the
* {@code windowBackground} of the parent activity for use as a launch splash view.
*
* Returns null if no {@code windowBackground} is set for the activity.
*/
private View createLaunchView() {
final Drawable launchScreenDrawable = getLaunchScreenDrawableFromActivityTheme();
if (launchScreenDrawable == null) {
return null;
}
final View view = new View(activity);
view.setLayoutParams(matchParent);
view.setBackground(launchScreenDrawable);
return view;
}

/**
* Extracts a {@link Drawable} from the parent activity's {@code windowBackground}.
*
* {@code android:windowBackground} is specifically reused instead of a custom defined meta-data
* because the Android framework can display it fast enough when launching the app as opposed
* to anything defined in the Activity subclass.
*
* Returns null if no {@code windowBackground} is set for the activity.
*/
private Drawable getLaunchScreenDrawableFromActivityTheme() {
TypedValue typedValue = new TypedValue();
if (!activity.getTheme().resolveAttribute(
android.R.attr.windowBackground,
typedValue,
true)) {;
return null;
}
if (typedValue.resourceId == 0) {
return null;
}
try {
return activity.getResources().getDrawable(typedValue.resourceId);
} catch (NotFoundException e) {
Log.e(TAG, "Referenced launch screen drawable resource '"
+ LAUNCH_DRAWABLE_META_DATA_KEY + "' does not exist");
return null;
}
}

/**
* Sets the root content view of the activity.
*
* If no launch screens are defined in the user application's AndroidManifest.xml as the
* activity's {@code windowBackground}, then set the {@link FlutterView} as the root.
*
* Otherwise, extract the {@code windowBackground}'s {@link Drawable} onto a new launch View to
* put in front of the {@link FlutterView}, remove the activity's {@code windowBackground},
* and finally remove the launch view when the {@link FlutterView} renders its first frame.
*/
private void setContentView() {
// No transient launch screen. Set the FlutterView as root.
if (launchView == null) {
activity.setContentView(flutterView);
return;
}

final FrameLayout layout = new FrameLayout(activity);
layout.setLayoutParams(matchParent);

layout.addView(flutterView);
layout.addView(launchView);

flutterView.addFirstFrameListener(new FlutterView.FirstFrameListener() {
@Override
public void onFirstFrame() {
// Views need to be unparented before adding directly to activity.
layout.removeAllViews();
FlutterActivityDelegate.this.activity.setContentView(
FlutterActivityDelegate.this.flutterView);
FlutterActivityDelegate.this.launchView = null;
FlutterActivityDelegate.this.flutterView.removeFirstFrameListener(this);
}
});

activity.setContentView(layout);
// Resets the activity theme from the one containing the launch screen in the window
// background to a blank one since the launch screen is now in a view in front of the
// FlutterView.
//
// We can make this configurable if users want it.
activity.setTheme(android.R.style.Theme_Black_NoTitleBar);
}

private class FlutterRegistrar implements Registrar {
private final String pluginKey;

Expand Down
33 changes: 32 additions & 1 deletion shell/platform/android/io/flutter/view/FlutterView.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;

import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.*;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.editing.TextInputPlugin;
Expand Down Expand Up @@ -101,6 +101,7 @@ static final class ViewportMetrics {
private final BasicMessageChannel<Object> mFlutterSystemChannel;
private final BroadcastReceiver mDiscoveryReceiver;
private final List<ActivityLifecycleListener> mActivityLifecycleListeners;
private final List<FirstFrameListener> mFirstFrameListeners;
private long mNativePlatformView;
private boolean mIsSoftwareRenderingEnabled = false; // using the software renderer or not

Expand Down Expand Up @@ -157,6 +158,7 @@ public void surfaceDestroyed(SurfaceHolder holder) {

mMessageHandlers = new HashMap<>();
mActivityLifecycleListeners = new ArrayList<>();
mFirstFrameListeners = new ArrayList<>();

// Configure the platform plugins and flutter channels.
mFlutterLocalizationChannel = new MethodChannel(this, "flutter/localization",
Expand Down Expand Up @@ -247,6 +249,21 @@ public void onMemoryPressure() {
mFlutterSystemChannel.send(message);
}

/**
* Provide a listener that will be called once when the FlutterView renders its first frame
* to the underlaying SurfaceView.
*/
public void addFirstFrameListener(FirstFrameListener listener) {
mFirstFrameListeners.add(listener);
}

/**
* Remove an existing first frame listener.
*/
public void removeFirstFrameListener(FirstFrameListener listener) {
mFirstFrameListeners.remove(listener);
}

public void setInitialRoute(String route) {
mFlutterNavigationChannel.invokeMethod("setInitialRoute", route);
}
Expand Down Expand Up @@ -693,6 +710,13 @@ private void updateSemantics(ByteBuffer buffer, String[] strings) {
}
}

// Called by native to notify first Flutter frame rendered.
private void onFirstFrame() {
for (FirstFrameListener listener : mFirstFrameListeners) {
listener.onFirstFrame();
}
}

// ACCESSIBILITY

private boolean mAccessibilityEnabled = false;
Expand Down Expand Up @@ -864,4 +888,11 @@ public void onReceive(Context context, Intent intent) {
}
}
}

/**
* Listener will be called on the Android UI thread once when Flutter renders the first frame.
*/
public interface FirstFrameListener {
void onFirstFrame();
}
}
9 changes: 9 additions & 0 deletions shell/platform/android/platform_view_android.cc
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,15 @@ void PlatformViewAndroid::Attach() {
UpdateThreadPriorities();

PostAddToShellTask();

rasterizer_->AddNextFrameCallback(
[this]() {
JNIEnv* env = fml::jni::AttachCurrentThread();
fml::jni::ScopedJavaLocalRef<jobject> view = flutter_view_.get(env);
if (!view.is_null()) {
FlutterViewOnFirstFrame(env, view.obj());
}
});
}

void PlatformViewAndroid::Detach() {
Expand Down
12 changes: 12 additions & 0 deletions shell/platform/android/platform_view_android_jni.cc
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ void FlutterViewUpdateSemantics(JNIEnv* env,
FTL_CHECK(env->ExceptionCheck() == JNI_FALSE);
}

static jmethodID g_on_first_frame_method = nullptr;
void FlutterViewOnFirstFrame(JNIEnv* env, jobject obj) {
env->CallVoidMethod(obj, g_on_first_frame_method);
FTL_CHECK(env->ExceptionCheck() == JNI_FALSE);
}

// Called By Java

static jlong Attach(JNIEnv* env, jclass clazz, jobject flutterView) {
Expand Down Expand Up @@ -342,6 +348,12 @@ bool PlatformViewAndroid::Register(JNIEnv* env) {
return false;
}

g_on_first_frame_method =
env->GetMethodID(g_flutter_view_class->obj(), "onFirstFrame", "()V");

if (g_on_first_frame_method == nullptr) {
return false;
}
return true;
}

Expand Down
2 changes: 2 additions & 0 deletions shell/platform/android/platform_view_android_jni.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ void FlutterViewUpdateSemantics(JNIEnv* env,
jobject buffer,
jobjectArray strings);

void FlutterViewOnFirstFrame(JNIEnv* env, jobject obj);

} // namespace shell

#endif // FLUTTER_SHELL_PLATFORM_ANDROID_PLATFORM_VIEW_ANDROID_JNI_H_
Loading

0 comments on commit f187a5c

Please sign in to comment.