Commit 8e651945 authored by yangwu.jia's avatar yangwu.jia

Merge branch 'feature/flutter_1.9_upgrade'

# Conflicts:
#	android/src/main/java/com/idlefish/flutterboost/BoostFlutterView.java
#	android/src/main/java/com/idlefish/flutterboost/XFlutterView.java
#	ios/Classes/container/FLBFlutterViewContainer.m
parents 42586f71 3d998dc4
......@@ -17,4 +17,3 @@ example/android/app/.classpath
example/android/app/.project
example/android/.project
flutter_boost
example
......@@ -26,6 +26,7 @@ android {
buildToolsVersion '27.0.3'
defaultConfig {
minSdkVersion 16
targetSdkVersion 28
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
lintOptions {
......@@ -34,9 +35,12 @@ android {
}
dependencies {
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support:support-v4:28.0.0'
implementation 'android.arch.lifecycle:common-java8:1.1.1'
implementation 'com.alibaba:fastjson:1.2.41'
implementation 'com.android.support:support-v4:26.1.0'
implementation 'com.android.support:appcompat-v7:26.1.0'
}
ext {
......
<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>
/*
* 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.idlefish.flutterboost;
import android.content.Context;
import com.idlefish.flutterboost.interfaces.IFlutterEngineProvider;
import com.idlefish.flutterboost.interfaces.IStateListener;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.view.FlutterMain;
public class BoostEngineProvider implements IFlutterEngineProvider {
private BoostFlutterEngine mEngine = null;
public BoostEngineProvider() {}
@Override
public BoostFlutterEngine createEngine(Context context) {
return new BoostFlutterEngine(context.getApplicationContext());
}
@Override
public BoostFlutterEngine provideEngine(Context context) {
Utils.assertCallOnMainThread();
if (mEngine == null) {
FlutterShellArgs flutterShellArgs = new FlutterShellArgs(new String[0]);
FlutterMain.ensureInitializationComplete(
context.getApplicationContext(), flutterShellArgs.toArray());
mEngine = createEngine(context.getApplicationContext());
final IStateListener stateListener = FlutterBoost.sInstance.mStateListener;
if(stateListener != null) {
stateListener.onEngineCreated(mEngine);
}
}
return mEngine;
}
@Override
public BoostFlutterEngine tryGetEngine() {
return mEngine;
}
public static void assertEngineRunning(){
final BoostFlutterEngine engine = FlutterBoost.singleton().engineProvider().tryGetEngine();
if(engine == null || !engine.isRunning()) {
throw new RuntimeException("engine is not running yet!");
}
}
}
package com.idlefish.flutterboost;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.Surface;
import android.view.View;
import com.idlefish.flutterboost.interfaces.IContainerRecord;
import com.idlefish.flutterboost.interfaces.IStateListener;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import io.flutter.app.FlutterPluginRegistry;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.editing.TextInputPlugin;
import io.flutter.plugin.platform.PlatformViewRegistry;
import io.flutter.view.FlutterMain;
import io.flutter.view.FlutterView;
import io.flutter.view.TextureRegistry;
public class BoostFlutterEngine extends FlutterEngine {
protected final Context mContext;
protected final BoostPluginRegistry mBoostPluginRegistry;
protected final DartExecutor.DartEntrypoint mEntrypoint;
protected final String mInitRoute;
private final FakeRender mFakeRender;
protected WeakReference<Activity> mCurrentActivityRef;
public BoostFlutterEngine(@NonNull Context context) {
this(context, null, null);
}
public BoostFlutterEngine(@NonNull Context context, DartExecutor.DartEntrypoint entrypoint, String initRoute) {
super(context);
mContext = context.getApplicationContext();
mBoostPluginRegistry = new BoostPluginRegistry(this, context);
if (entrypoint != null) {
mEntrypoint = entrypoint;
} else {
mEntrypoint = defaultDartEntrypoint(context);
}
if (initRoute != null) {
mInitRoute = initRoute;
} else {
mInitRoute = defaultInitialRoute(context);
}
FlutterJNI flutterJNI = null;
try {
Field field = FlutterEngine.class.getDeclaredField("flutterJNI");
field.setAccessible(true);
flutterJNI = (FlutterJNI) field.get(this);
} catch (Throwable t) {
try {
for(Field field:FlutterEngine.class.getDeclaredFields()) {
field.setAccessible(true);
Object o = field.get(this);
if(o instanceof FlutterJNI) {
flutterJNI = (FlutterJNI)o;
}
}
if(flutterJNI == null) {
throw new RuntimeException("FlutterJNI not found");
}
}catch (Throwable it){
Debuger.exception(it);
}
}
mFakeRender = new FakeRender(flutterJNI);
}
public void startRun(@Nullable Activity activity) {
mCurrentActivityRef = new WeakReference<>(activity);
if (!getDartExecutor().isExecutingDart()) {
Debuger.log("engine start running...");
getNavigationChannel().setInitialRoute(mInitRoute);
getDartExecutor().executeDartEntrypoint(mEntrypoint);
final IStateListener stateListener = FlutterBoost.sInstance.mStateListener;
if (stateListener != null) {
stateListener.onEngineStarted(this);
}
FlutterBoost.singleton().platform().registerPlugins(mBoostPluginRegistry);
if(activity != null) {
FlutterRenderer.ViewportMetrics metrics = new FlutterRenderer.ViewportMetrics();
metrics.devicePixelRatio = activity.getResources().getDisplayMetrics().density;
final View decor = activity.getWindow().getDecorView();
if(decor != null) {
metrics.width = decor.getWidth();
metrics.height = decor.getHeight();
}
if (metrics.width <= 0 || metrics.height <= 0) {
metrics.width = Utils.getMetricsWidth(activity);
metrics.height = Utils.getMetricsHeight(activity);
}
metrics.paddingTop = Utils.getStatusBarHeight(activity);
metrics.paddingRight = 0;
metrics.paddingBottom = 0;
metrics.paddingLeft = 0;
metrics.viewInsetTop = 0;
metrics.viewInsetRight = 0;
metrics.viewInsetBottom = 0;
metrics.viewInsetLeft = 0;
getRenderer().setViewportMetrics(metrics);
}
}
}
protected DartExecutor.DartEntrypoint defaultDartEntrypoint(Context context) {
return new DartExecutor.DartEntrypoint(
context.getResources().getAssets(),
FlutterMain.findAppBundlePath(context),
"main");
}
protected String defaultInitialRoute(Context context) {
return "/";
}
public BoostPluginRegistry getBoostPluginRegistry() {
return mBoostPluginRegistry;
}
public boolean isRunning() {
return getDartExecutor().isExecutingDart();
}
@NonNull
@Override
public FlutterRenderer getRenderer() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
boolean hit = false;
for (StackTraceElement st : stackTrace) {
if (st.getMethodName().equals("sendViewportMetricsToFlutter")) {
hit = true;
break;
}
}
if (hit) {
return mFakeRender;
} else {
return super.getRenderer();
}
}
public class BoostPluginRegistry extends FlutterPluginRegistry {
private final FlutterEngine mEngine;
public BoostPluginRegistry(FlutterEngine engine, Context context) {
super(engine, context);
mEngine = engine;
}
public PluginRegistry.Registrar registrarFor(String pluginKey) {
return new BoostRegistrar(mEngine, super.registrarFor(pluginKey));
}
}
public class BoostRegistrar implements PluginRegistry.Registrar {
private final PluginRegistry.Registrar mRegistrar;
private final FlutterEngine mEngine;
BoostRegistrar(FlutterEngine engine, PluginRegistry.Registrar registrar) {
mRegistrar = registrar;
mEngine = engine;
}
@Override
public Activity activity() {
Activity activity;
IContainerRecord record;
record = FlutterBoost.singleton().containerManager().getCurrentTopRecord();
if (record == null) {
record = FlutterBoost.singleton().containerManager().getLastGenerateRecord();
}
if (record == null) {
activity = FlutterBoost.singleton().currentActivity();
} else {
activity = record.getContainer().getContextActivity();
}
if (activity == null && mCurrentActivityRef != null) {
activity = mCurrentActivityRef.get();
}
if (activity == null) {
throw new RuntimeException("current has no valid Activity yet");
}
return activity;
}
@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);
}
}
private boolean viewportMetricsEqual(FlutterRenderer.ViewportMetrics a, FlutterRenderer.ViewportMetrics b) {
return a != null && b != null &&
a.height == b.height &&
a.width == b.width &&
a.devicePixelRatio == b.devicePixelRatio &&
a.paddingBottom == b.paddingBottom &&
a.paddingLeft == b.paddingLeft &&
a.paddingRight == b.paddingRight &&
a.paddingTop == b.paddingTop &&
a.viewInsetLeft == b.viewInsetLeft &&
a.viewInsetRight == b.viewInsetRight &&
a.viewInsetTop == b.viewInsetTop &&
a.viewInsetBottom == b.viewInsetBottom;
}
class FakeRender extends FlutterRenderer {
private ViewportMetrics last;
public FakeRender(FlutterJNI flutterJNI) {
super(flutterJNI);
}
@Override
public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) {
if (viewportMetrics.width > 0 && viewportMetrics.height > 0 /*&& !viewportMetricsEqual(last, viewportMetrics)*/) {
last = viewportMetrics;
Debuger.log("setViewportMetrics w:" + viewportMetrics.width + " h:" + viewportMetrics.height);
super.setViewportMetrics(viewportMetrics);
}
}
@Override
public void attachToRenderSurface(@NonNull RenderSurface renderSurface) {
Debuger.exception("should never called!");
}
@Override
public void detachFromRenderSurface() {
Debuger.exception("should never called!");
}
@Override
public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
Debuger.exception("should never called!");
}
@Override
public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
Debuger.exception("should never called!");
}
@Override
public SurfaceTextureEntry createSurfaceTexture() {
Debuger.exception("should never called!");
return null;
}
@Override
public void surfaceCreated(Surface surface) {
Debuger.exception("should never called!");
}
@Override
public void surfaceChanged(int width, int height) {
Debuger.exception("should never called!");
}
@Override
public void surfaceDestroyed() {
Debuger.exception("should never called!");
}
@Override
public Bitmap getBitmap() {
Debuger.exception("should never called!");
return null;
}
@Override
public void dispatchPointerDataPacket(ByteBuffer buffer, int position) {
Debuger.exception("should never called!");
}
@Override
public boolean isSoftwareRenderingEnabled() {
Debuger.exception("should never called!");
return false;
}
@Override
public void setAccessibilityFeatures(int flags) {
Debuger.exception("should never called!");
}
@Override
public void setSemanticsEnabled(boolean enabled) {
Debuger.exception("should never called!");
}
@Override
public void dispatchSemanticsAction(int id, int action, ByteBuffer args, int argsPosition) {
Debuger.exception("should never called!");
}
}
}
/*
* 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.idlefish.flutterboost;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.view.ViewCompat;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityNodeProvider;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.idlefish.flutterboost.interfaces.IStateListener;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
import io.flutter.embedding.android.FlutterView;
import io.flutter.plugin.platform.PlatformPlugin;
import io.flutter.view.AccessibilityBridge;
public class BoostFlutterView extends FrameLayout {
private BoostFlutterEngine mFlutterEngine;
private XFlutterView mFlutterView;
private PlatformPlugin mPlatformPlugin;
private Bundle mArguments;
private RenderingProgressCoverCreator mRenderingProgressCoverCreator;
private View mRenderingProgressCover;
private final List<OnFirstFrameRenderedListener> mFirstFrameRenderedListeners = new LinkedList<>();
private boolean mEngineAttached = false;
private boolean mNeedSnapshotWhenDetach = false;
private SnapshotView mSnapshot;
private final io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener mOnFirstFrameRenderedListener =
new io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener() {
@Override
public void onFirstFrameRendered() {
Debuger.log("BoostFlutterView onFirstFrameRendered");
if(mRenderingProgressCover != null && mRenderingProgressCover.getParent() != null) {
((ViewGroup)mRenderingProgressCover.getParent()).removeView(mRenderingProgressCover);
}
if(mNeedSnapshotWhenDetach) {
mSnapshot.dismissSnapshot(BoostFlutterView.this);
}
final Object[] listeners = mFirstFrameRenderedListeners.toArray();
for (Object obj : listeners) {
((OnFirstFrameRenderedListener) obj).onFirstFrameRendered(BoostFlutterView.this);
}
}
};
private final ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
ViewCompat.requestApplyInsets(mFlutterView);
}
};
public BoostFlutterView(Context context, BoostFlutterEngine engine, Bundle args, RenderingProgressCoverCreator creator) {
super(context);
mFlutterEngine = engine;
mArguments = args;
mRenderingProgressCoverCreator = creator;
init();
}
private void init() {
if (mFlutterEngine == null) {
mFlutterEngine = createFlutterEngine(getContext());
}
if (mArguments == null) {
mArguments = new Bundle();
}
mPlatformPlugin = new PlatformPlugin((Activity) getContext(), mFlutterEngine.getPlatformChannel());
mFlutterView = new XFlutterView(getContext(), getRenderMode(), getTransparencyMode());
addView(mFlutterView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
mSnapshot = new SnapshotView(getContext());
if(mRenderingProgressCoverCreator != null) {
mRenderingProgressCover = mRenderingProgressCoverCreator
.createRenderingProgressCover(getContext());
}else{
mRenderingProgressCover = createRenderingProgressCorver();
}
if(mRenderingProgressCover != null) {
addView(mRenderingProgressCover, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
mFlutterView.addOnFirstFrameRenderedListener(mOnFirstFrameRenderedListener);
mFlutterEngine.startRun((Activity)getContext());
final IStateListener stateListener = FlutterBoost.sInstance.mStateListener;
if(stateListener != null) {
stateListener.onFlutterViewInited(mFlutterEngine,this);
}
checkAssert();
}
private void checkAssert(){
try {
Method method = FlutterView.class.getDeclaredMethod("sendViewportMetricsToFlutter");
if(method == null) {
throw new Exception("method: FlutterView.sendViewportMetricsToFlutter not found!");
}
}catch (Throwable t){
Debuger.exception(t);
}
}
protected View createRenderingProgressCorver(){
FrameLayout frameLayout = new FrameLayout(getContext());
frameLayout.setBackgroundColor(Color.WHITE);
LinearLayout linearLayout = new LinearLayout(getContext());
linearLayout.setOrientation(LinearLayout.VERTICAL);
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.gravity = Gravity.CENTER;
frameLayout.addView(linearLayout,layoutParams);
ProgressBar progressBar = new ProgressBar(getContext());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER_HORIZONTAL;
linearLayout.addView(progressBar,params);
TextView textView = new TextView(getContext());
params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER_HORIZONTAL;
textView.setText("Frame Rendering...");
linearLayout.addView(textView,params);
return frameLayout;
}
protected BoostFlutterEngine createFlutterEngine(Context context) {
return FlutterBoost.singleton().engineProvider().provideEngine(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();
ViewCompat.requestApplyInsets(this);
getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalLayoutListener);
onDetach();
flutterEngine.getPluginRegistry().getPlatformViewsController().onFlutterViewDestroyed();
}
public BoostFlutterEngine getEngine(){
return mFlutterEngine;
}
public void onResume() {
Debuger.log("BoostFlutterView onResume");
// mFlutterEngine.getLifecycleChannel().appIsResumed();
}
// public void onPostResume() {
// Debuger.log("BoostFlutterView onPostResume");
// mPlatformPlugin.onPostResume();
// }
public void onPause() {
Debuger.log("BoostFlutterView onPause");
// mFlutterEngine.getLifecycleChannel().appIsInactive();
}
public void onStop() {
Debuger.log("BoostFlutterView onStop");
// mFlutterEngine.getLifecycleChannel().appIsPaused();
}
public void onAttach() {
Debuger.log("BoostFlutterView onAttach");
final IStateListener stateListener = FlutterBoost.sInstance.mStateListener;
if(stateListener != null) {
stateListener.beforeEngineAttach(mFlutterEngine,this);
}
mFlutterView.attachToFlutterEngine(mFlutterEngine);
mEngineAttached = true;
if(stateListener != null) {
stateListener.afterEngineAttached(mFlutterEngine,this);
}
}
public void toggleSnapshot() {
mSnapshot.toggleSnapshot(this);
}
public void toggleAttach() {
if(mEngineAttached) {
onDetach();
}else{
onAttach();
}
}
public void onDetach() {
Debuger.log("BoostFlutterView onDetach");
if(mNeedSnapshotWhenDetach) {
mSnapshot.showSnapshot(BoostFlutterView.this);
}
final IStateListener stateListener = FlutterBoost.sInstance.mStateListener;
if(stateListener != null) {
stateListener.beforeEngineDetach(mFlutterEngine,this);
}
mFlutterView.detachFromFlutterEngine();
mEngineAttached = false;
if(stateListener != null) {
stateListener.afterEngineDetached(mFlutterEngine,this);
}
}
public void onDestroy() {
Debuger.log("BoostFlutterView onDestroy");
mFlutterView.removeOnFirstFrameRenderedListener(mOnFirstFrameRenderedListener);
mFlutterView.release();
}
//混合栈的返回和原来Flutter的返回逻辑不同
public void onBackPressed() {
// Debuger.log("onBackPressed()");
// if (mFlutterEngine != null) {
// mFlutterEngine.getNavigationChannel().popRoute();
// } else {
// Debuger.log("Invoked onBackPressed() before BoostFlutterView was attached to an Activity.");
// }
}
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (mFlutterEngine != null) {
mFlutterEngine.getPluginRegistry().onRequestPermissionsResult(requestCode, permissions, grantResults);
} else {
Debuger.log("onRequestPermissionResult() invoked before BoostFlutterView was attached to an Activity.");
}
}
public void onNewIntent(Intent intent) {
if (mFlutterEngine != null) {
mFlutterEngine.getPluginRegistry().onNewIntent(intent);
} else {
Debuger.log("onNewIntent() invoked before BoostFlutterView was attached to an Activity.");
}
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mFlutterEngine != null) {
mFlutterEngine.getPluginRegistry().onActivityResult(requestCode, resultCode, data);
} else {
Debuger.log("onActivityResult() invoked before BoostFlutterView was attached to an Activity.");
}
}
public void onUserLeaveHint() {
if (mFlutterEngine != null) {
mFlutterEngine.getPluginRegistry().onUserLeaveHint();
} else {
Debuger.log("onUserLeaveHint() invoked before BoostFlutterView was attached to an Activity.");
}
}
public void onTrimMemory(int level) {
if (mFlutterEngine != null) {
if (level == 10) {
mFlutterEngine.getSystemChannel().sendMemoryPressureWarning();
}
} else {
Debuger.log("onTrimMemory() invoked before BoostFlutterView was attached to an Activity.");
}
}
public void onLowMemory() {
mFlutterEngine.getSystemChannel().sendMemoryPressureWarning();
}
public static class Builder {
private Context context;
private BoostFlutterEngine engine;
private FlutterView.RenderMode renderMode;
private FlutterView.TransparencyMode transparencyMode;
private RenderingProgressCoverCreator renderingProgressCoverCreator;
public Builder(Context ctx) {
this.context = ctx;
renderMode = FlutterView.RenderMode.surface;
transparencyMode = FlutterView.TransparencyMode.transparent;
}
public Builder flutterEngine(BoostFlutterEngine engine) {
this.engine = engine;
return this;
}
public Builder renderMode(FlutterView.RenderMode renderMode) {
this.renderMode = renderMode;
return this;
}
public Builder renderingProgressCoverCreator(RenderingProgressCoverCreator creator) {
this.renderingProgressCoverCreator = creator;
return this;
}
public Builder transparencyMode(FlutterView.TransparencyMode transparencyMode) {
this.transparencyMode = transparencyMode;
return 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,renderingProgressCoverCreator);
}
}
public interface OnFirstFrameRenderedListener {
void onFirstFrameRendered(BoostFlutterView view);
}
public interface RenderingProgressCoverCreator {
View createRenderingProgressCover(Context context);
}
}
package com.idlefish.flutterboost;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterEngine;
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 java.util.*;
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 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 boolean hasPlugin(String pluginKey) {
return this.pluginMap.containsKey(pluginKey);
}
public Object valuePublishedByPlugin(String pluginKey) {
return this.pluginMap.get(pluginKey);
}
public static class BoostRegistrarAggregate implements FlutterPlugin, ActivityAware {
private final Set<BoostRegistrar> shimRegistrars;
private FlutterPluginBinding flutterPluginBinding;
private ActivityPluginBinding activityPluginBinding;
public ActivityPluginBinding getActivityPluginBinding() {
return activityPluginBinding;
}
private BoostRegistrarAggregate() {
this.shimRegistrars = new HashSet();
}
public void addPlugin(BoostRegistrar shimRegistrar) {
this.shimRegistrars.add(shimRegistrar);
if (this.flutterPluginBinding != null) {
shimRegistrar.onAttachedToEngine(this.flutterPluginBinding);
}
if (this.activityPluginBinding != null) {
shimRegistrar.onAttachedToActivity(this.activityPluginBinding);
}
}
public void onAttachedToEngine(FlutterPluginBinding binding) {
this.flutterPluginBinding = binding;
Iterator var2 = this.shimRegistrars.iterator();
while (var2.hasNext()) {
BoostRegistrar shimRegistrar = (BoostRegistrar) var2.next();
shimRegistrar.onAttachedToEngine(binding);
}
}
public void onDetachedFromEngine(FlutterPluginBinding binding) {
Iterator var2 = this.shimRegistrars.iterator();
while (var2.hasNext()) {
BoostRegistrar shimRegistrar = (BoostRegistrar) var2.next();
shimRegistrar.onDetachedFromEngine(binding);
}
this.flutterPluginBinding = null;
}
public void onAttachedToActivity(ActivityPluginBinding binding) {
this.activityPluginBinding = binding;
Iterator var2 = this.shimRegistrars.iterator();
while (var2.hasNext()) {
BoostRegistrar shimRegistrar = (BoostRegistrar) var2.next();
shimRegistrar.onAttachedToActivity(binding);
}
}
public void onDetachedFromActivityForConfigChanges() {
Iterator var1 = this.shimRegistrars.iterator();
while (var1.hasNext()) {
BoostRegistrar shimRegistrar = (BoostRegistrar) var1.next();
shimRegistrar.onDetachedFromActivity();
}
this.activityPluginBinding = null;
}
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
Iterator var2 = this.shimRegistrars.iterator();
while (var2.hasNext()) {
BoostRegistrar shimRegistrar = (BoostRegistrar) var2.next();
shimRegistrar.onReattachedToActivityForConfigChanges(binding);
}
}
public void onDetachedFromActivity() {
Iterator var1 = this.shimRegistrars.iterator();
while (var1.hasNext()) {
BoostRegistrar shimRegistrar = (BoostRegistrar) var1.next();
shimRegistrar.onDetachedFromActivity();
}
}
}
}
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);
}
}
}
......@@ -69,14 +69,13 @@ public class ContainerRecord implements IContainerRecord {
@Override
public void onCreate() {
Utils.assertCallOnMainThread();
BoostEngineProvider.assertEngineRunning();
if (mState != STATE_UNKNOW) {
Debuger.exception("state error");
}
mState = STATE_CREATED;
mContainer.getBoostFlutterView().onResume();
// mContainer.getBoostFlutterView().onResume();
mProxy.create();
}
......@@ -92,9 +91,10 @@ public class ContainerRecord implements IContainerRecord {
mManager.pushRecord(this);
mProxy.appear();
mContainer.getBoostFlutterView().onAttach();
mProxy.appear();
}
@Override
......@@ -129,15 +129,15 @@ public class ContainerRecord implements IContainerRecord {
mProxy.destroy();
mContainer.getBoostFlutterView().onDestroy();
// mContainer.getBoostFlutterView().onDestroy();
mManager.removeRecord(this);
mManager.setContainerResult(this,-1,-1,null);
if (!mManager.hasContainerAppear()) {
mContainer.getBoostFlutterView().onPause();
mContainer.getBoostFlutterView().onStop();
// mContainer.getBoostFlutterView().onPause();
// mContainer.getBoostFlutterView().onStop();
}
}
......@@ -154,44 +154,45 @@ public class ContainerRecord implements IContainerRecord {
map.put("name", mContainer.getContainerUrl());
map.put("uniqueId", mUniqueId);
FlutterBoost.singleton().channel().sendEvent("lifecycle", map);
NewFlutterBoost.instance().channel().sendEvent("lifecycle", map);
mContainer.getBoostFlutterView().onBackPressed();
// 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 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();
}
......@@ -252,7 +253,7 @@ public class ContainerRecord implements IContainerRecord {
args.put("pageName", url);
args.put("params", params);
args.put("uniqueId", uniqueId);
FlutterBoost.singleton().channel().invokeMethod(method, args);
NewFlutterBoost.instance().channel().invokeMethod(method, args);
}
public void invokeChannelUnsafe(String method, String url, Map params, String uniqueId) {
......@@ -260,7 +261,7 @@ public class ContainerRecord implements IContainerRecord {
args.put("pageName", url);
args.put("params", params);
args.put("uniqueId", uniqueId);
FlutterBoost.singleton().channel().invokeMethodUnsafe(method, args);
NewFlutterBoost.instance().channel().invokeMethodUnsafe(method, args);
}
}
......
......@@ -60,7 +60,7 @@ public class Debuger {
public static boolean isDebug(){
try {
return FlutterBoost.singleton().platform().isDebug();
return NewFlutterBoost.instance().platform().isDebug();
}catch (Throwable t){
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
* 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.idlefish.flutterboost;
import android.app.Activity;
import android.app.Application;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import com.idlefish.flutterboost.interfaces.IContainerManager;
import com.idlefish.flutterboost.interfaces.IContainerRecord;
import com.idlefish.flutterboost.interfaces.IFlutterEngineProvider;
import com.idlefish.flutterboost.interfaces.IFlutterViewContainer;
import com.idlefish.flutterboost.interfaces.IPlatform;
import com.idlefish.flutterboost.interfaces.IStateListener;
import java.util.HashMap;
import java.util.Map;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class FlutterBoost {
static FlutterBoost sInstance = null;
public static synchronized void init(IPlatform platform) {
if (sInstance == null) {
sInstance = new FlutterBoost(platform);
}
if (platform.whenEngineStart() == IPlatform.IMMEDIATELY) {
sInstance.mEngineProvider
.provideEngine(platform.getApplication())
.startRun(null);
}
}
public static FlutterBoost singleton() {
if (sInstance == null) {
throw new RuntimeException("FlutterBoost not init yet");
}
return sInstance;
}
private final IPlatform mPlatform;
private final FlutterViewContainerManager mManager;
private final IFlutterEngineProvider mEngineProvider;
IStateListener mStateListener;
Activity mCurrentActiveActivity;
private FlutterBoost(IPlatform platform) {
mPlatform = platform;
mManager = new FlutterViewContainerManager();
IFlutterEngineProvider provider = platform.engineProvider();
if(provider == null) {
provider = new BoostEngineProvider();
}
mEngineProvider = provider;
platform.getApplication().registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks());
BoostChannel.addActionAfterRegistered(new BoostChannel.ActionAfterRegistered() {
@Override
public void onChannelRegistered(BoostChannel channel) {
channel.addMethodCallHandler(new BoostMethodHandler());
}
});
}
public IFlutterEngineProvider engineProvider() {
return sInstance.mEngineProvider;
}
public IContainerManager containerManager() {
return sInstance.mManager;
}
public IPlatform platform() {
return sInstance.mPlatform;
}
public BoostChannel channel() {
return BoostChannel.singleton();
}
public Activity currentActivity() {
return sInstance.mCurrentActiveActivity;
}
public IFlutterViewContainer findContainerById(String id) {
return mManager.findContainerById(id);
}
public void setStateListener(@Nullable IStateListener listener){
mStateListener = listener;
}
class ActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
if (platform().whenEngineStart() == IPlatform.ANY_ACTIVITY_CREATED) {
sInstance.mEngineProvider
.provideEngine(activity)
.startRun(activity);
}
}
@Override
public void onActivityStarted(Activity activity) {
if (mCurrentActiveActivity == null) {
Debuger.log("Application entry foreground");
if (mEngineProvider.tryGetEngine() != null) {
HashMap<String, String> map = new HashMap<>();
map.put("type", "foreground");
channel().sendEvent("lifecycle",map);
}
}
mCurrentActiveActivity = activity;
}
@Override
public void onActivityResumed(Activity activity) {
mCurrentActiveActivity = activity;
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
if (mCurrentActiveActivity == activity) {
Debuger.log("Application entry background");
if (mEngineProvider.tryGetEngine() != null) {
HashMap<String, String> map = new HashMap<>();
map.put("type", "background");
channel().sendEvent("lifecycle",map);
}
mCurrentActiveActivity = null;
}
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
if (mCurrentActiveActivity == activity) {
Debuger.log("Application entry background");
if (mEngineProvider.tryGetEngine() != null) {
HashMap<String, String> map = new HashMap<>();
map.put("type", "background");
channel().sendEvent("lifecycle",map);
}
mCurrentActiveActivity = null;
}
}
}
class BoostMethodHandler implements MethodChannel.MethodCallHandler {
@Override
public void onMethodCall(MethodCall methodCall, final MethodChannel.Result result) {
switch (methodCall.method) {
case "pageOnStart":
{
Map<String, Object> pageInfo = new HashMap<>();
try {
IContainerRecord record = mManager.getCurrentTopRecord();
if (record == null) {
record = mManager.getLastGenerateRecord();
}
if(record != null) {
pageInfo.put("name", record.getContainer().getContainerUrl());
pageInfo.put("params", record.getContainer().getContainerUrlParams());
pageInfo.put("uniqueId", record.uniqueId());
}
result.success(pageInfo);
} catch (Throwable t) {
result.error("no flutter page found!",t.getMessage(),t);
}
}
break;
case "openPage":
{
try {
Map<String,Object> params = methodCall.argument("urlParams");
Map<String,Object> exts = methodCall.argument("exts");
String url = methodCall.argument("url");
mManager.openContainer(url, params, exts, new FlutterViewContainerManager.OnResult() {
@Override
public void onResult(Map<String, Object> rlt) {
if (result != null) {
result.success(rlt);
}
}
});
}catch (Throwable t){
result.error("open page error",t.getMessage(),t);
}
}
break;
case "closePage":
{
try {
String uniqueId = methodCall.argument("uniqueId");
Map<String,Object> resultData = methodCall.argument("result");
Map<String,Object> exts = methodCall.argument("exts");
mManager.closeContainer(uniqueId, resultData,exts);
result.success(true);
}catch (Throwable t){
result.error("close page error",t.getMessage(),t);
}
}
break;
case "onShownContainerChanged":
{
try {
String newId = methodCall.argument("newName");
String oldId = methodCall.argument("oldName");
mManager.onShownContainerChanged(newId,oldId);
result.success(true);
}catch (Throwable t){
result.error("onShownContainerChanged",t.getMessage(),t);
}
}
break;
default:
{
result.notImplemented();
}
}
}
}
public static 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);
}
}
package com.idlefish.flutterboost;
import android.os.Handler;
import android.support.annotation.Nullable;
import com.idlefish.flutterboost.interfaces.IStateListener;
import com.idlefish.flutterboost.interfaces.IContainerRecord;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.*;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;
public class BoostChannel {
public class FlutterBoostPlugin {
private static BoostChannel sInstance;
private static FlutterBoostPlugin sInstance;
private final MethodChannel mMethodChannel;
private final Set<MethodChannel.MethodCallHandler> mMethodCallHandlers = new HashSet<>();
private final Map<String,Set<EventListener>> mEventListeners = new HashMap<>();
private final Map<String, Set<EventListener>> mEventListeners = new HashMap<>();
private static final Set<ActionAfterRegistered> sActions = new HashSet<>();
public static BoostChannel singleton() {
public static FlutterBoostPlugin singleton() {
if (sInstance == null) {
throw new RuntimeException("BoostChannel not register yet");
throw new RuntimeException("FlutterBoostPlugin not register yet");
}
return sInstance;
}
public static void addActionAfterRegistered(ActionAfterRegistered action) {
if(action == null) return;
if (action == null) return;
if(sInstance == null) {
if (sInstance == null) {
sActions.add(action);
}else{
} else {
action.onChannelRegistered(sInstance);
}
}
public static void registerWith(PluginRegistry.Registrar registrar) {
sInstance = new BoostChannel(registrar);
sInstance = new FlutterBoostPlugin(registrar);
for(ActionAfterRegistered a : sActions) {
for (ActionAfterRegistered a : sActions) {
a.onChannelRegistered(sInstance);
}
if(FlutterBoost.sInstance != null) {
final IStateListener stateListener = FlutterBoost.sInstance.mStateListener;
if (stateListener != null) {
stateListener.onChannelRegistered(registrar, sInstance);
}
}
// if (NewFlutterBoost.instance() != null) {
// final IStateListener stateListener = NewFlutterBoost.instance().mStateListener;
// if (stateListener != null) {
// stateListener.onChannelRegistered(registrar, sInstance);
// }
// }
sActions.clear();
}
private BoostChannel(PluginRegistry.Registrar registrar){
private FlutterBoostPlugin(PluginRegistry.Registrar registrar) {
mMethodChannel = new MethodChannel(registrar.messenger(), "flutter_boost");
mMethodChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
......@@ -78,26 +76,29 @@ public class BoostChannel {
}
}
if(listeners != null) {
for(Object o:listeners) {
((EventListener)o).onEvent(name,args);
if (listeners != null) {
for (Object o : listeners) {
((EventListener) o).onEvent(name, args);
}
}
}else{
} else {
Object[] handlers;
synchronized (mMethodCallHandlers) {
handlers = mMethodCallHandlers.toArray();
}
for(Object o:handlers) {
((MethodChannel.MethodCallHandler)o).onMethodCall(methodCall,result);
for (Object o : handlers) {
((MethodChannel.MethodCallHandler) o).onMethodCall(methodCall, result);
}
}
}
});
addMethodCallHandler(new BoostMethodHandler());
}
public void invokeMethodUnsafe(final String name,Serializable args){
public void invokeMethodUnsafe(final String name, Serializable args) {
invokeMethod(name, args, new MethodChannel.Result() {
@Override
public void success(@Nullable Object o) {
......@@ -106,17 +107,17 @@ public class BoostChannel {
@Override
public void error(String s, @Nullable String s1, @Nullable Object o) {
Debuger.log("invoke method "+name+" error:"+s+" | "+s1);
Debuger.log("invoke method " + name + " error:" + s + " | " + s1);
}
@Override
public void notImplemented() {
Debuger.log("invoke method "+name+" notImplemented");
Debuger.log("invoke method " + name + " notImplemented");
}
});
}
public void invokeMethod(final String name,Serializable args){
public void invokeMethod(final String name, Serializable args) {
invokeMethod(name, args, new MethodChannel.Result() {
@Override
public void success(@Nullable Object o) {
......@@ -125,18 +126,18 @@ public class BoostChannel {
@Override
public void error(String s, @Nullable String s1, @Nullable Object o) {
Debuger.exception("invoke method "+name+" error:"+s+" | "+s1);
Debuger.exception("invoke method " + name + " error:" + s + " | " + s1);
}
@Override
public void notImplemented() {
Debuger.exception("invoke method "+name+" notImplemented");
Debuger.exception("invoke method " + name + " notImplemented");
}
});
}
public void invokeMethod(final String name,Serializable args,MethodChannel.Result result){
if("__event__".equals(name)) {
public void invokeMethod(final String name, Serializable args, MethodChannel.Result result) {
if ("__event__".equals(name)) {
Debuger.exception("method name should not be __event__");
}
......@@ -144,7 +145,7 @@ public class BoostChannel {
}
public void addMethodCallHandler(MethodChannel.MethodCallHandler handler) {
synchronized (mMethodCallHandlers){
synchronized (mMethodCallHandlers) {
mMethodCallHandlers.add(handler);
}
}
......@@ -156,29 +157,29 @@ public class BoostChannel {
}
public void addEventListener(String name, EventListener listener) {
synchronized (mEventListeners){
synchronized (mEventListeners) {
Set<EventListener> set = mEventListeners.get(name);
if(set == null) {
if (set == null) {
set = new HashSet<>();
}
set.add(listener);
mEventListeners.put(name,set);
mEventListeners.put(name, set);
}
}
public void removeEventListener(EventListener listener) {
synchronized (mEventListeners) {
for(Set<EventListener> set:mEventListeners.values()) {
for (Set<EventListener> set : mEventListeners.values()) {
set.remove(listener);
}
}
}
public void sendEvent(String name,Map args){
public void sendEvent(String name, Map args) {
Map event = new HashMap();
event.put("name",name);
event.put("arguments",args);
mMethodChannel.invokeMethod("__event__",event);
event.put("name", name);
event.put("arguments", args);
mMethodChannel.invokeMethod("__event__", event);
}
public interface EventListener {
......@@ -186,6 +187,90 @@ public class BoostChannel {
}
public interface ActionAfterRegistered {
void onChannelRegistered(BoostChannel channel);
void onChannelRegistered(FlutterBoostPlugin channel);
}
class BoostMethodHandler implements MethodChannel.MethodCallHandler {
@Override
public void onMethodCall(MethodCall methodCall, final MethodChannel.Result result) {
FlutterViewContainerManager mManager = (FlutterViewContainerManager) NewFlutterBoost.instance().containerManager();
switch (methodCall.method) {
case "pageOnStart": {
Map<String, Object> pageInfo = new HashMap<>();
try {
IContainerRecord record = mManager.getCurrentTopRecord();
if (record == null) {
record = mManager.getLastGenerateRecord();
}
if (record != null) {
pageInfo.put("name", record.getContainer().getContainerUrl());
pageInfo.put("params", record.getContainer().getContainerUrlParams());
pageInfo.put("uniqueId", record.uniqueId());
}
result.success(pageInfo);
NewFlutterBoost.instance().setFlutterPostFrameCallTime(new Date().getTime());
} catch (Throwable t) {
result.error("no flutter page found!", t.getMessage(), t);
}
}
break;
case "openPage": {
try {
Map<String, Object> params = methodCall.argument("urlParams");
Map<String, Object> exts = methodCall.argument("exts");
String url = methodCall.argument("url");
mManager.openContainer(url, params, exts, new FlutterViewContainerManager.OnResult() {
@Override
public void onResult(Map<String, Object> rlt) {
if (result != null) {
result.success(rlt);
}
}
});
} catch (Throwable t) {
result.error("open page error", t.getMessage(), t);
}
}
break;
case "closePage": {
try {
String uniqueId = methodCall.argument("uniqueId");
Map<String, Object> resultData = methodCall.argument("result");
Map<String, Object> exts = methodCall.argument("exts");
mManager.closeContainer(uniqueId, resultData, exts);
result.success(true);
} catch (Throwable t) {
result.error("close page error", t.getMessage(), t);
}
}
break;
case "onShownContainerChanged": {
try {
String newId = methodCall.argument("newName");
String oldId = methodCall.argument("oldName");
mManager.onShownContainerChanged(newId, oldId);
result.success(true);
} catch (Throwable t) {
result.error("onShownContainerChanged", t.getMessage(), t);
}
}
break;
default: {
result.notImplemented();
}
}
}
}
}
......@@ -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) {
......@@ -111,9 +118,9 @@ public class FlutterViewContainerManager implements IContainerManager {
}
void openContainer(String url, Map<String, Object> urlParams, Map<String, Object> exts,OnResult onResult) {
Context context = FlutterBoost.singleton().currentActivity();
Context context = NewFlutterBoost.instance().currentActivity();
if(context == null) {
context = FlutterBoost.singleton().platform().getApplication();
context =NewFlutterBoost.instance().platform().getApplication();
}
if(urlParams == null) {
......@@ -128,11 +135,13 @@ public class FlutterViewContainerManager implements IContainerManager {
final String uniqueId = ContainerRecord.genUniqueId(url);
urlParams.put(IContainerRecord.UNIQ_KEY,uniqueId);
IContainerRecord currentTopRecord = getCurrentTopRecord();
if(onResult != null) {
mOnResults.put(uniqueId,onResult);
mOnResults.put(currentTopRecord.uniqueId(),onResult);
}
FlutterBoost.singleton().platform().openContainer(context,url,urlParams,requestCode,exts);
NewFlutterBoost.instance().platform().openContainer(context,url,urlParams,requestCode,exts);
}
IContainerRecord closeContainer(String uniqueId, Map<String, Object> result,Map<String,Object> exts) {
......@@ -148,7 +157,7 @@ public class FlutterViewContainerManager implements IContainerManager {
Debuger.exception("closeContainer can not find uniqueId:" + uniqueId);
}
FlutterBoost.singleton().platform().closeContainer(targetRecord,result,exts);
NewFlutterBoost.instance().platform().closeContainer(targetRecord,result,exts);
return targetRecord;
}
......
package com.idlefish.flutterboost;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
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;
public class NewFlutterBoost {
private Platform mPlatform;
private FlutterViewContainerManager mManager;
private FlutterEngine mEngine;
private Activity mCurrentActiveActivity;
private PluginRegistry mRegistry;
static NewFlutterBoost sInstance = null;
private long FlutterPostFrameCallTime=0;
public long getFlutterPostFrameCallTime(){
return FlutterPostFrameCallTime;
}
public void setFlutterPostFrameCallTime(long FlutterPostFrameCallTime){
this.FlutterPostFrameCallTime=FlutterPostFrameCallTime;
}
public static NewFlutterBoost instance() {
if (sInstance == null) {
sInstance = new NewFlutterBoost();
}
return sInstance;
}
public void init(Platform platform) {
mPlatform = platform;
mManager = new FlutterViewContainerManager();
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");
doInitialFlutter();
}
}
@Override
public void onActivityStarted(Activity activity) {
if (mCurrentActiveActivity == null) {
Debuger.log("Application entry foreground");
if (createEngine() != null) {
HashMap<String, String> map = new HashMap<>();
map.put("type", "foreground");
channel().sendEvent("lifecycle", map);
}
}
mCurrentActiveActivity = activity;
}
@Override
public void onActivityResumed(Activity activity) {
mCurrentActiveActivity = activity;
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
if (mCurrentActiveActivity == activity) {
Debuger.log("Application entry background");
if (createEngine() != null) {
HashMap<String, String> map = new HashMap<>();
map.put("type", "background");
channel().sendEvent("lifecycle", map);
}
mCurrentActiveActivity = null;
}
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
if (mCurrentActiveActivity == activity) {
Debuger.log("Application entry background");
if (createEngine() != null) {
HashMap<String, String> map = new HashMap<>();
map.put("type", "background");
channel().sendEvent("lifecycle", map);
}
mCurrentActiveActivity = null;
}
}
});
if (mPlatform.whenEngineStart() == ConfigBuilder.IMMEDIATELY) {
doInitialFlutter();
}
}
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;
}
// 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 (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();
}
public static class ConfigBuilder {
public static final String DEFAULT_DART_ENTRYPOINT = "main";
public static final String DEFAULT_INITIAL_ROUTE = "/";
public static int IMMEDIATELY = 0; //立即启动引擎
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.texture;
private Application mApp;
private INativeRouter router = null;
private BoostLifecycleListener lifecycleListener;
public ConfigBuilder(Application app, INativeRouter router) {
this.router = router;
this.mApp = app;
}
public ConfigBuilder renderMode(FlutterView.RenderMode renderMode) {
this.renderMode = renderMode;
return this;
}
public ConfigBuilder dartEntrypoint(@NonNull String dartEntrypoint) {
this.dartEntrypoint = dartEntrypoint;
return this;
}
public ConfigBuilder initialRoute(@NonNull String initialRoute) {
this.initialRoute = initialRoute;
return this;
}
public ConfigBuilder isDebug(boolean isDebug) {
this.isDebug = isDebug;
return this;
}
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() {
public Application getApplication() {
return ConfigBuilder.this.mApp;
}
public boolean isDebug() {
return ConfigBuilder.this.isDebug;
}
@Override
public String initialRoute() {
return ConfigBuilder.this.initialRoute;
}
public void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts) {
router.openContainer(context, url, urlParams, requestCode, exts);
}
public int whenEngineStart() {
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 IContainerManager containerManager() {
return sInstance.mManager;
}
public Platform platform() {
return sInstance.mPlatform;
}
public FlutterBoostPlugin channel() {
return FlutterBoostPlugin.singleton();
}
public Activity currentActivity() {
return sInstance.mCurrentActiveActivity;
}
public IFlutterViewContainer findContainerById(String id) {
return mManager.findContainerById(id);
}
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();
}
}
package com.idlefish.flutterboost;
import android.app.Application;
import android.content.Context;
import com.idlefish.flutterboost.interfaces.IContainerRecord;
import com.idlefish.flutterboost.interfaces.IFlutterEngineProvider;
import com.idlefish.flutterboost.interfaces.IPlatform;
import java.lang.reflect.Method;
import java.util.Map;
import io.flutter.embedding.android.FlutterView;
import io.flutter.plugin.common.PluginRegistry;
public abstract class Platform implements IPlatform {
public abstract class Platform {
@Override
public boolean isDebug() {
return false;
}
public abstract Application getApplication();
public abstract void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts);
public abstract int whenEngineStart();
public abstract int whenEngineDestroy();
public abstract FlutterView.RenderMode renderMode();
public abstract boolean isDebug();
public abstract String initialRoute();
public NewFlutterBoost.BoostLifecycleListener lifecycleListener;
@Override
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);
}
@Override
public IFlutterEngineProvider engineProvider() {
return new BoostEngineProvider();
}
@Override
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);
}
}
@Override
public int whenEngineStart() {
return ANY_ACTIVITY_CREATED;
}
}
package com.idlefish.flutterboost;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import java.util.LinkedList;
import java.util.List;
public class SnapshotView extends FrameLayout {
private ImageView mImg;
public SnapshotView(@NonNull Context context) {
super(context);
init();
}
public SnapshotView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public SnapshotView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
setBackgroundColor(Color.RED);
mImg = new ImageView(getContext());
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
params.gravity = Gravity.CENTER;
mImg.setScaleType(ImageView.ScaleType.FIT_XY);
mImg.setLayoutParams(params);
addView(mImg);
}
public void toggleSnapshot(BoostFlutterView flutterView){
if (!dismissSnapshot(flutterView)) {
showSnapshot(flutterView);
}
}
public boolean showSnapshot(BoostFlutterView flutterView){
if(flutterView == null) return false;
dismissSnapshot(flutterView);
final Bitmap bitmap = flutterView.getEngine().getRenderer().getBitmap();
if(bitmap == null || bitmap.isRecycled()) {
return false;
}
mImg.setImageBitmap(bitmap);
flutterView.addView(this,new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
Debuger.log("showSnapshot");
return true;
}
public boolean dismissSnapshot(BoostFlutterView flutterView){
List<View> snapshots = new LinkedList<>();
for(int index = 0;index < flutterView.getChildCount();index++){
View view = flutterView.getChildAt(index);
if(view instanceof SnapshotView) {
snapshots.add(view);
}
}
if(snapshots.isEmpty()) {
return false;
}else{
for(View v:snapshots) {
flutterView.removeView(v);
}
Debuger.log("dismissSnapshot");
return true;
}
}
}
package com.idlefish.flutterboost;
import com.idlefish.flutterboost.interfaces.IStateListener;
import io.flutter.plugin.common.PluginRegistry;
public class StateListener implements IStateListener {
@Override
public void onEngineCreated(BoostFlutterEngine engine) {
Debuger.log(">>onEngineCreated");
}
@Override
public void onEngineStarted(BoostFlutterEngine engine) {
Debuger.log(">>onEngineStarted");
}
@Override
public void onChannelRegistered(PluginRegistry.Registrar registrar, BoostChannel channel) {
Debuger.log(">>onFlutterViewInited");
}
@Override
public void onFlutterViewInited(BoostFlutterEngine engine, BoostFlutterView flutterView) {
Debuger.log(">>onFlutterViewInited");
}
@Override
public void beforeEngineAttach(BoostFlutterEngine engine, BoostFlutterView flutterView) {
Debuger.log(">>beforeEngineAttach");
}
@Override
public void afterEngineAttached(BoostFlutterEngine engine, BoostFlutterView flutterView) {
Debuger.log(">>afterEngineAttached");
}
@Override
public void beforeEngineDetach(BoostFlutterEngine engine, BoostFlutterView flutterView) {
Debuger.log(">>beforeEngineDetach");
}
@Override
public void afterEngineDetached(BoostFlutterEngine engine, BoostFlutterView flutterView) {
Debuger.log(">>afterEngineDetached");
}
}
......@@ -35,14 +35,14 @@ import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import com.alibaba.fastjson.JSON;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
public class Utils {
......@@ -263,7 +263,7 @@ public class Utils {
return;
}
String [] arr = new String[]{"mLastSrvView", "mServedView", "mNextServedView"};
String [] arr = new String[]{"mLastSrvView","mServedView", "mNextServedView"};
Field f = null;
Object obj_get = null;
for (int i = 0;i < arr.length;i ++) {
......@@ -272,21 +272,71 @@ public class Utils {
f = imm.getClass().getDeclaredField(param);
if (f.isAccessible() == false) {
f.setAccessible(true);
} // author: sodino mail:sodino@qq.com
}
obj_get = f.get(imm);
if (obj_get != null && obj_get instanceof View) {
View v_get = (View) obj_get;
if (v_get.getContext() == destContext) { // 被InputMethodManager持有引用的context是想要目标销毁的
f.set(imm, null); // 置空,破坏掉path to gc节点
if (v_get.getContext() == destContext) {
f.set(imm, null);
} else {
// 不是想要目标销毁的,即为又进了另一层界面了,不要处理,避免影响原逻辑,也就不用继续for循环了
break;
}
}
}catch(Throwable t){
t.printStackTrace();
// t.printStackTrace();
}
}
}
public static String assembleUrl(String url,Map<String, Object> urlParams){
StringBuilder targetUrl = new StringBuilder(url);
if(urlParams != null && !urlParams.isEmpty()) {
if(!targetUrl.toString().contains("?")){
targetUrl.append("?");
}
for(Map.Entry entry:urlParams.entrySet()) {
if(entry.getValue() instanceof Map ) {
Map<String,Object> params = (Map<String,Object> )entry.getValue();
for(Map.Entry param:params.entrySet()) {
String key = (String)param.getKey();
String value = null;
if(param.getValue() instanceof Map || param.getValue() instanceof List) {
try {
value = URLEncoder.encode(JSON.toJSONString(param.getValue()), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}else{
value = (param.getValue()==null?null:URLEncoder.encode( String.valueOf(param.getValue())));
}
if(value==null){
continue;
}
if(targetUrl.toString().endsWith("?")){
targetUrl.append(key).append("=").append(value);
}else{
targetUrl.append("&").append(key).append("=").append(value);
}
}
}
}
}
return targetUrl.toString();
}
}
\ No newline at end of file
package com.idlefish.flutterboost;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.Surface;
import android.view.TextureView;
import io.flutter.Log;
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class XFlutterTextureView extends TextureView implements FlutterRenderer.RenderSurface {
private static final String TAG = "XFlutterTextureView";
private boolean isSurfaceAvailableForRendering;
private boolean isAttachedToFlutterRenderer;
@Nullable
private FlutterRenderer flutterRenderer;
@NonNull
private Set<OnFirstFrameRenderedListener> onFirstFrameRenderedListeners;
private final SurfaceTextureListener surfaceTextureListener;
public XFlutterTextureView(@NonNull Context context) {
this(context, (AttributeSet)null);
}
public XFlutterTextureView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.isSurfaceAvailableForRendering = false;
this.isAttachedToFlutterRenderer = false;
this.onFirstFrameRenderedListeners = new HashSet();
this.surfaceTextureListener = new SurfaceTextureListener() {
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
Log.v("FlutterTextureView", "SurfaceTextureListener.onSurfaceTextureAvailable()");
XFlutterTextureView.this.isSurfaceAvailableForRendering = true;
if (XFlutterTextureView.this.isAttachedToFlutterRenderer) {
XFlutterTextureView.this.connectSurfaceToRenderer();
}
}
public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
Log.v("FlutterTextureView", "SurfaceTextureListener.onSurfaceTextureSizeChanged()");
if (XFlutterTextureView.this.isAttachedToFlutterRenderer) {
XFlutterTextureView.this.changeSurfaceSize(width, height);
}
}
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
}
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
Log.v("FlutterTextureView", "SurfaceTextureListener.onSurfaceTextureDestroyed()");
XFlutterTextureView.this.isSurfaceAvailableForRendering = false;
if (XFlutterTextureView.this.isAttachedToFlutterRenderer) {
XFlutterTextureView.this.disconnectSurfaceFromRenderer();
}
return true;
}
};
this.init();
}
private void init() {
this.setSurfaceTextureListener(this.surfaceTextureListener);
}
public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
Log.v("FlutterTextureView", "Attaching to FlutterRenderer.");
if (this.flutterRenderer != null) {
Log.v("FlutterTextureView", "Already connected to a FlutterRenderer. Detaching from old one and attaching to new one.");
this.flutterRenderer.detachFromRenderSurface();
}
this.flutterRenderer = flutterRenderer;
this.isAttachedToFlutterRenderer = true;
if (this.isSurfaceAvailableForRendering) {
Log.v("FlutterTextureView", "Surface is available for rendering. Connecting FlutterRenderer to Android surface.");
this.connectSurfaceToRenderer();
}
}
public void detachFromRenderer() {
if (this.flutterRenderer != null) {
if (this.getWindowToken() != null) {
Log.v("FlutterTextureView", "Disconnecting FlutterRenderer from Android surface.");
this.disconnectSurfaceFromRenderer();
}
this.flutterRenderer = null;
this.isAttachedToFlutterRenderer = false;
} else {
Log.w("FlutterTextureView", "detachFromRenderer() invoked when no FlutterRenderer was attached.");
}
}
private void connectSurfaceToRenderer() {
if (this.flutterRenderer != null && this.getSurfaceTexture() != null) {
Surface surface= new Surface(this.getSurfaceTexture());
this.flutterRenderer.surfaceCreated(surface);
surface.release();
} else {
throw new IllegalStateException("connectSurfaceToRenderer() should only be called when flutterRenderer and getSurfaceTexture() are non-null.");
}
}
private void changeSurfaceSize(int width, int height) {
if (this.flutterRenderer == null) {
throw new IllegalStateException("changeSurfaceSize() should only be called when flutterRenderer is non-null.");
} else {
Log.v("FlutterTextureView", "Notifying FlutterRenderer that Android surface size has changed to " + width + " x " + height);
this.flutterRenderer.surfaceChanged(width, height);
}
}
private void disconnectSurfaceFromRenderer() {
if (this.flutterRenderer == null) {
throw new IllegalStateException("disconnectSurfaceFromRenderer() should only be called when flutterRenderer is non-null.");
} else {
this.flutterRenderer.surfaceDestroyed();
}
}
public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
this.onFirstFrameRenderedListeners.add(listener);
}
public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
this.onFirstFrameRenderedListeners.remove(listener);
}
public void onFirstFrameRendered() {
Log.v("FlutterTextureView", "onFirstFrameRendered()");
Iterator var1 = this.onFirstFrameRenderedListeners.iterator();
while(var1.hasNext()) {
OnFirstFrameRenderedListener listener = (OnFirstFrameRenderedListener)var1.next();
listener.onFirstFrameRendered();
}
}
}
\ No newline at end of file
package com.idlefish.flutterboost;
import android.annotation.TargetApi;
import android.app.Activity;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
//import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Build;
import android.os.LocaleList;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.annotation.VisibleForTesting;
import android.support.v4.view.ViewCompat;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowInsets;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeProvider;
......@@ -22,24 +25,46 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.FrameLayout;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import io.flutter.embedding.android.AndroidKeyProcessor;
import io.flutter.embedding.android.AndroidTouchProcessor;
import io.flutter.embedding.android.FlutterSurfaceView;
import io.flutter.embedding.android.FlutterTextureView;
import io.flutter.embedding.android.FlutterView;
import io.flutter.Log;
import io.flutter.embedding.android.*;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
import io.flutter.plugin.editing.TextInputPlugin;
import io.flutter.plugin.platform.PlatformPlugin;
import io.flutter.plugin.platform.PlatformViewsController;
import io.flutter.view.AccessibilityBridge;
/**
* Displays a Flutter UI on an Android device.
* <p>
* A {@code FlutterView}'s UI is painted by a corresponding {@link FlutterEngine}.
* <p>
* A {@code FlutterView} can operate in 2 different {@link RenderMode}s:
* <ol>
* <li>{@link RenderMode#surface}, which paints a Flutter UI to a {@link android.view.SurfaceView}.
* This mode has the best performance, but a {@code FlutterView} in this mode cannot be positioned
* between 2 other Android {@code View}s in the z-index, nor can it be animated/transformed.
* Unless the special capabilities of a {@link android.graphics.SurfaceTexture} are required,
* developers should strongly prefer this render mode.</li>
* <li>{@link RenderMode#texture}, which paints a Flutter UI to a {@link android.graphics.SurfaceTexture}.
* This mode is not as performant as {@link RenderMode#surface}, but a {@code FlutterView} in this
* mode can be animated and transformed, as well as positioned in the z-index between 2+ other
* Android {@code Views}. Unless the special capabilities of a {@link android.graphics.SurfaceTexture}
* are required, developers should strongly prefer the {@link RenderMode#surface} render mode.</li>
* </ol>
* See <a>https://source.android.com/devices/graphics/arch-tv#surface_or_texture</a> for more
* information comparing {@link android.view.SurfaceView} and {@link android.view.TextureView}.
*/
public class XFlutterView extends FrameLayout {
private static final String TAG = "XFlutterView";
private static final String TAG = "FlutterView";
// Behavior configuration of this FlutterView.
@NonNull
......@@ -50,10 +75,14 @@ public class XFlutterView extends FrameLayout {
// Internal view hierarchy references.
@Nullable
private FlutterRenderer.RenderSurface renderSurface;
private final Set<OnFirstFrameRenderedListener> onFirstFrameRenderedListeners = new HashSet<>();
private boolean didRenderFirstFrame;
// Connections to a Flutter execution context.
@Nullable
private FlutterEngine flutterEngine;
@NonNull
private final Set<FlutterView.FlutterEngineAttachmentListener> flutterEngineAttachmentListeners = new HashSet<>();
// Components that process various types of Android View input and events,
// possibly storing intermediate state, and communicating those events to Flutter.
......@@ -69,6 +98,8 @@ public class XFlutterView extends FrameLayout {
@Nullable
private AccessibilityBridge accessibilityBridge;
private boolean hasAddFirstFrameRenderedListener=false;
// Directly implemented View behavior that communicates with Flutter.
private final FlutterRenderer.ViewportMetrics viewportMetrics = new FlutterRenderer.ViewportMetrics();
......@@ -79,17 +110,51 @@ public class XFlutterView extends FrameLayout {
}
};
private final OnFirstFrameRenderedListener onFirstFrameRenderedListener = new OnFirstFrameRenderedListener() {
@Override
public void onFirstFrameRendered() {
didRenderFirstFrame = true;
for (OnFirstFrameRenderedListener listener : onFirstFrameRenderedListeners) {
listener.onFirstFrameRendered();
}
}
};
/**
* Constructs a {@code FlutterView} programmatically, without any XML attributes.
* <p>
* <ul>
* <li>{@link #renderMode} defaults to {@link RenderMode#surface}.</li>
* <li>{@link #transparencyMode} defaults to {@link TransparencyMode#opaque}.</li>
* </ul>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/
public XFlutterView(@NonNull Context context) {
this(context, null, null, null);
}
/**
* Constructs a {@code FlutterView} programmatically, without any XML attributes,
* and allows selection of a {@link #renderMode}.
* <p>
* {@link #transparencyMode} defaults to {@link TransparencyMode#opaque}.
* <p>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/
public XFlutterView(@NonNull Context context, @NonNull FlutterView.RenderMode renderMode) {
this(context, null, renderMode, null);
}
/**
* Constructs a {@code FlutterView} programmatically, without any XML attributes,
* assumes the use of {@link RenderMode#surface}, and allows selection of a {@link #transparencyMode}.
* <p>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/
public XFlutterView(@NonNull Context context, @NonNull FlutterView.TransparencyMode transparencyMode) {
this(context, null, FlutterView.RenderMode.surface, transparencyMode);
}
......@@ -97,6 +162,9 @@ public class XFlutterView extends FrameLayout {
/**
* Constructs a {@code FlutterView} programmatically, without any XML attributes, and allows
* a selection of {@link #renderMode} and {@link #transparencyMode}.
* <p>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/
public XFlutterView(@NonNull Context context, @NonNull FlutterView.RenderMode renderMode, @NonNull FlutterView.TransparencyMode transparencyMode) {
this(context, null, renderMode, transparencyMode);
......@@ -104,9 +172,11 @@ public class XFlutterView extends FrameLayout {
/**
* Constructs a {@code FlutterSurfaceView} in an XML-inflation-compliant manner.
*
* // TODO(mattcarroll): expose renderMode in XML when build system supports R.attr
* <p>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/
// TODO(mattcarroll): expose renderMode in XML when build system supports R.attr
public XFlutterView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, null, null);
}
......@@ -121,18 +191,18 @@ public class XFlutterView extends FrameLayout {
}
private void init() {
Log.d(TAG, "Initializing FlutterView");
Log.v(TAG, "Initializing FlutterView");
switch (renderMode) {
case surface:
Log.d(TAG, "Internally creating a FlutterSurfaceView.");
Log.v(TAG, "Internally using a FlutterSurfaceView.");
FlutterSurfaceView flutterSurfaceView = new FlutterSurfaceView(getContext(), transparencyMode == FlutterView.TransparencyMode.transparent);
renderSurface = flutterSurfaceView;
addView(flutterSurfaceView);
break;
case texture:
Log.d(TAG, "Internally creating a FlutterTextureView.");
FlutterTextureView flutterTextureView = new FlutterTextureView(getContext());
Log.v(TAG, "Internally using a FlutterTextureView.");
XFlutterTextureView flutterTextureView = new XFlutterTextureView(getContext());
renderSurface = flutterTextureView;
addView(flutterTextureView);
break;
......@@ -143,12 +213,32 @@ public class XFlutterView extends FrameLayout {
setFocusableInTouchMode(true);
}
/**
* Returns true if an attached {@link FlutterEngine} has rendered at least 1 frame to this
* {@code FlutterView}.
* <p>
* Returns false if no {@link FlutterEngine} is attached.
* <p>
* This flag is specific to a given {@link FlutterEngine}. The following hypothetical timeline
* demonstrates how this flag changes over time.
* <ol>
* <li>{@code flutterEngineA} is attached to this {@code FlutterView}: returns false</li>
* <li>{@code flutterEngineA} renders its first frame to this {@code FlutterView}: returns true</li>
* <li>{@code flutterEngineA} is detached from this {@code FlutterView}: returns false</li>
* <li>{@code flutterEngineB} is attached to this {@code FlutterView}: returns false</li>
* <li>{@code flutterEngineB} renders its first frame to this {@code FlutterView}: returns true</li>
* </ol>
*/
public boolean hasRenderedFirstFrame() {
return didRenderFirstFrame;
}
/**
* Adds the given {@code listener} to this {@code FlutterView}, to be notified upon Flutter's
* first rendered frame.
*/
public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
renderSurface.addOnFirstFrameRenderedListener(listener);
onFirstFrameRenderedListeners.add(listener);
}
/**
......@@ -156,7 +246,7 @@ public class XFlutterView extends FrameLayout {
* {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}.
*/
public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
renderSurface.removeOnFirstFrameRenderedListener(listener);
onFirstFrameRenderedListeners.remove(listener);
}
//------- Start: Process View configuration that Flutter cares about. ------
......@@ -168,9 +258,11 @@ public class XFlutterView extends FrameLayout {
* change, device language change, device text scale factor change, etc.
*/
@Override
protected void onConfigurationChanged(Configuration newConfig) {
protected void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.v(TAG, "Configuration changed. Sending locales and user settings to Flutter.");
try {
sendLocalesToFlutter(newConfig);
sendUserSettingsToFlutter();
}catch (Throwable e){
......@@ -194,6 +286,9 @@ public class XFlutterView extends FrameLayout {
@Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
super.onSizeChanged(width, height, oldWidth, oldHeight);
Log.v(TAG, "Size changed. Sending Flutter new viewport metrics. FlutterView was "
+ oldWidth + " x " + oldHeight
+ ", it is now " + width + " x " + height);
viewportMetrics.width = width;
viewportMetrics.height = height;
sendViewportMetricsToFlutter();
......@@ -212,7 +307,12 @@ public class XFlutterView extends FrameLayout {
@Override
@TargetApi(20)
@RequiresApi(20)
public final WindowInsets onApplyWindowInsets(WindowInsets insets) {
// The annotations to suppress "InlinedApi" and "NewApi" lints prevent lint warnings
// caused by usage of Android Q APIs. These calls are safe because they are
// guarded.
@SuppressLint({"InlinedApi", "NewApi"})
@NonNull
public final WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) {
WindowInsets newInsets = super.onApplyWindowInsets(insets);
// Status bar (top) and left/right system insets should partially obscure the content (padding).
......@@ -226,6 +326,23 @@ public class XFlutterView extends FrameLayout {
viewportMetrics.viewInsetRight = 0;
viewportMetrics.viewInsetBottom = insets.getSystemWindowInsetBottom();
viewportMetrics.viewInsetLeft = 0;
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Insets systemGestureInsets = insets.getSystemGestureInsets();
// viewportMetrics.systemGestureInsetTop = systemGestureInsets.top;
// viewportMetrics.systemGestureInsetRight = systemGestureInsets.right;
// viewportMetrics.systemGestureInsetBottom = systemGestureInsets.bottom;
// viewportMetrics.systemGestureInsetLeft = systemGestureInsets.left;
// }
Log.v(TAG, "Updating window insets (onApplyWindowInsets()):\n"
+ "Status bar insets: Top: " + viewportMetrics.paddingTop
+ ", Left: " + viewportMetrics.paddingLeft + ", Right: " + viewportMetrics.paddingRight + "\n"
+ "Keyboard insets: Bottom: " + viewportMetrics.viewInsetBottom
+ ", Left: " + viewportMetrics.viewInsetLeft + ", Right: " + viewportMetrics.viewInsetRight
+ "System Gesture Insets - Left: " + viewportMetrics.systemGestureInsetLeft + ", Top: " + viewportMetrics.systemGestureInsetTop
+ ", Right: " + viewportMetrics.systemGestureInsetRight + ", Bottom: " + viewportMetrics.viewInsetBottom);
sendViewportMetricsToFlutter();
return newInsets;
......@@ -240,7 +357,7 @@ public class XFlutterView extends FrameLayout {
*/
@Override
@SuppressWarnings("deprecation")
protected boolean fitSystemWindows(Rect insets) {
protected boolean fitSystemWindows(@NonNull Rect insets) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
// Status bar, left/right system insets partially obscure content (padding).
viewportMetrics.paddingTop = insets.top;
......@@ -253,6 +370,13 @@ public class XFlutterView extends FrameLayout {
viewportMetrics.viewInsetRight = 0;
viewportMetrics.viewInsetBottom = insets.bottom;
viewportMetrics.viewInsetLeft = 0;
Log.v(TAG, "Updating window insets (fitSystemWindows()):\n"
+ "Status bar insets: Top: " + viewportMetrics.paddingTop
+ ", Left: " + viewportMetrics.paddingLeft + ", Right: " + viewportMetrics.paddingRight + "\n"
+ "Keyboard insets: Bottom: " + viewportMetrics.viewInsetBottom
+ ", Left: " + viewportMetrics.viewInsetLeft + ", Right: " + viewportMetrics.viewInsetRight);
sendViewportMetricsToFlutter();
return true;
} else {
......@@ -276,7 +400,8 @@ public class XFlutterView extends FrameLayout {
* rather than spread that logic throughout this {@code FlutterView}.
*/
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
@Nullable
public InputConnection onCreateInputConnection(@NonNull EditorInfo outAttrs) {
if (!isAttachedToFlutterEngine()) {
return super.onCreateInputConnection(outAttrs);
}
......@@ -284,6 +409,21 @@ public class XFlutterView extends FrameLayout {
return textInputPlugin.createInputConnection(this, outAttrs);
}
/**
* Allows a {@code View} that is not currently the input connection target to invoke commands on
* the {@link android.view.inputmethod.InputMethodManager}, which is otherwise disallowed.
* <p>
* Returns true to allow non-input-connection-targets to invoke methods on
* {@code InputMethodManager}, or false to exclusively allow the input connection target to invoke
* such methods.
*/
@Override
public boolean checkInputConnectionProxy(View view) {
return flutterEngine != null
? flutterEngine.getPlatformViewsController().checkInputConnectionProxy(view)
: super.checkInputConnectionProxy(view);
}
/**
* Invoked when key is released.
*
......@@ -292,13 +432,13 @@ public class XFlutterView extends FrameLayout {
* software keyboard is used, though a software keyboard may choose to invoke
* this method in some situations.
*
* {@link KeyEvent}s are sent from Android to Flutter. {@link AndroidKeyProcessor}
* {@link KeyEvent}s are sent from Android to Flutter. {@link }
* may do some additional work with the given {@link KeyEvent}, e.g., combine this
* {@code keyCode} with the previous {@code keyCode} to generate a unicode combined
* character.
*/
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
if (!isAttachedToFlutterEngine()) {
return super.onKeyUp(keyCode, event);
}
......@@ -315,13 +455,13 @@ public class XFlutterView extends FrameLayout {
* software keyboard is used, though a software keyboard may choose to invoke
* this method in some situations.
*
* {@link KeyEvent}s are sent from Android to Flutter. {@link AndroidKeyProcessor}
* {@link KeyEvent}s are sent from Android to Flutter. {@link }
* may do some additional work with the given {@link KeyEvent}, e.g., combine this
* {@code keyCode} with the previous {@code keyCode} to generate a unicode combined
* character.
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
if (!isAttachedToFlutterEngine()) {
return super.onKeyDown(keyCode, event);
}
......@@ -337,7 +477,7 @@ public class XFlutterView extends FrameLayout {
* method forwards all {@link MotionEvent} data from Android to Flutter.
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
public boolean onTouchEvent(@NonNull MotionEvent event) {
if (!isAttachedToFlutterEngine()) {
return super.onTouchEvent(event);
}
......@@ -362,7 +502,7 @@ public class XFlutterView extends FrameLayout {
* method forwards all {@link MotionEvent} data from Android to Flutter.
*/
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
boolean handled = isAttachedToFlutterEngine() && androidTouchProcessor.onGenericMotionEvent(event);
return handled ? true : super.onGenericMotionEvent(event);
}
......@@ -379,7 +519,7 @@ public class XFlutterView extends FrameLayout {
* processed here for accessibility purposes.
*/
@Override
public boolean onHoverEvent(MotionEvent event) {
public boolean onHoverEvent(@NonNull MotionEvent event) {
if (!isAttachedToFlutterEngine()) {
return super.onHoverEvent(event);
}
......@@ -395,6 +535,7 @@ public class XFlutterView extends FrameLayout {
//-------- Start: Accessibility -------
@Override
@Nullable
public AccessibilityNodeProvider getAccessibilityNodeProvider() {
if (accessibilityBridge != null && accessibilityBridge.isAccessibilityEnabled()) {
return accessibilityBridge;
......@@ -406,12 +547,8 @@ public class XFlutterView extends FrameLayout {
}
}
// TODO(mattcarroll): Confer with Ian as to why we need this method. Delete if possible, otherwise add comments.
private void resetWillNotDraw(boolean isAccessibilityEnabled, boolean isTouchExplorationEnabled) {
if(flutterEngine==null) return;
if(flutterEngine.getRenderer()==null) return;
if (!flutterEngine.getRenderer().isSoftwareRenderingEnabled()) {
setWillNotDraw(!(isAccessibilityEnabled || isTouchExplorationEnabled));
} else {
......@@ -431,9 +568,11 @@ public class XFlutterView extends FrameLayout {
* See {@link #detachFromFlutterEngine()} for information on how to detach from a
* {@link FlutterEngine}.
*/
public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
Log.d(TAG, "attachToFlutterEngine()");
public void attachToFlutterEngine(
@NonNull FlutterEngine flutterEngine
) {
Log.d(TAG, "Attaching to a FlutterEngine: " + flutterEngine);
if (isAttachedToFlutterEngine()) {
if (flutterEngine == this.flutterEngine) {
// We are already attached to this FlutterEngine
......@@ -442,47 +581,49 @@ public class XFlutterView extends FrameLayout {
}
// Detach from a previous FlutterEngine so we can attach to this new one.
Log.d(TAG, "Currently attached to a different engine. Detaching.");
Log.d(TAG, "Currently attached to a different engine. Detaching and then attaching"
+ " to new engine.");
detachFromFlutterEngine();
}
this.flutterEngine = flutterEngine;
// initialize PlatformViewsController
this.flutterEngine.getPluginRegistry().getPlatformViewsController().attach(getContext(),flutterEngine.getRenderer(),flutterEngine.getDartExecutor());
// Instruct our FlutterRenderer that we are now its designated RenderSurface.
this.flutterEngine.getRenderer().attachToRenderSurface(renderSurface);
FlutterRenderer flutterRenderer = this.flutterEngine.getRenderer();
didRenderFirstFrame = flutterRenderer.hasRenderedFirstFrame();
if(!hasAddFirstFrameRenderedListener){
flutterRenderer.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
hasAddFirstFrameRenderedListener=true;
}
flutterRenderer.attachToRenderSurface(renderSurface);
// Initialize various components that know how to process Android View I/O
// in a way that Flutter understands.
if(textInputPlugin==null){
textInputPlugin = new XTextInputPlugin(
this,
flutterEngine.getTextInputChannel()
flutterEngine.getTextInputChannel(),
this.flutterEngine.getPlatformViewsController()
);
}
textInputPlugin.setTextInputMethodHandler();
textInputPlugin.getInputMethodManager().restartInput(this);
androidKeyProcessor = new XAndroidKeyProcessor(
this.androidKeyProcessor = new XAndroidKeyProcessor(
this.flutterEngine.getKeyEventChannel(),
textInputPlugin
);
androidTouchProcessor = new AndroidTouchProcessor(this.flutterEngine.getRenderer());
if(accessibilityBridge==null){
accessibilityBridge = new AccessibilityBridge(
this.androidTouchProcessor = new AndroidTouchProcessor(this.flutterEngine.getRenderer());
this.accessibilityBridge = new AccessibilityBridge(
this,
flutterEngine.getAccessibilityChannel(),
(AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE),
getContext().getContentResolver(),
// TODO(mattcaroll): plumb the platform views controller to the accessibility bridge.
// https://github.com/flutter/flutter/issues/29618
null
this.flutterEngine.getPlatformViewsController()
);
accessibilityBridge.setOnAccessibilityChangeListener(onAccessibilityChangeListener);
resetWillNotDraw(
......@@ -490,28 +631,31 @@ public class XFlutterView extends FrameLayout {
accessibilityBridge.isTouchExplorationEnabled()
);
}
// Connect AccessibilityBridge to the PlatformViewsController within the FlutterEngine.
// This allows platform Views to hook into Flutter's overall accessibility system.
this.flutterEngine.getPlatformViewsController().attachAccessibilityBridge(accessibilityBridge);
// Inform the Android framework that it should retrieve a new InputConnection
// now that an engine is attached.
// TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin
textInputPlugin.getInputMethodManager().restartInput(this);
// Push View and Context related information from Android to Flutter.
sendUserSettingsToFlutter();
sendLocalesToFlutter(getResources().getConfiguration());
sendViewportMetricsToFlutter();
}
public void release(){
if(accessibilityBridge!=null){
accessibilityBridge.release();
// Notify engine attachment listeners of the attachment.
for (FlutterView.FlutterEngineAttachmentListener listener : flutterEngineAttachmentListeners) {
listener.onFlutterEngineAttachedToFlutterView(flutterEngine);
}
textInputPlugin.release();
// 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();
// }
}
/**
......@@ -525,45 +669,83 @@ public class XFlutterView extends FrameLayout {
* {@link FlutterEngine}.
*/
public void detachFromFlutterEngine() {
Log.d(TAG, "detachFromFlutterEngine()");
Log.d(TAG, "Detaching from a FlutterEngine: " + flutterEngine);
if (!isAttachedToFlutterEngine()) {
Log.d(TAG, "Not attached to an engine. Doing nothing.");
return;
}
Log.d(TAG, "Detaching from Flutter Engine");
// detach platformviews in page in case memory leak
flutterEngine.getPluginRegistry().getPlatformViewsController().detach();
// Notify engine attachment listeners of the detachment.
for (FlutterView.FlutterEngineAttachmentListener listener : flutterEngineAttachmentListeners) {
listener.onFlutterEngineDetachedFromFlutterView();
}
// Disconnect the FlutterEngine's PlatformViewsController from the AccessibilityBridge.
flutterEngine.getPlatformViewsController().detachAccessibiltyBridge();
// Disconnect and clean up the AccessibilityBridge.
accessibilityBridge.release();
accessibilityBridge = null;
// Inform the Android framework that it should retrieve a new InputConnection
// 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);
// Instruct our FlutterRenderer that we are no longer interested in being its RenderSurface.
// this.textInputPlugin.getInputMethodManager().restartInput(this);
flutterEngine.getRenderer().detachFromRenderSurface();
FlutterRenderer flutterRenderer = flutterEngine.getRenderer();
// didRenderFirstFrame = false;
flutterRenderer.removeOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
flutterRenderer.detachFromRenderSurface();
flutterEngine = null;
// TODO(mattcarroll): clear the surface when JNI doesn't blow up
// if (isSurfaceAvailableForRendering) {
// Canvas canvas = surfaceHolder.lockCanvas();
// canvas.drawColor(Color.RED);
// surfaceHolder.unlockCanvasAndPost(canvas);
// }
}
public void release(){
if(textInputPlugin!=null){
textInputPlugin.release();
}
}
private boolean isAttachedToFlutterEngine() {
/**
* Returns true if this {@code FlutterView} is currently attached to a {@link FlutterEngine}.
*/
@VisibleForTesting
public boolean isAttachedToFlutterEngine() {
return flutterEngine != null;
}
/**
* Returns the {@link FlutterEngine} to which this {@code FlutterView} is currently attached,
* or null if this {@code FlutterView} is not currently attached to a {@link FlutterEngine}.
*/
@VisibleForTesting
@Nullable
public FlutterEngine getAttachedFlutterEngine() {
return flutterEngine;
}
/**
* attached to/detaches from a {@link FlutterEngine}.
*/
@VisibleForTesting
public void addFlutterEngineAttachmentListener(@NonNull FlutterView.FlutterEngineAttachmentListener listener) {
flutterEngineAttachmentListeners.add(listener);
}
/**
*/
@VisibleForTesting
public void removeFlutterEngineAttachmentListener(@NonNull FlutterView.FlutterEngineAttachmentListener listener) {
flutterEngineAttachmentListeners.remove(listener);
}
/**
* Send the current {@link Locale} configuration to Flutter.
*
* FlutterEngine must be non-null when this method is invoked.
*/
@SuppressWarnings("deprecation")
private void sendLocalesToFlutter(Configuration config) {
private void sendLocalesToFlutter(@NonNull Configuration config) {
List<Locale> locales = new ArrayList<>();
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
LocaleList localeList = config.getLocales();
......@@ -595,14 +777,13 @@ public class XFlutterView extends FrameLayout {
.setUse24HourFormat(DateFormat.is24HourFormat(getContext()))
.send();
}
}
// TODO(mattcarroll): consider introducing a system channel for this communication instead of JNI
private void sendViewportMetricsToFlutter() {
Log.d(TAG, "sendViewportMetricsToFlutter()");
if (!isAttachedToFlutterEngine()) {
Log.w(TAG, "Tried to send viewport metrics from Android to Flutter but this FlutterView was not attached to a FlutterEngine.");
Log.w(TAG, "Tried to send viewport metrics from Android to Flutter but this "
+ "FlutterView was not attached to a FlutterEngine.");
return;
}
......
......@@ -12,6 +12,23 @@ 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;
......@@ -20,10 +37,9 @@ class XInputConnectionAdaptor extends BaseInputConnection {
private final Editable mEditable;
private int mBatchCount;
private InputMethodManager mImm;
private final Layout mLayout;
private static final MethodChannel.Result logger =
new ErrorLogResult("FlutterTextInput");
@SuppressWarnings("deprecation")
public XInputConnectionAdaptor(
View view,
int client,
......@@ -36,6 +52,9 @@ class XInputConnectionAdaptor extends BaseInputConnection {
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);
}
......@@ -126,12 +145,25 @@ class XInputConnectionAdaptor extends BaseInputConnection {
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 = Selection.getSelectionStart(mEditable);
int selEnd = Selection.getSelectionEnd(mEditable);
int selStart = clampIndexToEditable(Selection.getSelectionStart(mEditable), mEditable);
int selEnd = clampIndexToEditable(Selection.getSelectionEnd(mEditable), mEditable);
if (selEnd > selStart) {
// Delete the selection.
Selection.setSelection(mEditable, selStart);
......@@ -139,10 +171,33 @@ class XInputConnectionAdaptor extends BaseInputConnection {
updateEditingState();
return true;
} else if (selStart > 0) {
// Delete to the left of the cursor.
int newSel = Math.max(selStart - 1, 0);
Selection.setSelection(mEditable, newSel);
mEditable.delete(newSel, selStart);
// 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;
}
......
package com.idlefish.flutterboost;
import android.app.Activity;
import android.app.ActivityManager.TaskDescription;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.HapticFeedbackConstants;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.Window;
import java.util.List;
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
import io.flutter.plugin.common.ActivityLifecycleListener;
public class XPlatformPlugin implements ActivityLifecycleListener {
public static final int DEFAULT_SYSTEM_UI = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
private Activity activity;
private final PlatformChannel platformChannel;
private PlatformChannel.SystemChromeStyle currentTheme;
private int mEnabledOverlays;
private final PlatformChannel.PlatformMessageHandler mPlatformMessageHandler = new PlatformChannel.PlatformMessageHandler() {
@Override
public void playSystemSound(@NonNull PlatformChannel.SoundType soundType) {
XPlatformPlugin.this.playSystemSound(soundType);
}
@Override
public void vibrateHapticFeedback(@NonNull PlatformChannel.HapticFeedbackType feedbackType) {
XPlatformPlugin.this.vibrateHapticFeedback(feedbackType);
}
@Override
public void setPreferredOrientations(int androidOrientation) {
setSystemChromePreferredOrientations(androidOrientation);
}
@Override
public void setApplicationSwitcherDescription(@NonNull PlatformChannel.AppSwitcherDescription description) {
setSystemChromeApplicationSwitcherDescription(description);
}
@Override
public void showSystemOverlays(@NonNull List<PlatformChannel.SystemUiOverlay> overlays) {
setSystemChromeEnabledSystemUIOverlays(overlays);
}
@Override
public void restoreSystemUiOverlays() {
restoreSystemChromeSystemUIOverlays();
}
@Override
public void setSystemUiOverlayStyle(@NonNull PlatformChannel.SystemChromeStyle systemUiOverlayStyle) {
setSystemChromeSystemUIOverlayStyle(systemUiOverlayStyle);
}
@Override
public void popSystemNavigator() {
XPlatformPlugin.this.popSystemNavigator();
}
@Override
public CharSequence getClipboardData(@Nullable PlatformChannel.ClipboardContentFormat format) {
return XPlatformPlugin.this.getClipboardData(format);
}
@Override
public void setClipboardData(@NonNull String text) {
XPlatformPlugin.this.setClipboardData(text);
}
};
public XPlatformPlugin(Activity activity, PlatformChannel platformChannel) {
this.activity = activity;
this.platformChannel = platformChannel;
this.platformChannel.setPlatformMessageHandler(mPlatformMessageHandler);
mEnabledOverlays = DEFAULT_SYSTEM_UI;
}
private void playSystemSound(PlatformChannel.SoundType soundType) {
if (soundType == PlatformChannel.SoundType.CLICK) {
View view = activity.getWindow().getDecorView();
view.playSoundEffect(SoundEffectConstants.CLICK);
}
}
private void vibrateHapticFeedback(PlatformChannel.HapticFeedbackType feedbackType) {
View view = activity.getWindow().getDecorView();
switch (feedbackType) {
case STANDARD:
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
break;
case LIGHT_IMPACT:
view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
break;
case MEDIUM_IMPACT:
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
break;
case HEAVY_IMPACT:
// HapticFeedbackConstants.CONTEXT_CLICK from API level 23.
view.performHapticFeedback(6);
break;
case SELECTION_CLICK:
view.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
break;
}
}
private void setSystemChromePreferredOrientations(int androidOrientation) {
activity.setRequestedOrientation(androidOrientation);
}
private void setSystemChromeApplicationSwitcherDescription(PlatformChannel.AppSwitcherDescription description) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return;
}
// Linter refuses to believe we're only executing this code in API 28 unless we use distinct if blocks and
// hardcode the API 28 constant.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P && Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
activity.setTaskDescription(new TaskDescription(description.label));
}
if (Build.VERSION.SDK_INT >= 28) {
TaskDescription taskDescription = new TaskDescription(description.label, 0, description.color);
activity.setTaskDescription(taskDescription);
}
}
private void setSystemChromeEnabledSystemUIOverlays(List<PlatformChannel.SystemUiOverlay> overlaysToShow) {
// Start by assuming we want to hide all system overlays (like an immersive game).
int enabledOverlays = DEFAULT_SYSTEM_UI
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
if (overlaysToShow.size() == 0) {
enabledOverlays |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
}
// Re-add any desired system overlays.
for (int i = 0; i < overlaysToShow.size(); ++i) {
PlatformChannel.SystemUiOverlay overlayToShow = overlaysToShow.get(i);
switch (overlayToShow) {
case TOP_OVERLAYS:
enabledOverlays &= ~View.SYSTEM_UI_FLAG_FULLSCREEN;
break;
case BOTTOM_OVERLAYS:
enabledOverlays &= ~View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
enabledOverlays &= ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
break;
}
}
mEnabledOverlays = enabledOverlays;
updateSystemUiOverlays();
}
private void updateSystemUiOverlays(){
activity.getWindow().getDecorView().setSystemUiVisibility(mEnabledOverlays);
if (currentTheme != null) {
setSystemChromeSystemUIOverlayStyle(currentTheme);
}
}
private void restoreSystemChromeSystemUIOverlays() {
updateSystemUiOverlays();
}
private void setSystemChromeSystemUIOverlayStyle(PlatformChannel.SystemChromeStyle systemChromeStyle) {
Window window = activity.getWindow();
View view = window.getDecorView();
int flags = view.getSystemUiVisibility();
// You can change the navigation bar color (including translucent colors)
// in Android, but you can't change the color of the navigation buttons until Android O.
// LIGHT vs DARK effectively isn't supported until then.
// Build.VERSION_CODES.O
if (Build.VERSION.SDK_INT >= 26) {
if (systemChromeStyle.systemNavigationBarIconBrightness != null) {
switch (systemChromeStyle.systemNavigationBarIconBrightness) {
case DARK:
//View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
flags |= 0x10;
break;
case LIGHT:
flags &= ~0x10;
break;
}
}
if (systemChromeStyle.systemNavigationBarColor != null) {
window.setNavigationBarColor(systemChromeStyle.systemNavigationBarColor);
}
}
// Build.VERSION_CODES.M
if (Build.VERSION.SDK_INT >= 23) {
if (systemChromeStyle.statusBarIconBrightness != null) {
switch (systemChromeStyle.statusBarIconBrightness) {
case DARK:
// View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
flags |= 0x2000;
break;
case LIGHT:
flags &= ~0x2000;
break;
}
}
if (systemChromeStyle.statusBarColor != null) {
window.setStatusBarColor(systemChromeStyle.statusBarColor);
}
}
if (systemChromeStyle.systemNavigationBarDividerColor != null) {
// Not availible until Android P.
// window.setNavigationBarDividerColor(systemNavigationBarDividerColor);
}
view.setSystemUiVisibility(flags);
currentTheme = systemChromeStyle;
}
private void popSystemNavigator() {
activity.finish();
}
private CharSequence getClipboardData(PlatformChannel.ClipboardContentFormat format) {
ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = clipboard.getPrimaryClip();
if (clip == null)
return null;
if (format == null || format == PlatformChannel.ClipboardContentFormat.PLAIN_TEXT) {
return clip.getItemAt(0).coerceToText(activity);
}
return null;
}
private void setClipboardData(String text) {
ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("text label?", text);
clipboard.setPrimaryClip(clip);
}
@Override
public void onPostResume() {
updateSystemUiOverlays();
}
public void release(){
this.activity=null;
}
}
\ No newline at end of file
......@@ -11,9 +11,10 @@ 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.view.FlutterView;
import io.flutter.plugin.platform.PlatformViewsController;
/**
* Android implementation of the text input plugin.
......@@ -24,7 +25,9 @@ public class XTextInputPlugin {
@NonNull
private final InputMethodManager mImm;
@NonNull
private int mClient = 0;
private final TextInputChannel textInputChannel;
@NonNull
private InputTarget inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0);
@Nullable
private TextInputChannel.Configuration configuration;
@Nullable
......@@ -32,17 +35,29 @@ public class XTextInputPlugin {
private boolean mRestartInputPending;
@Nullable
private InputConnection lastInputConnection;
private TextInputChannel textInputChannel;
public XTextInputPlugin(View view, TextInputChannel mTextInputChannel) {
@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);
textInputChannel=mTextInputChannel;
this.textInputChannel = textInputChannel;
this.platformViewsController = platformViewsController;
// this.platformViewsController.attachTextInputPlugin(this);
}
public void release() {
mView = null;
}
public void setTextInputMethodHandler( ){
public void setTextInputMethodHandler() {
textInputChannel.setTextInputMethodHandler(new TextInputChannel.TextInputMethodHandler() {
@Override
public void show() {
......@@ -59,6 +74,11 @@ public class XTextInputPlugin {
setTextInputClient(textInputClientId, configuration);
}
@Override
public void setPlatformViewClient(int platformViewId) {
setPlatformViewTextInputClient(platformViewId);
}
@Override
public void setEditingState(TextInputChannel.TextEditState editingState) {
setTextInputEditingState(mView, editingState);
......@@ -70,15 +90,46 @@ public class XTextInputPlugin {
}
});
}
public void release(){
mView=null;
}
@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,
......@@ -107,6 +158,8 @@ public class XTextInputPlugin {
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) {
......@@ -129,8 +182,16 @@ public class XTextInputPlugin {
}
public InputConnection createInputConnection(View view, EditorInfo outAttrs) {
if (mClient == 0) {
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;
}
......@@ -159,7 +220,7 @@ public class XTextInputPlugin {
XInputConnectionAdaptor connection = new XInputConnectionAdaptor(
view,
mClient,
inputTarget.id,
textInputChannel,
mEditable
);
......@@ -175,24 +236,53 @@ public class XTextInputPlugin {
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) {
if(view==null) return;
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) {
mClient = client;
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) {
......@@ -222,6 +312,45 @@ public class XTextInputPlugin {
}
private void clearTextInputClient() {
mClient = 0;
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
/*
* 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.idlefish.flutterboost.containers;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.idlefish.flutterboost.BoostFlutterEngine;
import com.idlefish.flutterboost.BoostFlutterView;
import com.idlefish.flutterboost.FlutterBoost;
import com.idlefish.flutterboost.Utils;
import com.idlefish.flutterboost.interfaces.IFlutterViewContainer;
import com.idlefish.flutterboost.interfaces.IOperateSyncer;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import io.flutter.embedding.android.FlutterView;
import io.flutter.plugin.platform.PlatformPlugin;
public abstract class BoostFlutterActivity extends Activity implements IFlutterViewContainer {
protected BoostFlutterEngine mFlutterEngine;
protected BoostFlutterView mFlutterView;
protected IOperateSyncer mSyncer;
private Handler mHandler = new Handler(Looper.getMainLooper());
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
configureWindowForTransparency();
mSyncer = FlutterBoost.singleton().containerManager().generateSyncer(this);
mFlutterEngine = createFlutterEngine();
mFlutterView = createFlutterView(mFlutterEngine);
setContentView(mFlutterView);
mSyncer.onCreate();
configureStatusBarForFullscreenFlutterExperience();
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
mHandler.post(new Runnable() {
@Override
public void run() {
configureStatusBarForFullscreenFlutterExperience();
}
});
}
protected void configureWindowForTransparency() {
if (isBackgroundTransparent()) {
getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
);
}
}
protected void configureStatusBarForFullscreenFlutterExperience() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
}
Utils.setStatusBarLightMode(this,true);
}
protected BoostFlutterEngine createFlutterEngine(){
return FlutterBoost.singleton().engineProvider().provideEngine(this);
}
protected BoostFlutterView createFlutterView(BoostFlutterEngine engine){
BoostFlutterView.Builder builder = new BoostFlutterView.Builder(this);
return builder.flutterEngine(engine)
.renderMode(FlutterView.RenderMode.texture)
.transparencyMode(isBackgroundTransparent() ?
FlutterView.TransparencyMode.transparent :
FlutterView.TransparencyMode.opaque)
.renderingProgressCoverCreator(new BoostFlutterView.RenderingProgressCoverCreator() {
@Override
public View createRenderingProgressCover(Context context) {
return BoostFlutterActivity.this.createRenderingProgressCover();
}
})
.build();
}
protected boolean isBackgroundTransparent(){
return false;
}
protected View createRenderingProgressCover(){
FrameLayout frameLayout = new FrameLayout(this);
LinearLayout linearLayout = new LinearLayout(this);
linearLayout.setOrientation(LinearLayout.VERTICAL);
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.gravity = Gravity.CENTER;
frameLayout.addView(linearLayout,layoutParams);
ProgressBar progressBar = new ProgressBar(this);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER_HORIZONTAL;
linearLayout.addView(progressBar,params);
TextView textView = new TextView(this);
params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER_HORIZONTAL;
textView.setText("Frame Rendering...");
linearLayout.addView(textView,params);
return frameLayout;
}
@Override
protected void onResume() {
super.onResume();
mSyncer.onAppear();
mFlutterEngine.getLifecycleChannel().appIsResumed();
}
@Override
protected void onPause() {
mSyncer.onDisappear();
super.onPause();
mFlutterEngine.getLifecycleChannel().appIsInactive();
}
@Override
protected void onDestroy() {
Utils.fixInputMethodManagerLeak(this);
mSyncer.onDestroy();
super.onDestroy();
}
@Override
public void onBackPressed() {
mSyncer.onBackPressed();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
mSyncer.onNewIntent(intent);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
mSyncer.onActivityResult(requestCode,resultCode,data);
Map<String,Object> result = null;
if(data != null) {
Serializable rlt = data.getSerializableExtra(RESULT_KEY);
if(rlt instanceof Map) {
result = (Map<String,Object>)rlt;
}
}
mSyncer.onContainerResult(requestCode,resultCode,result);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
mSyncer.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
mSyncer.onTrimMemory(level);
}
@Override
public void onLowMemory() {
super.onLowMemory();
mSyncer.onLowMemory();
}
@Override
protected void onUserLeaveHint() {
super.onUserLeaveHint();
mSyncer.onUserLeaveHint();
}
@Override
public Activity getContextActivity() {
return this;
}
@Override
public BoostFlutterView getBoostFlutterView() {
return mFlutterView;
}
@Override
public void finishContainer(Map<String,Object> result) {
if(result != null) {
FlutterBoost.setBoostResult(this,new HashMap<>(result));
finish();
}else{
finish();
}
}
@Override
public void onContainerShown() {}
@Override
public void onContainerHidden() {}
}
package com.idlefish.flutterboost.containers;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import java.util.HashMap;
import java.util.Map;
public class BoostFlutterDefaultActivity extends BoostFlutterActivity {
@Override
public String getContainerUrl() {
return getIntent().getStringExtra("url");
}
@Override
public Map getContainerUrlParams() {
return (Map)(getIntent().getSerializableExtra("params"));
}
private static Intent intent(Context context, String url, HashMap<String, Object> params) {
final Intent intent = new Intent(context, BoostFlutterDefaultActivity.class);
intent.putExtra("url", url);
intent.putExtra("params", params);
return intent;
}
public static void open(Context context, String url, HashMap<String, Object> params) {
context.startActivity(intent(context, url, params));
}
public static void open(Activity activity, String url, HashMap<String, Object> params, int requestCode) {
activity.startActivityForResult(intent(activity, url, params), requestCode);
}
}
\ No newline at end of file
/*
* 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.idlefish.flutterboost.containers;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import com.idlefish.flutterboost.BoostFlutterEngine;
import com.idlefish.flutterboost.BoostFlutterView;
import com.idlefish.flutterboost.FlutterBoost;
import com.idlefish.flutterboost.Utils;
import com.idlefish.flutterboost.interfaces.IFlutterViewContainer;
import com.idlefish.flutterboost.interfaces.IOperateSyncer;
import java.util.Map;
import io.flutter.embedding.android.FlutterView;
import io.flutter.plugin.platform.PlatformPlugin;
abstract public class BoostFlutterFragment extends Fragment implements IFlutterViewContainer {
protected BoostFlutterEngine mFlutterEngine;
protected BoostFlutterView mFlutterView;
protected IOperateSyncer mSyncer;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mSyncer = FlutterBoost.singleton().containerManager().generateSyncer(this);
mFlutterEngine = createFlutterEngine();
mFlutterView = createFlutterView(mFlutterEngine);
mSyncer.onCreate();
return mFlutterView;
}
protected BoostFlutterEngine createFlutterEngine(){
return FlutterBoost.singleton().engineProvider().provideEngine(getContext());
}
protected BoostFlutterView createFlutterView(BoostFlutterEngine engine){
BoostFlutterView.Builder builder = new BoostFlutterView.Builder(getContextActivity());
return builder.flutterEngine(engine)
.renderMode(FlutterView.RenderMode.texture)
.transparencyMode(FlutterView.TransparencyMode.opaque)
.build();
}
@Override
public void onResume() {
super.onResume();
mSyncer.onAppear();
mFlutterEngine.getLifecycleChannel().appIsResumed();
}
@Override
public void onPause() {
mSyncer.onDisappear();
super.onPause();
mFlutterEngine.getLifecycleChannel().appIsInactive();
}
@Override
public void onDestroy() {
mSyncer.onDestroy();
super.onDestroy();
}
public void onBackPressed() {
mSyncer.onBackPressed();
}
public void onNewIntent(Intent intent) {
mSyncer.onNewIntent(intent);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
mSyncer.onActivityResult(requestCode,resultCode,data);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
mSyncer.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
public void onTrimMemory(int level) {
mSyncer.onTrimMemory(level);
}
@Override
public void onLowMemory() {
super.onLowMemory();
mSyncer.onLowMemory();
}
public void onUserLeaveHint() {
mSyncer.onUserLeaveHint();
}
@Override
public Activity getContextActivity() {
return getActivity();
}
@Override
public BoostFlutterView getBoostFlutterView() {
return mFlutterView;
}
@Override
public void finishContainer(Map<String,Object> result) {
getFragmentManager().popBackStack();
}
@Override
public void onContainerShown() {}
@Override
public void onContainerHidden() {}
}
package com.idlefish.flutterboost.containers;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.arch.lifecycle.Lifecycle;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
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;
import com.idlefish.flutterboost.interfaces.IFlutterViewContainer;
import com.idlefish.flutterboost.interfaces.IOperateSyncer;
import io.flutter.Log;
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;
import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer {
private static final String TAG = "FlutterActivityAndFragmentDelegate";
@NonNull
private Host host;
@Nullable
private FlutterEngine flutterEngine;
@Nullable
private FlutterSplashView flutterSplashView;
@Nullable
private XFlutterView flutterView;
@Nullable
private PlatformPlugin platformPlugin;
private boolean isFlutterEngineFromHost;
protected IOperateSyncer mSyncer;
FlutterActivityAndFragmentDelegate(@NonNull Host host) {
this.host = host;
}
void release() {
this.host = null;
this.flutterEngine = null;
this.flutterView = null;
this.platformPlugin = null;
}
@Nullable
FlutterEngine getFlutterEngine() {
return flutterEngine;
}
XFlutterView getFlutterView(){
return flutterView;
}
void onAttach(@NonNull Context context) {
ensureAlive();
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) {
setupFlutterEngine();
}
// Regardless of whether or not a FlutterEngine already existed, the PlatformPlugin
// is bound to a specific Activity. Therefore, it needs to be created and configured
// every time this Fragment attaches to a new Activity.
// TODO(mattcarroll): the PlatformPlugin needs to be reimagined because it implicitly takes
// control of the entire window. This is unacceptable for non-fullscreen
// use-cases.
platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);
host.configureFlutterEngine(flutterEngine);
host.getActivity().getWindow().setFormat(PixelFormat.TRANSLUCENT);
}
private void setupFlutterEngine() {
Log.d(TAG, "Setting up FlutterEngine.");
// Second, defer to subclasses for a custom FlutterEngine.
flutterEngine = host.provideFlutterEngine(host.getContext());
if (flutterEngine != null) {
isFlutterEngineFromHost = true;
return;
}
// Our host did not provide a custom FlutterEngine. Create a FlutterEngine to back our
// FlutterView.
Log.d(TAG, "No preferred FlutterEngine was provided. Creating a new FlutterEngine for"
+ " this NewFlutterFragment.");
isFlutterEngineFromHost = false;
}
@SuppressLint("ResourceType")
@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);
ensureAlive();
flutterView = new XFlutterView(host.getActivity(), NewFlutterBoost.instance().platform().renderMode(), host.getTransparencyMode());
flutterSplashView = new FlutterSplashView(host.getContext());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
flutterSplashView.setId(View.generateViewId());
} else {
// TODO(mattcarroll): Find a better solution to this ID. This is a random, static ID.
// It might conflict with other Views, and it means that only a single FlutterSplashView
// can exist in a View hierarchy at one time.
flutterSplashView.setId(486947586);
}
flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen());
mSyncer.onCreate();
return flutterSplashView;
}
void onStart() {
Log.v(TAG, "onStart()");
ensureAlive();
// We post() the code that attaches the FlutterEngine to our FlutterView because there is
// some kind of blocking logic on the native side when the surface is connected. That lag
// causes launching Activitys to wait a second or two before launching. By post()'ing this
// behavior we are able to move this blocking logic to after the Activity's launch.
// TODO(mattcarroll): figure out how to avoid blocking the MAIN thread when connecting a surface
}
void onResume() {
mSyncer.onAppear();
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();
Utils.setStatusBarLightMode(host.getActivity(),true);
}
void onPause() {
Log.v(TAG, "onPause()");
ensureAlive();
mSyncer.onDisappear();
flutterEngine.getLifecycleChannel().appIsInactive();
}
void onStop() {
Log.v(TAG, "onStop()");
ensureAlive();
}
void onDestroyView() {
Log.v(TAG, "onDestroyView()");
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();
}
void onDetach() {
Log.v(TAG, "onDetach()");
ensureAlive();
// Null out the platformPlugin to avoid a possible retain cycle between the plugin, this Fragment,
// and this Fragment's Activity.
if (platformPlugin != null) {
platformPlugin.destroy();
platformPlugin = null;
}
Utils.fixInputMethodManagerLeak(host.getActivity());
}
void onBackPressed() {
mSyncer.onBackPressed();
ensureAlive();
}
void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
mSyncer.onRequestPermissionsResult(requestCode, permissions, grantResults);
ensureAlive();
if (flutterEngine != null) {
Log.v(TAG, "Forwarding onRequestPermissionsResult() to FlutterEngine:\n"
+ "requestCode: " + requestCode + "\n"
+ "permissions: " + Arrays.toString(permissions) + "\n"
+ "grantResults: " + Arrays.toString(grantResults));
flutterEngine.getActivityControlSurface().onRequestPermissionsResult(requestCode, permissions, grantResults);
} else {
Log.w(TAG, "onRequestPermissionResult() invoked before NewFlutterFragment was attached to an Activity.");
}
}
void onNewIntent(@NonNull Intent intent) {
mSyncer.onNewIntent(intent);
ensureAlive();
if (flutterEngine != null) {
Log.v(TAG, "Forwarding onNewIntent() to FlutterEngine.");
flutterEngine.getActivityControlSurface().onNewIntent(intent);
} else {
Log.w(TAG, "onNewIntent() invoked before NewFlutterFragment was attached to an Activity.");
}
}
void onActivityResult(int requestCode, int resultCode, Intent data) {
mSyncer.onActivityResult(requestCode,resultCode,data);
Map<String,Object> result = null;
if(data != null) {
Serializable rlt = data.getSerializableExtra(RESULT_KEY);
if(rlt instanceof Map) {
result = (Map<String,Object>)rlt;
}
}
mSyncer.onContainerResult(requestCode,resultCode,result);
ensureAlive();
if (flutterEngine != null) {
Log.v(TAG, "Forwarding onActivityResult() to FlutterEngine:\n"
+ "requestCode: " + requestCode + "\n"
+ "resultCode: " + resultCode + "\n"
+ "data: " + data);
flutterEngine.getActivityControlSurface().onActivityResult(requestCode, resultCode, data);
} else {
Log.w(TAG, "onActivityResult() invoked before NewFlutterFragment was attached to an Activity.");
}
}
void onUserLeaveHint() {
ensureAlive();
if (flutterEngine != null) {
Log.v(TAG, "Forwarding onUserLeaveHint() to FlutterEngine.");
flutterEngine.getActivityControlSurface().onUserLeaveHint();
} else {
Log.w(TAG, "onUserLeaveHint() invoked before NewFlutterFragment was attached to an Activity.");
}
}
void onTrimMemory(int level) {
mSyncer.onTrimMemory(level);
ensureAlive();
if (flutterEngine != null) {
// Use a trim level delivered while the application is running so the
// framework has a chance to react to the notification.
if (level == TRIM_MEMORY_RUNNING_LOW) {
Log.v(TAG, "Forwarding onTrimMemory() to FlutterEngine. Level: " + level);
flutterEngine.getSystemChannel().sendMemoryPressureWarning();
}
} else {
Log.w(TAG, "onTrimMemory() invoked before NewFlutterFragment was attached to an Activity.");
}
}
void onLowMemory() {
Log.v(TAG, "Forwarding onLowMemory() to FlutterEngine.");
mSyncer.onLowMemory();
ensureAlive();
flutterEngine.getSystemChannel().sendMemoryPressureWarning();
}
/**
* Ensures that this delegate has not been {@link #release()}'ed.
* <p>
* An {@code IllegalStateException} is thrown if this delegate has been {@link #release()}'ed.
*/
private void ensureAlive() {
if (host == null) {
throw new IllegalStateException("Cannot execute method on a destroyed FlutterActivityAndFragmentDelegate.");
}
}
@Override
public Activity getContextActivity() {
return (Activity)this.host.getActivity();
}
@Override
public FlutterSplashView getBoostFlutterView() {
return this.flutterSplashView;
}
@Override
public void finishContainer(Map<String, Object> 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();
}
@Override
public Map getContainerUrlParams() {
return this.host.getContainerUrlParams();
}
@Override
public void onContainerShown() {
}
@Override
public void onContainerHidden() {
}
/**
* The {@link FlutterActivity} or {@link NewFlutterFragment} that owns this
* {@code FlutterActivityAndFragmentDelegate}.
*/
/* package */ interface Host extends SplashScreenProvider, FlutterEngineProvider, FlutterEngineConfigurator {
/**
* Returns the {@link Context} that backs the host {@link Activity} or {@code Fragment}.
*/
@NonNull
Context getContext();
/**
* Returns the host {@link Activity} or the {@code Activity} that is currently attached
* to the host {@code Fragment}.
*/
@Nullable
Activity getActivity();
/**
* Returns the {@link Lifecycle} that backs the host {@link Activity} or {@code Fragment}.
*/
@NonNull
Lifecycle getLifecycle();
/**
* Returns the {@link FlutterShellArgs} that should be used when initializing Flutter.
*/
@NonNull
FlutterShellArgs getFlutterShellArgs();
/**
* Returns the {@link FlutterView.RenderMode} used by the {@link FlutterView} that
* displays the {@link FlutterEngine}'s content.
*/
@NonNull
FlutterView.RenderMode getRenderMode();
/**
* Returns the {@link FlutterView.TransparencyMode} used by the {@link FlutterView} that
* displays the {@link FlutterEngine}'s content.
*/
@NonNull
FlutterView.TransparencyMode getTransparencyMode();
@Nullable
SplashScreen provideSplashScreen();
/**
* Returns the {@link FlutterEngine} that should be rendered to a {@link FlutterView}.
* <p>
* If {@code null} is returned, a new {@link FlutterEngine} will be created automatically.
*/
@Nullable
FlutterEngine provideFlutterEngine(@NonNull Context context);
/**
* Hook for the host to create/provide a {@link PlatformPlugin} if the associated
* Flutter experience should control system chrome.
*/
@Nullable
PlatformPlugin providePlatformPlugin(@Nullable Activity activity, @NonNull FlutterEngine flutterEngine);
/**
* Hook for the host to configure the {@link FlutterEngine} as desired.
*/
void configureFlutterEngine(@NonNull FlutterEngine flutterEngine);
/**
* Returns true if the {@link FlutterEngine}'s plugin system should be connected to the
* host {@link Activity}, allowing plugins to interact with it.
*/
boolean shouldAttachEngineToActivity();
String getContainerUrl() ;
Map getContainerUrlParams() ;
}
}
package com.idlefish.flutterboost.containers;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import com.idlefish.flutterboost.*;
import io.flutter.Log;
import io.flutter.embedding.android.FlutterView;
import io.flutter.embedding.android.SplashScreen;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
import java.util.Date;
/**
* {@code View} that displays a {@link SplashScreen} until a given {@link FlutterView}
* renders its first frame.
*/
public class FlutterSplashView extends FrameLayout {
private static String TAG = "FlutterSplashView";
private FlutterEngine mFlutterEngine;
@Nullable
private SplashScreen splashScreen;
@Nullable
private XFlutterView flutterView;
@Nullable
private View splashScreenView;
@Nullable
private Bundle splashScreenState;
@Nullable
private String transitioningIsolateId;
@Nullable
private String previousCompletedSplashIsolate;
private Handler handler = new Handler();
@NonNull
private final FlutterView.FlutterEngineAttachmentListener flutterEngineAttachmentListener = new FlutterView.FlutterEngineAttachmentListener() {
@Override
public void onFlutterEngineAttachedToFlutterView(@NonNull FlutterEngine engine) {
flutterView.removeFlutterEngineAttachmentListener(this);
// displayFlutterViewWithSplash(flutterView, splashScreen);
// splashScreenTransitionNeededNow();
}
@Override
public void onFlutterEngineDetachedFromFlutterView() {
}
};
@NonNull
private final OnFirstFrameRenderedListener onFirstFrameRenderedListener = new OnFirstFrameRenderedListener() {
int i=0;
@Override
public void onFirstFrameRendered() {
if(NewFlutterBoost.instance().platform().whenEngineStart()== NewFlutterBoost.ConfigBuilder.FLUTTER_ACTIVITY_CREATED){
long now=new Date().getTime();
long flutterPostFrameCallTime=NewFlutterBoost.instance().getFlutterPostFrameCallTime();
if(flutterPostFrameCallTime!=0&& (now-flutterPostFrameCallTime)>800){
if (splashScreen != null) {
transitionToFlutter();
}
return;
}
handler.postDelayed(new Runnable() {
@Override
public void run() {
onFirstFrameRenderedListener.onFirstFrameRendered();
}
}, 200);
}else{
if (splashScreen != null) {
transitionToFlutter();
}
}
}
};
@NonNull
private final Runnable onTransitionComplete = new Runnable() {
@Override
public void run() {
removeView(splashScreenView);
previousCompletedSplashIsolate = transitioningIsolateId;
}
};
public FlutterSplashView(@NonNull Context context) {
this(context, null, 0);
}
public FlutterSplashView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public FlutterSplashView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setSaveEnabled(true);
if (mFlutterEngine == null) {
mFlutterEngine = NewFlutterBoost.instance().engineProvider();
}
}
/**
* Displays the given {@code splashScreen} on top of the given {@code flutterView} until
* Flutter has rendered its first frame, then the {@code splashScreen} is transitioned away.
* <p>
* If no {@code splashScreen} is provided, this {@code FlutterSplashView} displays the
* given {@code flutterView} on its own.
*/
public void displayFlutterViewWithSplash(@NonNull XFlutterView flutterView, @Nullable SplashScreen splashScreen) {
// If we were displaying a previous FlutterView, remove it.
if (this.flutterView != null) {
this.flutterView.removeOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
removeView(this.flutterView);
}
// If we were displaying a previous splash screen View, remove it.
if (splashScreenView != null) {
removeView(splashScreenView);
}
// Display the new FlutterView.
this.flutterView = flutterView;
addView(flutterView);
this.splashScreen = splashScreen;
// Display the new splash screen, if needed.
if (splashScreen != null) {
splashScreenView = splashScreen.createSplashView(getContext(), splashScreenState);
splashScreenView.setBackgroundColor(Color.WHITE);
addView(this.splashScreenView);
flutterView.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
// if (splashScreen != null) {
// if (this.isSplashScreenNeededNow()) {
// Log.v(TAG, "Showing splash screen UI.");
// this.splashScreenView = splashScreen.createSplashView(this.getContext(), this.splashScreenState);
// this.addView(this.splashScreenView);
// flutterView.addOnFirstFrameRenderedListener(this.onFirstFrameRenderedListener);
// } else if (this.isSplashScreenTransitionNeededNow()) {
// Log.v(TAG, "Showing an immediate splash transition to Flutter due to previously interrupted transition.");
// this.splashScreenView = splashScreen.createSplashView(this.getContext(), this.splashScreenState);
// this.addView(this.splashScreenView);
// this.transitionToFlutter();
// } else if (!flutterView.isAttachedToFlutterEngine()) {
// Log.v(TAG, "FlutterView is not yet attached to a FlutterEngine. Showing nothing until a FlutterEngine is attached.");
// flutterView.addFlutterEngineAttachmentListener(this.flutterEngineAttachmentListener);
// }
// }
}
}
/**
* Returns true if current conditions require a splash UI to be displayed.
* <p>
* This method does not evaluate whether a previously interrupted splash transition needs
* to resume. See {@link #isSplashScreenTransitionNeededNow()} to answer that question.
*/
private boolean isSplashScreenNeededNow() {
return flutterView != null
&& flutterView.isAttachedToFlutterEngine()
&& !flutterView.hasRenderedFirstFrame()
&& !hasSplashCompleted();
}
/**
* Returns true if a previous splash transition was interrupted by recreation, e.g., an
* orientation change, and that previous transition should be resumed.
* <p>
* Not all splash screens are capable of remembering their transition progress. In those
* cases, this method will return false even if a previous visual transition was
* interrupted.
*/
private boolean isSplashScreenTransitionNeededNow() {
return flutterView != null
&& flutterView.isAttachedToFlutterEngine()
&& splashScreen != null
&& splashScreen.doesSplashViewRememberItsTransition()
&& wasPreviousSplashTransitionInterrupted();
}
/**
* Returns true if a splash screen was transitioning to a Flutter experience and was then
* interrupted, e.g., by an Android configuration change. Returns false otherwise.
* <p>
* Invoking this method expects that a {@code flutterView} exists and it is attached to a
* {@code FlutterEngine}.
*/
private boolean wasPreviousSplashTransitionInterrupted() {
if (flutterView == null) {
throw new IllegalStateException("Cannot determine if previous splash transition was " +
"interrupted when no FlutterView is set.");
}
if (!flutterView.isAttachedToFlutterEngine()) {
throw new IllegalStateException("Cannot determine if previous splash transition was "
+ "interrupted when no FlutterEngine is attached to our FlutterView. This question "
+ "depends on an isolate ID to differentiate Flutter experiences.");
}
return flutterView.hasRenderedFirstFrame() && !hasSplashCompleted();
}
/**
* Returns true if a splash UI for a specific Flutter experience has already completed.
* <p>
* A "specific Flutter experience" is defined as any experience with the same Dart isolate
* ID. The purpose of this distinction is to prevent a situation where a user gets past a
* splash UI, rotates the device (or otherwise triggers a recreation) and the splash screen
* reappears.
* <p>
* An isolate ID is deemed reasonable as a key for a completion event because a Dart isolate
* cannot be entered twice. Therefore, a single Dart isolate cannot return to an "un-rendered"
* state after having previously rendered content.
*/
private boolean hasSplashCompleted() {
if (flutterView == null) {
throw new IllegalStateException("Cannot determine if splash has completed when no FlutterView "
+ "is set.");
}
if (!flutterView.isAttachedToFlutterEngine()) {
throw new IllegalStateException("Cannot determine if splash has completed when no "
+ "FlutterEngine is attached to our FlutterView. This question depends on an isolate ID "
+ "to differentiate Flutter experiences.");
}
// A null isolate ID on a non-null FlutterEngine indicates that the Dart isolate has not
// been initialized. Therefore, no frame has been rendered for this engine, which means
// no splash screen could have completed yet.
return flutterView.getAttachedFlutterEngine().getDartExecutor().getIsolateServiceId() != null
&& flutterView.getAttachedFlutterEngine().getDartExecutor().getIsolateServiceId().equals(previousCompletedSplashIsolate);
}
/**
* Transitions a splash screen to the Flutter UI.
* <p>
* This method requires that our FlutterView be attached to an engine, and that engine have
* a Dart isolate ID. It also requires that a {@code splashScreen} exist.
*/
private void transitionToFlutter() {
transitioningIsolateId = flutterView.getAttachedFlutterEngine().getDartExecutor().getIsolateServiceId();
Log.v(TAG, "Transitioning splash screen to a Flutter UI. Isolate: " + transitioningIsolateId);
splashScreen.transitionToFlutter(onTransitionComplete);
}
public static class SavedState extends BaseSavedState {
public static Creator CREATOR = new Creator() {
public FlutterSplashView.SavedState createFromParcel(Parcel source) {
return new FlutterSplashView.SavedState(source);
}
public FlutterSplashView.SavedState[] newArray(int size) {
return new FlutterSplashView.SavedState[size];
}
};
private String previousCompletedSplashIsolate;
private Bundle splashScreenState;
SavedState(Parcelable superState) {
super(superState);
}
SavedState(Parcel source) {
super(source);
this.previousCompletedSplashIsolate = source.readString();
this.splashScreenState = source.readBundle(this.getClass().getClassLoader());
}
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeString(this.previousCompletedSplashIsolate);
out.writeBundle(this.splashScreenState);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
handler.removeCallbacksAndMessages(null);
}
public void onAttach() {
Debuger.log("BoostFlutterView onAttach");
flutterView.attachToFlutterEngine(mFlutterEngine);
}
public void onDetach() {
Debuger.log("BoostFlutterView onDetach");
flutterView.detachFromFlutterEngine();
}
}
\ No newline at end of file
package com.idlefish.flutterboost.containers;
import android.app.Activity;
import android.arch.lifecycle.Lifecycle;
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.LifecycleRegistry;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
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;
import io.flutter.embedding.android.SplashScreen;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.plugin.platform.PlatformPlugin;
import io.flutter.view.FlutterMain;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
public class NewBoostFlutterActivity extends Activity
implements FlutterActivityAndFragmentDelegate.Host,
LifecycleOwner {
private static final String TAG = "NewBoostFlutterActivity";
// Meta-data arguments, processed from manifest XML.
protected static final String SPLASH_SCREEN_META_DATA_KEY = "io.flutter.embedding.android.SplashScreenDrawable";
protected static final String NORMAL_THEME_META_DATA_KEY = "io.flutter.embedding.android.NormalTheme";
// Intent extra arguments.
protected static final String EXTRA_DART_ENTRYPOINT = "dart_entrypoint";
protected static final String EXTRA_BACKGROUND_MODE = "background_mode";
protected static final String EXTRA_DESTROY_ENGINE_WITH_ACTIVITY = "destroy_engine_with_activity";
protected static final String EXTRA_URL = "url";
protected static final String EXTRA_PARAMS = "params";
// Default configuration.
protected static final String DEFAULT_BACKGROUND_MODE = BackgroundMode.opaque.name();
public static Intent createDefaultIntent(@NonNull Context launchContext) {
return withNewEngine().build(launchContext);
}
public static NewEngineIntentBuilder withNewEngine() {
return new NewEngineIntentBuilder(NewBoostFlutterActivity.class);
}
public static class NewEngineIntentBuilder {
private final Class<? extends NewBoostFlutterActivity> activityClass;
private String backgroundMode = DEFAULT_BACKGROUND_MODE;
private String url = "";
private Map params = new HashMap();
protected NewEngineIntentBuilder(@NonNull Class<? extends NewBoostFlutterActivity> activityClass) {
this.activityClass = activityClass;
}
public NewEngineIntentBuilder url (@NonNull String url) {
this.url = url;
return this;
}
public NewEngineIntentBuilder params (@NonNull Map params) {
this.params = params;
return this;
}
public NewEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
this.backgroundMode = backgroundMode.name();
return this;
}
public Intent build(@NonNull Context context) {
SerializableMap serializableMap=new SerializableMap();
serializableMap.setMap(params);
return new Intent(context, activityClass)
.putExtra(EXTRA_BACKGROUND_MODE, backgroundMode)
.putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, false)
.putExtra(EXTRA_URL, url)
.putExtra(EXTRA_PARAMS, serializableMap);
}
}
public static class SerializableMap implements Serializable {
private Map<String,Object> map;
public Map<String, Object> getMap() {
return map;
}
public void setMap(Map<String, Object> map) {
this.map = map;
}
}
private FlutterActivityAndFragmentDelegate delegate;
@NonNull
private LifecycleRegistry lifecycle;
public NewBoostFlutterActivity() {
lifecycle = new LifecycleRegistry(this);
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
switchLaunchThemeForNormalTheme();
super.onCreate(savedInstanceState);
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
delegate = new FlutterActivityAndFragmentDelegate(this);
delegate.onAttach(this);
configureWindowForTransparency();
setContentView(createFlutterView());
configureStatusBarForFullscreenFlutterExperience();
}
private void switchLaunchThemeForNormalTheme() {
try {
ActivityInfo activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
if (activityInfo.metaData != null) {
int normalThemeRID = activityInfo.metaData.getInt(NORMAL_THEME_META_DATA_KEY, -1);
if (normalThemeRID != -1) {
setTheme(normalThemeRID);
}
} else {
Log.d(TAG, "Using the launch theme as normal theme.");
}
} catch (PackageManager.NameNotFoundException exception) {
Log.e(TAG, "Could not read meta-data for FlutterActivity. Using the launch theme as normal theme.");
}
}
@Nullable
@Override
public SplashScreen provideSplashScreen() {
Drawable manifestSplashDrawable = getSplashScreenFromManifest();
if (manifestSplashDrawable != null) {
return new DrawableSplashScreen(manifestSplashDrawable, ImageView.ScaleType.CENTER,500L);
} else {
return null;
}
}
/**
* Returns a {@link Drawable} to be used as a splash screen as requested by meta-data in the
* {@code AndroidManifest.xml} file, or null if no such splash screen is requested.
* <p>
* See {@link #SPLASH_SCREEN_META_DATA_KEY} for the meta-data key to be used in a
* manifest file.
*/
@Nullable
@SuppressWarnings("deprecation")
private Drawable getSplashScreenFromManifest() {
try {
ActivityInfo activityInfo = getPackageManager().getActivityInfo(
getComponentName(),
PackageManager.GET_META_DATA | PackageManager.GET_ACTIVITIES
);
Bundle metadata = activityInfo.metaData;
Integer splashScreenId = metadata != null ? metadata.getInt(SPLASH_SCREEN_META_DATA_KEY) : null;
return splashScreenId != null
? Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP
? getResources().getDrawable(splashScreenId, getTheme())
: getResources().getDrawable(splashScreenId)
: null;
} catch (PackageManager.NameNotFoundException e) {
// This is never expected to happen.
return null;
}
}
/**
* Sets this {@code Activity}'s {@code Window} background to be transparent, and hides the status
* bar, if this {@code Activity}'s desired {@link BackgroundMode} is {@link BackgroundMode#transparent}.
* <p>
* For {@code Activity} transparency to work as expected, the theme applied to this {@code Activity}
* must include {@code <item name="android:windowIsTranslucent">true</item>}.
*/
private void configureWindowForTransparency() {
BackgroundMode backgroundMode = getBackgroundMode();
if (backgroundMode == BackgroundMode.transparent) {
getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
);
}
}
@NonNull
protected View createFlutterView() {
return delegate.onCreateView(
null /* inflater */,
null /* container */,
null /* savedInstanceState */);
}
private void configureStatusBarForFullscreenFlutterExperience() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
}
}
protected XFlutterView getFlutterView(){
return delegate.getFlutterView();
}
@Override
protected void onStart() {
super.onStart();
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START);
delegate.onStart();
}
@Override
protected void onResume() {
super.onResume();
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
delegate.onResume();
}
@Override
public void onPostResume() {
super.onPostResume();
delegate.onPostResume();
}
@Override
protected void onPause() {
super.onPause();
delegate.onPause();
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
}
@Override
protected void onStop() {
super.onStop();
delegate.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
delegate.onDestroyView();
delegate.onDetach();
// lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
delegate.onActivityResult(requestCode, resultCode, data);
}
@Override
protected void onNewIntent(@NonNull Intent intent) {
// TODO(mattcarroll): change G3 lint rule that forces us to call super
super.onNewIntent(intent);
delegate.onNewIntent(intent);
}
@Override
public void onBackPressed() {
delegate.onBackPressed();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
delegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
public void onUserLeaveHint() {
delegate.onUserLeaveHint();
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
delegate.onTrimMemory(level);
}
/**
* {@link FlutterActivityAndFragmentDelegate.Host} method that is used by
* {@link FlutterActivityAndFragmentDelegate} to obtain a {@code Context} reference as
* needed.
*/
@Override
@NonNull
public Context getContext() {
return this;
}
/**
* {@link FlutterActivityAndFragmentDelegate.Host} method that is used by
* {@link FlutterActivityAndFragmentDelegate} to obtain an {@code Activity} reference as
* needed. This reference is used by the delegate to instantiate a {@link FlutterView},
* a {@link PlatformPlugin}, and to determine if the {@code Activity} is changing
* configurations.
*/
@Override
@NonNull
public Activity getActivity() {
return this;
}
/**
* {@link FlutterActivityAndFragmentDelegate.Host} method that is used by
* {@link FlutterActivityAndFragmentDelegate} to obtain a {@code Lifecycle} reference as
* needed. This reference is used by the delegate to provide Flutter plugins with access
* to lifecycle events.
*/
@Override
@NonNull
public Lifecycle getLifecycle() {
return lifecycle;
}
/**
* {@link FlutterActivityAndFragmentDelegate.Host} method that is used by
* {@link FlutterActivityAndFragmentDelegate} to obtain Flutter shell arguments when
* initializing Flutter.
*/
@NonNull
@Override
public FlutterShellArgs getFlutterShellArgs() {
return FlutterShellArgs.fromIntent(getIntent());
}
/**
* Returns true if Flutter is running in "debug mode", and false otherwise.
* <p>
* Debug mode allows Flutter to operate with hot reload and hot restart. Release mode does not.
*/
private boolean isDebuggable() {
return (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
/**
* {@link FlutterActivityAndFragmentDelegate.Host} method that is used by
* {@link FlutterActivityAndFragmentDelegate} to obtain the desired {@link FlutterView.RenderMode}
* that should be used when instantiating a {@link FlutterView}.
*/
@NonNull
@Override
public FlutterView.RenderMode getRenderMode() {
return getBackgroundMode() == BackgroundMode.opaque
? FlutterView.RenderMode.surface
: FlutterView.RenderMode.texture;
}
/**
* {@link FlutterActivityAndFragmentDelegate.Host} method that is used by
* {@link FlutterActivityAndFragmentDelegate} to obtain the desired
* {@link FlutterView.TransparencyMode} that should be used when instantiating a
* {@link FlutterView}.
*/
@NonNull
@Override
public FlutterView.TransparencyMode getTransparencyMode() {
return getBackgroundMode() == BackgroundMode.opaque
? FlutterView.TransparencyMode.opaque
: FlutterView.TransparencyMode.transparent;
}
/**
* The desired window background mode of this {@code Activity}, which defaults to
* {@link BackgroundMode#opaque}.
*/
@NonNull
protected BackgroundMode getBackgroundMode() {
if (getIntent().hasExtra(EXTRA_BACKGROUND_MODE)) {
return BackgroundMode.valueOf(getIntent().getStringExtra(EXTRA_BACKGROUND_MODE));
} else {
return BackgroundMode.opaque;
}
}
/**
* Hook for subclasses to easily provide a custom {@link FlutterEngine}.
* <p>
* This hook is where a cached {@link FlutterEngine} should be provided, if a cached
* {@link FlutterEngine} is desired.
*/
@Nullable
@Override
public FlutterEngine provideFlutterEngine(@NonNull Context context) {
// No-op. Hook for subclasses.
return NewFlutterBoost.instance().engineProvider();
}
/**
* Hook for subclasses to obtain a reference to the {@link FlutterEngine} that is owned
* by this {@code FlutterActivity}.
*/
@Nullable
protected FlutterEngine getFlutterEngine() {
return delegate.getFlutterEngine();
}
@Nullable
@Override
public PlatformPlugin providePlatformPlugin(@Nullable Activity activity, @NonNull FlutterEngine flutterEngine) {
if (activity != null) {
return new PlatformPlugin(getActivity(), flutterEngine.getPlatformChannel());
} else {
return null;
}
}
/**
* Hook for subclasses to easily configure a {@code FlutterEngine}, e.g., register
* plugins.
* <p>
* This method is called after {@link #provideFlutterEngine(Context)}.
*/
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
// No-op. Hook for subclasses.
}
@Override
public boolean shouldAttachEngineToActivity() {
return true;
}
@Override
public String getContainerUrl() {
if (getIntent().hasExtra(EXTRA_URL)) {
return getIntent().getStringExtra(EXTRA_URL);
}
return "";
}
@Override
public Map getContainerUrlParams() {
if (getIntent().hasExtra(EXTRA_PARAMS)) {
SerializableMap serializableMap= (SerializableMap)getIntent().getSerializableExtra(EXTRA_PARAMS);
return serializableMap.getMap();
}
Map<String,String> params = new HashMap<>();
return params;
}
/**
* The mode of the background of a {@code FlutterActivity}, either opaque or transparent.
*/
public enum BackgroundMode {
/**
* Indicates a FlutterActivity with an opaque background. This is the default.
*/
opaque,
/**
* Indicates a FlutterActivity with a transparent background.
*/
transparent
}
}
package com.idlefish.flutterboost.containers;
import android.app.Activity;
import android.arch.lifecycle.Lifecycle;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.view.LayoutInflater;
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;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
import io.flutter.plugin.platform.PlatformPlugin;
import io.flutter.view.FlutterMain;
import java.util.HashMap;
import java.util.Map;
public class NewFlutterFragment extends Fragment implements FlutterActivityAndFragmentDelegate.Host {
private static final String TAG = "NewFlutterFragment";
/**
* The Dart entrypoint method name that is executed upon initialization.
*/
protected static final String ARG_DART_ENTRYPOINT = "dart_entrypoint";
/**
* Initial Flutter route that is rendered in a Navigator widget.
*/
protected static final String ARG_INITIAL_ROUTE = "initial_route";
/**
* Path to Flutter's Dart code.
*/
protected static final String ARG_APP_BUNDLE_PATH = "app_bundle_path";
/**
* Flutter shell arguments.
*/
protected static final String ARG_FLUTTER_INITIALIZATION_ARGS = "initialization_args";
/**
* {@link FlutterView.RenderMode} to be used for the {@link FlutterView} in this
* {@code NewFlutterFragment}
*/
protected static final String ARG_FLUTTERVIEW_RENDER_MODE = "flutterview_render_mode";
/**
* {@link FlutterView.TransparencyMode} to be used for the {@link FlutterView} in this
* {@code NewFlutterFragment}
*/
protected static final String ARG_FLUTTERVIEW_TRANSPARENCY_MODE = "flutterview_transparency_mode";
/**
* See {@link #shouldAttachEngineToActivity()}.
*/
protected static final String ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY = "should_attach_engine_to_activity";
/**
* the created {@code NewFlutterFragment}.
*/
protected static final String ARG_CACHED_ENGINE_ID = "cached_engine_id";
/**
* True if the {@link FlutterEngine} in the created {@code NewFlutterFragment} should be destroyed
* when the {@code NewFlutterFragment} is destroyed, false if the {@link FlutterEngine} should
* outlive the {@code NewFlutterFragment}.
*/
protected static final String ARG_DESTROY_ENGINE_WITH_FRAGMENT = "destroy_engine_with_fragment";
protected static final String EXTRA_URL = "url";
protected static final String EXTRA_PARAMS = "params";
/**
* Creates a {@code NewFlutterFragment} with a default configuration.
* <p>
* {@code NewFlutterFragment}'s default configuration creates a new {@link FlutterEngine} within
* the {@code NewFlutterFragment} and uses the following settings:
* <ul>
* <li>Dart entrypoint: "main"</li>
* <li>Initial route: "/"</li>
* <li>Render mode: surface</li>
* <li>Transparency mode: transparent</li>
* </ul>
* <p>
* To use a new {@link FlutterEngine} with different settings, use {@link #withNewEngine()}.
* <p>
* To use a cached {@link FlutterEngine} instead of creating a new one, use
*/
@NonNull
public static NewFlutterFragment createDefault() {
return new NewEngineFragmentBuilder().build();
}
/**
* Returns a {@link NewEngineFragmentBuilder} to create a {@code NewFlutterFragment} with a new
* {@link FlutterEngine} and a desired engine configuration.
*/
@NonNull
public static NewEngineFragmentBuilder withNewEngine() {
return new NewEngineFragmentBuilder();
}
public static class NewEngineFragmentBuilder {
private final Class<? extends NewFlutterFragment> fragmentClass;
private FlutterShellArgs shellArgs = null;
private FlutterView.RenderMode renderMode = FlutterView.RenderMode.surface;
private FlutterView.TransparencyMode transparencyMode = FlutterView.TransparencyMode.transparent;
private boolean shouldAttachEngineToActivity = true;
private String url = "";
private Map params = new HashMap();
/**
* Constructs a {@code NewEngineFragmentBuilder} that is configured to construct an instance of
* {@code NewFlutterFragment}.
*/
public NewEngineFragmentBuilder() {
fragmentClass = NewFlutterFragment.class;
}
/**
* Constructs a {@code NewEngineFragmentBuilder} that is configured to construct an instance of
* {@code subclass}, which extends {@code NewFlutterFragment}.
*/
public NewEngineFragmentBuilder(@NonNull Class<? extends NewFlutterFragment> subclass) {
fragmentClass = subclass;
}
/**
* Any special configuration arguments for the Flutter engine
*/
@NonNull
public NewEngineFragmentBuilder flutterShellArgs(@NonNull FlutterShellArgs shellArgs) {
this.shellArgs = shellArgs;
return this;
}
/**
* Render Flutter either as a {@link FlutterView.RenderMode#surface} or a
* {@link FlutterView.RenderMode#texture}. You should use {@code surface} unless
* you have a specific reason to use {@code texture}. {@code texture} comes with
* a significant performance impact, but {@code texture} can be displayed
* beneath other Android {@code View}s and animated, whereas {@code surface}
* cannot.
*/
@NonNull
public NewEngineFragmentBuilder renderMode(@NonNull FlutterView.RenderMode renderMode) {
this.renderMode = renderMode;
return this;
}
public NewEngineFragmentBuilder url (@NonNull String url) {
this.url = url;
return this;
}
public NewEngineFragmentBuilder params (@NonNull Map params) {
this.params = params;
return this;
}
/**
* Support a {@link FlutterView.TransparencyMode#transparent} background within {@link FlutterView},
* or force an {@link FlutterView.TransparencyMode#opaque} background.
* <p>
* See {@link FlutterView.TransparencyMode} for implications of this selection.
*/
@NonNull
public NewEngineFragmentBuilder transparencyMode(@NonNull FlutterView.TransparencyMode transparencyMode) {
this.transparencyMode = transparencyMode;
return this;
}
@NonNull
protected Bundle createArgs() {
Bundle args = new Bundle();
// TODO(mattcarroll): determine if we should have an explicit FlutterTestFragment instead of conflating.
if (null != shellArgs) {
args.putStringArray(ARG_FLUTTER_INITIALIZATION_ARGS, shellArgs.toArray());
}
NewBoostFlutterActivity.SerializableMap serializableMap=new NewBoostFlutterActivity.SerializableMap();
serializableMap.setMap(params);
args.putString(EXTRA_URL, url);
args.putSerializable(EXTRA_PARAMS, serializableMap);
args.putString(ARG_FLUTTERVIEW_RENDER_MODE, renderMode != null ? renderMode.name() : FlutterView.RenderMode.surface.name());
args.putString(ARG_FLUTTERVIEW_TRANSPARENCY_MODE, transparencyMode != null ? transparencyMode.name() : FlutterView.TransparencyMode.transparent.name());
args.putBoolean(ARG_DESTROY_ENGINE_WITH_FRAGMENT, true);
return args;
}
/**
* Constructs a new {@code NewFlutterFragment} (or a subclass) that is configured based on
* properties set on this {@code Builder}.
*/
@NonNull
public <T extends NewFlutterFragment> T build() {
try {
@SuppressWarnings("unchecked")
T frag = (T) fragmentClass.getDeclaredConstructor().newInstance();
if (frag == null) {
throw new RuntimeException("The NewFlutterFragment subclass sent in the constructor ("
+ fragmentClass.getCanonicalName() + ") does not match the expected return type.");
}
Bundle args = createArgs();
frag.setArguments(args);
return frag;
} catch (Exception e) {
throw new RuntimeException("Could not instantiate NewFlutterFragment subclass (" + fragmentClass.getName() + ")", e);
}
}
}
// Delegate that runs all lifecycle and OS hook logic that is common between
// FlutterActivity and NewFlutterFragment. See the FlutterActivityAndFragmentDelegate
// implementation for details about why it exists.
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.
setArguments(new Bundle());
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
delegate = new FlutterActivityAndFragmentDelegate(this);
delegate.onAttach(context);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return delegate.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onStart() {
super.onStart();
delegate.onStart();
}
@Override
public void onResume() {
super.onResume();
delegate.onResume();
}
// TODO(mattcarroll): determine why this can't be in onResume(). Comment reason, or move if possible.
@ActivityCallThrough
public void onPostResume() {
delegate.onPostResume();
}
@Override
public void onPause() {
super.onPause();
delegate.onPause();
}
@Override
public void onStop() {
super.onStop();
delegate.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
delegate.onDestroyView();
}
@Override
public void onDetach() {
super.onDetach();
delegate.onDetach();
delegate.release();
delegate = null;
}
@ActivityCallThrough
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
delegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@ActivityCallThrough
public void onNewIntent(@NonNull Intent intent) {
delegate.onNewIntent(intent);
}
@ActivityCallThrough
public void onBackPressed() {
delegate.onBackPressed();
}
/**
* A result has been returned after an invocation of {@link Fragment#startActivityForResult(Intent, int)}.
* <p>
*
* @param requestCode request code sent with {@link Fragment#startActivityForResult(Intent, int)}
* @param resultCode code representing the result of the {@code Activity} that was launched
* @param data any corresponding return data, held within an {@code Intent}
*/
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
delegate.onActivityResult(requestCode, resultCode, data);
}
@ActivityCallThrough
public void onUserLeaveHint() {
delegate.onUserLeaveHint();
}
/**
* Callback invoked when memory is low.
* <p>
* This implementation forwards a memory pressure warning to the running Flutter app.
* <p>
*
* @param level level
*/
@ActivityCallThrough
public void onTrimMemory(int level) {
delegate.onTrimMemory(level);
}
/**
* Callback invoked when memory is low.
* <p>
* This implementation forwards a memory pressure warning to the running Flutter app.
*/
@Override
public void onLowMemory() {
super.onLowMemory();
delegate.onLowMemory();
}
@NonNull
private Context getContextCompat() {
return Build.VERSION.SDK_INT >= 23
? getContext()
: getActivity();
}
/**
* {@link FlutterActivityAndFragmentDelegate.Host} method that is used by
* {@link FlutterActivityAndFragmentDelegate} to obtain Flutter shell arguments when
* initializing Flutter.
*/
@Override
@NonNull
public FlutterShellArgs getFlutterShellArgs() {
String[] flutterShellArgsArray = getArguments().getStringArray(ARG_FLUTTER_INITIALIZATION_ARGS);
return new FlutterShellArgs(
flutterShellArgsArray != null ? flutterShellArgsArray : new String[]{}
);
}
/**
* Returns the desired {@link FlutterView.RenderMode} for the {@link FlutterView} displayed in
* this {@code NewFlutterFragment}.
* <p>
* Defaults to {@link FlutterView.RenderMode#surface}.
* <p>
* Used by this {@code NewFlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host}
*/
@Override
@NonNull
public FlutterView.RenderMode getRenderMode() {
String renderModeName = getArguments().getString(
ARG_FLUTTERVIEW_RENDER_MODE,
FlutterView.RenderMode.surface.name()
);
return FlutterView.RenderMode.valueOf(renderModeName);
}
/**
* Returns the desired {@link FlutterView.TransparencyMode} for the {@link FlutterView} displayed in
* this {@code NewFlutterFragment}.
* <p>
* Defaults to {@link FlutterView.TransparencyMode#transparent}.
* <p>
* Used by this {@code NewFlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host}
*/
@Override
@NonNull
public FlutterView.TransparencyMode getTransparencyMode() {
String transparencyModeName = getArguments().getString(
ARG_FLUTTERVIEW_TRANSPARENCY_MODE,
FlutterView.TransparencyMode.transparent.name()
);
return FlutterView.TransparencyMode.valueOf(transparencyModeName);
}
@Override
@Nullable
public SplashScreen provideSplashScreen() {
FragmentActivity parentActivity = getActivity();
if (parentActivity instanceof SplashScreenProvider) {
SplashScreenProvider splashScreenProvider = (SplashScreenProvider) parentActivity;
return splashScreenProvider.provideSplashScreen();
}
return null;
}
@Override
@Nullable
public FlutterEngine provideFlutterEngine(@NonNull Context context) {
return NewFlutterBoost.instance().engineProvider();
}
/**
* Hook for subclasses to obtain a reference to the {@link FlutterEngine} that is owned
* by this {@code FlutterActivity}.
*/
@Nullable
public FlutterEngine getFlutterEngine() {
return delegate.getFlutterEngine();
}
@Nullable
@Override
public PlatformPlugin providePlatformPlugin(@Nullable Activity activity, @NonNull FlutterEngine flutterEngine) {
if (activity != null) {
return new PlatformPlugin(getActivity(), flutterEngine.getPlatformChannel());
} else {
return null;
}
}
/**
* Configures a {@link FlutterEngine} after its creation.
* <p>
* This method is called after {@link #provideFlutterEngine(Context)}, and after the given
* {@link FlutterEngine} has been attached to the owning {@code FragmentActivity}. See
* {@link io.flutter.embedding.engine.plugins.activity.ActivityControlSurface#attachToActivity(Activity, Lifecycle)}.
* <p>
* It is possible that the owning {@code FragmentActivity} opted not to connect itself as
* an {@link io.flutter.embedding.engine.plugins.activity.ActivityControlSurface}. In that
* case, any configuration, e.g., plugins, must not expect or depend upon an available
* {@code Activity} at the time that this method is invoked.
* <p>
* The default behavior of this method is to defer to the owning {@code FragmentActivity}
* as a {@link FlutterEngineConfigurator}. Subclasses can override this method if the
* subclass needs to override the {@code FragmentActivity}'s behavior, or add to it.
* <p>
* Used by this {@code NewFlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host}
*/
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
FragmentActivity attachedActivity = getActivity();
if (attachedActivity instanceof FlutterEngineConfigurator) {
((FlutterEngineConfigurator) attachedActivity).configureFlutterEngine(flutterEngine);
}
}
/**
* See {@link NewEngineFragmentBuilder#shouldAttachEngineToActivity()} and
* <p>
* Used by this {@code NewFlutterFragment}'s {@link FlutterActivityAndFragmentDelegate}
*/
@Override
public boolean shouldAttachEngineToActivity() {
return true;
}
@Override
public String getContainerUrl() {
return getArguments().getString(EXTRA_URL);
}
@Override
public Map getContainerUrlParams() {
NewBoostFlutterActivity.SerializableMap serializableMap= (NewBoostFlutterActivity.SerializableMap) getArguments().getSerializable(EXTRA_PARAMS);
return serializableMap.getMap();
}
/**
* Annotates methods in {@code NewFlutterFragment} that must be called by the containing
* {@code Activity}.
*/
@interface ActivityCallThrough {
}
}
\ No newline at end of file
/*
* 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.idlefish.flutterboost.interfaces;
import android.content.Context;
import com.idlefish.flutterboost.BoostFlutterEngine;
/**
* a flutter engine provider
*/
public interface IFlutterEngineProvider {
/**
* create flutter engine, we just hold a single instance now
* @param context
* @return
*/
BoostFlutterEngine createEngine(Context context);
/**
* provide a flutter engine
* @param context
* @return
*/
BoostFlutterEngine provideEngine(Context context);
/**
* may return null
* @return
*/
BoostFlutterEngine tryGetEngine();
}
......@@ -25,7 +25,7 @@ package com.idlefish.flutterboost.interfaces;
import android.app.Activity;
import com.idlefish.flutterboost.BoostFlutterView;
import com.idlefish.flutterboost.containers.FlutterSplashView;
import java.util.Map;
......@@ -41,7 +41,7 @@ public interface IFlutterViewContainer {
* provide a flutter view
* @return
*/
BoostFlutterView getBoostFlutterView();
FlutterSplashView getBoostFlutterView();
/**
* call to destroy the container
......
package com.idlefish.flutterboost.interfaces;
import android.content.Context;
import java.util.Map;
public interface INativeRouter {
void openContainer(Context context, String url, Map<String,Object> urlParams, int requestCode, Map<String,Object> exts);
}
/*
* 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.idlefish.flutterboost.interfaces;
import android.app.Application;
import android.content.Context;
import java.util.Map;
import io.flutter.plugin.common.PluginRegistry;
/**
* APIs that platform(Android App) must provide
*/
public interface IPlatform {
int IMMEDIATELY = 0; //立即启动引擎
int ANY_ACTIVITY_CREATED = 1; //当有任何Activity创建时,启动引擎
/**
* get current application
* @return
*/
Application getApplication();
/**
* debug or not
* @return
*/
boolean isDebug();
/**
* register plugins
* @return
*/
void registerPlugins(PluginRegistry registry);
void openContainer(Context context,String url,Map<String,Object> urlParams,int requestCode,Map<String,Object> exts);
void closeContainer(IContainerRecord record, Map<String,Object> result, Map<String,Object> exts);
IFlutterEngineProvider engineProvider();
/**
* @return
*
* IMMEDIATELY //立即
* ANY_ACTIVITY_CREATED //当有任何Activity创建的时候
* LAZY //懒加载,尽可能延后
*/
int whenEngineStart();
}
package com.idlefish.flutterboost.interfaces;
import com.idlefish.flutterboost.BoostChannel;
import com.idlefish.flutterboost.BoostFlutterEngine;
import com.idlefish.flutterboost.BoostFlutterView;
import io.flutter.plugin.common.PluginRegistry;
public interface IStateListener {
void onEngineCreated(BoostFlutterEngine engine);
void onEngineStarted(BoostFlutterEngine engine);
void onChannelRegistered(PluginRegistry.Registrar registrar, BoostChannel channel);
void onFlutterViewInited(BoostFlutterEngine engine, BoostFlutterView flutterView);
void beforeEngineAttach(BoostFlutterEngine engine, BoostFlutterView flutterView);
void afterEngineAttached(BoostFlutterEngine engine, BoostFlutterView flutterView);
void beforeEngineDetach(BoostFlutterEngine engine, BoostFlutterView flutterView);
void afterEngineDetached(BoostFlutterEngine engine, BoostFlutterView flutterView);
}
......@@ -25,7 +25,7 @@ apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 26
compileSdkVersion 28
lintOptions {
disable 'InvalidPackage'
......@@ -35,7 +35,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.taobao.idlefish.flutterboostexample"
minSdkVersion 16
targetSdkVersion 26
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
......@@ -58,6 +58,6 @@ dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.android.support:support-v4:26.1.0'
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support:appcompat-v7:28.0.0'
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.taobao.idlefish.flutterboostexample">
<!-- The INTERNET permission is required for development. Specifically,
......@@ -33,24 +34,32 @@
</intent-filter>
</activity>
<activity
android:name=".FlutterPageActivity"
android:name="com.idlefish.flutterboost.containers.NewBoostFlutterActivity"
android:theme="@style/Theme.AppCompat"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"/>
android:windowSoftInputMode="adjustResize" >
<meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/page_loading"/>
</activity>
<activity
android:name=".FlutterFragmentPageActivity"
android:theme="@style/Theme.AppCompat"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"/>
android:windowSoftInputMode="adjustResize">
<meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/page_loading"/>
</activity>
<activity
android:name=".NativePageActivity"
android:theme="@style/Theme.AppCompat"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
android:windowSoftInputMode="adjustResize"/>
</application>
</manifest>
package com.taobao.idlefish.flutterboostexample;
import android.os.Bundle;
import android.support.annotation.Nullable;
import com.idlefish.flutterboost.containers.BoostFlutterFragment;
import java.util.HashMap;
import java.util.Map;
public class FlutterFragment extends BoostFlutterFragment {
@Override
public void setArguments(@Nullable Bundle args) {
if(args == null) {
args = new Bundle();
args.putString("tag","");
}
super.setArguments(args);
}
public void setTabTag(String tag) {
Bundle args = new Bundle();
args.putString("tag",tag);
super.setArguments(args);
}
@Override
public String getContainerUrl() {
return "flutterFragment";
}
@Override
public Map getContainerUrlParams() {
Map<String,String> params = new HashMap<>();
params.put("tag",getArguments().getString("tag"));
return params;
}
public static FlutterFragment instance(String tag){
FlutterFragment fragment = new FlutterFragment();
fragment.setTabTag(tag);
return fragment;
}
}
package com.taobao.idlefish.flutterboostexample;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
......@@ -9,12 +12,17 @@ import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import com.idlefish.flutterboost.containers.NewFlutterFragment;
import io.flutter.embedding.android.DrawableSplashScreen;
import io.flutter.embedding.android.SplashScreen;
import io.flutter.embedding.android.SplashScreenProvider;
import io.flutter.plugin.platform.PlatformPlugin;
public class FlutterFragmentPageActivity extends AppCompatActivity implements View.OnClickListener {
public class FlutterFragmentPageActivity extends AppCompatActivity implements View.OnClickListener, SplashScreenProvider {
protected static final String SPLASH_SCREEN_META_DATA_KEY = "io.flutter.embedding.android.SplashScreenDrawable";
private FlutterFragment mFragment;
private NewFlutterFragment mFragment;
private View mTab1;
private View mTab2;
......@@ -61,16 +69,18 @@ public class FlutterFragmentPageActivity extends AppCompatActivity implements Vi
if(mTab1 == v) {
mTab1.setBackgroundColor(Color.YELLOW);
mFragment = FlutterFragment.instance("tab1");
mFragment= new NewFlutterFragment.NewEngineFragmentBuilder().url("flutterFragment").build();
}else if(mTab2 == v) {
mTab2.setBackgroundColor(Color.YELLOW);
mFragment = FlutterFragment.instance("tab2");
mFragment= new NewFlutterFragment.NewEngineFragmentBuilder().url("flutterFragment").build();
}else if(mTab3 == v) {
mTab3.setBackgroundColor(Color.YELLOW);
mFragment = FlutterFragment.instance("tab3");
mFragment= new NewFlutterFragment.NewEngineFragmentBuilder().url("flutterFragment").build();
}else{
mTab4.setBackgroundColor(Color.YELLOW);
mFragment = FlutterFragment.instance("tab4");
mFragment= new NewFlutterFragment.NewEngineFragmentBuilder().url("flutterFragment").build();
}
getSupportFragmentManager()
......@@ -84,4 +94,34 @@ public class FlutterFragmentPageActivity extends AppCompatActivity implements Vi
super.onResume();
mTab1.performClick();
}
@Nullable
@Override
public SplashScreen provideSplashScreen() {
Drawable manifestSplashDrawable = getSplashScreenFromManifest();
if (manifestSplashDrawable != null) {
return new DrawableSplashScreen(manifestSplashDrawable, ImageView.ScaleType.CENTER,500L);
} else {
return null;
}
}
private Drawable getSplashScreenFromManifest() {
try {
ActivityInfo activityInfo = getPackageManager().getActivityInfo(
getComponentName(),
PackageManager.GET_META_DATA | PackageManager.GET_ACTIVITIES
);
Bundle metadata = activityInfo.metaData;
Integer splashScreenId = metadata != null ? metadata.getInt(SPLASH_SCREEN_META_DATA_KEY) : null;
return splashScreenId != null
? Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP
? getResources().getDrawable(splashScreenId, getTheme())
: getResources().getDrawable(splashScreenId)
: null;
} catch (PackageManager.NameNotFoundException e) {
// This is never expected to happen.
return null;
}
}
}
package com.taobao.idlefish.flutterboostexample;
import com.idlefish.flutterboost.containers.BoostFlutterActivity;
import java.util.HashMap;
import java.util.Map;
public class FlutterPageActivity extends BoostFlutterActivity {
/**
* 该方法返回当前Activity在Flutter层对应的name,
* 混合栈将会在flutter层根据这个名字,在注册的Route表中查找对应的Widget
*
* 在flutter层有注册函数:
* FlutterBoost.singleton.registerPageBuilders({
* 'first': (pageName, params, _) => FirstRouteWidget(),
* 'second': (pageName, params, _) => SecondRouteWidget(),
* ...
* });
*
* 该方法中返回的就是注册的key:first , second
*
* @return
*/
@Override
public String getContainerUrl() {
return "flutterPage";
}
/**
* 该方法返回的参数将会传递给上层的flutter对应的Widget
*
* 在flutter层有注册函数:
* FlutterBoost.singleton.registerPageBuilders({
* 'first': (pageName, params, _) => FirstRouteWidget(),
* 'second': (pageName, params, _) => SecondRouteWidget(),
* ...
* });
*
* 该方法返回的参数就会封装成上面的params
*
* @return
*/
@Override
public Map getContainerUrlParams() {
Map<String,String> params = new HashMap<>();
params.put("aaa","bbb");
return params;
}
}
......@@ -45,6 +45,8 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
@Override
public void onClick(View v) {
Map params = new HashMap();
params.put("test1","v_test1");
params.put("test2","v_test2");
//Add some params if needed.
if (v == mOpenNative) {
PageRouter.openPageByUrl(this, PageRouter.NATIVE_PAGE_URL , params);
......
......@@ -3,66 +3,60 @@ package com.taobao.idlefish.flutterboostexample;
import android.app.Application;
import android.content.Context;
import com.idlefish.flutterboost.BoostChannel;
import com.idlefish.flutterboost.BoostEngineProvider;
import com.idlefish.flutterboost.BoostFlutterEngine;
import com.idlefish.flutterboost.FlutterBoost;
import com.idlefish.flutterboost.Platform;
import com.idlefish.flutterboost.interfaces.IFlutterEngineProvider;
import android.util.Log;
import com.idlefish.flutterboost.*;
import java.util.Map;
import io.flutter.app.FlutterApplication;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.view.FlutterMain;
import com.idlefish.flutterboost.interfaces.INativeRouter;
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();
FlutterBoost.init(new Platform() {
INativeRouter router =new INativeRouter() {
@Override
public Application getApplication() {
return MyApplication.this;
public void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts) {
String assembleUrl=Utils.assembleUrl(url,urlParams);
PageRouter.openPageByUrl(context,assembleUrl, urlParams);
}
};
NewFlutterBoost.BoostLifecycleListener lifecycleListener= new NewFlutterBoost.BoostLifecycleListener() {
@Override
public boolean isDebug() {
return true;
public void onEngineCreated() {
}
@Override
public void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts) {
PageRouter.openPageByUrl(context, url, urlParams, requestCode);
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 IFlutterEngineProvider engineProvider() {
return new BoostEngineProvider() {
@Override
public BoostFlutterEngine createEngine(Context context) {
return new BoostFlutterEngine(context, new DartExecutor.DartEntrypoint(
context.getResources().getAssets(),
FlutterMain.findAppBundlePath(context),
"main"), "/");
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);
@Override
public int whenEngineStart() {
return ANY_ACTIVITY_CREATED;
}
});
BoostChannel.addActionAfterRegistered(new BoostChannel.ActionAfterRegistered() {
@Override
public void onChannelRegistered(BoostChannel channel) {
//platform view register should use FlutterPluginRegistry instread of BoostPluginRegistry
TextPlatformViewPlugin.register(FlutterBoost.singleton().engineProvider().tryGetEngine().getPluginRegistry());
}
});
}
}
......@@ -33,6 +33,9 @@ public class NativePageActivity extends AppCompatActivity implements View.OnClic
@Override
public void onClick(View v) {
Map params = new HashMap();
params.put("test1","v_test1");
params.put("test2","v_test2");
if (v == mOpenNative) {
PageRouter.openPageByUrl(this, PageRouter.NATIVE_PAGE_URL,params);
} else if (v == mOpenFlutter) {
......
......@@ -3,24 +3,45 @@ package com.taobao.idlefish.flutterboostexample;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import android.util.Log;
import com.idlefish.flutterboost.containers.NewBoostFlutterActivity;
import java.util.HashMap;
import java.util.Map;
public class PageRouter {
public final static Map<String, String> pageName = new HashMap<String, String>() {{
put("first", "first");
put("second", "second");
put("tab", "tab");
put("sample://flutterPage", "flutterPage");
}};
public static final String NATIVE_PAGE_URL = "sample://nativePage";
public static final String FLUTTER_PAGE_URL = "sample://flutterPage";
public static final String FLUTTER_FRAGMENT_PAGE_URL = "sample://flutterFragmentPage";
public static boolean openPageByUrl(Context context, String url,Map params) {
return openPageByUrl(context, url,params, 0);
public static boolean openPageByUrl(Context context, String url, Map params) {
return openPageByUrl(context, url, params, 0);
}
public static boolean openPageByUrl(Context context, String url, Map params, int requestCode) {
String path = url.split("\\?")[0];
Log.i("openPageByUrl",path);
try {
if (url.startsWith(FLUTTER_PAGE_URL)) {
context.startActivity(new Intent(context, FlutterPageActivity.class));
return true;
if (pageName.containsKey(path)) {
Intent intent = NewBoostFlutterActivity.withNewEngine().url(pageName.get(path)).params(params)
.backgroundMode(NewBoostFlutterActivity.BackgroundMode.opaque).build(context);
context.startActivity(intent);
} else if (url.startsWith(FLUTTER_FRAGMENT_PAGE_URL)) {
context.startActivity(new Intent(context, FlutterFragmentPageActivity.class));
return true;
......@@ -33,5 +54,6 @@ public class PageRouter {
} catch (Throwable t) {
return false;
}
return false;
}
}
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));
}
}
......@@ -3,12 +3,12 @@
//
#import "GeneratedPluginRegistrant.h"
#import <flutter_boost/BoostChannel.h>
#import <flutter_boost/FlutterBoostPlugin.h>
@implementation GeneratedPluginRegistrant
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
[BoostChannel registerWithRegistrar:[registry registrarForPlugin:@"BoostChannel"]];
[FlutterBoostPlugin registerWithRegistrar:[registry registrarForPlugin:@"FlutterBoostPlugin"]];
}
@end
......@@ -20,13 +20,13 @@ 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, _) {
print("flutterPage params:$params");
return FlutterRouteWidget();
return FlutterRouteWidget(params:params);
},
});
}
......
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
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
......@@ -14,7 +16,8 @@ class FirstRouteWidget extends StatelessWidget {
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");
print(
"call me when page is finished. did recieve second route result $value");
});
},
),
......@@ -66,29 +69,81 @@ class TabRouteWidget extends StatelessWidget {
}
}
class FlutterRouteWidget 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;
final String message;
FlutterRouteWidget({this.message});
@override
_FlutterRouteWidgetState createState() => _FlutterRouteWidgetState();
}
class _FlutterRouteWidgetState extends State<FlutterRouteWidget> {
final TextEditingController _usernameController = TextEditingController();
@override
Widget build(BuildContext context) {
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: Column(
body: SingleChildScrollView(
child:Container(
margin: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
margin: const EdgeInsets.only(top: 80.0),
margin: const EdgeInsets.only(top: 10.0,bottom: 20.0),
child: Text(
message ?? "This is a flutter activity",
message ?? "This is a flutter activity \n params:${widget.params}",
style: TextStyle(fontSize: 28.0, color: Colors.blue),
),
alignment: AlignmentDirectional.center,
),
Expanded(child: Container()),
// Expanded(child: Container()),
const CupertinoTextField(
prefix: Icon(
CupertinoIcons.person_solid,
color: CupertinoColors.lightBackgroundGray,
size: 28.0,
),
padding: EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0),
clearButtonMode: OverlayVisibilityMode.editing,
textCapitalization: TextCapitalization.words,
autocorrect: false,
decoration: BoxDecoration(
border: Border(bottom: BorderSide(width: 0.0, color: CupertinoColors.inactiveGray)),
),
placeholder: 'Name',
),
InkWell(
child: Container(
padding: const EdgeInsets.all(8.0),
......@@ -101,8 +156,59 @@ class FlutterRouteWidget extends StatelessWidget {
///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb
onTap: () =>
FlutterBoost.singleton.open("sample://nativePage", urlParams: {
onTap: () => FlutterBoost.singleton
.open("sample://nativePage", urlParams: {
"query": {"aaa": "bbb"}
}),
),
InkWell(
child: Container(
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.all(8.0),
color: Colors.yellow,
child: Text(
'open first',
style: TextStyle(fontSize: 22.0, color: Colors.black),
)),
///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb
onTap: () => FlutterBoost.singleton
.open("first", urlParams: {
"query": {"aaa": "bbb"}
}),
),
InkWell(
child: Container(
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.all(8.0),
color: Colors.yellow,
child: Text(
'open second',
style: TextStyle(fontSize: 22.0, color: Colors.black),
)),
///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb
onTap: () => FlutterBoost.singleton
.open("second", urlParams: {
"query": {"aaa": "bbb"}
}),
),
InkWell(
child: Container(
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.all(8.0),
color: Colors.yellow,
child: Text(
'open tab',
style: TextStyle(fontSize: 22.0, color: Colors.black),
)),
///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb
onTap: () => FlutterBoost.singleton
.open("tab", urlParams: {
"query": {"aaa": "bbb"}
}),
),
......@@ -118,8 +224,8 @@ class FlutterRouteWidget extends StatelessWidget {
///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb
onTap: () =>
FlutterBoost.singleton.open("sample://flutterPage", urlParams: {
onTap: () => FlutterBoost.singleton
.open("sample://flutterPage", urlParams: {
"query": {"aaa": "bbb"}
}),
),
......@@ -133,8 +239,23 @@ class FlutterRouteWidget extends StatelessWidget {
style: TextStyle(fontSize: 22.0, color: Colors.black),
)),
onTap: () {
Navigator.push(
context, MaterialPageRoute(builder: (_) => PushWidget()));
Navigator.push(context,
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(
......@@ -146,11 +267,14 @@ class FlutterRouteWidget extends StatelessWidget {
'open flutter fragment page',
style: TextStyle(fontSize: 22.0, color: Colors.black),
)),
onTap: () =>
FlutterBoost.singleton.open("sample://flutterFragmentPage"),
onTap: () => FlutterBoost.singleton
.open("sample://flutterFragmentPage"),
),
],
),
),
),
);
}
}
......
......@@ -149,7 +149,7 @@ static NSUInteger kInstanceCounter = 0;
- (void)setEnableForRunnersBatch:(BOOL)enable{
//dummy function
// NSLog(@"[DEBUG]- I did nothing, I am innocent");
NSLog(@"[DEBUG]- I did nothing, I am innocent");
}
#pragma mark - Life circle methods
......@@ -177,7 +177,7 @@ static NSUInteger kInstanceCounter = 0;
}
[super viewWillAppear:animated];
// //instead of calling [super viewWillAppear:animated];, call super's super
//instead of calling [super viewWillAppear:animated];, call super's super
// struct objc_super target = {
// .super_class = class_getSuperclass([FlutterViewController class]),
// .receiver = self,
......@@ -222,7 +222,7 @@ static NSUInteger kInstanceCounter = 0;
params:_params
uniqueId:self.uniqueIDString];
[super viewDidDisappear:animated];
//// instead of calling [super viewDidDisappear:animated];, call super's super
// instead of calling [super viewDidDisappear:animated];, call super's super
// struct objc_super target = {
// .super_class = class_getSuperclass([FlutterViewController class]),
// .receiver = self,
......
......@@ -22,6 +22,7 @@
* THE SOFTWARE.
*/
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'container/boost_container.dart';
......@@ -60,6 +61,26 @@ class FlutterBoost {
PrePushRoute prePush,
PostPushRoute postPush}) {
if(Platform.isAndroid){
WidgetsBinding.instance.addPostFrameCallback((_){
singleton.channel.invokeMethod<Map>('pageOnStart').then((Map pageInfo){
if (pageInfo == null || pageInfo.isEmpty) return;
if (pageInfo.containsKey("name") &&
pageInfo.containsKey("params") &&
pageInfo.containsKey("uniqueId")) {
ContainerCoordinator.singleton.nativeContainerDidShow(
pageInfo["name"], pageInfo["params"], pageInfo["uniqueId"]);
}
});
});
}
return (BuildContext context, Widget child) {
assert(child is Navigator, 'child must be Navigator, what is wrong?');
......
......@@ -19,7 +19,7 @@ dependencies:
flutter:
plugin:
androidPackage: com.idlefish.flutterboost
pluginClass: BoostChannel
pluginClass: FlutterBoostPlugin
# To add assets to your plugin package, add an assets section, like this:
# assets:
......
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