-
Notifications
You must be signed in to change notification settings - Fork 6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Get rid of FlutterNativeView, introduce FlutterEngine and FlutterRend…
…erer (#6195) * Copied over FlutterView. Copied a number of dependencies due to package change. Grouped related methods within FlutterView. Deleted a few unused methods. * Moved BinaryMessenger out of FlutterView and into FlutterEngine, moved native calls from FlutterView to FlutterEngine. * Introduced concept of a FlutterRenderer. It doesn't have any meaningful behavior yet. Need strategy for resolving FlutterNativeView vs FlutterEngine first. * Removed discovery behavior from FlutterView to match recent PR: #6157 * Replaced FlutterNativeView with FlutterEngine and FlutterRenderer. Eliminated FlutterEngine references from within FlutterView. Centralized all JNI calls within a FlutterJNI and updated the C side accordingly. * Added comments to FlutterEngine, FlutterRender, FlutterFragment, and FlutterView. * Removed some extraneous code. Adjusted first frame notification slightly. * Fixed formatting in platform_view_android_jni.cc * Update licenses file. * Exposed FlutterEngine from plugin system. Updated some access modifiers as needed. * PR Updates.
- Loading branch information
1 parent
e88a285
commit 124d2ff
Showing
20 changed files
with
1,943 additions
and
1,454 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
262 changes: 262 additions & 0 deletions
262
shell/platform/android/io/flutter/embedding/FlutterEngine.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 must not be invoked twice on the same | ||
* {@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 { | ||
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; | ||
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; | ||
|
||
public FlutterEngine( | ||
Context context, | ||
Resources resources, | ||
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() { | ||
// TODO(mattcarroll): what impact does "isBackgroundView' have? | ||
nativeObjectReference = flutterJNI.nativeAttach(this, isBackgroundView); | ||
|
||
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? | ||
// 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): Implement observatory lookup for "flutter attach" | ||
public String getObservatoryUrl() { | ||
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); | ||
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 ------ | ||
} |
Oops, something went wrong.