diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index dc342fc1df265875017f0a7fcc382096c7428fc1..a6c6bf96bbbc3455e9f93d18e30ec4b45b4e6856 100755 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,8 +1,6 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.idlefish.flutterboost"> - <application> - <activity android:name="com.idlefish.flutterboost.containers.BoostFlutterDefaultActivity" /> - </application> + </manifest> diff --git a/android/src/main/java/com/idlefish/flutterboost/BoostPluginRegistry.java b/android/src/main/java/com/idlefish/flutterboost/BoostPluginRegistry.java index 942dd5ebb600ba95790d9bf5e64ed86aa897f177..8cf001db72cf8a29bc4f5c6da9292207243da26c 100644 --- a/android/src/main/java/com/idlefish/flutterboost/BoostPluginRegistry.java +++ b/android/src/main/java/com/idlefish/flutterboost/BoostPluginRegistry.java @@ -1,144 +1,142 @@ package com.idlefish.flutterboost; -import android.app.Activity; -import android.content.Context; -import androidx.annotation.Nullable; -import com.idlefish.flutterboost.interfaces.IContainerRecord; -import io.flutter.app.FlutterPluginRegistry; + +import io.flutter.Log; import io.flutter.embedding.engine.FlutterEngine; -import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry; -import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.PluginRegistry; -import io.flutter.plugin.platform.PlatformViewRegistry; -import io.flutter.view.FlutterView; -import io.flutter.view.TextureRegistry; -import java.lang.ref.WeakReference; +import java.util.*; -public class BoostPluginRegistry extends ShimPluginRegistry { - protected WeakReference<Activity> mCurrentActivityRef; - private FlutterEngine mEngine; - private Context mContext; - public BoostPluginRegistry(FlutterEngine engine, Context context) { - super(engine); - mEngine = engine; - mContext=context; - } +public class BoostPluginRegistry implements PluginRegistry { + private static final String TAG = "ShimPluginRegistry"; + private final FlutterEngine flutterEngine; + private final Map<String, Object> pluginMap = new HashMap(); + private final BoostRegistrarAggregate shimRegistrarAggregate; + + + public BoostRegistrarAggregate getRegistrarAggregate() { + return shimRegistrarAggregate; + } + + + public BoostPluginRegistry(FlutterEngine flutterEngine) { + this.flutterEngine = flutterEngine; + this.shimRegistrarAggregate = new BoostRegistrarAggregate(); + this.flutterEngine.getPlugins().add(this.shimRegistrarAggregate); + } - public PluginRegistry.Registrar registrarFor(String pluginKey) { - return new BoostRegistrar(mEngine, super.registrarFor(pluginKey)); + public Registrar registrarFor(String pluginKey) { + Log.v("ShimPluginRegistry", "Creating plugin Registrar for '" + pluginKey + "'"); + if (this.pluginMap.containsKey(pluginKey)) { + throw new IllegalStateException("Plugin key " + pluginKey + " is already in use"); + } else { + this.pluginMap.put(pluginKey, (Object) null); + BoostRegistrar registrar = new BoostRegistrar(pluginKey, this.pluginMap); + this.shimRegistrarAggregate.addPlugin(registrar); + return registrar; } + } - public void currentActivity(@Nullable Activity activity) { - mCurrentActivityRef = new WeakReference<>(activity); + public boolean hasPlugin(String pluginKey) { + return this.pluginMap.containsKey(pluginKey); } - public class BoostRegistrar implements PluginRegistry.Registrar { + public Object valuePublishedByPlugin(String pluginKey) { + return this.pluginMap.get(pluginKey); + } - private final PluginRegistry.Registrar mRegistrar; - private final FlutterEngine mEngine; + public static class BoostRegistrarAggregate implements FlutterPlugin, ActivityAware { + private final Set<BoostRegistrar> shimRegistrars; + private FlutterPluginBinding flutterPluginBinding; + private ActivityPluginBinding activityPluginBinding; - BoostRegistrar(FlutterEngine engine, PluginRegistry.Registrar registrar) { - mRegistrar = registrar; - mEngine = engine; + public ActivityPluginBinding getActivityPluginBinding() { + return activityPluginBinding; } - @Override - public Activity activity() { - Activity activity; - IContainerRecord record; + private BoostRegistrarAggregate() { + this.shimRegistrars = new HashSet(); + } - record = NewFlutterBoost.instance().containerManager().getCurrentTopRecord(); - if (record == null) { - record = NewFlutterBoost.instance().containerManager().getLastGenerateRecord(); + public void addPlugin(BoostRegistrar shimRegistrar) { + this.shimRegistrars.add(shimRegistrar); + if (this.flutterPluginBinding != null) { + shimRegistrar.onAttachedToEngine(this.flutterPluginBinding); } - if (record == null) { - activity = NewFlutterBoost.instance().currentActivity(); - } else { - activity = record.getContainer().getContextActivity(); + if (this.activityPluginBinding != null) { + shimRegistrar.onAttachedToActivity(this.activityPluginBinding); } - if (activity == null && mCurrentActivityRef != null) { - activity = mCurrentActivityRef.get(); - } + } + + public void onAttachedToEngine(FlutterPluginBinding binding) { + this.flutterPluginBinding = binding; + Iterator var2 = this.shimRegistrars.iterator(); - if (activity == null) { - throw new RuntimeException("current has no valid Activity yet"); + while (var2.hasNext()) { + BoostRegistrar shimRegistrar = (BoostRegistrar) var2.next(); + shimRegistrar.onAttachedToEngine(binding); } - return activity; } - @Override - public Context context() { - return BoostPluginRegistry.this.mContext; - } + public void onDetachedFromEngine(FlutterPluginBinding binding) { + Iterator var2 = this.shimRegistrars.iterator(); - @Override - public Context activeContext() { - return BoostPluginRegistry.this.mContext; - } + while (var2.hasNext()) { + BoostRegistrar shimRegistrar = (BoostRegistrar) var2.next(); + shimRegistrar.onDetachedFromEngine(binding); + } - @Override - public BinaryMessenger messenger() { - return mEngine.getDartExecutor(); + this.flutterPluginBinding = null; } - @Override - public TextureRegistry textures() { - return mEngine.getRenderer(); - } + public void onAttachedToActivity(ActivityPluginBinding binding) { + this.activityPluginBinding = binding; + Iterator var2 = this.shimRegistrars.iterator(); - @Override - public PlatformViewRegistry platformViewRegistry() { - return mEngine.getPlatformViewsController().getRegistry(); - } + while (var2.hasNext()) { + BoostRegistrar shimRegistrar = (BoostRegistrar) var2.next(); + shimRegistrar.onAttachedToActivity(binding); + } - @Override - public FlutterView view() { - throw new RuntimeException("should not use!!!"); } - @Override - public String lookupKeyForAsset(String s) { - return mRegistrar.lookupKeyForAsset(s); - } + public void onDetachedFromActivityForConfigChanges() { + Iterator var1 = this.shimRegistrars.iterator(); - @Override - public String lookupKeyForAsset(String s, String s1) { - return mRegistrar.lookupKeyForAsset(s, s1); - } + while (var1.hasNext()) { + BoostRegistrar shimRegistrar = (BoostRegistrar) var1.next(); + shimRegistrar.onDetachedFromActivity(); + } - @Override - public PluginRegistry.Registrar publish(Object o) { - return mRegistrar.publish(o); + this.activityPluginBinding = null; } - @Override - public PluginRegistry.Registrar addRequestPermissionsResultListener(PluginRegistry.RequestPermissionsResultListener requestPermissionsResultListener) { - return mRegistrar.addRequestPermissionsResultListener(requestPermissionsResultListener); - } + public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { + Iterator var2 = this.shimRegistrars.iterator(); - @Override - public PluginRegistry.Registrar addActivityResultListener(PluginRegistry.ActivityResultListener activityResultListener) { - return mRegistrar.addActivityResultListener(activityResultListener); - } + while (var2.hasNext()) { + BoostRegistrar shimRegistrar = (BoostRegistrar) var2.next(); + shimRegistrar.onReattachedToActivityForConfigChanges(binding); + } - @Override - public PluginRegistry.Registrar addNewIntentListener(PluginRegistry.NewIntentListener newIntentListener) { - return mRegistrar.addNewIntentListener(newIntentListener); } - @Override - public PluginRegistry.Registrar addUserLeaveHintListener(PluginRegistry.UserLeaveHintListener userLeaveHintListener) { - return mRegistrar.addUserLeaveHintListener(userLeaveHintListener); - } + public void onDetachedFromActivity() { + Iterator var1 = this.shimRegistrars.iterator(); + + while (var1.hasNext()) { + BoostRegistrar shimRegistrar = (BoostRegistrar) var1.next(); + shimRegistrar.onDetachedFromActivity(); + } - @Override - public PluginRegistry.Registrar addViewDestroyListener(PluginRegistry.ViewDestroyListener viewDestroyListener) { - return mRegistrar.addViewDestroyListener(viewDestroyListener); } } - } +} + diff --git a/android/src/main/java/com/idlefish/flutterboost/BoostRegistrar.java b/android/src/main/java/com/idlefish/flutterboost/BoostRegistrar.java new file mode 100644 index 0000000000000000000000000000000000000000..2a612e9d1b8a70c029d0754b30059ec86571bca3 --- /dev/null +++ b/android/src/main/java/com/idlefish/flutterboost/BoostRegistrar.java @@ -0,0 +1,203 @@ +package com.idlefish.flutterboost; + +import android.app.Activity; +import android.content.Context; +import android.support.annotation.NonNull; +import io.flutter.Log; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.PluginRegistry.ActivityResultListener; +import io.flutter.plugin.common.PluginRegistry.NewIntentListener; +import io.flutter.plugin.common.PluginRegistry.Registrar; +import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener; +import io.flutter.plugin.common.PluginRegistry.UserLeaveHintListener; +import io.flutter.plugin.common.PluginRegistry.ViewDestroyListener; +import io.flutter.plugin.platform.PlatformViewRegistry; +import io.flutter.view.FlutterMain; +import io.flutter.view.FlutterNativeView; +import io.flutter.view.FlutterView; +import io.flutter.view.TextureRegistry; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +class BoostRegistrar implements Registrar, FlutterPlugin, ActivityAware { + private static final String TAG = "ShimRegistrar"; + private final Map<String, Object> globalRegistrarMap; + private final String pluginId; + private final Set<ViewDestroyListener> viewDestroyListeners = new HashSet(); + private final Set<RequestPermissionsResultListener> requestPermissionsResultListeners = new HashSet(); + private final Set<ActivityResultListener> activityResultListeners = new HashSet(); + private final Set<NewIntentListener> newIntentListeners = new HashSet(); + private final Set<UserLeaveHintListener> userLeaveHintListeners = new HashSet(); + private FlutterPluginBinding pluginBinding; + private ActivityPluginBinding activityPluginBinding; + + public BoostRegistrar(@NonNull String pluginId, @NonNull Map<String, Object> globalRegistrarMap) { + this.pluginId = pluginId; + this.globalRegistrarMap = globalRegistrarMap; + } + + public Activity activity() { + if(this.activityPluginBinding != null){ + return this.activityPluginBinding.getActivity(); + } + if(NewFlutterBoost.instance().currentActivity()!=null){ + return NewFlutterBoost.instance().currentActivity(); + } + return null; + } + + public Context context() { + return this.pluginBinding != null ? this.pluginBinding.getApplicationContext() : null; + } + + public Context activeContext() { + return (Context)(this.activityPluginBinding == null ? this.context() : this.activity()); + } + + public BinaryMessenger messenger() { + return this.pluginBinding != null ? this.pluginBinding.getFlutterEngine().getDartExecutor() : null; + } + + public TextureRegistry textures() { + return this.pluginBinding != null ? this.pluginBinding.getFlutterEngine().getRenderer() : null; + } + + public PlatformViewRegistry platformViewRegistry() { + return this.pluginBinding != null ? this.pluginBinding.getFlutterEngine().getPlatformViewsController().getRegistry() : null; + } + + public FlutterView view() { + throw new UnsupportedOperationException("The new embedding does not support the old FlutterView."); + } + + public String lookupKeyForAsset(String asset) { + return FlutterMain.getLookupKeyForAsset(asset); + } + + public String lookupKeyForAsset(String asset, String packageName) { + return FlutterMain.getLookupKeyForAsset(asset, packageName); + } + + public Registrar publish(Object value) { + this.globalRegistrarMap.put(this.pluginId, value); + return this; + } + + public Registrar addRequestPermissionsResultListener(RequestPermissionsResultListener listener) { + this.requestPermissionsResultListeners.add(listener); + if (this.activityPluginBinding != null) { + this.activityPluginBinding.addRequestPermissionsResultListener(listener); + } + + return this; + } + + public Registrar addActivityResultListener(ActivityResultListener listener) { + this.activityResultListeners.add(listener); + if (this.activityPluginBinding != null) { + this.activityPluginBinding.addActivityResultListener(listener); + } + + return this; + } + + public Registrar addNewIntentListener(NewIntentListener listener) { + this.newIntentListeners.add(listener); + if (this.activityPluginBinding != null) { + this.activityPluginBinding.addOnNewIntentListener(listener); + } + + return this; + } + + public Registrar addUserLeaveHintListener(UserLeaveHintListener listener) { + this.userLeaveHintListeners.add(listener); + if (this.activityPluginBinding != null) { + this.activityPluginBinding.addOnUserLeaveHintListener(listener); + } + + return this; + } + + @NonNull + public Registrar addViewDestroyListener(@NonNull ViewDestroyListener listener) { + this.viewDestroyListeners.add(listener); + return this; + } + + public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { + Log.v("ShimRegistrar", "Attached to FlutterEngine."); + this.pluginBinding = binding; + } + + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + Log.v("ShimRegistrar", "Detached from FlutterEngine."); + Iterator var2 = this.viewDestroyListeners.iterator(); + + while(var2.hasNext()) { + ViewDestroyListener listener = (ViewDestroyListener)var2.next(); + listener.onViewDestroy((FlutterNativeView)null); + } + + this.pluginBinding = null; + } + + public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { + Log.v("ShimRegistrar", "Attached to an Activity."); + this.activityPluginBinding = binding; + this.addExistingListenersToActivityPluginBinding(); + } + + public void onDetachedFromActivityForConfigChanges() { + Log.v("ShimRegistrar", "Detached from an Activity for config changes."); + this.activityPluginBinding = null; + } + + public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { + Log.v("ShimRegistrar", "Reconnected to an Activity after config changes."); + this.activityPluginBinding = binding; + this.addExistingListenersToActivityPluginBinding(); + } + + public void onDetachedFromActivity() { + Log.v("ShimRegistrar", "Detached from an Activity."); + this.activityPluginBinding = null; + } + + private void addExistingListenersToActivityPluginBinding() { + Iterator var1 = this.requestPermissionsResultListeners.iterator(); + + while(var1.hasNext()) { + RequestPermissionsResultListener listener = (RequestPermissionsResultListener)var1.next(); + this.activityPluginBinding.addRequestPermissionsResultListener(listener); + } + + var1 = this.activityResultListeners.iterator(); + + while(var1.hasNext()) { + ActivityResultListener listener = (ActivityResultListener)var1.next(); + this.activityPluginBinding.addActivityResultListener(listener); + } + + var1 = this.newIntentListeners.iterator(); + + while(var1.hasNext()) { + NewIntentListener listener = (NewIntentListener)var1.next(); + this.activityPluginBinding.addOnNewIntentListener(listener); + } + + var1 = this.userLeaveHintListeners.iterator(); + + while(var1.hasNext()) { + UserLeaveHintListener listener = (UserLeaveHintListener)var1.next(); + this.activityPluginBinding.addOnUserLeaveHintListener(listener); + } + + } +} diff --git a/android/src/main/java/com/idlefish/flutterboost/ContainerRecord.java b/android/src/main/java/com/idlefish/flutterboost/ContainerRecord.java index 5606307737a51e446428ed6bbbb0ec26bb34bfec..8b3e502ead7b9fee0a0b27de6688bd61faef51e0 100755 --- a/android/src/main/java/com/idlefish/flutterboost/ContainerRecord.java +++ b/android/src/main/java/com/idlefish/flutterboost/ContainerRecord.java @@ -175,6 +175,7 @@ public class ContainerRecord implements IContainerRecord { @Override public void onContainerResult(int requestCode, int resultCode, Map<String, Object> result) { + mManager.setContainerResult(this, requestCode,resultCode, result); } @@ -192,41 +193,6 @@ public class ContainerRecord implements IContainerRecord { public void onLowMemory() { } -// -// @Override -// public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { -// mContainer.getBoostFlutterView().onRequestPermissionsResult(requestCode, permissions, grantResults); -// } -// -// @Override -// public void onNewIntent(Intent intent) { -// mContainer.getBoostFlutterView().onNewIntent(intent); -// } -// -// @Override -// public void onActivityResult(int requestCode, int resultCode, Intent data) { -// mContainer.getBoostFlutterView().onActivityResult(requestCode, resultCode, data); -// } -// -// @Override -// public void onContainerResult(int requestCode, int resultCode, Map<String, Object> result) { -// mManager.setContainerResult(this, requestCode,resultCode, result); -// } -// -// @Override -// public void onUserLeaveHint() { -// mContainer.getBoostFlutterView().onUserLeaveHint(); -// } -// -// @Override -// public void onTrimMemory(int level) { -// mContainer.getBoostFlutterView().onTrimMemory(level); -// } -// -// @Override -// public void onLowMemory() { -// mContainer.getBoostFlutterView().onLowMemory(); -// } private class MethodChannelProxy { diff --git a/android/src/main/java/com/idlefish/flutterboost/FlutterViewContainerManager.java b/android/src/main/java/com/idlefish/flutterboost/FlutterViewContainerManager.java index a148ac695d3bce892a171550becbedc141753128..815e14c9f69d38b7c089955f57436a97a457f87d 100755 --- a/android/src/main/java/com/idlefish/flutterboost/FlutterViewContainerManager.java +++ b/android/src/main/java/com/idlefish/flutterboost/FlutterViewContainerManager.java @@ -84,6 +84,13 @@ public class FlutterViewContainerManager implements IContainerManager { void removeRecord(IContainerRecord record) { mRecordStack.remove(record); mRecordMap.remove(record.getContainer()); + if(mRecordStack.empty()){ + if( NewFlutterBoost.instance().platform().whenEngineDestroy()== NewFlutterBoost.ConfigBuilder.All_FLUTTER_ACTIVITY_DESTROY){ + NewFlutterBoost.instance().boostDestroy(); + } + } + + } void setContainerResult(IContainerRecord record,int requestCode, int resultCode, Map<String,Object> result) { diff --git a/android/src/main/java/com/idlefish/flutterboost/NewFlutterBoost.java b/android/src/main/java/com/idlefish/flutterboost/NewFlutterBoost.java index 59087422b6999df433086d44e0b5ef47b88c0b27..0ee180adb9b27a5744eeaefbc0bd525dfd7c73f9 100644 --- a/android/src/main/java/com/idlefish/flutterboost/NewFlutterBoost.java +++ b/android/src/main/java/com/idlefish/flutterboost/NewFlutterBoost.java @@ -10,9 +10,14 @@ import com.idlefish.flutterboost.interfaces.*; import io.flutter.Log; import io.flutter.embedding.android.FlutterView; import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; +import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry; +import io.flutter.plugin.common.PluginRegistry; import io.flutter.view.FlutterMain; +import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; @@ -21,12 +26,11 @@ public class NewFlutterBoost { private Platform mPlatform; private FlutterViewContainerManager mManager; - + private FlutterEngine mEngine; private Activity mCurrentActiveActivity; - private BoostPluginRegistry mRegistry; + private PluginRegistry mRegistry; static NewFlutterBoost sInstance = null; - public static NewFlutterBoost instance() { if (sInstance == null) { sInstance = new NewFlutterBoost(); @@ -41,19 +45,18 @@ public class NewFlutterBoost { mManager = new FlutterViewContainerManager(); - mRegistry = new BoostPluginRegistry(this.engineProvider(), - mPlatform.getApplication()); platform.getApplication().registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { Log.e("bbbb1", "xxxxx"); - + mCurrentActiveActivity=activity; if (mPlatform.whenEngineStart() == ConfigBuilder.ANY_ACTIVITY_CREATED) { Log.e("bbbb2", "xxxxx"); - mRegistry.currentActivity(activity); - doInitialFlutterViewRun(mPlatform,mRegistry); + + doInitialFlutter(); + } } @@ -62,7 +65,7 @@ public class NewFlutterBoost { if (mCurrentActiveActivity == null) { Debuger.log("Application entry foreground"); - if (NewFlutterBoost.instance().engineProvider() != null) { + if (createEngine() != null) { HashMap<String, String> map = new HashMap<>(); map.put("type", "foreground"); channel().sendEvent("lifecycle", map); @@ -86,7 +89,7 @@ public class NewFlutterBoost { if (mCurrentActiveActivity == activity) { Debuger.log("Application entry background"); - if (mPlatform.engineProvider() != null) { + if (createEngine() != null) { HashMap<String, String> map = new HashMap<>(); map.put("type", "background"); channel().sendEvent("lifecycle", map); @@ -105,7 +108,7 @@ public class NewFlutterBoost { if (mCurrentActiveActivity == activity) { Debuger.log("Application entry background"); - if (mPlatform.engineProvider() != null) { + if (createEngine() != null) { HashMap<String, String> map = new HashMap<>(); map.put("type", "background"); channel().sendEvent("lifecycle", map); @@ -117,39 +120,46 @@ public class NewFlutterBoost { if (mPlatform.whenEngineStart() == ConfigBuilder.IMMEDIATELY) { - doInitialFlutterViewRun(mPlatform,mRegistry); + doInitialFlutter(); } } - private void doInitialFlutterViewRun(Platform platform,BoostPluginRegistry registry) { - // Don't attempt to start a FlutterEngine if we're using a cached FlutterEngine. -// if (host.getCachedEngineId() != null) { -// return; -// } - FlutterEngine flutterEngine = platform.engineProvider(); + public void doInitialFlutter() { + + + if(mEngine!=null) return; + FlutterEngine flutterEngine = createEngine(); + if(mPlatform.lifecycleListener!=null){ + mPlatform.lifecycleListener.onEngineCreated(); + } if (flutterEngine.getDartExecutor().isExecutingDart()) { // No warning is logged because this situation will happen on every config // change if the developer does not choose to retain the Fragment instance. // So this is expected behavior in many cases. return; } - platform.registerPlugins(registry); + + // The engine needs to receive the Flutter app's initial route before executing any // Dart code to ensure that the initial route arrives in time to be applied. - if (platform.initialRoute() != null) { - flutterEngine.getNavigationChannel().setInitialRoute(platform.initialRoute()); + if (mPlatform.initialRoute() != null) { + flutterEngine.getNavigationChannel().setInitialRoute(mPlatform.initialRoute()); } // Configure the Dart entrypoint and execute it. DartExecutor.DartEntrypoint entrypoint = new DartExecutor.DartEntrypoint( FlutterMain.findAppBundlePath(), "main" ); + flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint); + mRegistry = new BoostPluginRegistry(createEngine()); + registerPlugins(); + } @@ -161,20 +171,28 @@ public class NewFlutterBoost { public static int ANY_ACTIVITY_CREATED = 1; //当有任何Activity创建时,å¯åŠ¨å¼•æ“Ž + public static int FLUTTER_ACTIVITY_CREATED = 2; //当有flutterActivity创建时,å¯åŠ¨å¼•æ“Ž + + + public static int APP_EXit = 0; //所有flutter Activity destory 时,销æ¯engine + public static int All_FLUTTER_ACTIVITY_DESTROY = 1; //所有flutter Activity destory 时,销æ¯engine private String dartEntrypoint = DEFAULT_DART_ENTRYPOINT; private String initialRoute = DEFAULT_INITIAL_ROUTE; private int whenEngineStart = ANY_ACTIVITY_CREATED; + private int whenEngineDestory = APP_EXit; private boolean isDebug = false; - private FlutterView.RenderMode renderMode = FlutterView.RenderMode.surface; + private FlutterView.RenderMode renderMode = FlutterView.RenderMode.texture; private Application mApp; private INativeRouter router = null; + private BoostLifecycleListener lifecycleListener; + public ConfigBuilder(Application app, INativeRouter router) { this.router = router; this.mApp = app; @@ -200,11 +218,19 @@ public class NewFlutterBoost { return this; } - public ConfigBuilder whenEngineStart(@NonNull int whenEngineStart) { + public ConfigBuilder whenEngineStart( int whenEngineStart) { this.whenEngineStart = whenEngineStart; return this; } + public ConfigBuilder whenEngineDestory( int whenEngineDestory) { + this.whenEngineDestory = whenEngineDestory; + return this; + } + public ConfigBuilder lifecycleListener( BoostLifecycleListener lifecycleListener) { + this.lifecycleListener = lifecycleListener; + return this; + } public Platform build() { Platform platform = new Platform() { @@ -232,22 +258,24 @@ public class NewFlutterBoost { return ConfigBuilder.this.whenEngineStart; } + @Override + public int whenEngineDestroy() { + return ConfigBuilder.this.whenEngineDestory; + } + public FlutterView.RenderMode renderMode() { return ConfigBuilder.this.renderMode; } }; + platform.lifecycleListener=this.lifecycleListener; + return platform; } } - - public FlutterEngine engineProvider() { - return sInstance.mPlatform.engineProvider(); - } - public IContainerManager containerManager() { return sInstance.mManager; } @@ -268,7 +296,63 @@ public class NewFlutterBoost { return mManager.findContainerById(id); } - public BoostPluginRegistry getPluginRegistry(){ + public PluginRegistry getPluginRegistry(){ return mRegistry; } + + private FlutterEngine createEngine(){ + if (mEngine == null) { + + FlutterMain.startInitialization(mPlatform.getApplication()); + + FlutterShellArgs flutterShellArgs = new FlutterShellArgs(new String[0]); + FlutterMain.ensureInitializationComplete( + mPlatform.getApplication().getApplicationContext(), flutterShellArgs.toArray()); + + mEngine = new FlutterEngine(mPlatform.getApplication().getApplicationContext()); + } + return mEngine; + + } + + private void registerPlugins() { + try { + Class clz = Class.forName("io.flutter.plugins.GeneratedPluginRegistrant"); + Method method = clz.getDeclaredMethod("registerWith", PluginRegistry.class); + method.invoke(null, mRegistry); + } catch (Throwable t) { + throw new RuntimeException(t); + } + + if(mPlatform.lifecycleListener!=null){ + mPlatform.lifecycleListener.onPluginsRegistered(); + } + + + } + + public FlutterEngine engineProvider() { + return mEngine; + } + + + public void boostDestroy(){ + if(mEngine!=null){ + mEngine.destroy(); + } + if(mPlatform.lifecycleListener!=null){ + mPlatform.lifecycleListener.onEngineDestroy(); + } + mEngine=null; + mRegistry=null; + mCurrentActiveActivity=null; + } + + + public interface BoostLifecycleListener { + void onEngineCreated(); + void onPluginsRegistered(); + void onEngineDestroy(); + } + } diff --git a/android/src/main/java/com/idlefish/flutterboost/Platform.java b/android/src/main/java/com/idlefish/flutterboost/Platform.java index beafdf6684161411d992bc4b359f68b10c1d9edf..b00bab93b728758e349181f322639e8ee24da482 100644 --- a/android/src/main/java/com/idlefish/flutterboost/Platform.java +++ b/android/src/main/java/com/idlefish/flutterboost/Platform.java @@ -8,57 +8,33 @@ import java.lang.reflect.Method; import java.util.Map; import io.flutter.embedding.android.FlutterView; -import io.flutter.embedding.engine.FlutterEngine; -import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.plugin.common.PluginRegistry; -import io.flutter.view.FlutterMain; public abstract class Platform { + public abstract Application getApplication(); - public FlutterEngine mEngine ; + public abstract void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts); - public abstract Application getApplication(); + public abstract int whenEngineStart(); + public abstract int whenEngineDestroy(); - public abstract void openContainer(Context context, String url, Map<String,Object> urlParams, int requestCode, Map<String,Object> exts); + public abstract FlutterView.RenderMode renderMode(); - public abstract int whenEngineStart() ; + public abstract boolean isDebug(); - public abstract FlutterView.RenderMode renderMode(); - - public abstract boolean isDebug() ; - - public abstract String initialRoute(); + public abstract String initialRoute(); + public NewFlutterBoost.BoostLifecycleListener lifecycleListener; public void closeContainer(IContainerRecord record, Map<String, Object> result, Map<String, Object> exts) { - if(record == null) return; + if (record == null) return; record.getContainer().finishContainer(result); } - public FlutterEngine engineProvider() { - if (mEngine == null) { - FlutterShellArgs flutterShellArgs = new FlutterShellArgs(new String[0]); - FlutterMain.ensureInitializationComplete( - getApplication().getApplicationContext(), flutterShellArgs.toArray()); - - mEngine = new FlutterEngine( getApplication().getApplicationContext()); - } - return mEngine; - } - - public void registerPlugins(PluginRegistry registry) { - try { - Class clz = Class.forName("io.flutter.plugins.GeneratedPluginRegistrant"); - Method method = clz.getDeclaredMethod("registerWith",PluginRegistry.class); - method.invoke(null,registry); - }catch (Throwable t){ - throw new RuntimeException(t); - } - } } diff --git a/android/src/main/java/com/idlefish/flutterboost/Utils.java b/android/src/main/java/com/idlefish/flutterboost/Utils.java index ae5357d04af7bfaeaf28d486e4c337eaf69354db..0f48d4383246105924b37f5124fa1496f2cae37a 100755 --- a/android/src/main/java/com/idlefish/flutterboost/Utils.java +++ b/android/src/main/java/com/idlefish/flutterboost/Utils.java @@ -263,7 +263,7 @@ public class Utils { return; } - String [] arr = new String[]{"mServedView", "mNextServedView"}; + String [] arr = new String[]{"mLastSrvView","mServedView", "mNextServedView"}; Field f = null; Object obj_get = null; for (int i = 0;i < arr.length;i ++) { @@ -283,7 +283,7 @@ public class Utils { } } }catch(Throwable t){ - t.printStackTrace(); +// t.printStackTrace(); } } } diff --git a/android/src/main/java/com/idlefish/flutterboost/XAndroidKeyProcessor.java b/android/src/main/java/com/idlefish/flutterboost/XAndroidKeyProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..78df9eb1ab59fd645e00e3764f8bc0b1309dbfee --- /dev/null +++ b/android/src/main/java/com/idlefish/flutterboost/XAndroidKeyProcessor.java @@ -0,0 +1,99 @@ +package com.idlefish.flutterboost; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; + +import io.flutter.embedding.engine.systemchannels.KeyEventChannel; +import io.flutter.plugin.editing.TextInputPlugin; + +public class XAndroidKeyProcessor { + @NonNull + private final KeyEventChannel keyEventChannel; + @NonNull + private final XTextInputPlugin textInputPlugin; + + private int combiningCharacter; + + public XAndroidKeyProcessor(@NonNull KeyEventChannel keyEventChannel, @NonNull XTextInputPlugin textInputPlugin) { + this.keyEventChannel = keyEventChannel; + this.textInputPlugin = textInputPlugin; + } + + + public void onKeyUp(@NonNull KeyEvent keyEvent) { + Character complexCharacter = applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar()); + keyEventChannel.keyUp( + new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter) + ); + } + + public void onKeyDown(@NonNull KeyEvent keyEvent) { + if (textInputPlugin.getLastInputConnection() != null + && textInputPlugin.getInputMethodManager().isAcceptingText()) { + textInputPlugin.getLastInputConnection().sendKeyEvent(keyEvent); + } + + Character complexCharacter = applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar()); + keyEventChannel.keyDown( + new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter) + ); + } + + /** + * Applies the given Unicode character in {@code newCharacterCodePoint} to a previously + * entered Unicode combining character and returns the combination of these characters + * if a combination exists. + * <p> + * This method mutates {@link #combiningCharacter} over time to combine characters. + * <p> + * One of the following things happens in this method: + * <ul> + * <li>If no previous {@link #combiningCharacter} exists and the {@code newCharacterCodePoint} + * is not a combining character, then {@code newCharacterCodePoint} is returned.</li> + * <li>If no previous {@link #combiningCharacter} exists and the {@code newCharacterCodePoint} + * is a combining character, then {@code newCharacterCodePoint} is saved as the + * {@link #combiningCharacter} and null is returned.</li> + * <li>If a previous {@link #combiningCharacter} exists and the {@code newCharacterCodePoint} + * is also a combining character, then the {@code newCharacterCodePoint} is combined with + * the existing {@link #combiningCharacter} and null is returned.</li> + * <li>If a previous {@link #combiningCharacter} exists and the {@code newCharacterCodePoint} + * is not a combining character, then the {@link #combiningCharacter} is applied to the + * regular {@code newCharacterCodePoint} and the resulting complex character is returned. The + * {@link #combiningCharacter} is cleared.</li> + * </ul> + * <p> + * The following reference explains the concept of a "combining character": + * https://en.wikipedia.org/wiki/Combining_character + */ + @Nullable + private Character applyCombiningCharacterToBaseCharacter(int newCharacterCodePoint) { + if (newCharacterCodePoint == 0) { + return null; + } + + Character complexCharacter = (char) newCharacterCodePoint; + boolean isNewCodePointACombiningCharacter = (newCharacterCodePoint & KeyCharacterMap.COMBINING_ACCENT) != 0; + if (isNewCodePointACombiningCharacter) { + // If a combining character was entered before, combine this one with that one. + int plainCodePoint = newCharacterCodePoint & KeyCharacterMap.COMBINING_ACCENT_MASK; + if (combiningCharacter != 0) { + combiningCharacter = KeyCharacterMap.getDeadChar(combiningCharacter, plainCodePoint); + } else { + combiningCharacter = plainCodePoint; + } + } else { + // The new character is a regular character. Apply combiningCharacter to it, if it exists. + if (combiningCharacter != 0) { + int combinedChar = KeyCharacterMap.getDeadChar(combiningCharacter, newCharacterCodePoint); + if (combinedChar > 0) { + complexCharacter = (char) combinedChar; + } + combiningCharacter = 0; + } + } + + return complexCharacter; + } +} \ No newline at end of file diff --git a/android/src/main/java/com/idlefish/flutterboost/XFlutterView.java b/android/src/main/java/com/idlefish/flutterboost/XFlutterView.java index 38336cb5f9bcc007bf3cbc14f62e61cb45967e7f..186b220af4bf8056fccc5a731438e12be2c18395 100644 --- a/android/src/main/java/com/idlefish/flutterboost/XFlutterView.java +++ b/android/src/main/java/com/idlefish/flutterboost/XFlutterView.java @@ -90,14 +90,16 @@ public class XFlutterView extends FrameLayout { // These components essentially add some additional behavioral logic on top of // existing, stateless system channels, e.g., KeyEventChannel, TextInputChannel, etc. @Nullable - private TextInputPlugin textInputPlugin; + private XTextInputPlugin textInputPlugin; @Nullable - private AndroidKeyProcessor androidKeyProcessor; + private XAndroidKeyProcessor androidKeyProcessor; @Nullable private AndroidTouchProcessor androidTouchProcessor; @Nullable private AccessibilityBridge accessibilityBridge; + private boolean hasAddFirstFrameRenderedListener=false; + // Directly implemented View behavior that communicates with Flutter. private final FlutterRenderer.ViewportMetrics viewportMetrics = new FlutterRenderer.ViewportMetrics(); @@ -566,6 +568,7 @@ public class XFlutterView extends FrameLayout { * See {@link #detachFromFlutterEngine()} for information on how to detach from a * {@link FlutterEngine}. */ + public void attachToFlutterEngine( @NonNull FlutterEngine flutterEngine ) { @@ -582,27 +585,35 @@ public class XFlutterView extends FrameLayout { + " to new engine."); detachFromFlutterEngine(); } - + this.requestFocus(); this.flutterEngine = flutterEngine; // Instruct our FlutterRenderer that we are now its designated RenderSurface. FlutterRenderer flutterRenderer = this.flutterEngine.getRenderer(); didRenderFirstFrame = flutterRenderer.hasRenderedFirstFrame(); + if(!hasAddFirstFrameRenderedListener){ + flutterRenderer.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener); + hasAddFirstFrameRenderedListener=true; + } flutterRenderer.attachToRenderSurface(renderSurface); - flutterRenderer.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener); // Initialize various components that know how to process Android View I/O // in a way that Flutter understands. - if(this.textInputPlugin!=null){ - this.textInputPlugin.destroy(); - resolveMemoryLeaks(); + + if(textInputPlugin==null){ + textInputPlugin = new XTextInputPlugin( + this, + flutterEngine.getTextInputChannel(), + this.flutterEngine.getPlatformViewsController() + ); } - this.textInputPlugin = new TextInputPlugin(this, this.flutterEngine.getDartExecutor(), this.flutterEngine.getPlatformViewsController()); + textInputPlugin.setTextInputMethodHandler(); + textInputPlugin.getInputMethodManager().restartInput(this); - this.androidKeyProcessor = new AndroidKeyProcessor( + this.androidKeyProcessor = new XAndroidKeyProcessor( this.flutterEngine.getKeyEventChannel(), textInputPlugin ); @@ -643,9 +654,9 @@ public class XFlutterView extends FrameLayout { // If the first frame has already been rendered, notify all first frame listeners. // Do this after all other initialization so that listeners don't inadvertently interact // with a FlutterView that is only partially attached to a FlutterEngine. - if (didRenderFirstFrame) { - onFirstFrameRenderedListener.onFirstFrameRendered(); - } +// if (didRenderFirstFrame) { +// onFirstFrameRenderedListener.onFirstFrameRendered(); +// } } /** @@ -681,9 +692,8 @@ public class XFlutterView extends FrameLayout { // now that the engine is detached. The new InputConnection will be null, which // signifies that this View does not process input (until a new engine is attached). // TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin - textInputPlugin.getInputMethodManager().restartInput(this); - textInputPlugin.destroy(); - resolveMemoryLeaks(); + +// resolveMemoryLeaks(); // Instruct our FlutterRenderer that we are no longer interested in being its RenderSurface. FlutterRenderer flutterRenderer = flutterEngine.getRenderer(); // didRenderFirstFrame = false; @@ -691,7 +701,9 @@ public class XFlutterView extends FrameLayout { flutterRenderer.detachFromRenderSurface(); flutterEngine = null; } - + public void release(){ + textInputPlugin.release(); + } public void resolveMemoryLeaks(){ try { diff --git a/android/src/main/java/com/idlefish/flutterboost/XInputConnectionAdaptor.java b/android/src/main/java/com/idlefish/flutterboost/XInputConnectionAdaptor.java new file mode 100644 index 0000000000000000000000000000000000000000..3500c47ac2906ac6591f4819b25d9947656b4d32 --- /dev/null +++ b/android/src/main/java/com/idlefish/flutterboost/XInputConnectionAdaptor.java @@ -0,0 +1,263 @@ +package com.idlefish.flutterboost; + +import android.content.Context; +import android.text.Editable; +import android.text.Selection; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; + +import io.flutter.embedding.engine.systemchannels.TextInputChannel; +import io.flutter.plugin.common.ErrorLogResult; +import io.flutter.plugin.common.MethodChannel; +import android.content.Context; +import android.text.DynamicLayout; +import android.text.Editable; +import android.text.Layout; +import android.text.Layout.Directions; +import android.text.Selection; +import android.text.TextPaint; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; + +import io.flutter.embedding.engine.systemchannels.TextInputChannel; +import io.flutter.Log; +import io.flutter.plugin.common.ErrorLogResult; +import io.flutter.plugin.common.MethodChannel; + +class XInputConnectionAdaptor extends BaseInputConnection { + private final View mFlutterView; + private final int mClient; + private final TextInputChannel textInputChannel; + private final Editable mEditable; + private int mBatchCount; + private InputMethodManager mImm; + private final Layout mLayout; + + @SuppressWarnings("deprecation") + public XInputConnectionAdaptor( + View view, + int client, + TextInputChannel textInputChannel, + Editable editable + ) { + super(view, true); + mFlutterView = view; + mClient = client; + this.textInputChannel = textInputChannel; + mEditable = editable; + mBatchCount = 0; + // We create a dummy Layout with max width so that the selection + // shifting acts as if all text were in one line. + mLayout = new DynamicLayout(mEditable, new TextPaint(), Integer.MAX_VALUE, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + mImm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + } + + // Send the current state of the editable to Flutter. + private void updateEditingState() { + // If the IME is in the middle of a batch edit, then wait until it completes. + if (mBatchCount > 0) + return; + + int selectionStart = Selection.getSelectionStart(mEditable); + int selectionEnd = Selection.getSelectionEnd(mEditable); + int composingStart = BaseInputConnection.getComposingSpanStart(mEditable); + int composingEnd = BaseInputConnection.getComposingSpanEnd(mEditable); + + mImm.updateSelection(mFlutterView, + selectionStart, selectionEnd, + composingStart, composingEnd); + + textInputChannel.updateEditingState( + mClient, + mEditable.toString(), + selectionStart, + selectionEnd, + composingStart, + composingEnd + ); + } + + @Override + public Editable getEditable() { + return mEditable; + } + + @Override + public boolean beginBatchEdit() { + mBatchCount++; + return super.beginBatchEdit(); + } + + @Override + public boolean endBatchEdit() { + boolean result = super.endBatchEdit(); + mBatchCount--; + updateEditingState(); + return result; + } + + @Override + public boolean commitText(CharSequence text, int newCursorPosition) { + boolean result = super.commitText(text, newCursorPosition); + updateEditingState(); + return result; + } + + @Override + public boolean deleteSurroundingText(int beforeLength, int afterLength) { + if (Selection.getSelectionStart(mEditable) == -1) + return true; + + boolean result = super.deleteSurroundingText(beforeLength, afterLength); + updateEditingState(); + return result; + } + + @Override + public boolean setComposingRegion(int start, int end) { + boolean result = super.setComposingRegion(start, end); + updateEditingState(); + return result; + } + + @Override + public boolean setComposingText(CharSequence text, int newCursorPosition) { + boolean result; + if (text.length() == 0) { + result = super.commitText(text, newCursorPosition); + } else { + result = super.setComposingText(text, newCursorPosition); + } + updateEditingState(); + return result; + } + + @Override + public boolean setSelection(int start, int end) { + boolean result = super.setSelection(start, end); + updateEditingState(); + return result; + } + + // Sanitizes the index to ensure the index is within the range of the + // contents of editable. + private static int clampIndexToEditable(int index, Editable editable) { + int clamped = Math.max(0, Math.min(editable.length(), index)); + if (clamped != index) { + Log.d("flutter", "Text selection index was clamped (" + + index + "->" + clamped + + ") to remain in bounds. This may not be your fault, as some keyboards may select outside of bounds." + ); + } + return clamped; + } + + @Override + public boolean sendKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) { + int selStart = clampIndexToEditable(Selection.getSelectionStart(mEditable), mEditable); + int selEnd = clampIndexToEditable(Selection.getSelectionEnd(mEditable), mEditable); + if (selEnd > selStart) { + // Delete the selection. + Selection.setSelection(mEditable, selStart); + mEditable.delete(selStart, selEnd); + updateEditingState(); + return true; + } else if (selStart > 0) { + // Delete to the left/right of the cursor depending on direction of text. + // TODO(garyq): Explore how to obtain per-character direction. The + // isRTLCharAt() call below is returning blanket direction assumption + // based on the first character in the line. + boolean isRtl = mLayout.isRtlCharAt(mLayout.getLineForOffset(selStart)); + try { + if (isRtl) { + Selection.extendRight(mEditable, mLayout); + } else { + Selection.extendLeft(mEditable, mLayout); + } + } catch (IndexOutOfBoundsException e) { + // On some Chinese devices (primarily Huawei, some Xiaomi), + // on initial app startup before focus is lost, the + // Selection.extendLeft and extendRight calls always extend + // from the index of the initial contents of mEditable. This + // try-catch will prevent crashing on Huawei devices by falling + // back to a simple way of deletion, although this a hack and + // will not handle emojis. + Selection.setSelection(mEditable, selStart, selStart - 1); + } + int newStart = clampIndexToEditable(Selection.getSelectionStart(mEditable), mEditable); + int newEnd = clampIndexToEditable(Selection.getSelectionEnd(mEditable), mEditable); + Selection.setSelection(mEditable, Math.min(newStart, newEnd)); + // Min/Max the values since RTL selections will start at a higher + // index than they end at. + mEditable.delete(Math.min(newStart, newEnd), Math.max(newStart, newEnd)); + updateEditingState(); + return true; + } + } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) { + int selStart = Selection.getSelectionStart(mEditable); + int newSel = Math.max(selStart - 1, 0); + setSelection(newSel, newSel); + return true; + } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) { + int selStart = Selection.getSelectionStart(mEditable); + int newSel = Math.min(selStart + 1, mEditable.length()); + setSelection(newSel, newSel); + return true; + } else { + // Enter a character. + int character = event.getUnicodeChar(); + if (character != 0) { + int selStart = Math.max(0, Selection.getSelectionStart(mEditable)); + int selEnd = Math.max(0, Selection.getSelectionEnd(mEditable)); + if (selEnd != selStart) + mEditable.delete(selStart, selEnd); + mEditable.insert(selStart, String.valueOf((char) character)); + setSelection(selStart + 1, selStart + 1); + updateEditingState(); + } + return true; + } + } + return false; + } + + @Override + public boolean performEditorAction(int actionCode) { + switch (actionCode) { + case EditorInfo.IME_ACTION_NONE: + textInputChannel.newline(mClient); + break; + case EditorInfo.IME_ACTION_UNSPECIFIED: + textInputChannel.unspecifiedAction(mClient); + break; + case EditorInfo.IME_ACTION_GO: + textInputChannel.go(mClient); + break; + case EditorInfo.IME_ACTION_SEARCH: + textInputChannel.search(mClient); + break; + case EditorInfo.IME_ACTION_SEND: + textInputChannel.send(mClient); + break; + case EditorInfo.IME_ACTION_NEXT: + textInputChannel.next(mClient); + break; + case EditorInfo.IME_ACTION_PREVIOUS: + textInputChannel.previous(mClient); + break; + default: + case EditorInfo.IME_ACTION_DONE: + textInputChannel.done(mClient); + break; + } + return true; + } +} \ No newline at end of file diff --git a/android/src/main/java/com/idlefish/flutterboost/XTextInputPlugin.java b/android/src/main/java/com/idlefish/flutterboost/XTextInputPlugin.java new file mode 100644 index 0000000000000000000000000000000000000000..37c9795b9320233face36a95b962c31ae635d0bc --- /dev/null +++ b/android/src/main/java/com/idlefish/flutterboost/XTextInputPlugin.java @@ -0,0 +1,356 @@ +package com.idlefish.flutterboost; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.Editable; +import android.text.InputType; +import android.text.Selection; +import android.view.View; +import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; + +import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.embedding.engine.systemchannels.TextInputChannel; +import io.flutter.plugin.platform.PlatformViewsController; + +/** + * Android implementation of the text input plugin. + */ +public class XTextInputPlugin { + @NonNull + private View mView; + @NonNull + private final InputMethodManager mImm; + @NonNull + private final TextInputChannel textInputChannel; + @NonNull + private InputTarget inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0); + @Nullable + private TextInputChannel.Configuration configuration; + @Nullable + private Editable mEditable; + private boolean mRestartInputPending; + @Nullable + private InputConnection lastInputConnection; + @NonNull + private PlatformViewsController platformViewsController; + + // When true following calls to createInputConnection will return the cached lastInputConnection if the input + // target is a platform view. See the comments on lockPlatformViewInputConnection for more details. + private boolean isInputConnectionLocked; + + public XTextInputPlugin(View view, @NonNull TextInputChannel textInputChannel, @NonNull PlatformViewsController platformViewsController) { + mView = view; + mImm = (InputMethodManager) view.getContext().getSystemService( + Context.INPUT_METHOD_SERVICE); + this.textInputChannel = textInputChannel; + + this.platformViewsController = platformViewsController; +// this.platformViewsController.attachTextInputPlugin(this); + } + + public void release() { + mView = null; + } + + public void setTextInputMethodHandler() { + + textInputChannel.setTextInputMethodHandler(new TextInputChannel.TextInputMethodHandler() { + @Override + public void show() { + showTextInput(mView); + } + + @Override + public void hide() { + hideTextInput(mView); + } + + @Override + public void setClient(int textInputClientId, TextInputChannel.Configuration configuration) { + setTextInputClient(textInputClientId, configuration); + } + + @Override + public void setPlatformViewClient(int platformViewId) { + setPlatformViewTextInputClient(platformViewId); + } + + @Override + public void setEditingState(TextInputChannel.TextEditState editingState) { + setTextInputEditingState(mView, editingState); + } + + @Override + public void clearClient() { + clearTextInputClient(); + } + }); + } + + @NonNull + public InputMethodManager getInputMethodManager() { + return mImm; + } + + /*** + * Use the current platform view input connection until unlockPlatformViewInputConnection is called. + * + * The current input connection instance is cached and any following call to @{link createInputConnection} returns + * the cached connection until unlockPlatformViewInputConnection is called. + * + * This is a no-op if the current input target isn't a platform view. + * + * This is used to preserve an input connection when moving a platform view from one virtual display to another. + */ + public void lockPlatformViewInputConnection() { + if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW) { + isInputConnectionLocked = true; + } + } + + /** + * Unlocks the input connection. + * <p> + * See also: @{link lockPlatformViewInputConnection}. + */ + public void unlockPlatformViewInputConnection() { + isInputConnectionLocked = false; + } + + /** + * Detaches the text input plugin from the platform views controller. + * <p> + * The TextInputPlugin instance should not be used after calling this. + */ + public void destroy() { + platformViewsController.detachTextInputPlugin(); + } + + private static int inputTypeFromTextInputType( + TextInputChannel.InputType type, + boolean obscureText, + boolean autocorrect, + TextInputChannel.TextCapitalization textCapitalization + ) { + if (type.type == TextInputChannel.TextInputType.DATETIME) { + return InputType.TYPE_CLASS_DATETIME; + } else if (type.type == TextInputChannel.TextInputType.NUMBER) { + int textType = InputType.TYPE_CLASS_NUMBER; + if (type.isSigned) { + textType |= InputType.TYPE_NUMBER_FLAG_SIGNED; + } + if (type.isDecimal) { + textType |= InputType.TYPE_NUMBER_FLAG_DECIMAL; + } + return textType; + } else if (type.type == TextInputChannel.TextInputType.PHONE) { + return InputType.TYPE_CLASS_PHONE; + } + + int textType = InputType.TYPE_CLASS_TEXT; + if (type.type == TextInputChannel.TextInputType.MULTILINE) { + textType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE; + } else if (type.type == TextInputChannel.TextInputType.EMAIL_ADDRESS) { + textType |= InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS; + } else if (type.type == TextInputChannel.TextInputType.URL) { + textType |= InputType.TYPE_TEXT_VARIATION_URI; + } else if (type.type == TextInputChannel.TextInputType.VISIBLE_PASSWORD) { + textType |= InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; + } + + if (obscureText) { + // Note: both required. Some devices ignore TYPE_TEXT_FLAG_NO_SUGGESTIONS. + textType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS; + textType |= InputType.TYPE_TEXT_VARIATION_PASSWORD; + } else { + if (autocorrect) textType |= InputType.TYPE_TEXT_FLAG_AUTO_CORRECT; + } + + if (textCapitalization == TextInputChannel.TextCapitalization.CHARACTERS) { + textType |= InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS; + } else if (textCapitalization == TextInputChannel.TextCapitalization.WORDS) { + textType |= InputType.TYPE_TEXT_FLAG_CAP_WORDS; + } else if (textCapitalization == TextInputChannel.TextCapitalization.SENTENCES) { + textType |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES; + } + + return textType; + } + + public InputConnection createInputConnection(View view, EditorInfo outAttrs) { + if (inputTarget.type == InputTarget.Type.NO_TARGET) { + lastInputConnection = null; + return null; + } + + if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW) { + if (isInputConnectionLocked) { + return lastInputConnection; + } + lastInputConnection = platformViewsController.getPlatformViewById(inputTarget.id).onCreateInputConnection(outAttrs); + return lastInputConnection; + } + + outAttrs.inputType = inputTypeFromTextInputType( + configuration.inputType, + configuration.obscureText, + configuration.autocorrect, + configuration.textCapitalization + ); + outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN; + int enterAction; + if (configuration.inputAction == null) { + // If an explicit input action isn't set, then default to none for multi-line fields + // and done for single line fields. + enterAction = (InputType.TYPE_TEXT_FLAG_MULTI_LINE & outAttrs.inputType) != 0 + ? EditorInfo.IME_ACTION_NONE + : EditorInfo.IME_ACTION_DONE; + } else { + enterAction = configuration.inputAction; + } + if (configuration.actionLabel != null) { + outAttrs.actionLabel = configuration.actionLabel; + outAttrs.actionId = enterAction; + } + outAttrs.imeOptions |= enterAction; + + XInputConnectionAdaptor connection = new XInputConnectionAdaptor( + view, + inputTarget.id, + textInputChannel, + mEditable + ); + outAttrs.initialSelStart = Selection.getSelectionStart(mEditable); + outAttrs.initialSelEnd = Selection.getSelectionEnd(mEditable); + + lastInputConnection = connection; + return lastInputConnection; + } + + @Nullable + public InputConnection getLastInputConnection() { + return lastInputConnection; + } + + /** + * Clears a platform view text input client if it is the current input target. + * <p> + * This is called when a platform view is disposed to make sure we're not hanging to a stale input + * connection. + */ + public void clearPlatformViewClient(int platformViewId) { + if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW && inputTarget.id == platformViewId) { + inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0); + hideTextInput(mView); + mImm.restartInput(mView); + mRestartInputPending = false; + } + } + + private void showTextInput(View view) { + view.requestFocus(); + mImm.showSoftInput(view, 0); + } + + private void hideTextInput(View view) { + // Note: a race condition may lead to us hiding the keyboard here just after a platform view has shown it. + // This can only potentially happen when switching focus from a Flutter text field to a platform view's text + // field(by text field here I mean anything that keeps the keyboard open). + // See: https://github.com/flutter/flutter/issues/34169 + mImm.hideSoftInputFromWindow(view.getApplicationWindowToken(), 0); + } + + private void setTextInputClient(int client, TextInputChannel.Configuration configuration) { + inputTarget = new InputTarget(InputTarget.Type.FRAMEWORK_CLIENT, client); + this.configuration = configuration; + mEditable = Editable.Factory.getInstance().newEditable(""); + + // setTextInputClient will be followed by a call to setTextInputEditingState. + // Do a restartInput at that time. + mRestartInputPending = true; + unlockPlatformViewInputConnection(); + } + + private void setPlatformViewTextInputClient(int platformViewId) { + // We need to make sure that the Flutter view is focused so that no imm operations get short circuited. + // Not asking for focus here specifically manifested in a but on API 28 devices where the platform view's + // request to show a keyboard was ignored. + mView.requestFocus(); + inputTarget = new InputTarget(InputTarget.Type.PLATFORM_VIEW, platformViewId); + mImm.restartInput(mView); + mRestartInputPending = false; + } + + private void applyStateToSelection(TextInputChannel.TextEditState state) { + int selStart = state.selectionStart; + int selEnd = state.selectionEnd; + if (selStart >= 0 && selStart <= mEditable.length() && selEnd >= 0 + && selEnd <= mEditable.length()) { + Selection.setSelection(mEditable, selStart, selEnd); + } else { + Selection.removeSelection(mEditable); + } + } + + private void setTextInputEditingState(View view, TextInputChannel.TextEditState state) { + if (!mRestartInputPending && state.text.equals(mEditable.toString())) { + applyStateToSelection(state); + mImm.updateSelection(mView, Math.max(Selection.getSelectionStart(mEditable), 0), + Math.max(Selection.getSelectionEnd(mEditable), 0), + BaseInputConnection.getComposingSpanStart(mEditable), + BaseInputConnection.getComposingSpanEnd(mEditable)); + } else { + mEditable.replace(0, mEditable.length(), state.text); + applyStateToSelection(state); + mImm.restartInput(view); + mRestartInputPending = false; + } + } + + private void clearTextInputClient() { + if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW) { + // Focus changes in the framework tree have no guarantees on the order focus nodes are notified. A node + // that lost focus may be notified before or after a node that gained focus. + // When moving the focus from a Flutter text field to an AndroidView, it is possible that the Flutter text + // field's focus node will be notified that it lost focus after the AndroidView was notified that it gained + // focus. When this happens the text field will send a clearTextInput command which we ignore. + // By doing this we prevent the framework from clearing a platform view input client(the only way to do so + // is to set a new framework text client). I don't see an obvious use case for "clearing" a platform views + // text input client, and it may be error prone as we don't know how the platform view manages the input + // connection and we probably shouldn't interfere. + // If we ever want to allow the framework to clear a platform view text client we should probably consider + // changing the focus manager such that focus nodes that lost focus are notified before focus nodes that + // gained focus as part of the same focus event. + return; + } + inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0); + unlockPlatformViewInputConnection(); + } + + static private class InputTarget { + enum Type { + NO_TARGET, + // InputConnection is managed by the TextInputPlugin, and events are forwarded to the Flutter framework. + FRAMEWORK_CLIENT, + // InputConnection is managed by an embedded platform view. + PLATFORM_VIEW + } + + public InputTarget(@NonNull Type type, int id) { + this.type = type; + this.id = id; + } + + @NonNull + Type type; + // The ID of the input target. + // + // For framework clients this is the framework input connection client ID. + // For platform views this is the platform view's ID. + int id; + } +} \ No newline at end of file diff --git a/android/src/main/java/com/idlefish/flutterboost/containers/FlutterActivityAndFragmentDelegate.java b/android/src/main/java/com/idlefish/flutterboost/containers/FlutterActivityAndFragmentDelegate.java index d66af46e3ee10b89a612447f0a8361351ebb4e3c..26cc05deed5d8cb2e6034dc1662f845955e53cff 100644 --- a/android/src/main/java/com/idlefish/flutterboost/containers/FlutterActivityAndFragmentDelegate.java +++ b/android/src/main/java/com/idlefish/flutterboost/containers/FlutterActivityAndFragmentDelegate.java @@ -15,9 +15,11 @@ import android.view.ViewGroup; import java.io.Serializable; import java.util.Arrays; +import java.util.HashMap; import java.util.Map; +import com.idlefish.flutterboost.BoostPluginRegistry; import com.idlefish.flutterboost.NewFlutterBoost; import com.idlefish.flutterboost.Utils; import com.idlefish.flutterboost.XFlutterView; @@ -28,6 +30,7 @@ import io.flutter.app.FlutterActivity; import io.flutter.embedding.android.*; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterShellArgs; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.platform.PlatformPlugin; import io.flutter.view.FlutterMain; @@ -74,12 +77,15 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContaine return flutterEngine; } + XFlutterView getFlutterView(){ + return flutterView; + } void onAttach(@NonNull Context context) { ensureAlive(); - - initializeFlutter(context); - + if (NewFlutterBoost.instance().platform().whenEngineStart() == NewFlutterBoost.ConfigBuilder.FLUTTER_ACTIVITY_CREATED) { + NewFlutterBoost.instance().doInitialFlutter(); + } // When "retain instance" is true, the FlutterEngine will survive configuration // changes. Therefore, we create a new one only if one does not already exist. if (flutterEngine == null) { @@ -94,47 +100,16 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContaine // use-cases. platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine); - if (host.shouldAttachEngineToActivity()) { - // Notify any plugins that are currently attached to our FlutterEngine that they - // are now attached to an Activity. - // - // Passing this Fragment's Lifecycle should be sufficient because as long as this Fragment - // is attached to its Activity, the lifecycles should be in sync. Once this Fragment is - // detached from its Activity, that Activity will be detached from the FlutterEngine, too, - // which means there shouldn't be any possibility for the Fragment Lifecycle to get out of - // sync with the Activity. We use the Fragment's Lifecycle because it is possible that the - // attached Activity is not a LifecycleOwner. - Log.d(TAG, "Attaching FlutterEngine to the Activity that owns this Fragment."); - flutterEngine.getActivityControlSurface().attachToActivity( - host.getActivity(), - host.getLifecycle() - ); - } host.configureFlutterEngine(flutterEngine); } - private void initializeFlutter(@NonNull Context context) { - FlutterMain.ensureInitializationComplete( - context.getApplicationContext(), - host.getFlutterShellArgs().toArray() - ); - } + private void setupFlutterEngine() { Log.d(TAG, "Setting up FlutterEngine."); - // First, check if the host wants to use a cached FlutterEngine. -// String cachedEngineId = host.getCachedEngineId(); -// if (cachedEngineId != null) { -// flutterEngine = FlutterEngineCache.getInstance().get(cachedEngineId); -// isFlutterEngineFromHost = true; -// if (flutterEngine == null) { -// throw new IllegalStateException("The requested cached FlutterEngine did not exist in the FlutterEngineCache: '" + cachedEngineId + "'"); -// } -// return; -// } // Second, defer to subclasses for a custom FlutterEngine. flutterEngine = host.provideFlutterEngine(host.getContext()); @@ -147,7 +122,6 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContaine // FlutterView. Log.d(TAG, "No preferred FlutterEngine was provided. Creating a new FlutterEngine for" + " this NewFlutterFragment."); - flutterEngine = new FlutterEngine(host.getContext()); isFlutterEngineFromHost = false; } @@ -156,6 +130,11 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContaine @NonNull View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { Log.v(TAG, "Creating FlutterView."); + flutterEngine.getActivityControlSurface().attachToActivity( + host.getActivity(), + host.getLifecycle() + ); + mSyncer = NewFlutterBoost.instance().containerManager().generateSyncer(this); @@ -198,22 +177,24 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContaine Log.v(TAG, "onResume()"); ensureAlive(); flutterEngine.getLifecycleChannel().appIsResumed(); + + BoostPluginRegistry registry= (BoostPluginRegistry)NewFlutterBoost.instance().getPluginRegistry(); + ActivityPluginBinding binding=registry.getRegistrarAggregate().getActivityPluginBinding(); + if(binding!=null&&(binding.getActivity()!=this.host.getActivity())){ + flutterEngine.getActivityControlSurface().attachToActivity( + host.getActivity(), + host.getLifecycle() + ); + } + } void onPostResume() { Log.v(TAG, "onPostResume()"); ensureAlive(); - if (flutterEngine != null) { - if (platformPlugin != null) { - // TODO(mattcarroll): find a better way to handle the update of UI overlays than calling through - // to platformPlugin. We're implicitly entangling the Window, Activity, Fragment, - // and engine all with this one call. - platformPlugin.updateSystemUiOverlays(); - } - } else { - Log.w(TAG, "onPostResume() invoked before NewFlutterFragment was attached to an Activity."); - } + Utils.setStatusBarLightMode(host.getActivity(),true); + } @@ -229,7 +210,8 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContaine void onStop() { Log.v(TAG, "onStop()"); ensureAlive(); -// flutterView.detachFromFlutterEngine(); + + } void onDestroyView() { @@ -237,6 +219,14 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContaine mSyncer.onDestroy(); ensureAlive(); + BoostPluginRegistry registry= (BoostPluginRegistry)NewFlutterBoost.instance().getPluginRegistry(); + ActivityPluginBinding binding=registry.getRegistrarAggregate().getActivityPluginBinding(); + if(binding!=null&&(binding.getActivity()==this.host.getActivity())){ + registry.getRegistrarAggregate().onDetachedFromActivityForConfigChanges(); + flutterEngine.getActivityControlSurface().detachFromActivityForConfigChanges(); + + } + flutterView.release(); } @@ -244,15 +234,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContaine Log.v(TAG, "onDetach()"); ensureAlive(); - if (host.shouldAttachEngineToActivity()) { - // Notify plugins that they are no longer attached to an Activity. - Log.d(TAG, "Detaching FlutterEngine from the Activity that owns this Fragment."); - if (host.getActivity().isChangingConfigurations()) { - flutterEngine.getActivityControlSurface().detachFromActivityForConfigChanges(); - } else { - flutterEngine.getActivityControlSurface().detachFromActivity(); - } - } + // Null out the platformPlugin to avoid a possible retain cycle between the plugin, this Fragment, // and this Fragment's Activity. @@ -263,16 +245,6 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContaine Utils.fixInputMethodManagerLeak(host.getActivity()); - // Destroy our FlutterEngine if we're not set to retain it. -// if (host.shouldDestroyEngineWithHost()) { -// flutterEngine.destroy(); -// -// if (host.getCachedEngineId() != null) { -// FlutterEngineCache.getInstance().remove(host.getCachedEngineId()); -// } -// -// flutterEngine = null; -// } } @@ -280,12 +252,6 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContaine mSyncer.onBackPressed(); ensureAlive(); -// if (flutterEngine != null) { -// Log.v(TAG, "Forwarding onBackPressed() to FlutterEngine."); -// flutterEngine.getNavigationChannel().popRoute(); -// } else { -// Log.w(TAG, "Invoked onBackPressed() before NewFlutterFragment was attached to an Activity."); -// } } @@ -402,10 +368,26 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContaine @Override public void finishContainer(Map<String, Object> result) { - this.host.finishContainer(result); + + if(result != null) { + setBoostResult(this.host.getActivity(),new HashMap<>(result)); + this.host.getActivity().finish(); + }else{ + this.host.getActivity().finish(); + } + } + + + public void setBoostResult(Activity activity, HashMap result) { + Intent intent = new Intent(); + if (result != null) { + intent.putExtra(IFlutterViewContainer.RESULT_KEY, result); + } + activity.setResult(Activity.RESULT_OK, intent); + } @Override public String getContainerUrl() { return this.host.getContainerUrl(); @@ -503,7 +485,6 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContaine - void finishContainer(Map<String, Object> result) ; String getContainerUrl() ; diff --git a/android/src/main/java/com/idlefish/flutterboost/containers/FlutterSplashView.java b/android/src/main/java/com/idlefish/flutterboost/containers/FlutterSplashView.java index 500eaa9d2ec0f604284c81afa3545aa180fa2e1c..01a2a7505a09852e1a72a14e4636f9b301e449c2 100644 --- a/android/src/main/java/com/idlefish/flutterboost/containers/FlutterSplashView.java +++ b/android/src/main/java/com/idlefish/flutterboost/containers/FlutterSplashView.java @@ -59,9 +59,8 @@ public class FlutterSplashView extends FrameLayout { private final OnFirstFrameRenderedListener onFirstFrameRenderedListener = new OnFirstFrameRenderedListener() { @Override public void onFirstFrameRendered() { - if (splashScreen != null&&!hasRendered) { + if (splashScreen != null) { transitionToFlutter(); - hasRendered=true; } } }; @@ -292,6 +291,9 @@ public class FlutterSplashView extends FrameLayout { } } + + + public void onAttach() { Debuger.log("BoostFlutterView onAttach"); diff --git a/android/src/main/java/com/idlefish/flutterboost/containers/NewBoostFlutterActivity.java b/android/src/main/java/com/idlefish/flutterboost/containers/NewBoostFlutterActivity.java index 2b4f0621379500b5745e9703492e6c851468f2ea..5ac813a57238467987c63920351d9fdbac01c542 100644 --- a/android/src/main/java/com/idlefish/flutterboost/containers/NewBoostFlutterActivity.java +++ b/android/src/main/java/com/idlefish/flutterboost/containers/NewBoostFlutterActivity.java @@ -21,6 +21,7 @@ import android.view.*; import android.widget.*; import com.idlefish.flutterboost.NewFlutterBoost; import com.idlefish.flutterboost.Utils; +import com.idlefish.flutterboost.XFlutterView; import io.flutter.Log; import io.flutter.embedding.android.DrawableSplashScreen; import io.flutter.embedding.android.FlutterView; @@ -241,8 +242,12 @@ public class NewBoostFlutterActivity extends Activity window.setStatusBarColor(Color.TRANSPARENT); window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI); } - Utils.setStatusBarLightMode(this,true); + + } + + protected XFlutterView getFlutterView(){ + return delegate.getFlutterView(); } @Override @@ -469,12 +474,6 @@ public class NewBoostFlutterActivity extends Activity - @Override - public void finishContainer(Map<String, Object> result) { - Activity activity= this.getActivity(); - - activity.finish(); - } @Override public String getContainerUrl() { diff --git a/android/src/main/java/com/idlefish/flutterboost/containers/NewFlutterFragment.java b/android/src/main/java/com/idlefish/flutterboost/containers/NewFlutterFragment.java index b15be1ed4d6e8ddf725e9545c3f3bcb2cd98fc60..5d0a91e4201c108f082dcb60d461a5cc0abc296c 100644 --- a/android/src/main/java/com/idlefish/flutterboost/containers/NewFlutterFragment.java +++ b/android/src/main/java/com/idlefish/flutterboost/containers/NewFlutterFragment.java @@ -15,6 +15,7 @@ import android.view.View; import android.view.ViewGroup; import com.idlefish.flutterboost.NewFlutterBoost; +import com.idlefish.flutterboost.XFlutterView; import io.flutter.embedding.android.*; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterShellArgs; @@ -233,6 +234,10 @@ public class NewFlutterFragment extends Fragment implements FlutterActivityAndFr private FlutterActivityAndFragmentDelegate delegate; + protected XFlutterView getFlutterView(){ + return delegate.getFlutterView(); + } + public NewFlutterFragment() { // Ensure that we at least have an empty Bundle of arguments so that we don't // need to continually check for null arguments before grabbing one. @@ -485,16 +490,11 @@ public class NewFlutterFragment extends Fragment implements FlutterActivityAndFr */ @Override public boolean shouldAttachEngineToActivity() { - return getArguments().getBoolean(ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY); + return true; } - @Override - public void finishContainer(Map<String, Object> result) { - Activity activity = this.getActivity(); - activity.finish(); - } @Override public String getContainerUrl() { diff --git a/example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/MyApplication.java b/example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/MyApplication.java index 59ca3d86ca690f25f08430561daa740735d0da19..4565910b06e62e6bca99197a9a8ec509aedb68ce 100755 --- a/example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/MyApplication.java +++ b/example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/MyApplication.java @@ -3,19 +3,21 @@ package com.taobao.idlefish.flutterboostexample; import android.app.Application; import android.content.Context; +import android.util.Log; import com.idlefish.flutterboost.*; -import com.idlefish.flutterboost.interfaces.IContainerRecord; import java.util.Map; import com.idlefish.flutterboost.interfaces.INativeRouter; -import io.flutter.app.FlutterApplication; +import io.flutter.embedding.android.FlutterView; +import io.flutter.plugin.common.MethodChannel; + +public class MyApplication extends Application { + -public class MyApplication extends FlutterApplication { @Override public void onCreate() { super.onCreate(); - INativeRouter router =new INativeRouter() { @Override public void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts) { @@ -25,12 +27,36 @@ public class MyApplication extends FlutterApplication { }; + NewFlutterBoost.BoostLifecycleListener lifecycleListener= new NewFlutterBoost.BoostLifecycleListener() { + @Override + public void onEngineCreated() { + + } + + @Override + public void onPluginsRegistered() { + MethodChannel mMethodChannel = new MethodChannel( NewFlutterBoost.instance().engineProvider().getDartExecutor(), "methodChannel"); + Log.e("MyApplication","MethodChannel create"); + TextPlatformViewPlugin.register(NewFlutterBoost.instance().getPluginRegistry().registrarFor("TextPlatformViewPlugin")); + + } + + @Override + public void onEngineDestroy() { + + } + }; Platform platform= new NewFlutterBoost .ConfigBuilder(this,router) .isDebug(true) .whenEngineStart(NewFlutterBoost.ConfigBuilder.ANY_ACTIVITY_CREATED) + .renderMode(FlutterView.RenderMode.texture) + .lifecycleListener(lifecycleListener) .build(); NewFlutterBoost.instance().init(platform); + + + } } diff --git a/example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/TextPlatformViewPlugin.java b/example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/TextPlatformViewPlugin.java index 8ed60aa3b350e724321bef3abcd6bbcbd9f92e1d..95e0d77a0f49abcaf812e1742d33792cf481c71a 100644 --- a/example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/TextPlatformViewPlugin.java +++ b/example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/TextPlatformViewPlugin.java @@ -1,11 +1,12 @@ package com.taobao.idlefish.flutterboostexample; import io.flutter.app.FlutterPluginRegistry; +import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.StandardMessageCodec; public class TextPlatformViewPlugin { - public static void register(FlutterPluginRegistry registry) { - registry.getPlatformViewsController().getRegistry().registerViewFactory("plugins.test/view", + public static void register(PluginRegistry.Registrar registrar) { + registrar.platformViewRegistry().registerViewFactory("plugins.test/view", new TextPlatformViewFactory(StandardMessageCodec.INSTANCE)); } } diff --git a/example/lib/main.dart b/example/lib/main.dart index e4f35631f08fed8228a71dbbff6755fbdaf91712..3331ee9c5c57e3162391b3c812363aff34aac088 100755 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -20,6 +20,7 @@ class _MyAppState extends State<MyApp> { 'first': (pageName, params, _) => FirstRouteWidget(), 'second': (pageName, params, _) => SecondRouteWidget(), 'tab': (pageName, params, _) => TabRouteWidget(), + 'platformView': (pageName, params, _) => PlatformRouteWidget(), 'flutterFragment': (pageName, params, _) => FragmentRouteWidget(params), ///å¯ä»¥åœ¨native层通过 getContainerParams æ¥ä¼ 递å‚æ•° 'flutterPage': (pageName, params, _) { diff --git a/example/lib/platform_view.dart b/example/lib/platform_view.dart new file mode 100644 index 0000000000000000000000000000000000000000..26d5b91670600d51310cdd8650a7f6ecd8d228cd --- /dev/null +++ b/example/lib/platform_view.dart @@ -0,0 +1,51 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +typedef void TextViewCreatedCallback(TextViewController controller); + +class TextView extends StatefulWidget { + const TextView({ + Key key, + this.onTextViewCreated, + }) : super(key: key); + + final TextViewCreatedCallback onTextViewCreated; + + @override + State<StatefulWidget> createState() => _TextViewState(); +} + +class _TextViewState extends State<TextView> { + @override + Widget build(BuildContext context) { + if (defaultTargetPlatform == TargetPlatform.android) { + return AndroidView( + viewType: 'plugins.test/view', + onPlatformViewCreated: _onPlatformViewCreated, + ); + } + return Text( + '$defaultTargetPlatform is not yet supported by the text_view plugin'); + } + + void _onPlatformViewCreated(int id) { + if (widget.onTextViewCreated == null) { + return; + } + widget.onTextViewCreated(new TextViewController._(id)); + } +} + +class TextViewController { + TextViewController._(int id) + : _channel = new MethodChannel('plugins.felix.angelov/textview_$id'); + + final MethodChannel _channel; + + Future<void> setText(String text) async { + assert(text != null); + return _channel.invokeMethod('setText', text); + } +} \ No newline at end of file diff --git a/example/lib/simple_page_widgets.dart b/example/lib/simple_page_widgets.dart index 1dbee6f8af40e9ae96bb6b04aee746a62c66c0b2..a3893a779b7fe11b6978346beb97a326fd2ca356 100755 --- a/example/lib/simple_page_widgets.dart +++ b/example/lib/simple_page_widgets.dart @@ -1,6 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_boost/flutter_boost.dart'; +import 'package:flutter_boost_example/platform_view.dart'; class FirstRouteWidget extends StatelessWidget { @override @@ -68,6 +69,28 @@ class TabRouteWidget extends StatelessWidget { } } +class PlatformRouteWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title:Text("Platform Route"), + ), + body: Center( + child: RaisedButton( + child: TextView(), + onPressed: () { + print("open second page!"); + FlutterBoost.singleton.open("second").then((Map value) { + print( + "call me when page is finished. did recieve second route result $value"); + }); + }, + ), + ), + ); + } +} class FlutterRouteWidget extends StatefulWidget { FlutterRouteWidget({this.params,this.message}); final Map params; @@ -85,6 +108,10 @@ class _FlutterRouteWidgetState extends State<FlutterRouteWidget> { final String message=widget.message; return Scaffold( appBar: AppBar( + brightness:Brightness.light, + backgroundColor: Colors.white, + textTheme:new TextTheme(title: TextStyle(color: Colors.black)) , + title: Text('flutter_boost_example'), ), body: SingleChildScrollView( @@ -216,6 +243,21 @@ class _FlutterRouteWidgetState extends State<FlutterRouteWidget> { MaterialPageRoute(builder: (_) => PushWidget())); }, ), + + InkWell( + child: Container( + padding: const EdgeInsets.all(8.0), + margin: const EdgeInsets.all(8.0), + color: Colors.yellow, + child: Text( + 'push Platform demo', + style: TextStyle(fontSize: 22.0, color: Colors.black), + )), + onTap: () { + Navigator.push(context, + MaterialPageRoute(builder: (_) => PlatformRouteWidget())); + }, + ), InkWell( child: Container( padding: const EdgeInsets.all(8.0),