Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get rid of FlutterNativeView, introduce FlutterEngine and FlutterRenderer #6195

Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivityDele
FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivityEvents.java
FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterFragmentActivity.java
FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterPluginRegistry.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/legacy/FlutterNativeView.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/legacy/FlutterPluginRegistry.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/legacy/PluginRegistry.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/BasicMessageChannel.java
Expand Down Expand Up @@ -202,6 +201,9 @@ FILE: ../../../flutter/lib/ui/natives.dart
FILE: ../../../flutter/lib/ui/window/window.h
FILE: ../../../flutter/shell/common/skia_event_tracer_impl.cc
FILE: ../../../flutter/shell/platform/android/apk_asset_provider.cc
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/FlutterEngine.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/FlutterJNI.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/FlutterRenderer.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/JSONUtil.java
FILE: ../../../flutter/shell/platform/darwin/desktop/Info.plist
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Flutter.podspec
Expand Down
18 changes: 18 additions & 0 deletions shell/platform/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,24 @@ java_library("flutter_shell_java") {
supports_android = true

java_files = [
"io/flutter/embedding/FlutterActivity.java",
"io/flutter/embedding/FlutterEngine.java",
"io/flutter/embedding/FlutterFragment.java",
"io/flutter/embedding/FlutterJNI.java",
"io/flutter/embedding/FlutterRenderer.java",
"io/flutter/embedding/FlutterShellArgs.java",
"io/flutter/embedding/FlutterView.java",
"io/flutter/embedding/legacy/AccessibilityBridge.java",
"io/flutter/embedding/legacy/FlutterPluginRegistry.java",
"io/flutter/embedding/legacy/InputConnectionAdaptor.java",
"io/flutter/embedding/legacy/PlatformViewFactory.java",
"io/flutter/embedding/legacy/PlatformViewRegistry.java",
"io/flutter/embedding/legacy/PlatformViewRegistryImpl.java",
"io/flutter/embedding/legacy/PlatformViewsController.java",
"io/flutter/embedding/legacy/PluginRegistry.java",
"io/flutter/embedding/legacy/SingleViewPresentation.java",
"io/flutter/embedding/legacy/TextInputPlugin.java",
"io/flutter/embedding/legacy/VirtualDisplayController.java",
"io/flutter/app/FlutterActivity.java",
"io/flutter/app/FlutterActivityDelegate.java",
"io/flutter/app/FlutterActivityEvents.java",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
Expand Down Expand Up @@ -65,6 +66,7 @@ public class FlutterActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate()");

FlutterShellArgs args = FlutterShellArgs.fromIntent(getIntent());
// TODO(mattcarroll): Change FlutterMain to accept FlutterShellArgs and move additional constants in
Expand All @@ -79,6 +81,7 @@ public void onCreate(Bundle savedInstanceState) {
@Override
public void onPostResume() {
super.onPostResume();
Log.d(TAG, "onPostResume()");
flutterFragment.onPostResume();
}

Expand All @@ -90,6 +93,7 @@ protected void onNewIntent(Intent intent) {

@Override
public void onBackPressed() {
Log.d(TAG, "onBackPressed()");
flutterFragment.onBackPressed();
}

Expand All @@ -107,6 +111,7 @@ public void onUserLeaveHint() {
* {@code FrameLayout} will hold a {@code FlutterFragment}, which displays a {@code FlutterView}.
*/
private void createFlutterFragmentContainer() {
Log.d(TAG, "createFlutterFragmentContainer()");
FrameLayout container = new FrameLayout(this);
container.setId(CONTAINER_ID);
container.setLayoutParams(new ViewGroup.LayoutParams(
Expand All @@ -122,6 +127,7 @@ private void createFlutterFragmentContainer() {
* a reference to that {@code FlutterFragment} is retained in {@code flutterFragment}.
*/
private void createFlutterFragment() {
Log.d(TAG, "createFlutterFragment()");
FragmentManager fragmentManager = getFragmentManager();

flutterFragment = (FlutterFragment) fragmentManager.findFragmentByTag(TAG_FLUTTER_FRAGMENT);
Expand Down
262 changes: 262 additions & 0 deletions shell/platform/android/io/flutter/embedding/FlutterEngine.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
package io.flutter.embedding;

import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;

import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import io.flutter.embedding.legacy.FlutterPluginRegistry;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.view.FlutterRunArguments;

/**
* A single Flutter execution environment.
*
* A {@code FlutterEngine} can execute in the background, or it can be rendered to the screen by
* using the accompanying {@link FlutterRenderer}. Rendering can be started and stopped, thus
* allowing a {@code FlutterEngine} to move from UI interaction to data-only processing and then
* back to UI interaction.
*
* To start running Flutter within this {@code FlutterEngine}, use {@link #runFromBundle(FlutterRunArguments)}.
* The {@link #runFromBundle(FlutterRunArguments)} method may not be invoked twice on the same
matthew-carroll marked this conversation as resolved.
Show resolved Hide resolved
* {@code FlutterEngine}.
*
* To start rendering Flutter content to the screen, use {@link #getRenderer()} to obtain a
* {@link FlutterRenderer} and then attach a {@link FlutterRenderer.RenderSurface}. Consider using
* a {@link FlutterView} as a {@link FlutterRenderer.RenderSurface}.
* TODO(mattcarroll): for the above instructions for RenderSurface to be true, we need to refactor
* the native relationships and also the PlatformViewsController relationship.
*/
public class FlutterEngine implements BinaryMessenger {
matthew-carroll marked this conversation as resolved.
Show resolved Hide resolved
private static final String TAG = "FlutterEngine";

private final Resources resources;
private final FlutterJNI flutterJNI;
private final FlutterRenderer renderer;
private final FlutterPluginRegistry pluginRegistry;
private final Map<String, BinaryMessageHandler> mMessageHandlers;
matthew-carroll marked this conversation as resolved.
Show resolved Hide resolved
private final Map<Integer, BinaryReply> mPendingReplies = new HashMap<>();
private long nativeObjectReference;
private boolean isBackgroundView; // TODO(mattcarroll): rename to something without "view"
private boolean applicationIsRunning;
private int mNextReplyId = 1;

FlutterEngine(
Context context,
matthew-carroll marked this conversation as resolved.
Show resolved Hide resolved
Resources resources,
matthew-carroll marked this conversation as resolved.
Show resolved Hide resolved
boolean isBackgroundView
) {
this.flutterJNI = new FlutterJNI();
this.resources = resources;
this.isBackgroundView = isBackgroundView;
pluginRegistry = new FlutterPluginRegistry(this, context);
mMessageHandlers = new HashMap<>();

attach();
// TODO(mattcarroll): FlutterRenderer is temporally coupled to attach(). Remove that coupling if possible.
this.renderer = new FlutterRenderer(flutterJNI, nativeObjectReference);
}

private void attach() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I was initially assuming that "attach" is about attaching to a FlutterRenderer, maybe worth considering to call it something like attachJni ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be a good a change. This terminology is a holdover from FlutterNativeView.

// TODO(mattcarroll): what impact does "isBackgroundView' have?
matthew-carroll marked this conversation as resolved.
Show resolved Hide resolved
nativeObjectReference = flutterJNI.nativeAttach(this, isBackgroundView);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to call pluginRegistry.attach() here?

if (!isAttached()) {
throw new RuntimeException("FlutterEngine failed to attach to its native Object reference.");
}
}

private void assertAttached() {
if (!isAttached()) throw new AssertionError("Platform view is not attached");
}

@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean isAttached() {
return nativeObjectReference != 0;
}

public void detach() {
pluginRegistry.detach();
// TODO(mattcarroll): why do we have a nativeDetach() method? can we get rid of this?
flutterJNI.nativeDetach(nativeObjectReference);
}

public void destroy() {
pluginRegistry.destroy();
flutterJNI.nativeDestroy(nativeObjectReference);
nativeObjectReference = 0;
applicationIsRunning = false;
}

public void runFromBundle(FlutterRunArguments args) {
if (args.bundlePath == null) {
throw new AssertionError("A bundlePath must be specified");
} else if (args.entrypoint == null) {
throw new AssertionError("An entrypoint must be specified");
}
runFromBundleInternal(args.bundlePath, args.entrypoint, args.libraryPath, args.defaultPath);
}

private void runFromBundleInternal(
String bundlePath,
String entrypoint,
String libraryPath,
String defaultPath
) {
assertAttached();

if (applicationIsRunning) {
throw new AssertionError("This Flutter engine instance is already running an application");
}

flutterJNI.nativeRunBundleAndSnapshotFromLibrary(nativeObjectReference, bundlePath,
defaultPath, entrypoint, libraryPath, resources.getAssets());

applicationIsRunning = true;
}

public boolean isApplicationRunning() {
return applicationIsRunning;
}

// TODO(mattcarroll): what does this callback actually represent?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This represents a hot restart / cold reload (e.g shift-R), this was added so we can dispose all embedded platform views when the user hot restarts the app.

// Called by native to notify when the engine is restarted (cold reload).
@SuppressWarnings("unused")
private void onPreEngineRestart() {
if (pluginRegistry == null)
return;
pluginRegistry.onPreEngineRestart();
}

public FlutterRenderer getRenderer() {
return renderer;
}

public FlutterPluginRegistry getPluginRegistry() {
return pluginRegistry;
}

// TODO(mattcarroll): Is this method really all about plugins? or does it have other implications?
public void attachViewAndActivity(Activity activity) {
pluginRegistry.attach(this, activity);
}

// TODO(mattcarroll): This method says it's unused. Is that true? Do we need it?
public String getObservatoryUri() {
matthew-carroll marked this conversation as resolved.
Show resolved Hide resolved
return flutterJNI.nativeGetObservatoryUri();
}

//------- START ISOLATE METHOD CHANNEL COMMS ------
@Override
public void setMessageHandler(String channel, BinaryMessageHandler handler) {
if (handler == null) {
mMessageHandlers.remove(channel);
} else {
mMessageHandlers.put(channel, handler);
}
}

@Override
public void send(String channel, ByteBuffer message) {
send(channel, message, null);
}

@Override
public void send(String channel, ByteBuffer message, BinaryReply callback) {
if (!isAttached()) {
Log.d(TAG, "FlutterView.send called on a detached view, channel=" + channel);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should this say FlutterEngine ?

return;
}

int replyId = 0;
if (callback != null) {
replyId = mNextReplyId++;
mPendingReplies.put(replyId, callback);
}
if (message == null) {
flutterJNI.nativeDispatchEmptyPlatformMessage(nativeObjectReference, channel, replyId);
} else {
flutterJNI.nativeDispatchPlatformMessage(
nativeObjectReference, channel, message, message.position(), replyId);
}
}

// Called by native to send us a platform message.
@SuppressWarnings("unused")
private void handlePlatformMessage(final String channel, byte[] message, final int replyId) {
assertAttached();
BinaryMessageHandler handler = mMessageHandlers.get(channel);
if (handler != null) {
try {
final ByteBuffer buffer = (message == null ? null : ByteBuffer.wrap(message));
handler.onMessage(buffer, new BinaryReply() {
private final AtomicBoolean done = new AtomicBoolean(false);
@Override
public void reply(ByteBuffer reply) {
if (!isAttached()) {
Log.d(TAG,
"handlePlatformMessage replying to a detached view, channel="
+ channel);
return;
}
if (done.getAndSet(true)) {
throw new IllegalStateException("Reply already submitted");
}
if (reply == null) {
flutterJNI.nativeInvokePlatformMessageEmptyResponseCallback(nativeObjectReference, replyId);
} else {
flutterJNI.nativeInvokePlatformMessageResponseCallback(nativeObjectReference, replyId, reply, reply.position());
}
}
});
} catch (Exception ex) {
Log.e(TAG, "Uncaught exception in binary message listener", ex);
flutterJNI.nativeInvokePlatformMessageEmptyResponseCallback(nativeObjectReference, replyId);
}
return;
}
flutterJNI.nativeInvokePlatformMessageEmptyResponseCallback(nativeObjectReference, replyId);
}

// Called by native to respond to a platform message that we sent.
@SuppressWarnings("unused")
private void handlePlatformMessageResponse(int replyId, byte[] reply) {
BinaryReply callback = mPendingReplies.remove(replyId);
if (callback != null) {
try {
callback.reply(reply == null ? null : ByteBuffer.wrap(reply));
} catch (Exception ex) {
Log.e(TAG, "Uncaught exception in binary message reply handler", ex);
}
}
}
//------- END ISOLATE METHOD CHANNEL COMMS -----

//------ START NATIVE CALLBACKS THAT SHOULD BE MOVED TO FlutterRenderer ------
// Called by native to update the semantics/accessibility tree.
@SuppressWarnings("unused")
private void updateSemantics(ByteBuffer buffer, String[] strings) {
Log.d(TAG, "updateSemantics()");
renderer.updateSemantics(buffer, strings);
}

// Called by native to update the custom accessibility actions.
@SuppressWarnings("unused")
private void updateCustomAccessibilityActions(ByteBuffer buffer, String[] strings) {
Log.d(TAG, "updateCustomAccessibilityActions()");
renderer.updateCustomAccessibilityActions(buffer, strings);
}

// Called by native to notify first Flutter frame rendered.
@SuppressWarnings("unused")
private void onFirstFrame() {
Log.d(TAG, "onFirstFrame()");
renderer.onFirstFrameRendered();
}
//------ END NATIVE CALLBACKS THAT SHOULD BE MOVED TO FlutterRenderer ------
}
Loading