Commit 9a469c08 authored by XinLei's avatar XinLei Committed by GitHub

Merge pull request #5 from alibaba/master

sync 2019/11/13
parents 3050ace3 4cbc0d60
......@@ -17,4 +17,3 @@ example/android/app/.classpath
example/android/app/.project
example/android/.project
flutter_boost
example
......@@ -9,6 +9,12 @@ The main changes are as following:
4. We did some code refactoring, the main logic became more straightforward.
## 0.1.60
The version of the flutter SDK requires v1.9.1+hotfixes, or it will compile error.
### API changes
From the point of API changes, we did some refactoring as following:
#### iOS API changes
......
This diff is collapsed.
This diff is collapsed.
......@@ -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 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.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(FlutterBoost.instance().currentActivity()!=null){
return FlutterBoost.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);
FlutterBoost.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);
FlutterBoost.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);
FlutterBoost.instance().channel().invokeMethodUnsafe(method, args);
}
}
......
......@@ -60,7 +60,7 @@ public class Debuger {
public static boolean isDebug(){
try {
return FlutterBoost.singleton().platform().isDebug();
return FlutterBoost.instance().platform().isDebug();
}catch (Throwable t){
return false;
}
......
This diff is collapsed.
......@@ -25,7 +25,6 @@ package com.idlefish.flutterboost;
import android.content.Context;
import android.text.TextUtils;
import android.util.SparseArray;
import com.idlefish.flutterboost.interfaces.IContainerManager;
import com.idlefish.flutterboost.interfaces.IContainerRecord;
......@@ -41,7 +40,6 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;
public class FlutterViewContainerManager implements IContainerManager {
......@@ -84,6 +82,13 @@ public class FlutterViewContainerManager implements IContainerManager {
void removeRecord(IContainerRecord record) {
mRecordStack.remove(record);
mRecordMap.remove(record.getContainer());
if(mRecordMap.isEmpty()){
if( FlutterBoost.instance().platform().whenEngineDestroy()== FlutterBoost.ConfigBuilder.All_FLUTTER_ACTIVITY_DESTROY){
FlutterBoost.instance().boostDestroy();
}
}
}
void setContainerResult(IContainerRecord record,int requestCode, int resultCode, Map<String,Object> result) {
......@@ -111,9 +116,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 = FlutterBoost.instance().currentActivity();
if(context == null) {
context = FlutterBoost.singleton().platform().getApplication();
context = FlutterBoost.instance().platform().getApplication();
}
if(urlParams == null) {
......@@ -128,11 +133,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);
FlutterBoost.instance().platform().openContainer(context,url,urlParams,requestCode,exts);
}
IContainerRecord closeContainer(String uniqueId, Map<String, Object> result,Map<String,Object> exts) {
......@@ -148,7 +155,7 @@ public class FlutterViewContainerManager implements IContainerManager {
Debuger.exception("closeContainer can not find uniqueId:" + uniqueId);
}
FlutterBoost.singleton().platform().closeContainer(targetRecord,result,exts);
FlutterBoost.instance().platform().closeContainer(targetRecord,result,exts);
return targetRecord;
}
......
package com.idlefish.flutterboost;
import android.app.Application;
import android.content.Context;
import android.util.Log;
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 FlutterBoost.BoostLifecycleListener lifecycleListener;
public FlutterBoost.BoostPluginsRegister pluginsRegister;
@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) {
public void registerPlugins(PluginRegistry mRegistry) {
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);
Method method = clz.getDeclaredMethod("registerWith", PluginRegistry.class);
method.invoke(null, mRegistry);
} catch (Throwable t) {
Log.i("flutterboost.platform",t.toString());
}
}
@Override
public int whenEngineStart() {
return ANY_ACTIVITY_CREATED;
if(pluginsRegister!=null){
pluginsRegister.registerPlugins(mRegistry);
}
if (lifecycleListener!= null) {
lifecycleListener.onPluginsRegistered();
}
}
}
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
......@@ -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.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() {}
}
......@@ -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
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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