Skip to content

Commit

Permalink
View destroy listener (flutter#4347)
Browse files Browse the repository at this point in the history
[Android] Allow persisting a FlutterNativeView across activities.
  • Loading branch information
zanderso authored Nov 13, 2017
1 parent bb55d65 commit 7804e85
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 9 deletions.
19 changes: 19 additions & 0 deletions shell/platform/android/io/flutter/app/FlutterActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import io.flutter.app.FlutterActivityDelegate.ViewFactory;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import io.flutter.view.FlutterNativeView;
import io.flutter.view.FlutterView;

/**
Expand Down Expand Up @@ -47,6 +48,18 @@ public FlutterView createFlutterView(Context context) {
return null;
}

/**
* Hook for subclasses to customize the creation of the
* {@code FlutterNativeView}.
*
* <p>The default implementation returns {@code null}, which will cause the
* activity to use a newly instantiated native view object.</p>
*/
@Override
public FlutterNativeView createFlutterNativeView() {
return null;
}

@Override
public final boolean hasPlugin(String key) {
return pluginRegistry.hasPlugin(key);
Expand All @@ -68,6 +81,12 @@ protected void onCreate(Bundle savedInstanceState) {
eventDelegate.onCreate(savedInstanceState);
}

@Override
protected void onResume() {
super.onResume();
eventDelegate.onResume();
}

@Override
protected void onDestroy() {
eventDelegate.onDestroy();
Expand Down
72 changes: 67 additions & 5 deletions shell/platform/android/io/flutter/app/FlutterActivityDelegate.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
Expand Down Expand Up @@ -34,6 +35,7 @@
import io.flutter.plugin.platform.PlatformPlugin;
import io.flutter.util.Preconditions;
import io.flutter.view.FlutterMain;
import io.flutter.view.FlutterNativeView;
import io.flutter.view.FlutterView;
import io.flutter.view.TextureRegistry;

Expand Down Expand Up @@ -75,9 +77,16 @@ public final class FlutterActivityDelegate
* <p>A delegate's view factory will be consulted during
* {@link #onCreate(Bundle)}. If it returns {@code null}, then the delegate
* will fall back to instantiating a new full-screen {@code FlutterView}.</p>
*
* <p>A delegate's native view factory will be consulted during
* {@link #onCreate(Bundle)}. If it returns {@code null}, then the delegate
* will fall back to instantiating a new {@code FlutterNativeView}. This is
* useful for applications to override to reuse the FlutterNativeView held
* e.g. by a pre-existing background service.</p>
*/
public interface ViewFactory {
FlutterView createFlutterView(Context context);
FlutterNativeView createFlutterNativeView();
}

private final Activity activity;
Expand All @@ -87,6 +96,7 @@ public interface ViewFactory {
private final List<ActivityResultListener> activityResultListeners = new ArrayList<>(0);
private final List<NewIntentListener> newIntentListeners = new ArrayList<>(0);
private final List<UserLeaveHintListener> userLeaveHintListeners = new ArrayList<>(0);
private final List<ViewDestroyListener> viewDestroyListeners = new ArrayList<>(0);

private FlutterView flutterView;
private View launchView;
Expand Down Expand Up @@ -156,7 +166,8 @@ public void onCreate(Bundle savedInstanceState) {

flutterView = viewFactory.createFlutterView(activity);
if (flutterView == null) {
flutterView = new FlutterView(activity);
FlutterNativeView nativeView = viewFactory.createFlutterNativeView();
flutterView = new FlutterView(activity, null, nativeView);
flutterView.setLayoutParams(matchParent);
activity.setContentView(flutterView);
launchView = createLaunchView();
Expand All @@ -165,12 +176,18 @@ public void onCreate(Bundle savedInstanceState) {
}
}

if (loadIntent(activity.getIntent())) {
// When an activity is created for the first time, we direct the
// FlutterView to re-use a pre-existing Isolate rather than create a new
// one. This is so that an Isolate coming in from the ViewFactory is
// used.
final boolean reuseIsolate = true;

if (loadIntent(activity.getIntent(), reuseIsolate)) {
return;
}
String appBundlePath = FlutterMain.findAppBundlePath(activity.getApplicationContext());
if (appBundlePath != null) {
flutterView.runFromBundle(appBundlePath, null);
flutterView.runFromBundle(appBundlePath, null, "main", reuseIsolate);
}
}

Expand All @@ -193,13 +210,29 @@ private boolean isDebuggable() {

@Override
public void onPause() {
Application app = (Application) activity.getApplicationContext();
if (app instanceof FlutterApplication) {
FlutterApplication flutterApp = (FlutterApplication) app;
if (this.equals(flutterApp.getCurrentActivity())) {
Log.i(TAG, "onPause setting current activity to null");
flutterApp.setCurrentActivity(null);
}
}
if (flutterView != null) {
flutterView.onPause();
}
}

@Override
public void onResume() {
Application app = (Application) activity.getApplicationContext();
if (app instanceof FlutterApplication) {
FlutterApplication flutterApp = (FlutterApplication) app;
Log.i(TAG, "onResume setting current activity to this");
flutterApp.setCurrentActivity(activity);
} else {
Log.i(TAG, "onResume app wasn't a FlutterApplication!!");
}
}

@Override
Expand All @@ -211,8 +244,26 @@ public void onPostResume() {

@Override
public void onDestroy() {
Application app = (Application) activity.getApplicationContext();
if (app instanceof FlutterApplication) {
FlutterApplication flutterApp = (FlutterApplication) app;
if (this.equals(flutterApp.getCurrentActivity())) {
Log.i(TAG, "onDestroy setting current activity to null");
flutterApp.setCurrentActivity(null);
}
}
if (flutterView != null) {
flutterView.destroy();
boolean destroy = true;
for (ViewDestroyListener listener : viewDestroyListeners) {
destroy = destroy && !listener.onViewDestroy(flutterView.getFlutterNativeView());
}
if (destroy) {
flutterView.destroy();
} else {
// Detach, but do not destroy the FlutterView if a plugin
// expressed interest in its FlutterNativeView.
flutterView.detach();
}
}
}

Expand Down Expand Up @@ -278,6 +329,11 @@ private static String[] getArgsFromIntent(Intent intent) {
}

private boolean loadIntent(Intent intent) {
final boolean reuseIsolate = false;
return loadIntent(intent, reuseIsolate);
}

private boolean loadIntent(Intent intent, boolean reuseIsolate) {
String action = intent.getAction();
if (Intent.ACTION_RUN.equals(action)) {
String route = intent.getStringExtra("route");
Expand All @@ -290,7 +346,7 @@ private boolean loadIntent(Intent intent) {
if (route != null) {
flutterView.setInitialRoute(route);
}
flutterView.runFromBundle(appBundlePath, intent.getStringExtra("snapshot"));
flutterView.runFromBundle(appBundlePath, intent.getStringExtra("snapshot"), "main", reuseIsolate);
return true;
}

Expand Down Expand Up @@ -476,5 +532,11 @@ public Registrar addUserLeaveHintListener(UserLeaveHintListener listener) {
userLeaveHintListeners.add(listener);
return this;
}

@Override
public Registrar addViewDestroyListener(ViewDestroyListener listener) {
viewDestroyListeners.add(listener);
return this;
}
}
}
9 changes: 9 additions & 0 deletions shell/platform/android/io/flutter/app/FlutterApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package io.flutter.app;

import android.app.Activity;
import android.app.Application;

import io.flutter.view.FlutterMain;
Expand All @@ -18,4 +19,12 @@ public void onCreate() {
super.onCreate();
FlutterMain.startInitialization(this);
}

private Activity mCurrentActivity = null;
public Activity getCurrentActivity() {
return mCurrentActivity;
}
public void setCurrentActivity(Activity mCurrentActivity) {
this.mCurrentActivity = mCurrentActivity;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import io.flutter.app.FlutterActivityDelegate.ViewFactory;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import io.flutter.view.FlutterNativeView;
import io.flutter.view.FlutterView;

/**
Expand Down Expand Up @@ -58,6 +59,11 @@ public FlutterView createFlutterView(Context context) {
return null;
}

@Override
public FlutterNativeView createFlutterNativeView() {
return null;
}

@Override
public final boolean hasPlugin(String key) {
return pluginRegistry.hasPlugin(key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import android.app.Activity;
import android.content.Intent;
import io.flutter.view.FlutterNativeView;
import io.flutter.view.FlutterView;
import io.flutter.view.TextureRegistry;

Expand Down Expand Up @@ -140,6 +141,15 @@ interface Registrar {
* @return this {@link Registrar}.
*/
Registrar addUserLeaveHintListener(UserLeaveHintListener listener);

/**
* Adds a callback allowing the plugin to take part in handling incoming
* calls to {@link Activity#onDestroy()}.
*
* @param listener a {@link ViewDestroyListener} callback.
* @return this {@link Registrar}.
*/
Registrar addViewDestroyListener(ViewDestroyListener listener);
}

/**
Expand Down Expand Up @@ -182,4 +192,13 @@ interface NewIntentListener {
interface UserLeaveHintListener {
void onUserLeaveHint();
}

/**
* Delegate interface for handling an {@link Activity}'s onDestroy
* method being called. A plugin that implements this interface can
* adopt the FlutterNativeView by retaining a reference and returning true.
*/
interface ViewDestroyListener {
boolean onViewDestroy(FlutterNativeView view);
}
}
16 changes: 13 additions & 3 deletions shell/platform/android/io/flutter/view/FlutterNativeView.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import java.util.HashMap;
import java.util.Map;

class FlutterNativeView implements BinaryMessenger {
public class FlutterNativeView implements BinaryMessenger {
private static final String TAG = "FlutterNativeView";

private final Map<String, BinaryMessageHandler> mMessageHandlers;
Expand All @@ -21,23 +21,32 @@ class FlutterNativeView implements BinaryMessenger {
private long mNativePlatformView;
private FlutterView mFlutterView;

FlutterNativeView(FlutterView flutterView) {
public FlutterNativeView(FlutterView flutterView) {
mFlutterView = flutterView;
attach(this);
assertAttached();
mMessageHandlers = new HashMap<>();
}

FlutterNativeView() {
public FlutterNativeView() {
this(null);
}

public void detach() {
mFlutterView = null;
nativeDetach(mNativePlatformView);
}

public void destroy() {
mFlutterView = null;
nativeDestroy(mNativePlatformView);
mNativePlatformView = 0;
}

public void setFlutterView(FlutterView flutterView) {
mFlutterView = flutterView;
}

public boolean isAttached() {
return mNativePlatformView != 0;
}
Expand Down Expand Up @@ -168,6 +177,7 @@ private void onFirstFrame() {

private static native long nativeAttach(FlutterNativeView view);
private static native void nativeDestroy(long nativePlatformViewAndroid);
private static native void nativeDetach(long nativePlatformViewAndroid);

private static native void nativeRunBundleAndSnapshot(long nativePlatformViewAndroid,
String bundlePath,
Expand Down
29 changes: 28 additions & 1 deletion shell/platform/android/io/flutter/view/FlutterView.java
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ public FlutterView(Context context) {
}

public FlutterView(Context context, AttributeSet attrs) {
this(context, attrs, null);
}

public FlutterView(Context context, AttributeSet attrs, FlutterNativeView nativeView) {
super(context, attrs);

mIsSoftwareRenderingEnabled = nativeGetIsSoftwareRenderingEnabled();
Expand All @@ -124,7 +128,12 @@ public FlutterView(Context context, AttributeSet attrs) {
setFocusable(true);
setFocusableInTouchMode(true);

mNativeView = new FlutterNativeView(this);
if (nativeView == null) {
mNativeView = new FlutterNativeView(this);
} else {
mNativeView = nativeView;
mNativeView.setFlutterView(this);
}

int color = 0xFF000000;
TypedValue typedValue = new TypedValue();
Expand Down Expand Up @@ -235,6 +244,10 @@ public boolean onKeyDown(int keyCode, KeyEvent event) {
return super.onKeyDown(keyCode, event);
}

public FlutterNativeView getFlutterNativeView() {
return mNativeView;
}

public void addActivityLifecycleListener(ActivityLifecycleListener listener) {
mActivityLifecycleListeners.add(listener);
}
Expand Down Expand Up @@ -310,6 +323,20 @@ float getDevicePixelRatio() {
return mMetrics.devicePixelRatio;
}

public FlutterNativeView detach() {
if (!isAttached())
return null;
if (mDiscoveryReceiver != null) {
getContext().unregisterReceiver(mDiscoveryReceiver);
}
getHolder().removeCallback(mSurfaceCallback);
mNativeView.detach();

FlutterNativeView view = mNativeView;
mNativeView = null;
return view;
}

public void destroy() {
if (!isAttached())
return;
Expand Down
Loading

0 comments on commit 7804e85

Please sign in to comment.