1 // Copyright 2013 The Flutter Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package io.flutter.embedding.engine.dart; 6 7 import android.content.res.AssetManager; 8 import android.support.annotation.NonNull; 9 import android.support.annotation.Nullable; 10 import android.support.annotation.UiThread; 11 12 import java.nio.ByteBuffer; 13 14 import io.flutter.Log; 15 import io.flutter.embedding.engine.FlutterJNI; 16 import io.flutter.plugin.common.BinaryMessenger; 17 import io.flutter.plugin.common.StringCodec; 18 import io.flutter.view.FlutterCallbackInformation; 19 import io.flutter.view.FlutterMain; 20 21 /** 22 * Configures, bootstraps, and starts executing Dart code. 23 * <p> 24 * WARNING: THIS CLASS IS EXPERIMENTAL. DO NOT SHIP A DEPENDENCY ON THIS CODE. 25 * IF YOU USE IT, WE WILL BREAK YOU. 26 * <p> 27 * To specify a top-level Dart function to execute, use a {@link DartEntrypoint} to tell 28 * {@link DartExecutor} where to find the Dart code to execute, and which Dart function to use as 29 * the entrypoint. To execute the entrypoint, pass the {@link DartEntrypoint} to 30 * {@link #executeDartEntrypoint(DartEntrypoint)}. 31 * <p> 32 * To specify a Dart callback to execute, use a {@link DartCallback}. A given Dart callback must 33 * be registered with the Dart VM to be invoked by a {@link DartExecutor}. To execute the callback, 34 * pass the {@link DartCallback} to {@link #executeDartCallback(DartCallback)}. 35 * TODO(mattcarroll): add a reference to docs about background/plugin execution 36 * <p> 37 * Once started, a {@link DartExecutor} cannot be stopped. The associated Dart code will execute 38 * until it completes, or until the {@link io.flutter.embedding.engine.FlutterEngine} that owns 39 * this {@link DartExecutor} is destroyed. 40 */ 41 public class DartExecutor implements BinaryMessenger { 42 private static final String TAG = "DartExecutor"; 43 44 @NonNull 45 private final FlutterJNI flutterJNI; 46 @NonNull 47 private final AssetManager assetManager; 48 @NonNull 49 private final DartMessenger messenger; 50 private boolean isApplicationRunning = false; 51 @Nullable 52 private String isolateServiceId; 53 @Nullable 54 private IsolateServiceIdListener isolateServiceIdListener; 55 56 private final BinaryMessenger.BinaryMessageHandler isolateChannelMessageHandler = 57 new BinaryMessenger.BinaryMessageHandler() { 58 @Override 59 public void onMessage(ByteBuffer message, final BinaryReply callback) { 60 isolateServiceId = StringCodec.INSTANCE.decodeMessage(message); 61 if (isolateServiceIdListener != null) { 62 isolateServiceIdListener.onIsolateServiceIdAvailable(isolateServiceId); 63 } 64 } 65 }; 66 DartExecutor(@onNull FlutterJNI flutterJNI, @NonNull AssetManager assetManager)67 public DartExecutor(@NonNull FlutterJNI flutterJNI, @NonNull AssetManager assetManager) { 68 this.flutterJNI = flutterJNI; 69 this.assetManager = assetManager; 70 this.messenger = new DartMessenger(flutterJNI); 71 messenger.setMessageHandler("flutter/isolate", isolateChannelMessageHandler); 72 } 73 74 /** 75 * Invoked when the {@link io.flutter.embedding.engine.FlutterEngine} that owns this 76 * {@link DartExecutor} attaches to JNI. 77 * <p> 78 * When attached to JNI, this {@link DartExecutor} begins handling 2-way communication to/from 79 * the Dart execution context. This communication is facilitate via 2 APIs: 80 * <ul> 81 * <li>{@link BinaryMessenger}, which sends messages to Dart</li> 82 * <li>{@link PlatformMessageHandler}, which receives messages from Dart</li> 83 * </ul> 84 */ onAttachedToJNI()85 public void onAttachedToJNI() { 86 Log.v(TAG, "Attached to JNI. Registering the platform message handler for this Dart execution context."); 87 flutterJNI.setPlatformMessageHandler(messenger); 88 } 89 90 /** 91 * Invoked when the {@link io.flutter.embedding.engine.FlutterEngine} that owns this 92 * {@link DartExecutor} detaches from JNI. 93 * <p> 94 * When detached from JNI, this {@link DartExecutor} stops handling 2-way communication to/from 95 * the Dart execution context. 96 */ onDetachedFromJNI()97 public void onDetachedFromJNI() { 98 Log.v(TAG, "Detached from JNI. De-registering the platform message handler for this Dart execution context."); 99 flutterJNI.setPlatformMessageHandler(null); 100 } 101 102 /** 103 * Is this {@link DartExecutor} currently executing Dart code? 104 * 105 * @return true if Dart code is being executed, false otherwise 106 */ isExecutingDart()107 public boolean isExecutingDart() { 108 return isApplicationRunning; 109 } 110 111 /** 112 * Starts executing Dart code based on the given {@code dartEntrypoint}. 113 * <p> 114 * See {@link DartEntrypoint} for configuration options. 115 * 116 * @param dartEntrypoint specifies which Dart function to run, and where to find it 117 */ executeDartEntrypoint(@onNull DartEntrypoint dartEntrypoint)118 public void executeDartEntrypoint(@NonNull DartEntrypoint dartEntrypoint) { 119 if (isApplicationRunning) { 120 Log.w(TAG, "Attempted to run a DartExecutor that is already running."); 121 return; 122 } 123 124 Log.v(TAG, "Executing Dart entrypoint: " + dartEntrypoint); 125 126 flutterJNI.runBundleAndSnapshotFromLibrary( 127 dartEntrypoint.pathToBundle, 128 dartEntrypoint.dartEntrypointFunctionName, 129 null, 130 assetManager 131 ); 132 133 isApplicationRunning = true; 134 } 135 136 /** 137 * Starts executing Dart code based on the given {@code dartCallback}. 138 * <p> 139 * See {@link DartCallback} for configuration options. 140 * 141 * @param dartCallback specifies which Dart callback to run, and where to find it 142 */ executeDartCallback(@onNull DartCallback dartCallback)143 public void executeDartCallback(@NonNull DartCallback dartCallback) { 144 if (isApplicationRunning) { 145 Log.w(TAG, "Attempted to run a DartExecutor that is already running."); 146 return; 147 } 148 149 Log.v(TAG, "Executing Dart callback: " + dartCallback); 150 151 flutterJNI.runBundleAndSnapshotFromLibrary( 152 dartCallback.pathToBundle, 153 dartCallback.callbackHandle.callbackName, 154 dartCallback.callbackHandle.callbackLibraryPath, 155 dartCallback.androidAssetManager 156 ); 157 158 isApplicationRunning = true; 159 } 160 161 //------ START BinaryMessenger ----- 162 163 /** 164 * Sends the given {@code message} from Android to Dart over the given {@code channel}. 165 * 166 * @param channel the name of the logical channel used for the message. 167 * @param message the message payload, a direct-allocated {@link ByteBuffer} with the message bytes 168 */ 169 @Override 170 @UiThread send(@onNull String channel, @Nullable ByteBuffer message)171 public void send(@NonNull String channel, @Nullable ByteBuffer message) { 172 messenger.send(channel, message, null); 173 } 174 175 /** 176 * Sends the given {@code messages} from Android to Dart over the given {@code channel} and 177 * then has the provided {@code callback} invoked when the Dart side responds. 178 * 179 * @param channel the name of the logical channel used for the message. 180 * @param message the message payload, a direct-allocated {@link ByteBuffer} with the message bytes 181 * between position zero and current position, or null. 182 * @param callback a callback invoked when the Dart application responds to the message 183 */ 184 @Override 185 @UiThread send(@onNull String channel, @Nullable ByteBuffer message, @Nullable BinaryMessenger.BinaryReply callback)186 public void send(@NonNull String channel, @Nullable ByteBuffer message, @Nullable BinaryMessenger.BinaryReply callback) { 187 messenger.send(channel, message, callback); 188 } 189 190 /** 191 * Sets the given {@link io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler} as the 192 * singular handler for all incoming messages received from the Dart side of this Dart execution 193 * context. 194 * 195 * @param channel the name of the channel. 196 * @param handler a {@link BinaryMessageHandler} to be invoked on incoming messages, or null. 197 */ 198 @Override 199 @UiThread setMessageHandler(@onNull String channel, @Nullable BinaryMessenger.BinaryMessageHandler handler)200 public void setMessageHandler(@NonNull String channel, @Nullable BinaryMessenger.BinaryMessageHandler handler) { 201 messenger.setMessageHandler(channel, handler); 202 } 203 204 /** 205 * Returns the number of pending channel callback replies. 206 * 207 * <p>When sending messages to the Flutter application using {@link BinaryMessenger#send(String, 208 * ByteBuffer, io.flutter.plugin.common.BinaryMessenger.BinaryReply)}, developers can optionally 209 * specify a reply callback if they expect a reply from the Flutter application. 210 * 211 * <p>This method tracks all the pending callbacks that are waiting for response, and is supposed 212 * to be called from the main thread (as other methods). Calling from a different thread could 213 * possibly capture an indeterministic internal state, so don't do it. 214 * 215 * <p>Currently, it's mainly useful for a testing framework like Espresso to determine whether all 216 * the async channel callbacks are handled and the app is idle. 217 */ 218 @UiThread getPendingChannelResponseCount()219 public int getPendingChannelResponseCount() { 220 return messenger.getPendingChannelResponseCount(); 221 } 222 223 //------ END BinaryMessenger ----- 224 225 /** 226 * Returns an identifier for this executor's primary isolate. This identifier can be used 227 * in queries to the Dart service protocol. 228 */ 229 @Nullable getIsolateServiceId()230 public String getIsolateServiceId() { 231 return isolateServiceId; 232 } 233 234 /** 235 * Callback interface invoked when the isolate identifier becomes available. 236 */ 237 interface IsolateServiceIdListener { onIsolateServiceIdAvailable(@onNull String isolateServiceId)238 void onIsolateServiceIdAvailable(@NonNull String isolateServiceId); 239 } 240 241 /** 242 * Set a listener that will be notified when an isolate identifier is available for this 243 * executor's primary isolate. 244 */ setIsolateServiceIdListener(@ullable IsolateServiceIdListener listener)245 public void setIsolateServiceIdListener(@Nullable IsolateServiceIdListener listener) { 246 isolateServiceIdListener = listener; 247 if (isolateServiceIdListener != null && isolateServiceId != null) { 248 isolateServiceIdListener.onIsolateServiceIdAvailable(isolateServiceId); 249 } 250 } 251 252 /** 253 * Configuration options that specify which Dart entrypoint function is executed and where 254 * to find that entrypoint and other assets required for Dart execution. 255 */ 256 public static class DartEntrypoint { 257 @NonNull createDefault()258 public static DartEntrypoint createDefault() { 259 return new DartEntrypoint( 260 FlutterMain.findAppBundlePath(), 261 "main" 262 ); 263 } 264 265 /** 266 * The path within the AssetManager where the app will look for assets. 267 */ 268 @NonNull 269 public final String pathToBundle; 270 271 /** 272 * The name of a Dart function to execute. 273 */ 274 @NonNull 275 public final String dartEntrypointFunctionName; 276 DartEntrypoint( @onNull String pathToBundle, @NonNull String dartEntrypointFunctionName )277 public DartEntrypoint( 278 @NonNull String pathToBundle, 279 @NonNull String dartEntrypointFunctionName 280 ) { 281 this.pathToBundle = pathToBundle; 282 this.dartEntrypointFunctionName = dartEntrypointFunctionName; 283 } 284 285 @Override 286 @NonNull toString()287 public String toString() { 288 return "DartEntrypoint( bundle path: " + pathToBundle + ", function: " + dartEntrypointFunctionName + " )"; 289 } 290 291 @Override equals(Object o)292 public boolean equals(Object o) { 293 if (this == o) return true; 294 if (o == null || getClass() != o.getClass()) return false; 295 296 DartEntrypoint that = (DartEntrypoint) o; 297 298 if (!pathToBundle.equals(that.pathToBundle)) return false; 299 return dartEntrypointFunctionName.equals(that.dartEntrypointFunctionName); 300 } 301 302 @Override hashCode()303 public int hashCode() { 304 int result = pathToBundle.hashCode(); 305 result = 31 * result + dartEntrypointFunctionName.hashCode(); 306 return result; 307 } 308 } 309 310 /** 311 * Configuration options that specify which Dart callback function is executed and where 312 * to find that callback and other assets required for Dart execution. 313 */ 314 public static class DartCallback { 315 /** 316 * Standard Android AssetManager, provided from some {@code Context} or {@code Resources}. 317 */ 318 public final AssetManager androidAssetManager; 319 320 /** 321 * The path within the AssetManager where the app will look for assets. 322 */ 323 public final String pathToBundle; 324 325 /** 326 * A Dart callback that was previously registered with the Dart VM. 327 */ 328 public final FlutterCallbackInformation callbackHandle; 329 DartCallback( @onNull AssetManager androidAssetManager, @NonNull String pathToBundle, @NonNull FlutterCallbackInformation callbackHandle )330 public DartCallback( 331 @NonNull AssetManager androidAssetManager, 332 @NonNull String pathToBundle, 333 @NonNull FlutterCallbackInformation callbackHandle 334 ) { 335 this.androidAssetManager = androidAssetManager; 336 this.pathToBundle = pathToBundle; 337 this.callbackHandle = callbackHandle; 338 } 339 340 @Override 341 @NonNull toString()342 public String toString() { 343 return "DartCallback( bundle path: " + pathToBundle 344 + ", library path: " + callbackHandle.callbackLibraryPath 345 + ", function: " + callbackHandle.callbackName + " )"; 346 } 347 } 348 } 349