• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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