Flutter Engine
The Flutter Engine
FlutterActivityDelegate.java
Go to the documentation of this file.
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
5package io.flutter.app;
6
7import android.animation.Animator;
8import android.animation.AnimatorListenerAdapter;
9import android.app.Activity;
10import android.app.Application;
11import android.content.Context;
12import android.content.Intent;
13import android.content.pm.ActivityInfo;
14import android.content.pm.ApplicationInfo;
15import android.content.pm.PackageManager;
16import android.content.pm.PackageManager.NameNotFoundException;
17import android.content.res.Configuration;
18import android.content.res.Resources.NotFoundException;
19import android.graphics.drawable.Drawable;
20import android.os.Bundle;
21import android.util.TypedValue;
22import android.view.View;
23import android.view.ViewGroup;
24import android.view.Window;
25import android.view.WindowManager.LayoutParams;
26import io.flutter.Log;
27import io.flutter.plugin.common.PluginRegistry;
28import io.flutter.plugin.platform.PlatformPlugin;
29import io.flutter.util.Preconditions;
30import io.flutter.view.FlutterMain;
31import io.flutter.view.FlutterNativeView;
32import io.flutter.view.FlutterRunArguments;
33import io.flutter.view.FlutterView;
34import java.util.ArrayList;
35
36/**
37 * Deprecated class that performs the actual work of tying Android {@link android.app.Activity}
38 * instances to Flutter.
39 *
40 * <p>This exists as a dedicated class (as opposed to being integrated directly into {@link
41 * FlutterActivity}) to facilitate applications that don't wish to subclass {@code FlutterActivity}.
42 * The most obvious example of when this may come in handy is if an application wishes to subclass
43 * the Android v4 support library's {@code FragmentActivity}.
44 *
45 * <p><b>Usage:</b>
46 *
47 * <p>To wire this class up to your activity, simply forward the events defined in {@link
48 * FlutterActivityEvents} from your activity to an instance of this class. Optionally, you can make
49 * your activity implement {@link PluginRegistry} and/or {@link
50 * io.flutter.view.FlutterView.Provider} and forward those methods to this class as well.
51 *
52 * @deprecated {@link io.flutter.embedding.android.FlutterActivity} is the new API that now replaces
53 * this class and {@link io.flutter.app.FlutterActivity}. See
54 * https://flutter.dev/go/android-project-migration for more migration details.
55 */
56@Deprecated
57public final class FlutterActivityDelegate
59 private static final String SPLASH_SCREEN_META_DATA_KEY =
60 "io.flutter.app.android.SplashScreenUntilFirstFrame";
61 private static final String TAG = "FlutterActivityDelegate";
62 private static final LayoutParams matchParent =
63 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
64
65 /**
66 * Specifies the mechanism by which Flutter views are created during the operation of a {@code
67 * FlutterActivityDelegate}.
68 *
69 * <p>A delegate's view factory will be consulted during {@link #onCreate(Bundle)}. If it returns
70 * {@code null}, then the delegate will fall back to instantiating a new full-screen {@code
71 * FlutterView}.
72 *
73 * <p>A delegate's native view factory will be consulted during {@link #onCreate(Bundle)}. If it
74 * returns {@code null}, then the delegate will fall back to instantiating a new {@code
75 * FlutterNativeView}. This is useful for applications to override to reuse the FlutterNativeView
76 * held e.g. by a pre-existing background service.
77 */
78 public interface ViewFactory {
80
82
83 /**
84 * Hook for subclasses to indicate that the {@code FlutterNativeView} returned by {@link
85 * #createFlutterNativeView()} should not be destroyed when this activity is destroyed.
86 *
87 * @return Whether the FlutterNativeView is retained.
88 */
90 }
91
92 private final Activity activity;
93 private final ViewFactory viewFactory;
94 private FlutterView flutterView;
95 private View launchView;
96
97 public FlutterActivityDelegate(Activity activity, ViewFactory viewFactory) {
98 this.activity = Preconditions.checkNotNull(activity);
99 this.viewFactory = Preconditions.checkNotNull(viewFactory);
100 }
101
102 @Override
104 return flutterView;
105 }
106
107 // The implementation of PluginRegistry forwards to flutterView.
108 @Override
109 public boolean hasPlugin(String key) {
110 return flutterView.getPluginRegistry().hasPlugin(key);
111 }
112
113 @Override
114 @SuppressWarnings("unchecked")
115 public <T> T valuePublishedByPlugin(String pluginKey) {
116 return (T) flutterView.getPluginRegistry().valuePublishedByPlugin(pluginKey);
117 }
118
119 @Override
120 public Registrar registrarFor(String pluginKey) {
121 return flutterView.getPluginRegistry().registrarFor(pluginKey);
122 }
123
124 @Override
126 int requestCode, String[] permissions, int[] grantResults) {
127 return flutterView
129 .onRequestPermissionsResult(requestCode, permissions, grantResults);
130 }
131
132 @Override
133 public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
134 return flutterView.getPluginRegistry().onActivityResult(requestCode, resultCode, data);
135 }
136
137 @Override
138 public void onCreate(Bundle savedInstanceState) {
139 Window window = activity.getWindow();
140 window.addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
141 window.setStatusBarColor(0x40000000);
142 window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
143
144 String[] args = getArgsFromIntent(activity.getIntent());
145 FlutterMain.ensureInitializationComplete(activity.getApplicationContext(), args);
146
147 flutterView = viewFactory.createFlutterView(activity);
148 if (flutterView == null) {
149 FlutterNativeView nativeView = viewFactory.createFlutterNativeView();
150 flutterView = new FlutterView(activity, null, nativeView);
151 flutterView.setLayoutParams(matchParent);
152 activity.setContentView(flutterView);
153 launchView = createLaunchView();
154 if (launchView != null) {
155 addLaunchView();
156 }
157 }
158
159 if (loadIntent(activity.getIntent())) {
160 return;
161 }
162
163 String appBundlePath = FlutterMain.findAppBundlePath();
164 if (appBundlePath != null) {
165 runBundle(appBundlePath);
166 }
167 }
168
169 @Override
170 public void onNewIntent(Intent intent) {
171 // Only attempt to reload the Flutter Dart code during development. Use
172 // the debuggable flag as an indicator that we are in development mode.
173 if (!isDebuggable() || !loadIntent(intent)) {
174 flutterView.getPluginRegistry().onNewIntent(intent);
175 }
176 }
177
178 private boolean isDebuggable() {
179 return (activity.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
180 }
181
182 @Override
183 public void onPause() {
184 Application app = (Application) activity.getApplicationContext();
185 if (app instanceof FlutterApplication) {
187 if (activity.equals(flutterApp.getCurrentActivity())) {
188 flutterApp.setCurrentActivity(null);
189 }
190 }
191 if (flutterView != null) {
192 flutterView.onPause();
193 }
194 }
195
196 @Override
197 public void onStart() {
198 if (flutterView != null) {
199 flutterView.onStart();
200 }
201 }
202
203 @Override
204 public void onResume() {
205 Application app = (Application) activity.getApplicationContext();
206 if (app instanceof FlutterApplication) {
208 flutterApp.setCurrentActivity(activity);
209 }
210 }
211
212 @Override
213 public void onStop() {
214 flutterView.onStop();
215 }
216
217 @Override
218 public void onPostResume() {
219 if (flutterView != null) {
220 flutterView.onPostResume();
221 }
222 }
223
224 @Override
225 public void onDestroy() {
226 Application app = (Application) activity.getApplicationContext();
227 if (app instanceof FlutterApplication) {
229 if (activity.equals(flutterApp.getCurrentActivity())) {
230 flutterApp.setCurrentActivity(null);
231 }
232 }
233 if (flutterView != null) {
234 final boolean detach =
235 flutterView.getPluginRegistry().onViewDestroy(flutterView.getFlutterNativeView());
236 if (detach || viewFactory.retainFlutterNativeView()) {
237 // Detach, but do not destroy the FlutterView if a plugin
238 // expressed interest in its FlutterNativeView.
239 flutterView.detach();
240 } else {
241 flutterView.destroy();
242 }
243 }
244 }
245
246 @Override
247 public boolean onBackPressed() {
248 if (flutterView != null) {
249 flutterView.popRoute();
250 return true;
251 }
252 return false;
253 }
254
255 @Override
256 public void onUserLeaveHint() {
257 flutterView.getPluginRegistry().onUserLeaveHint();
258 }
259
260 @Override
261 public void onWindowFocusChanged(boolean hasFocus) {
262 flutterView.getPluginRegistry().onWindowFocusChanged(hasFocus);
263 }
264
265 @Override
266 public void onTrimMemory(int level) {
267 // Use a trim level delivered while the application is running so the
268 // framework has a chance to react to the notification.
269 if (level == TRIM_MEMORY_RUNNING_LOW) {
270 flutterView.onMemoryPressure();
271 }
272 }
273
274 @Override
275 public void onLowMemory() {
276 flutterView.onMemoryPressure();
277 }
278
279 @Override
280 public void onConfigurationChanged(Configuration newConfig) {}
281
282 private static String[] getArgsFromIntent(Intent intent) {
283 // Before adding more entries to this list, consider that arbitrary
284 // Android applications can generate intents with extra data and that
285 // there are many security-sensitive args in the binary.
286 ArrayList<String> args = new ArrayList<>();
287 if (intent.getBooleanExtra("trace-startup", false)) {
288 args.add("--trace-startup");
289 }
290 if (intent.getBooleanExtra("start-paused", false)) {
291 args.add("--start-paused");
292 }
293 if (intent.getBooleanExtra("disable-service-auth-codes", false)) {
294 args.add("--disable-service-auth-codes");
295 }
296 if (intent.getBooleanExtra("use-test-fonts", false)) {
297 args.add("--use-test-fonts");
298 }
299 if (intent.getBooleanExtra("enable-dart-profiling", false)) {
300 args.add("--enable-dart-profiling");
301 }
302 if (intent.getBooleanExtra("enable-software-rendering", false)) {
303 args.add("--enable-software-rendering");
304 }
305 if (intent.getBooleanExtra("skia-deterministic-rendering", false)) {
306 args.add("--skia-deterministic-rendering");
307 }
308 if (intent.getBooleanExtra("trace-skia", false)) {
309 args.add("--trace-skia");
310 }
311 if (intent.getBooleanExtra("trace-systrace", false)) {
312 args.add("--trace-systrace");
313 }
314 if (intent.hasExtra("trace-to-file")) {
315 args.add("--trace-to-file=" + intent.getStringExtra("trace-to-file"));
316 }
317 if (intent.getBooleanExtra("dump-skp-on-shader-compilation", false)) {
318 args.add("--dump-skp-on-shader-compilation");
319 }
320 if (intent.getBooleanExtra("cache-sksl", false)) {
321 args.add("--cache-sksl");
322 }
323 if (intent.getBooleanExtra("purge-persistent-cache", false)) {
324 args.add("--purge-persistent-cache");
325 }
326 if (intent.getBooleanExtra("verbose-logging", false)) {
327 args.add("--verbose-logging");
328 }
329 int vmServicePort = intent.getIntExtra("vm-service-port", 0);
330 if (vmServicePort > 0) {
331 args.add("--vm-service-port=" + Integer.toString(vmServicePort));
332 } else {
333 // TODO(bkonyi): remove once flutter_tools no longer uses this option.
334 // See https://github.com/dart-lang/sdk/issues/50233
335 vmServicePort = intent.getIntExtra("observatory-port", 0);
336 if (vmServicePort > 0) {
337 args.add("--vm-service-port=" + Integer.toString(vmServicePort));
338 }
339 }
340 if (intent.getBooleanExtra("endless-trace-buffer", false)) {
341 args.add("--endless-trace-buffer");
342 }
343 // NOTE: all flags provided with this argument are subject to filtering
344 // based on a list of allowed flags in shell/common/switches.cc. If any
345 // flag provided is not allowed, the process will immediately terminate.
346 if (intent.hasExtra("dart-flags")) {
347 args.add("--dart-flags=" + intent.getStringExtra("dart-flags"));
348 }
349 if (!args.isEmpty()) {
350 String[] argsArray = new String[args.size()];
351 return args.toArray(argsArray);
352 }
353 return null;
354 }
355
356 private boolean loadIntent(Intent intent) {
357 String action = intent.getAction();
358 if (Intent.ACTION_RUN.equals(action)) {
359 String route = intent.getStringExtra("route");
360 String appBundlePath = intent.getDataString();
361 if (appBundlePath == null) {
362 // Fall back to the installation path if no bundle path was specified.
363 appBundlePath = FlutterMain.findAppBundlePath();
364 }
365 if (route != null) {
366 flutterView.setInitialRoute(route);
367 }
368
369 runBundle(appBundlePath);
370 return true;
371 }
372
373 return false;
374 }
375
376 private void runBundle(String appBundlePath) {
377 if (!flutterView.getFlutterNativeView().isApplicationRunning()) {
378 FlutterRunArguments args = new FlutterRunArguments();
379 args.bundlePath = appBundlePath;
380 args.entrypoint = "main";
381 flutterView.runFromBundle(args);
382 }
383 }
384
385 /**
386 * Creates a {@link View} containing the same {@link Drawable} as the one set as the {@code
387 * windowBackground} of the parent activity for use as a launch splash view.
388 *
389 * <p>Returns null if no {@code windowBackground} is set for the activity.
390 */
391 private View createLaunchView() {
392 if (!showSplashScreenUntilFirstFrame()) {
393 return null;
394 }
395 final Drawable launchScreenDrawable = getLaunchScreenDrawableFromActivityTheme();
396 if (launchScreenDrawable == null) {
397 return null;
398 }
399 final View view = new View(activity);
400 view.setLayoutParams(matchParent);
401 view.setBackground(launchScreenDrawable);
402 return view;
403 }
404
405 /**
406 * Extracts a {@link Drawable} from the parent activity's {@code windowBackground}.
407 *
408 * <p>{@code android:windowBackground} is specifically reused instead of a other attributes
409 * because the Android framework can display it fast enough when launching the app as opposed to
410 * anything defined in the Activity subclass.
411 *
412 * <p>Returns null if no {@code windowBackground} is set for the activity.
413 */
414 @SuppressWarnings("deprecation")
415 private Drawable getLaunchScreenDrawableFromActivityTheme() {
416 TypedValue typedValue = new TypedValue();
417 if (!activity.getTheme().resolveAttribute(android.R.attr.windowBackground, typedValue, true)) {
418 return null;
419 }
420 if (typedValue.resourceId == 0) {
421 return null;
422 }
423 try {
424 return activity.getResources().getDrawable(typedValue.resourceId);
425 } catch (NotFoundException e) {
426 Log.e(TAG, "Referenced launch screen windowBackground resource does not exist");
427 return null;
428 }
429 }
430
431 /**
432 * Let the user specify whether the activity's {@code windowBackground} is a launch screen and
433 * should be shown until the first frame via a <meta-data> tag in the activity.
434 */
435 private Boolean showSplashScreenUntilFirstFrame() {
436 try {
437 ActivityInfo activityInfo =
438 activity
439 .getPackageManager()
440 .getActivityInfo(activity.getComponentName(), PackageManager.GET_META_DATA);
441 Bundle metadata = activityInfo.metaData;
442 return metadata != null && metadata.getBoolean(SPLASH_SCREEN_META_DATA_KEY);
443 } catch (NameNotFoundException e) {
444 return false;
445 }
446 }
447
448 /**
449 * Show and then automatically animate out the launch view.
450 *
451 * <p>If a launch screen is defined in the user application's AndroidManifest.xml as the
452 * activity's {@code windowBackground}, display it on top of the {@link FlutterView} and remove
453 * the activity's {@code windowBackground}.
454 *
455 * <p>Fade it out and remove it when the {@link FlutterView} renders its first frame.
456 */
457 private void addLaunchView() {
458 if (launchView == null) {
459 return;
460 }
461
462 activity.addContentView(launchView, matchParent);
463 flutterView.addFirstFrameListener(
464 new FlutterView.FirstFrameListener() {
465 @Override
466 public void onFirstFrame() {
467 FlutterActivityDelegate.this
468 .launchView
469 .animate()
470 .alpha(0f)
471 // Use Android's default animation duration.
472 .setListener(
473 new AnimatorListenerAdapter() {
474 @Override
475 public void onAnimationEnd(Animator animation) {
476 // Views added to an Activity's addContentView is always added to its
477 // root FrameLayout.
478 ((ViewGroup) FlutterActivityDelegate.this.launchView.getParent())
479 .removeView(FlutterActivityDelegate.this.launchView);
480 FlutterActivityDelegate.this.launchView = null;
481 }
482 });
483
484 FlutterActivityDelegate.this.flutterView.removeFirstFrameListener(this);
485 }
486 });
487
488 // Resets the activity theme from the one containing the launch screen in the window
489 // background to a blank one since the launch screen is now in a view in front of the
490 // FlutterView.
491 //
492 // We can make this configurable if users want it.
493 activity.setTheme(android.R.style.Theme_Black_NoTitleBar);
494 }
495}
boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
boolean onActivityResult(int requestCode, int resultCode, Intent data)
void onConfigurationChanged(Configuration newConfig)
public< T > T valuePublishedByPlugin(String pluginKey)
FlutterActivityDelegate(Activity activity, ViewFactory viewFactory)
void setCurrentActivity(Activity mCurrentActivity)
boolean onActivityResult(int requestCode, int resultCode, Intent data)
public< T > T valuePublishedByPlugin(String pluginKey)
Registrar registrarFor(String pluginKey)
boolean onViewDestroy(FlutterNativeView view)
boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
static< T > T checkNotNull(T reference)
static String findAppBundlePath()
static void ensureInitializationComplete( @NonNull Context applicationContext, @Nullable String[] args)
void addFirstFrameListener(FirstFrameListener listener)
void setInitialRoute(String route)
FlutterNativeView getFlutterNativeView()
FlutterNativeView detach()
void runFromBundle(FlutterRunArguments args)
FlutterPluginRegistry getPluginRegistry()
GLFWwindow * window
Definition: main.cc:45
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
FlutterView createFlutterView(Context context)
void Log(const char *format,...) SK_PRINTF_LIKE(1
Definition: TestRunner.cpp:137
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace Enable an endless trace buffer The default is a ring buffer This is useful when very old events need to viewed For during application launch Memory usage will continue to grow indefinitely however route
Definition: switches.h:137
#define T
Definition: precompiler.cc:65
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:63