Commit 83982112 authored by Yacumima's avatar Yacumima

use embedding engine

parent f4427cf1
/*
* The MIT License (MIT)
*
*
* Copyright (c) 2019 Alibaba Group
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
......@@ -24,71 +24,72 @@
package com.taobao.idlefish.flutterboost;
import android.app.Activity;
import android.content.Context;
import android.os.Looper;
import android.support.annotation.NonNull;
import com.taobao.idlefish.flutterboost.interfaces.IFlutterViewContainer;
import com.taobao.idlefish.flutterboost.interfaces.IFlutterViewProvider;
import com.taobao.idlefish.flutterboost.interfaces.IPlatform;
import com.taobao.idlefish.flutterboost.interfaces.IFlutterEngineProvider;
public class FlutterViewProvider implements IFlutterViewProvider {
import io.flutter.app.FlutterPluginRegistry;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.PluginRegistry;
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;
private final IPlatform mPlatform;
public class BoostEngineProvider implements IFlutterEngineProvider {
private BoostFlutterNativeView mFlutterNativeView = null;
private BoostFlutterView mFlutterView = null;
FlutterViewProvider(IPlatform platform){
mPlatform = platform;
}
static final BoostEngineProvider sInstance = new BoostEngineProvider();
private BoostEngine mEngine = null;
private BoostEngineProvider() {
}
@Override
public BoostFlutterView createFlutterView(IFlutterViewContainer container) {
Activity activity = mPlatform.getMainActivity();
public FlutterEngine createEngine(Context context) {
if(activity == null) {
Debuger.log("create Flutter View not with MainActivity");
activity = container.getActivity();
}
Utils.assertCallOnMainThread();
if (mFlutterView == null) {
mFlutterView = new BoostFlutterView(activity, null, createFlutterNativeView(container));
}
return mFlutterView;
}
if (mEngine == null) {
FlutterShellArgs flutterShellArgs = new FlutterShellArgs(new String[0]);
FlutterMain.ensureInitializationComplete(
context.getApplicationContext(), flutterShellArgs.toArray());
@Override
public BoostFlutterNativeView createFlutterNativeView(IFlutterViewContainer container) {
if (mFlutterNativeView == null) {
mFlutterNativeView = new BoostFlutterNativeView(container.getActivity().getApplicationContext());
mEngine = new BoostEngine(context.getApplicationContext());
mEngine.startRun();
}
return mFlutterNativeView;
return mEngine;
}
@Override
public BoostFlutterView tryGetFlutterView() {
return mFlutterView;
public FlutterEngine tryGetEngine() {
return mEngine;
}
@Override
public void stopFlutterView() {
final BoostFlutterView view = mFlutterView;
if(view != null) {
view.boostStop();
}
}
public static class BoostEngine extends FlutterEngine {
final Context mContext;
@Override
public void reset() {
if(mFlutterNativeView != null) {
mFlutterNativeView.boostDestroy();
mFlutterNativeView = null;
public BoostEngine(@NonNull Context context) {
super(context);
mContext = context;
}
if(mFlutterView != null) {
mFlutterView.boostDestroy();
mFlutterView = null;
public void startRun() {
if (!getDartExecutor().isExecutingDart()) {
getNavigationChannel().setInitialRoute("/");
DartExecutor.DartEntrypoint entryPoint = new DartExecutor.DartEntrypoint(
mContext.getResources().getAssets(),
FlutterMain.findAppBundlePath(mContext),
"main");
getDartExecutor().executeDartEntrypoint(entryPoint);
}
}
}
}
/*
* The MIT License (MIT)
*
*
* Copyright (c) 2019 Alibaba Group
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
......@@ -23,190 +23,242 @@
*/
package com.taobao.idlefish.flutterboost;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Build;
import android.support.v4.view.ViewCompat;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.WindowInsets;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.taobao.idlefish.flutterboost.NavigationService.NavigationService;
import java.util.LinkedList;
import java.util.List;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import io.flutter.embedding.android.FlutterView;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
import io.flutter.plugin.platform.PlatformPlugin;
import io.flutter.view.FlutterNativeView;
import io.flutter.view.FlutterView;
public class BoostFlutterView extends FrameLayout {
public class BoostFlutterView extends FlutterView {
private FlutterEngine mFlutterEngine;
private boolean mFirstFrameCalled = false;
private boolean mResumed = false;
private WindowInsets mCurrentWindowInsets;
private FlutterView mFlutterView;
private BoostCallback mBoostCallback;
private PlatformPlugin mPlatformPlugin;
public BoostFlutterView(Context context, AttributeSet attrs, FlutterNativeView nativeView) {
super(context, attrs, nativeView);
super.addFirstFrameListener(new FirstFrameListener() {
@Override
public void onFirstFrame() {
mFirstFrameCalled = true;
private Bundle mArguments;
private BoostPluginRegistry mBoostPluginRegistry;
private final List<OnFirstFrameRenderedListener> mFirstFrameRenderedListeners = new LinkedList<>();
private final OnFirstFrameRenderedListener mOnFirstFrameRenderedListener = new OnFirstFrameRenderedListener() {
@Override
public void onFirstFrameRendered() {
final Object[] listeners = mFirstFrameRenderedListeners.toArray();
for (Object obj : listeners) {
((OnFirstFrameRenderedListener) obj).onFirstFrameRendered();
}
});
try {
Field field = FlutterView.class.getDeclaredField("mSurfaceCallback");
field.setAccessible(true);
SurfaceHolder.Callback cb = (SurfaceHolder.Callback)field.get(this);
getHolder().removeCallback(cb);
mBoostCallback = new BoostCallback(cb);
getHolder().addCallback(mBoostCallback);
}catch (Throwable t){
Debuger.exception(t);
}
};
public BoostFlutterView(Context context, FlutterEngine engine, Bundle args) {
super(context);
mFlutterEngine = engine;
mArguments = args;
init();
}
@Override
public void onStart() {
//do nothing...
private void init() {
if (mFlutterEngine == null) {
mFlutterEngine = createFlutterEngine(getContext());
}
if (mArguments == null) {
mArguments = new Bundle();
}
mPlatformPlugin = new PlatformPlugin((Activity) getContext(), mFlutterEngine.getPlatformChannel());
mFlutterView = new FlutterView(getContext(), getRenderMode(), getTransparencyMode());
addView(mFlutterView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
mFlutterView.addOnFirstFrameRenderedListener(mOnFirstFrameRenderedListener);
mBoostPluginRegistry = new BoostPluginRegistry(mFlutterEngine,(Activity)getContext());
FlutterBoostPlugin.platform().onRegisterPlugins(mBoostPluginRegistry);
}
@Override
public void onPostResume() {
//do nothing...
requestFocus();
protected FlutterEngine createFlutterEngine(Context context) {
return BoostEngineProvider.sInstance.createEngine(context);
}
public void addFirstFrameRendered(OnFirstFrameRenderedListener listener) {
mFirstFrameRenderedListeners.add(listener);
}
public void removeFirstFrameRendered(OnFirstFrameRenderedListener listener) {
mFirstFrameRenderedListeners.remove(listener);
}
protected FlutterView.RenderMode getRenderMode() {
String renderModeName = mArguments.getString("flutterview_render_mode", FlutterView.RenderMode.surface.name());
return FlutterView.RenderMode.valueOf(renderModeName);
}
protected FlutterView.TransparencyMode getTransparencyMode() {
String transparencyModeName = mArguments.getString("flutterview_transparency_mode", FlutterView.TransparencyMode.transparent.name());
return FlutterView.TransparencyMode.valueOf(transparencyModeName);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mPlatformPlugin.onPostResume();
}
public void onResume() {
Debuger.log("BoostFlutterView onResume");
mFlutterEngine.getLifecycleChannel().appIsResumed();
}
// public void onPostResume() {
// Debuger.log("BoostFlutterView onPostResume");
// mPlatformPlugin.onPostResume();
// }
public void onPause() {
//do nothing...
Debuger.log("BoostFlutterView onPause");
mFlutterEngine.getLifecycleChannel().appIsInactive();
}
@Override
public void onStop() {
//do nothing...
Debuger.log("BoostFlutterView onStop");
mFlutterEngine.getLifecycleChannel().appIsPaused();
}
@Override
public FlutterNativeView detach() {
//do nothing...
return getFlutterNativeView();
public void onAttach() {
Debuger.log("BoostFlutterView onAttach");
mFlutterView.attachToFlutterEngine(mFlutterEngine);
}
@Override
public void destroy() {
//do nothing...
public void onDetach() {
Debuger.log("BoostFlutterView onDetach");
mFlutterView.removeOnFirstFrameRenderedListener(mOnFirstFrameRenderedListener);
mFlutterView.detachFromFlutterEngine();
}
public void onDestroy() {
Debuger.log("BoostFlutterView onDestroy");
mPlatformPlugin = null;
mFlutterEngine = null;
}
@Override
public Bitmap getBitmap() {
if(getFlutterNativeView() == null || !getFlutterNativeView().isAttached()) {
Debuger.exception("FlutterView not attached!");
return null;
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
onDetach();
}
public void onBackPressed() {
Log.d("FlutterFragment", "onBackPressed()");
if (mFlutterEngine != null) {
mFlutterEngine.getNavigationChannel().popRoute();
} else {
Log.w("FlutterFragment", "Invoked onBackPressed() before FlutterFragment was attached to an Activity.");
}
return super.getBitmap();
}
public boolean firstFrameCalled() {
return mFirstFrameCalled;
}
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (mFlutterEngine != null) {
mFlutterEngine.getPluginRegistry().onRequestPermissionsResult(requestCode, permissions, grantResults);
} else {
Log.w("FlutterFragment", "onRequestPermissionResult() invoked before FlutterFragment was attached to an Activity.");
}
}
public void boostResume() {
if (!mResumed) {
mResumed = true;
super.onPostResume();
Debuger.log("resume flutter view");
public void onNewIntent(Intent intent) {
if (mFlutterEngine != null) {
mFlutterEngine.getPluginRegistry().onNewIntent(intent);
} else {
Log.w("FlutterFragment", "onNewIntent() invoked before FlutterFragment was attached to an Activity.");
}
}
public void boostStop() {
if (mResumed) {
super.onStop();
Debuger.log("stop flutter view");
mResumed = false;
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mFlutterEngine != null) {
mFlutterEngine.getPluginRegistry().onActivityResult(requestCode, resultCode, data);
} else {
Log.w("FlutterFragment", "onActivityResult() invoked before FlutterFragment was attached to an Activity.");
}
}
public boolean isResumed() {
return mResumed;
}
public void boostDestroy() {
super.destroy();
public void onUserLeaveHint() {
if (mFlutterEngine != null) {
mFlutterEngine.getPluginRegistry().onUserLeaveHint();
} else {
Log.w("FlutterFragment", "onUserLeaveHint() invoked before FlutterFragment was attached to an Activity.");
}
}
public void scheduleFrame(){
if (mResumed) {
Map<String,String> map = new HashMap<>();
map.put("type","scheduleFrame");
NavigationService.getService().emitEvent(map);
public void onTrimMemory(int level) {
if (mFlutterEngine != null) {
if (level == 10) {
mFlutterEngine.getSystemChannel().sendMemoryPressureWarning();
}
} else {
Log.w("FlutterFragment", "onTrimMemory() invoked before FlutterFragment was attached to an Activity.");
}
}
class BoostCallback implements SurfaceHolder.Callback {
public void onLowMemory() {
mFlutterEngine.getSystemChannel().sendMemoryPressureWarning();
}
final SurfaceHolder.Callback mCallback;
public static class Builder {
private Context context;
private FlutterEngine engine;
private FlutterView.RenderMode renderMode;
private FlutterView.TransparencyMode transparencyMode;
BoostCallback(SurfaceHolder.Callback cb){
this.mCallback = cb;
public Builder(Context ctx) {
this.context = ctx;
renderMode = FlutterView.RenderMode.surface;
transparencyMode = FlutterView.TransparencyMode.transparent;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
//Debuger.log("flutterView surfaceCreated");
try {
mCallback.surfaceCreated(holder);
}catch (Throwable t){
Debuger.exception(t);
}
public Builder flutterEngine(FlutterEngine engine) {
this.engine = engine;
return this;
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
//Debuger.log("flutterView surfaceChanged");
try {
mCallback.surfaceChanged(holder,format,width,height);
scheduleFrame();
}catch (Throwable t){
Debuger.exception(t);
}
public Builder renderMode(FlutterView.RenderMode renderMode) {
this.renderMode = renderMode;
return this;
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
//Debuger.log("flutterView surfaceDestroyed");
try {
mCallback.surfaceDestroyed(holder);
}catch (Throwable t){
Debuger.exception(t);
}
public Builder transparencyMode(FlutterView.TransparencyMode transparencyMode) {
this.transparencyMode = transparencyMode;
return this;
}
}
@Override
protected void onAttachedToWindow() {
//Debuger.log("flutterView onAttachedToWindow");
super.onAttachedToWindow();
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// final WindowInsets windowInsets = getRootWindowInsets();
// if(windowInsets != null) {
// if(mCurrentWindowInsets == null ||
// !TextUtils.equals(windowInsets.toString(),mCurrentWindowInsets.toString())) {
// Debuger.log("setWindowInsets "+windowInsets.toString());
//
// mCurrentWindowInsets = windowInsets;
// super.onApplyWindowInsets(mCurrentWindowInsets);
// }
// }
// }else {
// ViewCompat.requestApplyInsets(this);
// }
ViewCompat.requestApplyInsets(this);
public BoostFlutterView build() {
Bundle args = new Bundle();
args.putString("flutterview_render_mode", renderMode != null ? renderMode.name() : FlutterView.RenderMode.surface.name());
args.putString("flutterview_transparency_mode", transparencyMode != null ? transparencyMode.name() : FlutterView.TransparencyMode.transparent.name());
return new BoostFlutterView(context, engine, args);
}
}
}
package com.taobao.idlefish.flutterboost;
import android.app.Activity;
import android.content.Context;
import io.flutter.app.FlutterPluginRegistry;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.platform.PlatformViewRegistry;
import io.flutter.view.FlutterView;
import io.flutter.view.TextureRegistry;
public class BoostPluginRegistry extends FlutterPluginRegistry {
private final FlutterEngine mEngine;
private final Activity mActivity;
public BoostPluginRegistry(FlutterEngine engine, Activity activity) {
super(engine, activity);
mEngine = engine;
mActivity = activity;
}
public PluginRegistry.Registrar registrarFor(String pluginKey) {
return new BoostRegistrar(mActivity,mEngine,super.registrarFor(pluginKey));
}
public static class BoostRegistrar implements PluginRegistry.Registrar {
private final PluginRegistry.Registrar mRegistrar;
private final FlutterEngine mEngine;
private final Activity mActivity;
BoostRegistrar(Activity activity, FlutterEngine engine, PluginRegistry.Registrar registrar) {
mRegistrar = registrar;
mEngine = engine;
mActivity = activity;
}
@Override
public Activity activity() {
return mActivity;
}
@Override
public Context context() {
return mRegistrar.context();
}
@Override
public Context activeContext() {
return mRegistrar.activeContext();
}
@Override
public BinaryMessenger messenger() {
return mEngine.getDartExecutor();
}
@Override
public TextureRegistry textures() {
return mEngine.getRenderer();
}
@Override
public PlatformViewRegistry platformViewRegistry() {
return mRegistrar.platformViewRegistry();
}
@Override
public FlutterView view() {
throw new RuntimeException("should not use!!!");
}
@Override
public String lookupKeyForAsset(String s) {
return mRegistrar.lookupKeyForAsset(s);
}
@Override
public String lookupKeyForAsset(String s, String s1) {
return mRegistrar.lookupKeyForAsset(s,s1);
}
@Override
public PluginRegistry.Registrar publish(Object o) {
return mRegistrar.publish(o);
}
@Override
public PluginRegistry.Registrar addRequestPermissionsResultListener(PluginRegistry.RequestPermissionsResultListener requestPermissionsResultListener) {
return mRegistrar.addRequestPermissionsResultListener(requestPermissionsResultListener);
}
@Override
public PluginRegistry.Registrar addActivityResultListener(PluginRegistry.ActivityResultListener activityResultListener) {
return mRegistrar.addActivityResultListener(activityResultListener);
}
@Override
public PluginRegistry.Registrar addNewIntentListener(PluginRegistry.NewIntentListener newIntentListener) {
return mRegistrar.addNewIntentListener(newIntentListener);
}
@Override
public PluginRegistry.Registrar addUserLeaveHintListener(PluginRegistry.UserLeaveHintListener userLeaveHintListener) {
return mRegistrar.addUserLeaveHintListener(userLeaveHintListener);
}
@Override
public PluginRegistry.Registrar addViewDestroyListener(PluginRegistry.ViewDestroyListener viewDestroyListener) {
return mRegistrar.addViewDestroyListener(viewDestroyListener);
}
}
}
......@@ -23,27 +23,26 @@
*/
package com.taobao.idlefish.flutterboost;
import android.os.Handler;
import android.content.Intent;
import com.taobao.idlefish.flutterboost.NavigationService.NavigationService;
import com.taobao.idlefish.flutterboost.interfaces.IContainerManager;
import com.taobao.idlefish.flutterboost.interfaces.IContainerRecord;
import com.taobao.idlefish.flutterboost.interfaces.IFlutterViewContainer;
import java.util.HashMap;
import java.util.Map;
import fleamarket.taobao.com.xservicekit.handler.MessageResult;
public class ContainerRecord implements IContainerRecord {
private final IContainerManager mManager;
private final FlutterViewContainerManager mManager;
private final IFlutterViewContainer mContainer;
private final String mUniqueId;
private final Handler mHandler = new Handler();
private int mState = STATE_UNKNOW;
private MethodChannelProxy mProxy = new MethodChannelProxy();
public ContainerRecord(IContainerManager manager, IFlutterViewContainer container) {
ContainerRecord(FlutterViewContainerManager manager, IFlutterViewContainer container) {
mUniqueId = System.currentTimeMillis() + "-" + hashCode();
mManager = manager;
mContainer = container;
......@@ -66,54 +65,119 @@ public class ContainerRecord implements IContainerRecord {
@Override
public void onCreate() {
Utils.assertCallOnMainThread();
if(mState != STATE_UNKNOW) {
Debuger.exception("state error");
}
mState = STATE_CREATED;
mContainer.getBoostFlutterView().boostResume();
mContainer.getBoostFlutterView().onResume();
mProxy.create();
}
@Override
public void onAppear() {
Utils.assertCallOnMainThread();
if(mState != STATE_CREATED && mState != STATE_DISAPPEAR) {
Debuger.exception("state error");
}
mState = STATE_APPEAR;
mContainer.getBoostFlutterView().boostResume();
mManager.pushRecord(this);
mContainer.getBoostFlutterView().onAttach();
mProxy.appear();
}
@Override
public void onDisappear() {
mProxy.disappear();
mState = STATE_DISAPPEAR;
Utils.assertCallOnMainThread();
/**
* Bug workaround:
* If current container is finishing, we should call destroy flutter page early.
*/
if(mContainer.isFinishing()) {
mHandler.post(new Runnable() {
@Override
public void run() {
mProxy.destroy();
}
});
if(mState != STATE_APPEAR) {
Debuger.exception("state error");
}
mState = STATE_DISAPPEAR;
mProxy.disappear();
mContainer.getBoostFlutterView().onDetach();
mManager.popRecord(this);
}
@Override
public void onDestroy() {
mProxy.destroy();
Utils.assertCallOnMainThread();
if(mState != STATE_DISAPPEAR) {
Debuger.exception("state error");
}
mState = STATE_DESTROYED;
mProxy.destroy();
mManager.removeRecord(this);
if(!mManager.hasContainerAppear()) {
mContainer.getBoostFlutterView().onPause();
mContainer.getBoostFlutterView().onStop();
}
}
@Override
public void onBackPressed() {
Utils.assertCallOnMainThread();
if(mState == STATE_UNKNOW || mState == STATE_DESTROYED) {
Debuger.exception("state error");
}
Map<String, String> map = new HashMap<>();
map.put("type", "backPressedCallback");
map.put("name", mContainer.getContainerName());
map.put("uniqueId", mUniqueId);
NavigationService.getService().emitEvent(map);
mContainer.getBoostFlutterView().onBackPressed();
}
@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 onUserLeaveHint() {
mContainer.getBoostFlutterView().onUserLeaveHint();
}
@Override
public void onTrimMemory(int level) {
mContainer.getBoostFlutterView().onTrimMemory(level);
}
@Override
public void onResult(Map Result) {
NavigationService.onNativePageResult(
genResult("onNativePageResult"),
mUniqueId,
mUniqueId,
Result,
mContainer.getContainerParams()
);
public void onLowMemory() {
mContainer.getBoostFlutterView().onLowMemory();
}
private class MethodChannelProxy {
private int mState = STATE_UNKNOW;
......
......@@ -28,16 +28,14 @@ import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import com.alibaba.fastjson.JSON;
import com.taobao.idlefish.flutterboost.NavigationService.NavigationService;
import com.taobao.idlefish.flutterboost.interfaces.IContainerRecord;
import com.taobao.idlefish.flutterboost.interfaces.IFlutterViewContainer;
import com.taobao.idlefish.flutterboost.loader.ServiceLoader;
import com.taobao.idlefish.flutterboost.interfaces.IContainerManager;
import com.taobao.idlefish.flutterboost.interfaces.IFlutterViewProvider;
import com.taobao.idlefish.flutterboost.interfaces.IFlutterEngineProvider;
import com.taobao.idlefish.flutterboost.interfaces.IPlatform;
import java.io.UnsupportedEncodingException;
......@@ -50,7 +48,6 @@ import fleamarket.taobao.com.xservicekit.handler.MessageResult;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.view.FlutterView;
public class FlutterBoostPlugin implements MethodChannel.MethodCallHandler, Application.ActivityLifecycleCallbacks {
......@@ -70,12 +67,12 @@ public class FlutterBoostPlugin implements MethodChannel.MethodCallHandler, Appl
channel.setMethodCallHandler(sInstance);
}
public static IFlutterViewProvider viewProvider() {
public static IFlutterEngineProvider engineProvider() {
if (sInstance == null) {
throw new RuntimeException("FlutterBoostPlugin not init yet");
}
return sInstance.mViewProvider;
return BoostEngineProvider.sInstance;
}
public static IContainerManager containerManager() {
......@@ -104,7 +101,6 @@ public class FlutterBoostPlugin implements MethodChannel.MethodCallHandler, Appl
private final IPlatform mPlatform;
private final IContainerManager mManager;
private final IFlutterViewProvider mViewProvider;
private final PageResultMediator mMediator;
......@@ -112,7 +108,6 @@ public class FlutterBoostPlugin implements MethodChannel.MethodCallHandler, Appl
private FlutterBoostPlugin(IPlatform platform) {
mPlatform = platform;
mViewProvider = new FlutterViewProvider(platform);
mManager = new FlutterViewContainerManager();
mMediator = new PageResultMediator();
}
......@@ -140,10 +135,6 @@ public class FlutterBoostPlugin implements MethodChannel.MethodCallHandler, Appl
ctx = currentActivity();
}
if (ctx == null) {
ctx = sInstance.mPlatform.getMainActivity();
}
if (ctx == null) {
ctx = sInstance.mPlatform.getApplication();
}
......@@ -248,7 +239,7 @@ public class FlutterBoostPlugin implements MethodChannel.MethodCallHandler, Appl
if (mCurrentActiveActivity == null) {
Debuger.log("Application entry foreground");
if (mViewProvider.tryGetFlutterView() != null) {
if (BoostEngineProvider.sInstance.tryGetEngine() != null) {
Map<String, String> map = new HashMap<>();
map.put("type", "foreground");
NavigationService.getService().emitEvent(map);
......@@ -272,7 +263,7 @@ public class FlutterBoostPlugin implements MethodChannel.MethodCallHandler, Appl
if (mCurrentActiveActivity == activity) {
Debuger.log("Application entry background");
if (mViewProvider.tryGetFlutterView() != null) {
if (BoostEngineProvider.sInstance.tryGetEngine() != null) {
Map<String, String> map = new HashMap<>();
map.put("type", "background");
NavigationService.getService().emitEvent(map);
......@@ -291,7 +282,7 @@ public class FlutterBoostPlugin implements MethodChannel.MethodCallHandler, Appl
if (mCurrentActiveActivity == activity) {
Debuger.log("Application entry background");
if (mViewProvider.tryGetFlutterView() != null) {
if (BoostEngineProvider.sInstance.tryGetEngine() != null) {
Map<String, String> map = new HashMap<>();
map.put("type", "background");
NavigationService.getService().emitEvent(map);
......@@ -321,15 +312,15 @@ public class FlutterBoostPlugin implements MethodChannel.MethodCallHandler, Appl
activity.setResult(Activity.RESULT_OK, intent);
}
public static void onBoostResult(IFlutterViewContainer container, int requestCode, int resultCode, Intent intent) {
Map map = new HashMap();
if (intent != null) {
map.put("result", intent.getSerializableExtra(IFlutterViewContainer.RESULT_KEY));
}
map.put("requestCode", requestCode);
map.put("responseCode", resultCode);
containerManager().onContainerResult(container, map);
}
// public static void onBoostResult(IFlutterViewContainer container, int requestCode, int resultCode, Intent intent) {
// Map map = new HashMap();
// if (intent != null) {
// map.put("result", intent.getSerializableExtra(IFlutterViewContainer.RESULT_KEY));
// }
// map.put("requestCode", requestCode);
// map.put("responseCode", resultCode);
// containerManager().onContainerResult(container, map);
// }
}
......@@ -23,212 +23,89 @@
*/
package com.taobao.idlefish.flutterboost;
import android.app.Activity;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import com.taobao.idlefish.flutterboost.NavigationService.NavigationService;
import com.taobao.idlefish.flutterboost.interfaces.IContainerManager;
import com.taobao.idlefish.flutterboost.interfaces.IContainerRecord;
import com.taobao.idlefish.flutterboost.interfaces.IFlutterViewContainer;
import com.taobao.idlefish.flutterboost.interfaces.IOperateSyncer;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.view.FlutterMain;
import io.flutter.view.FlutterRunArguments;
import java.util.Stack;
public class FlutterViewContainerManager implements IContainerManager {
private final Map<IFlutterViewContainer, IContainerRecord> mRecords = new LinkedHashMap<>();
private final Instrument mInstrument;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final Map<IFlutterViewContainer, IContainerRecord> mRecordMap = new LinkedHashMap<>();
private final Set<ContainerRef> mRefs = new HashSet<>();
private final Stack<IContainerRecord> mRecordStack = new Stack<>();
private IContainerRecord mCurrentTopRecord;
FlutterViewContainerManager() {}
FlutterViewContainerManager() {
mInstrument = new Instrument(this);
}
@Override
public PluginRegistry onContainerCreate(IFlutterViewContainer container) {
assertCallOnMainThread();
public IOperateSyncer generateSyncer(IFlutterViewContainer container) {
Utils.assertCallOnMainThread();
BoostEngineProvider.sInstance.createEngine(container.getContextActivity());
ContainerRecord record = new ContainerRecord(this, container);
if (mRecords.put(container, record) != null) {
if (mRecordMap.put(container, record) != null) {
Debuger.exception("container:" + container.getContainerName() + " already exists!");
return new PluginRegistryImpl(container.getActivity(), container.getBoostFlutterView());
}
mRefs.add(new ContainerRef(record.uniqueId(),container));
FlutterMain.ensureInitializationComplete(container.getActivity().getApplicationContext(), null);
BoostFlutterView flutterView = FlutterBoostPlugin.viewProvider().createFlutterView(container);
if (!flutterView.getFlutterNativeView().isApplicationRunning()) {
String appBundlePath = FlutterMain.findAppBundlePath(container.getActivity().getApplicationContext());
if (appBundlePath != null) {
FlutterRunArguments arguments = new FlutterRunArguments();
arguments.bundlePath = appBundlePath;
arguments.entrypoint = "main";
flutterView.runFromBundle(arguments);
}
}
mInstrument.performCreate(record);
return new PluginRegistryImpl(container.getActivity(), container.getBoostFlutterView());
return record;
}
@Override
public void onContainerAppear(IFlutterViewContainer container) {
assertCallOnMainThread();
final IContainerRecord record = mRecords.get(container);
if (record == null) {
Debuger.exception("container:" + container.getContainerName() + " not exists yet!");
return;
public void pushRecord(IContainerRecord record) {
if(!mRecordMap.containsValue(record)) {
Debuger.exception("invalid record!");
}
mInstrument.performAppear(record);
mCurrentTopRecord = record;
mRecordStack.push(record);
}
@Override
public void onContainerDisappear(IFlutterViewContainer container) {
assertCallOnMainThread();
final IContainerRecord record = mRecords.get(container);
if (record == null) {
Debuger.exception("container:" + container.getContainerName() + " not exists yet!");
return;
}
mInstrument.performDisappear(record);
if (!container.isFinishing()) {
checkIfFlutterViewNeedStopLater();
}
}
@Override
public void onContainerDestroy(IFlutterViewContainer container) {
assertCallOnMainThread();
if (mCurrentTopRecord != null
&& mCurrentTopRecord.getContainer() == container) {
mCurrentTopRecord = null;
public void popRecord(IContainerRecord record) {
if(mRecordStack.peek() == record) {
mRecordStack.pop();
}
final IContainerRecord record = mRecords.remove(container);
if (record == null) {
Debuger.exception("container:" + container.getContainerName() + " not exists yet!");
return;
}
mInstrument.performDestroy(record);
checkIfFlutterViewNeedStopLater();
}
private void checkIfFlutterViewNeedStopLater() {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (!hasContainerAppear()) {
FlutterBoostPlugin.viewProvider().stopFlutterView();
}
}
}, 250);
public void removeRecord(IContainerRecord record) {
mRecordStack.remove(record);
mRecordMap.remove(record.getContainer());
}
@Override
public void onBackPressed(IFlutterViewContainer container) {
assertCallOnMainThread();
final IContainerRecord record = mRecords.get(container);
if (record == null) {
Debuger.exception("container:" + container.getContainerName() + " not exists yet!");
return;
}
Map<String, String> map = new HashMap<>();
map.put("type", "backPressedCallback");
map.put("name", container.getContainerName());
map.put("uniqueId", record.uniqueId());
NavigationService.getService().emitEvent(map);
}
@Override
public void destroyContainerRecord(String name, String uniqueId) {
assertCallOnMainThread();
boolean done = false;
for (Map.Entry<IFlutterViewContainer, IContainerRecord> entry : mRecords.entrySet()) {
public IFlutterViewContainer closeContainer(String uniqueId, Map<String, Object> result) {
for (Map.Entry<IFlutterViewContainer, IContainerRecord> entry : mRecordMap.entrySet()) {
if (TextUtils.equals(uniqueId, entry.getValue().uniqueId())) {
entry.getKey().destroyContainer();
done = true;
break;
entry.getKey().finishContainer();
return entry.getKey();
}
}
if (!done) {
Debuger.exception("destroyContainerRecord can not find name:" + name + " uniqueId:" + uniqueId);
}
}
Debuger.exception("closeContainer can not find uniqueId:" + uniqueId);
@Override
public void onContainerResult(IFlutterViewContainer container, Map result) {
final IContainerRecord record = mRecords.get(container);
if (record == null) {
Debuger.exception("container:" + container.getContainerName() + " not exists yet!");
return;
}
record.onResult(result);
}
@Override
public void setContainerResult(String uniqueId, Map result) {
if (result == null) {
Debuger.exception("setContainerResult result is null");
return;
}
if (!(result instanceof HashMap)) {
result = new HashMap();
result.putAll(result);
}
boolean done = false;
for (Map.Entry<IFlutterViewContainer, IContainerRecord> entry : mRecords.entrySet()) {
if (TextUtils.equals(uniqueId, entry.getValue().uniqueId())) {
entry.getKey().setBoostResult((HashMap) result);
done = true;
break;
}
}
if (!done) {
Debuger.exception("setContainerResult can not find uniqueId:" + uniqueId);
}
return null;
}
@Override
public IContainerRecord getCurrentTopRecord() {
return mCurrentTopRecord;
if(mRecordStack.isEmpty()) return null;
return mRecordStack.peek();
}
@Override
public IContainerRecord getLastRecord() {
final Collection<IContainerRecord> values = mRecords.values();
public IContainerRecord getLastGenerateRecord() {
final Collection<IContainerRecord> values = mRecordMap.values();
if(!values.isEmpty()) {
final ArrayList<IContainerRecord> array = new ArrayList<>(values);
return array.get(array.size()-1);
......@@ -239,7 +116,7 @@ public class FlutterViewContainerManager implements IContainerManager {
@Override
public IFlutterViewContainer findContainerById(String uniqueId) {
IFlutterViewContainer target = null;
for (Map.Entry<IFlutterViewContainer, IContainerRecord> entry : mRecords.entrySet()) {
for (Map.Entry<IFlutterViewContainer, IContainerRecord> entry : mRecordMap.entrySet()) {
if (TextUtils.equals(uniqueId, entry.getValue().uniqueId())) {
target = entry.getKey();
break;
......@@ -259,12 +136,12 @@ public class FlutterViewContainerManager implements IContainerManager {
@Override
public void onShownContainerChanged(String old, String now) {
assertCallOnMainThread();
Utils.assertCallOnMainThread();
IFlutterViewContainer oldContainer = null;
IFlutterViewContainer nowContainer = null;
for (Map.Entry<IFlutterViewContainer, IContainerRecord> entry : mRecords.entrySet()) {
for (Map.Entry<IFlutterViewContainer, IContainerRecord> entry : mRecordMap.entrySet()) {
if (TextUtils.equals(old, entry.getValue().uniqueId())) {
oldContainer = entry.getKey();
}
......@@ -289,9 +166,7 @@ public class FlutterViewContainerManager implements IContainerManager {
@Override
public boolean hasContainerAppear() {
assertCallOnMainThread();
for (Map.Entry<IFlutterViewContainer, IContainerRecord> entry : mRecords.entrySet()) {
for (Map.Entry<IFlutterViewContainer, IContainerRecord> entry : mRecordMap.entrySet()) {
if (entry.getValue().getState() == IContainerRecord.STATE_APPEAR) {
return true;
}
......@@ -300,15 +175,6 @@ public class FlutterViewContainerManager implements IContainerManager {
return false;
}
@Override
public void reset() {
}
private void assertCallOnMainThread() {
if (Looper.myLooper() != Looper.getMainLooper()) {
Debuger.exception("must call method on main thread");
}
}
public static class ContainerRef {
public final String uniqueId;
......@@ -319,28 +185,4 @@ public class FlutterViewContainerManager implements IContainerManager {
this.container = new WeakReference<>(container);
}
}
public static class PluginRegistryImpl implements PluginRegistry {
final BoostFlutterView mBoostFlutterView;
PluginRegistryImpl(Activity activity, BoostFlutterView flutterView) {
mBoostFlutterView = flutterView;
}
@Override
public Registrar registrarFor(String pluginKey) {
return mBoostFlutterView.getPluginRegistry().registrarFor(pluginKey);
}
@Override
public boolean hasPlugin(String key) {
return mBoostFlutterView.getPluginRegistry().hasPlugin(key);
}
@Override
public <T> T valuePublishedByPlugin(String pluginKey) {
return mBoostFlutterView.getPluginRegistry().valuePublishedByPlugin(pluginKey);
}
}
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Alibaba Group
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.taobao.idlefish.flutterboost;
import com.taobao.idlefish.flutterboost.interfaces.IContainerRecord;
import com.taobao.idlefish.flutterboost.interfaces.IContainerManager;
public class Instrument {
private final IContainerManager mManager;
Instrument(IContainerManager manager) {
mManager = manager;
}
public void performCreate(IContainerRecord record) {
final int currentState = record.getState();
if (currentState != IContainerRecord.STATE_UNKNOW) {
Debuger.exception("performCreate state error, current state:" + currentState);
return;
}
record.onCreate();
}
public void performAppear(IContainerRecord record) {
final int currentState = record.getState();
if (currentState != IContainerRecord.STATE_CREATED
&& currentState != IContainerRecord.STATE_DISAPPEAR) {
Debuger.exception("performAppear state error, current state:" + currentState);
return;
}
record.onAppear();
}
public void performDisappear(IContainerRecord record) {
final int currentState = record.getState();
if (currentState != IContainerRecord.STATE_APPEAR) {
Debuger.exception("performDisappear state error , current state:" + currentState);
return;
}
record.onDisappear();
}
public void performDestroy(IContainerRecord record) {
final int currentState = record.getState();
if (currentState != IContainerRecord.STATE_DISAPPEAR) {
Debuger.exception("performDestroy state error, current state:" + currentState);
}
record.onDestroy();
}
}
......@@ -39,7 +39,7 @@
private boolean onCall(MessageResult<Boolean> result,String uniqueId,String pageName,Map params,Boolean animated){
FlutterBoostPlugin.containerManager().destroyContainerRecord(pageName,uniqueId);
FlutterBoostPlugin.containerManager().closeContainer(uniqueId,null);
result.success(true);
return true;
}
......
......@@ -38,8 +38,8 @@
private boolean onCall(MessageResult<Boolean> result,String uniqueId,String key,Map resultData,Map params){
FlutterBoostPlugin.containerManager().setContainerResult(uniqueId,resultData);
result.success(true);
// FlutterBoostPlugin.containerManager().setContainerResult(uniqueId,resultData);
// result.success(true);
return true;
}
......
......@@ -27,6 +27,7 @@
import com.taobao.idlefish.flutterboost.FlutterBoostPlugin;
import com.taobao.idlefish.flutterboost.FlutterViewContainerManager;
import com.taobao.idlefish.flutterboost.interfaces.IContainerRecord;
import com.taobao.idlefish.flutterboost.interfaces.IFlutterViewContainer;
import java.util.ArrayList;
import java.util.HashMap;
......@@ -47,7 +48,7 @@
.containerManager().getCurrentTopRecord();
if(record == null) {
record = FlutterBoostPlugin.containerManager().getLastRecord();
record = FlutterBoostPlugin.containerManager().getLastGenerateRecord();
}
pageInfo.put("name",record.getContainer().getContainerName());
......
......@@ -25,9 +25,16 @@ package com.taobao.idlefish.flutterboost;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Looper;
public class Utils {
public static void assertCallOnMainThread() {
if (Looper.myLooper() != Looper.getMainLooper()) {
Debuger.exception("must call method on main thread");
}
}
public static boolean checkImageValid(final Bitmap bitmap) {
if (null == bitmap) {
return false;
......
/*
* The MIT License (MIT)
*
*
* Copyright (c) 2019 Alibaba Group
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
......@@ -24,161 +24,123 @@
package com.taobao.idlefish.flutterboost.containers;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import com.taobao.idlefish.flutterboost.BoostFlutterView;
import com.taobao.idlefish.flutterboost.FlutterBoostPlugin;
import com.taobao.idlefish.flutterboost.interfaces.IFlutterViewContainer;
import com.taobao.idlefish.flutterboost.interfaces.IOperateSyncer;
import java.util.HashMap;
import java.util.Map;
import io.flutter.app.FlutterActivity;
import io.flutter.view.FlutterNativeView;
import io.flutter.view.FlutterView;
import io.flutter.embedding.android.FlutterView;
abstract public class BoostFlutterActivity extends FlutterActivity implements IFlutterViewContainer {
public abstract class BoostFlutterActivity extends Activity implements IFlutterViewContainer {
private FlutterContent mFlutterContent;
private BoostFlutterView mFlutterView;
private IOperateSyncer mSyncer;
@Override
protected void onCreate(Bundle savedInstanceState) {
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
mFlutterContent = new FlutterContent(this);
BoostFlutterView.Builder builder = new BoostFlutterView.Builder(this);
mFlutterView = builder.renderMode(FlutterView.RenderMode.texture)
.transparencyMode(FlutterView.TransparencyMode.opaque)
.build();
setContentView(mFlutterContent);
setContentView(mFlutterView);
FlutterBoostPlugin.containerManager().onContainerCreate(this);
onRegisterPlugins(this);
mSyncer = FlutterBoostPlugin.containerManager().generateSyncer(this);
mSyncer.onCreate();
}
@Override
protected void onPostResume() {
super.onPostResume();
FlutterBoostPlugin.containerManager().onContainerAppear(BoostFlutterActivity.this);
mFlutterContent.attachFlutterView(getBoostFlutterView());
protected void onResume() {
super.onResume();
mSyncer.onAppear();
}
@Override
protected void onPause() {
mFlutterContent.detachFlutterView();
FlutterBoostPlugin.containerManager().onContainerDisappear(this);
mSyncer.onDisappear();
super.onPause();
}
@Override
protected void onDestroy() {
FlutterBoostPlugin.containerManager().onContainerDestroy(this);
mFlutterContent.destroy();
mSyncer.onDestroy();
super.onDestroy();
}
public FlutterView createFlutterView(Context context) {
return FlutterBoostPlugin.viewProvider().createFlutterView(this);
}
@Override
public FlutterNativeView createFlutterNativeView() {
return FlutterBoostPlugin.viewProvider().createFlutterNativeView(this);
public void onBackPressed() {
super.onBackPressed();
mSyncer.onBackPressed();
}
@Override
public boolean retainFlutterNativeView() {
return true;
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
mSyncer.onNewIntent(intent);
}
protected View createSplashScreenView() {
FrameLayout frameLayout = new FrameLayout(this);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER;
frameLayout.addView(new ProgressBar(this), params);
return frameLayout;
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
mSyncer.onActivityResult(requestCode,resultCode,data);
}
protected View createFlutterInitCoverView() {
View initCover = new View(this);
initCover.setBackgroundColor(Color.WHITE);
return initCover;
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
mSyncer.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
public void onContainerShown() {
mFlutterContent.onContainerShown();
public void onTrimMemory(int level) {
super.onTrimMemory(level);
mSyncer.onTrimMemory(level);
}
@Override
public void onContainerHidden() {
mFlutterContent.onContainerHidden();
public void onLowMemory() {
super.onLowMemory();
mSyncer.onLowMemory();
}
@Override
public void onBackPressed() {
FlutterBoostPlugin.containerManager().onBackPressed(this);
protected void onUserLeaveHint() {
super.onUserLeaveHint();
mSyncer.onUserLeaveHint();
}
@Override
public Activity getActivity() {
public Activity getContextActivity() {
return this;
}
@Override
public BoostFlutterView getBoostFlutterView() {
return (BoostFlutterView) getFlutterView();
return mFlutterView;
}
@Override
public void destroyContainer() {
public void finishContainer() {
finish();
}
@Override
public abstract String getContainerName();
public void onContainerShown() {}
@Override
public abstract Map getContainerParams();
public void onContainerHidden() {}
@Override
public void setBoostResult(HashMap result) {
FlutterBoostPlugin.setBoostResult(this, result);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
FlutterBoostPlugin.onBoostResult(this,requestCode,resultCode,data);
}
class FlutterContent extends FlutterViewStub {
public FlutterContent(Context context) {
super(context);
}
@Override
public View createFlutterInitCoverView() {
return BoostFlutterActivity.this.createFlutterInitCoverView();
}
@Override
public BoostFlutterView getBoostFlutterView() {
return BoostFlutterActivity.this.getBoostFlutterView();
}
@Override
public View createSplashScreenView() {
return BoostFlutterActivity.this.createSplashScreenView();
}
Intent data = new Intent();
data.putExtra(RESULT_KEY,result);
setResult(RESULT_OK,data);
}
}
......@@ -23,138 +23,120 @@
*/
package com.taobao.idlefish.flutterboost.containers;
import android.content.Context;
import android.graphics.Color;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import com.taobao.idlefish.flutterboost.BoostFlutterView;
import com.taobao.idlefish.flutterboost.Debuger;
import com.taobao.idlefish.flutterboost.FlutterBoostPlugin;
import com.taobao.idlefish.flutterboost.interfaces.IFlutterViewContainer;
import com.taobao.idlefish.flutterboost.interfaces.IOperateSyncer;
import java.util.HashMap;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.embedding.android.FlutterView;
abstract public class BoostFlutterFragment extends Fragment implements IFlutterViewContainer {
FlutterContent mContent;
PluginRegistry mRegistry;
boolean resumed = false;
private BoostFlutterView mFlutterView;
private IOperateSyncer mSyncer;
@Nullable
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
BoostFlutterView.Builder builder = new BoostFlutterView.Builder(getContextActivity());
mFlutterView = builder.renderMode(FlutterView.RenderMode.texture)
.transparencyMode(FlutterView.TransparencyMode.opaque)
.build();
mRegistry = FlutterBoostPlugin.containerManager().onContainerCreate(this);
onRegisterPlugins(mRegistry);
}
mSyncer = FlutterBoostPlugin.containerManager().generateSyncer(this);
mSyncer.onCreate();
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
mContent = new FlutterContent(getActivity());
return mContent;
return mFlutterView;
}
@Override
public void onResume() {
super.onResume();
if (!resumed) {
resumed = true;
FlutterBoostPlugin.containerManager().onContainerAppear(this);
mContent.attachFlutterView(getBoostFlutterView());
Log.e("FlutterBoost", "FlutterMenuFragment resume");
}
mSyncer.onAppear();
}
@Override
public void onPause() {
mSyncer.onDisappear();
super.onPause();
if (resumed) {
resumed = false;
mContent.snapshot();
FlutterBoostPlugin.containerManager().onContainerDisappear(this);
Log.e("FlutterBoost", "FlutterMenuFragment stop");
}
}
@Override
public void onDestroy() {
mSyncer.onDestroy();
super.onDestroy();
if (mContent != null) {
mContent.destroy();
}
FlutterBoostPlugin.containerManager().onContainerDestroy(this);
}
@Override
public void onContainerShown() {
mContent.onContainerShown();
public void onBackPressed() {
mSyncer.onBackPressed();
}
@Override
public void onContainerHidden() {
mContent.onContainerHidden();
public void onNewIntent(Intent intent) {
mSyncer.onNewIntent(intent);
}
@Override
public BoostFlutterView getBoostFlutterView() {
return FlutterBoostPlugin.viewProvider().createFlutterView(this);
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
mSyncer.onActivityResult(requestCode,resultCode,data);
}
@Override
public boolean isFinishing() {
return getActivity().isFinishing();
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
mSyncer.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
protected View createSplashScreenView() {
FrameLayout layout = new FrameLayout(getContext());
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER;
layout.addView(new ProgressBar(getContext()),params);
return layout;
public void onTrimMemory(int level) {
mSyncer.onTrimMemory(level);
}
protected View createFlutterInitCoverView() {
View initCover = new View(getActivity());
initCover.setBackgroundColor(Color.WHITE);
return initCover;
@Override
public void onLowMemory() {
super.onLowMemory();
mSyncer.onLowMemory();
}
public void onUserLeaveHint() {
mSyncer.onUserLeaveHint();
}
@Override
public void setBoostResult(HashMap result) {
public Activity getContextActivity() {
return getActivity();
}
class FlutterContent extends FlutterViewStub {
@Override
public BoostFlutterView getBoostFlutterView() {
return mFlutterView;
}
public FlutterContent(Context context) {
super(context);
}
@Override
public void finishContainer() {
getActivity().finish();
}
@Override
public View createFlutterInitCoverView() {
return BoostFlutterFragment.this.createFlutterInitCoverView();
}
@Override
public void onContainerShown() {}
@Override
public BoostFlutterView getBoostFlutterView() {
return BoostFlutterFragment.this.getBoostFlutterView();
}
@Override
public void onContainerHidden() {}
@Override
public View createSplashScreenView() {
return BoostFlutterFragment.this.createSplashScreenView();
}
@Override
public void setBoostResult(HashMap result) {
Intent data = new Intent();
data.putExtra(RESULT_KEY,result);
getActivity().setResult(Activity.RESULT_OK,data);
}
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Alibaba Group
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.taobao.idlefish.flutterboost.containers;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import com.taobao.idlefish.flutterboost.BoostFlutterView;
import com.taobao.idlefish.flutterboost.Debuger;
abstract public class FlutterViewStub extends FrameLayout {
public static final Handler sHandler = new ProcessHandler(Looper.getMainLooper());
protected Bitmap mBitmap;
protected ImageView mSnapshot;
protected FrameLayout mStub;
protected View mCover;
protected View mSplashScreenView;
public FlutterViewStub(Context context) {
super(context);
mStub = new FrameLayout(context);
mStub.setBackgroundColor(Color.WHITE);
addView(mStub, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
mSnapshot = new ImageView(context);
mSnapshot.setScaleType(ImageView.ScaleType.FIT_CENTER);
mSnapshot.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
mCover = createFlutterInitCoverView();
addView(mCover, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
final BoostFlutterView flutterView = getBoostFlutterView();
if (!flutterView.firstFrameCalled()) {
mSplashScreenView = createSplashScreenView();
addView(mSplashScreenView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
}
public void onContainerShown() {
Debuger.log("onContainerShown");
sHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (mSplashScreenView != null) {
FlutterViewStub.this.removeView(mSplashScreenView);
mSplashScreenView = null;
}
if (mCover != null) {
FlutterViewStub.this.removeView(mCover);
}
if (mSnapshot.getParent() == FlutterViewStub.this) {
FlutterViewStub.this.removeView(mSnapshot);
mSnapshot.setImageBitmap(null);
if (mBitmap != null && !mBitmap.isRecycled()) {
mBitmap.recycle();
mBitmap = null;
}
}
getBoostFlutterView().scheduleFrame();
getBoostFlutterView().requestFocus();
getBoostFlutterView().invalidate();
}
}, 167);
}
public void onContainerHidden() {
//Debuger.log("onContainerHidden");
}
public void snapshot() {
if (mStub.getChildCount() <= 0) return;
if (mSnapshot.getParent() != null) return;
BoostFlutterView flutterView = (BoostFlutterView) mStub.getChildAt(0);
mBitmap = flutterView.getBitmap();
if (mBitmap != null && !mBitmap.isRecycled()) {
mSnapshot.setImageBitmap(mBitmap);
addView(mSnapshot);
}
}
public void attachFlutterView(final BoostFlutterView flutterView) {
if (flutterView.getParent() != mStub) {
sHandler.removeMessages(ProcessHandler.MSG_DETACH);
Debuger.log("attachFlutterView");
if (flutterView.getParent() != null) {
((ViewGroup) flutterView.getParent()).removeView(flutterView);
}
mStub.addView(flutterView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
}
public void detachFlutterView() {
if (mStub.getChildCount() <= 0) return;
final BoostFlutterView flutterView = (BoostFlutterView) mStub.getChildAt(0);
if (flutterView == null) return;
if (mSnapshot.getParent() == null) {
mBitmap = flutterView.getBitmap();
if (mBitmap != null && !mBitmap.isRecycled()) {
mSnapshot.setImageBitmap(mBitmap);
Debuger.log("snapshot view");
addView(mSnapshot);
}
}
Message msg = new Message();
msg.what = ProcessHandler.MSG_DETACH;
msg.obj = new Runnable() {
@Override
public void run() {
if (flutterView.getParent() != null && flutterView.getParent() == mStub) {
Debuger.log("detachFlutterView");
mStub.removeView(flutterView);
}
}
};
sHandler.sendMessageDelayed(msg,18);
}
public void destroy() {
removeAllViews();
mSnapshot.setImageBitmap(null);
if (mBitmap != null && !mBitmap.isRecycled()) {
mBitmap.recycle();
mBitmap = null;
}
}
protected View createFlutterInitCoverView() {
View initCover = new View(getContext());
initCover.setBackgroundColor(Color.WHITE);
return initCover;
}
protected View createSplashScreenView() {
FrameLayout layout = new FrameLayout(getContext());
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER;
layout.addView(new ProgressBar(getContext()),params);
return layout;
}
abstract protected BoostFlutterView getBoostFlutterView();
public static class ProcessHandler extends Handler {
static final int MSG_DETACH = 175101;
ProcessHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.obj instanceof Runnable) {
Runnable run = (Runnable) msg.obj;
run.run();
}
}
}
}
......@@ -23,106 +23,21 @@
*/
package com.taobao.idlefish.flutterboost.interfaces;
import android.app.Activity;
import java.util.Map;
import io.flutter.plugin.common.PluginRegistry;
public interface IContainerManager {
/**
* call by native side when container create
* @param container
* @return
*/
PluginRegistry onContainerCreate(IFlutterViewContainer container);
/**
* call by native side when container appear
* @param container
* @return
*/
void onContainerAppear(IFlutterViewContainer container);
/**
* call by native side when container disappear
* @param container
* @return
*/
void onContainerDisappear(IFlutterViewContainer container);
/**
* call by native side when container destroy
* @param container
* @return
*/
void onContainerDestroy(IFlutterViewContainer container);
IOperateSyncer generateSyncer(IFlutterViewContainer container);
/**
* call by native side when back key pressed
* @param container
* @return
*/
void onBackPressed(IFlutterViewContainer container);
IFlutterViewContainer closeContainer(String uniqueId,Map<String,Object> result);
/**
* call by flutter side when need destroy container
* @param name
* @param uq
*/
void destroyContainerRecord(String name,String uq);
/**
* call by native side when container handle a result (onActivityResult)
* @param container
* @param result
*/
void onContainerResult(IFlutterViewContainer container,Map result);
/**
* call by flutter side when flutter want set a result for request (setResult)
* @param uniqueId
* @param result
*/
void setContainerResult(String uniqueId,Map result);
/**
* get current interactive container
* @return
*/
IContainerRecord getCurrentTopRecord();
/**
* get last created container
* @return
*/
IContainerRecord getLastRecord();
IContainerRecord getLastGenerateRecord();
/**
* find a container
* @param uniqueId
* @return
*/
IFlutterViewContainer findContainerById(String uniqueId);
/**
* call by flutter side when a container shown or hidden
* @param old
* @param now
*/
void onShownContainerChanged(String old,String now);
void onShownContainerChanged(String oldUniqueId,String nowUniqueId);
/**
* is any container appear now
* @return
*/
boolean hasContainerAppear();
/**
* no use
*/
void reset();
}
......@@ -23,12 +23,10 @@
*/
package com.taobao.idlefish.flutterboost.interfaces;
import java.util.Map;
/**
* a container record, which use map a flutter page
*/
public interface IContainerRecord {
public interface IContainerRecord extends IOperateSyncer{
int STATE_UNKNOW = 0;
int STATE_CREATED = 1;
int STATE_APPEAR = 2;
......@@ -38,9 +36,4 @@ public interface IContainerRecord {
String uniqueId();
IFlutterViewContainer getContainer();
int getState();
void onCreate();
void onAppear();
void onDisappear();
void onDestroy();
void onResult(Map Result);
}
......@@ -21,32 +21,31 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.taobao.idlefish.flutterboost;
package com.taobao.idlefish.flutterboost.interfaces;
import android.content.Context;
import io.flutter.view.FlutterNativeView;
import com.taobao.idlefish.flutterboost.BoostFlutterView;
public class BoostFlutterNativeView extends FlutterNativeView {
import io.flutter.embedding.engine.FlutterEngine;
public BoostFlutterNativeView(Context context) {
super(context);
}
/**
* a flutter view provider
*/
public interface IFlutterEngineProvider {
public void detachFromFlutterView() {
//do nothing...
}
/**
* create flutter engine, we just hold a single instance now
* @param context
* @return
*/
FlutterEngine createEngine(Context context);
public void detach() {
//do nothing...
}
/**
* may return null
* @return
*/
FlutterEngine tryGetEngine();
@Override
public void destroy() {
//do nothing...
}
public void boostDestroy() {
super.destroy();
}
}
......@@ -37,7 +37,7 @@ import io.flutter.plugin.common.PluginRegistry;
public interface IFlutterViewContainer {
String RESULT_KEY = "_flutter_result_";
Activity getActivity();
Activity getContextActivity();
/**
* provide a flutter view
......@@ -48,7 +48,7 @@ public interface IFlutterViewContainer {
/**
* call to destroy the container
*/
void destroyContainer();
void finishContainer();
/**
* container name
......@@ -72,21 +72,9 @@ public interface IFlutterViewContainer {
*/
void onContainerHidden();
/**
* is container finishing
* @return
*/
boolean isFinishing();
/**
* call by flutter side to set result
* @param result
*/
void setBoostResult(HashMap result);
/**
* register flutter plugins
* @param registry
*/
void onRegisterPlugins(PluginRegistry registry);
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Alibaba Group
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.taobao.idlefish.flutterboost.interfaces;
import com.taobao.idlefish.flutterboost.BoostFlutterNativeView;
import com.taobao.idlefish.flutterboost.BoostFlutterView;
/**
* a flutter view provider
*/
public interface IFlutterViewProvider {
/**
* create flutter view, we just hold a single instance now
* @param container
* @return
*/
BoostFlutterView createFlutterView(IFlutterViewContainer container);
/**
* single instance also
* @param container
* @return
*/
BoostFlutterNativeView createFlutterNativeView(IFlutterViewContainer container);
/**
* may return null
* @return
*/
BoostFlutterView tryGetFlutterView();
/**
* release flutter view
*/
void stopFlutterView();
/**
* reset all refrence
*/
void reset();
}
package com.taobao.idlefish.flutterboost.interfaces;
import android.content.Intent;
public interface IOperateSyncer {
void onCreate();
void onAppear();
void onDisappear();
void onDestroy();
void onBackPressed();
void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults);
void onNewIntent(Intent intent);
void onActivityResult(int requestCode, int resultCode, Intent data);
void onUserLeaveHint();
void onTrimMemory(int level);
void onLowMemory();
}
......@@ -29,6 +29,8 @@ import android.content.Context;
import java.util.Map;
import io.flutter.plugin.common.PluginRegistry;
/**
* APIs that platform(Android App) must provide
*/
......@@ -41,10 +43,10 @@ public interface IPlatform {
Application getApplication();
/**
* get main activity, which must always exist at the bottom of task stack.
* register plugins
* @return
*/
Activity getMainActivity();
void onRegisterPlugins(PluginRegistry registry);
/**
* debug or not
......
......@@ -28,11 +28,6 @@ public class FlutterFragment extends BoostFlutterFragment {
super.setArguments(args);
}
@Override
public void onRegisterPlugins(PluginRegistry registry) {
GeneratedPluginRegistrant.registerWith(registry);
}
@Override
public String getContainerName() {
return "flutterFragment";
......@@ -45,11 +40,6 @@ public class FlutterFragment extends BoostFlutterFragment {
return params;
}
@Override
public void destroyContainer() {
}
public static FlutterFragment instance(String tag){
FlutterFragment fragment = new FlutterFragment();
fragment.setTabTag(tag);
......
......@@ -10,11 +10,6 @@ import io.flutter.plugins.GeneratedPluginRegistrant;
public class FlutterPageActivity extends BoostFlutterActivity {
@Override
public void onRegisterPlugins(PluginRegistry registry) {
GeneratedPluginRegistrant.registerWith(registry);
}
/**
* 该方法返回当前Activity在Flutter层对应的name,
* 混合栈将会在flutter层根据这个名字,在注册的Route表中查找对应的Widget
......
package com.taobao.idlefish.flutterboostexample;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import com.taobao.idlefish.flutterboost.Debuger;
import com.taobao.idlefish.flutterboost.FlutterBoostPlugin;
......@@ -13,6 +10,8 @@ import com.taobao.idlefish.flutterboost.interfaces.IPlatform;
import java.util.Map;
import io.flutter.app.FlutterApplication;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MyApplication extends FlutterApplication {
@Override
......@@ -25,17 +24,9 @@ public class MyApplication extends FlutterApplication {
return MyApplication.this;
}
/**
* 获取应用入口的Activity,这个Activity在应用交互期间应该是一直在栈底的
* @return
*/
@Override
public Activity getMainActivity() {
if (MainActivity.sRef != null) {
return MainActivity.sRef.get();
}
return null;
public void onRegisterPlugins(PluginRegistry registry) {
GeneratedPluginRegistrant.registerWith(registry);
}
@Override
......
Future<Map<String,dynamic>> open(String url,{Map<String,dynamic> urlParams,Map<String,dynamic> exts}){
}
void close(String id,{Map<String,dynamic> result,Map<String,dynamic> exts}){
}
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment