Commit 832193c1 authored by nightfallsad's avatar nightfallsad Committed by GitHub

Merge pull request #5 from alibaba/master

merge code
parents ad1b99c2 2f523fd6
---
name: Bug report
about: Describe this issue template's purpose here.
title: ''
labels: ''
assignees: ''
---
<!-- Thank you for using Flutter Boost!
If you are looking for support about how to get start, please read README first
-->
## Steps to Reproduce
**A small application to reproduce the bug(最小化可复现的demo)**
<!--
Please tell us exactly how to reproduce the problem you are running into.
Please attach a small application (ideally just one main.dart file) that
reproduces the problem. You could use https://gist.github.com/ for this.
If the problem is with your application's rendering, then please attach
a screenshot and explain what the problem is.
-->
1. ...
2. ...
3. ...
<!--
Please tell us which target platform(s) the problem occurs (Android / iOS / Web / macOS / Linux / Windows)
Which target OS version, for Web, browser, is the test system running?
Does the problem occur on emulator/simulator as well as on physical devices?
-->
**Flutter Boost Version**
**Target Platform:**
**Target OS version/browser:**
**Devices:**
## Logs
add your crash log or something else.
<!-- Finally, paste the output of running `flutter doctor -v` here. -->
```
```
# Configuration for probot-no-response - https://github.com/probot/no-response
# Number of days of inactivity before an Issue is closed for lack of response
daysUntilClose: 21
# Label requiring a response
responseRequiredLabel: more-information-needed
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
closeComment: >
This issue has been automatically closed because there has been no response
to our request for more information from the original author. With only the
information that is currently in the issue, we don't have enough information
to take action. Please reach out if you have or find the answers we need so
that we can investigate further.
Thanks for your contribution.
...@@ -17,4 +17,3 @@ example/android/app/.classpath ...@@ -17,4 +17,3 @@ example/android/app/.classpath
example/android/app/.project example/android/app/.project
example/android/.project example/android/.project
flutter_boost flutter_boost
example
...@@ -9,6 +9,43 @@ The main changes are as following: ...@@ -9,6 +9,43 @@ The main changes are as following:
4. We did some code refactoring, the main logic became more straightforward. 4. We did some code refactoring, the main logic became more straightforward.
## 0.1.60
A better implementation to support Flutter v1.9.1+hotfixes
Change the content
android:
1. based on the v1.9.1+hotfixes branch of flutter
2. Solve major bugs, such as page parameter passing
3. Support platformview
4. Support androidx branch :feature/flutter_1.9_androidx_upgrade
5. Resolve memory leaks
6. Rewrite part of the code
7. API changes
8. Improved demo and added many demo cases
ios:
1.based on the v1.9.1+hotfixes branch of flutter
2.bugfixed
## 0.1.61
android:
Fixed bugs
iOS:
no change
## 0.1.63
android:
Fixed bugs
iOS:
no change
### API changes ### API changes
From the point of API changes, we did some refactoring as following: From the point of API changes, we did some refactoring as following:
#### iOS API changes #### iOS API changes
......
### 在FlutterBoost下如何管理Flutter页面的生命周期?原生的Flutter的AppLifecycleState事件会不一致,比如ViewAppear会导致app状态suspending或者paused。混合栈怎么处理?
回答:在混合栈下,页面事件基于以下自定义的事件:
```dart
enum ContainerLifeCycle {
Init,
Appear,
WillDisappear,
Disappear,
Destroy,
Background,
Foreground
}
```
对于页面事件重复,请参考下面的FAQ。
### 如何判断flutter的widget或者container是当前可见的?
回答:有个api可以判断当前页面是否可见:
```dart
bool isTopContainer = FlutterBoost.BoostContainer.of(context).onstage
```
传入你widget的context,就能判断你的widget是否是可见的
基于这个API,可以判断你的widget是否可见,从而避免接收一些重复的生命周期消息。参考这个issue:https://github.com/alibaba/flutter_boost/issues/498
### 您好,我想请教一下flutter_boost有关的问题:ABC三个都是flutter页面,从 A页面 -> B页面 -> C页面,当打开C页面时希望自动关掉B页面,当从C页面返回时直接返回A页面,可有什么方法?
回答:你只需要操作Native层的UINavigationController里的vc数组就可以了。就如同平时你操作普通的UIViewController一样。因为FlutterBoost对Native层的FlutterViewController和Dart层的flutter page的生命周期管理是一致的,当FlutterViewController被销毁,其在dart层管理的flutter page也会自动被销毁。
### 在ios中voice over打开,demo在点击交互会crash
回答:无障碍模式下目前Flutter Engine有bug,已经提交issue和PR给flutter啦。请参考这个issue:https://github.com/alibaba/flutter_boost/issues/488及其分析。提交给flutter的PR见这里:https://github.com/flutter/engine/pull/14155
### 似乎官方已经提供了混合栈的功能,参考这里:https://flutter.dev/docs/development/add-to-app; FlutterBoost是否有存在的必要?
回答:官方的解决方案仅仅是在native侧对FlutterViewController和Flutterengine进行解耦,如此可以一个FlutterEngine切换不同的FlutterViewController或者Activity进行渲染。但其并未解决Native和Flutter页面混合的问题,无法保证两侧的页面生命周期一致。即使是Flutter官方针对这个问题也是建议使用FlutterBoost。
其差别主要有:
|*|FlutterBoost1.5 |Flutter官方方案 |其他框架|
|----|----|----|----|
|是否支持混合页面之间随意跳转 |Y |N |Y|
|一致的页面生命周期管理(多Flutter页面) |Y |N |?|
|是否支持页面间数据传递(回传等) |Y |N |N|
|是否支持测滑手势 |Y |Y |Y|
|是否支持跨页的hero动画 |N |Y |N|
|内存等资源占用是否可控 |Y |Y |Y|
|是否提供一致的页面route方案 |Y |Y |N|
|iOS和Android能力及接口是否一致 |Y |N |N|
|框架是否稳定,支持Flutter1.9 |Y |N |?|
|是否已经支持到View级别混合 |N |N |N|
同时FlutterBoost也提供了一次性创建混合工程的命令:flutterboot。代码参考:https://github.com/alibaba-flutter/flutter-boot
This diff is collapsed.
...@@ -2,31 +2,10 @@ ...@@ -2,31 +2,10 @@
<img src="flutter_boost.png"> <img src="flutter_boost.png">
</p> </p>
# 项目最新动态
目前已经支持flutter 1.9版本。
对应的flutter 版本是:v1.9.1-hotfixes
flutter boost分支是:feature/flutter_1.9_upgrade
flutter boost androidx 分支是:feature/flutter_1.9_androidx_upgrade
代码引入方式:
```java
flutter_boost:
git:
url: 'https://github.com/alibaba/flutter_boost.git'
ref: 'feature/flutter_1.9_upgrade'
```
# Release Note # Release Note
请查看最新版本0.1.50的release note 确认变更,[0.1.50 release note](https://github.com/alibaba/flutter_boost/releases) 请查看最新版本0.1.63的release note 确认变更,[0.1.63 release note](https://github.com/alibaba/flutter_boost/releases)
# FlutterBoost # FlutterBoost
...@@ -34,274 +13,79 @@ flutter boost androidx 分支是:feature/flutter_1.9_androidx_upgrade ...@@ -34,274 +13,79 @@ flutter boost androidx 分支是:feature/flutter_1.9_androidx_upgrade
# 前置条件 # 前置条件
在继续之前,您需要将Flutter集成到你现有的项目中。flutter sdk 的版本需要 v1.5.4-hotfixes,否则会编译失败.
# 安装
## 在Flutter项目中添加依赖项。
打开pubspec.yaml并将以下行添加到依赖项:
```json
flutter_boost: ^0.1.54
```
或者可以直接依赖github的项目的版本,Tag,pub发布会有延迟,推荐直接依赖Github项目
```java
flutter_boost:
git:
url: 'https://github.com/alibaba/flutter_boost.git'
ref: '0.1.54'
```
## Dart代码的集成
将init代码添加到App App
```dart
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
///register page widget builders,the key is pageName
FlutterBoost.singleton.registerPageBuilders({
'sample://firstPage': (pageName, params, _) => FirstRouteWidget(),
'sample://secondPage': (pageName, params, _) => SecondRouteWidget(),
});
}
@override
Widget build(BuildContext context) => MaterialApp(
title: 'Flutter Boost example',
builder: FlutterBoost.init(), ///init container manager
home: Container());
}
```
## iOS代码集成。 在继续之前,您需要将Flutter集成到你现有的项目中。flutter sdk 的版本需要 v1.9.1-hotfixes,否则会编译失败.
注意:需要将libc++ 加入 "Linked Frameworks and Libraries" 中。 # FAQ
请阅读这篇文章:
<a href="Frequently Asked Question.md">FAQ</a>
使用FLBFlutterAppDelegate作为AppDelegate的超类
```objectivec
@interface AppDelegate : FLBFlutterAppDelegate <UIApplicationDelegate>
@end
```
# boost 版本说明
为您的应用程序实现FLBPlatform协议方法。 1. 0.1.50 是基于flutter v1.5.4-hotfixes 分支,android 如果其他flutter版本或者分支 会编译错误
```objectivec 2. 0.1.51--0.1.54 是对0.1.50的bugfix
@interface PlatformRouterImp : NSObject<FLBPlatform>
@property (nonatomic,strong) UINavigationController *navigationController;
+ (PlatformRouterImp *)sharedRouter; 3. 0.1.60 是基于flutter v1.9.1-hotfixes 分支,android如果其他flutter分支会编译错误,该版本不支持andriodx
@end 4. 0.1.61-- 0.1.63 是对0.1.60 的bugfix
@implementation PlatformRouterImp 5. 关于androidx 的支持声明
- (void)openPage:(NSString *)name 目前androidx 分支为 v0.1.61-androidx-hotfixes
params:(NSDictionary *)params
animated:(BOOL)animated
completion:(void (^)(BOOL))completion
{
if([params[@"present"] boolValue]){
FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
[vc setName:name params:params];
[self.navigationController presentViewController:vc animated:animated completion:^{}];
}else{
FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
[vc setName:name params:params];
[self.navigationController pushViewController:vc animated:animated];
}
}
是基于flutter v1.9.1-hotfixes 分支,如果其他分支会编译错误
- (void)closePage:(NSString *)uid animated:(BOOL)animated params:(NSDictionary *)params completion:(void (^)(BOOL))completion 和0.1.60代码同步, bugfix 也会合入该分支。
{
FLBFlutterViewContainer *vc = (id)self.navigationController.presentedViewController;
if([vc isKindOfClass:FLBFlutterViewContainer.class] && [vc.uniqueIDString isEqual: uid]){
[vc dismissViewControllerAnimated:animated completion:^{}];
}else{
[self.navigationController popViewControllerAnimated:animated];
}
}
@end
```
在应用程序开头使用FLBPlatform初始化FlutterBoost。
```objc # 安装
PlatformRouterImp *router = [PlatformRouterImp new];
[FlutterBoostPlugin.sharedInstance startFlutterWithPlatformrouter
onStart^FlutterEngine *engine{
}];
```
## Android代码集成。 ## 在Flutter项目中添加依赖项。
在Application.onCreate()中初始化FlutterBoost
```java
public class MyApplication extends FlutterApplication {
@Override
public void onCreate() {
super.onCreate();
FlutterBoostPlugin.init(new IPlatform() {
@Override
public Application getApplication() {
return MyApplication.this;
}
@Override
public boolean isDebug() {
return true;
}
@Override
public void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts) {
PageRouter.openPageByUrl(context,url,urlParams,requestCode);
}
@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"),"/");
}
};
}
@Override
public int whenEngineStart() {
return ANY_ACTIVITY_CREATED;
}
});
}
```
# 基本用法 打开pubspec.yaml并将以下行添加到依赖项:
## 概念
所有页面路由请求都将发送到Native路由器。Native路由器与Native Container Manager通信,Native Container Manager负责构建和销毁Native Containers。 support分支
```json
## 使用Flutter Boost Native Container用Native代码打开Flutter页面。 flutter_boost:
git:
url: 'https://github.com/alibaba/flutter_boost.git'
ref: '0.1.63'
```objc
FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
[vc setName:name params:params];
[self.navigationController presentViewController:vc animated:animated completion:^{}];
``` ```
但是,这种方式无法获取页面返回的数据,建议你按上面的example实现类似于PlatformRouterImp这样的路由器,然后通过以下方式来打开/关闭页面 androidx分支
```json
```objc flutter_boost:
//push the page git:
[FlutterBoostPlugin open:@"first" urlParams:@{kPageCallBackId:@"MycallbackId#1"} exts:@{@"animated":@(YES)} onPageFinished:^(NSDictionary *result) { url: 'https://github.com/alibaba/flutter_boost.git'
NSLog(@"call me when page finished, and your result is:%@", result); ref: 'v0.1.61-androidx-hotfixes'
} completion:^(BOOL f) {
NSLog(@"page is opened");
}];
//prsent the page
[FlutterBoostPlugin open:@"second" urlParams:@{@"present":@(YES),kPageCallBackId:@"MycallbackId#2"} exts:@{@"animated":@(YES)} onPageFinished:^(NSDictionary *result) {
NSLog(@"call me when page finished, and your result is:%@", result);
} completion:^(BOOL f) {
NSLog(@"page is presented");
}];
//close the page
[FlutterBoostPlugin close:yourUniqueId result:yourdata exts:exts completion:nil];
```
Android
```java
public class FlutterPageActivity extends BoostFlutterActivity {
@Override
public String getContainerUrl() {
//specify the page name register in FlutterBoost
return "sample://firstPage";
}
@Override
public Map getContainerUrlParams() {
//params of the page
Map<String,String> params = new HashMap<>();
params.put("key","value");
return params;
}
}
``` ```
或者用Fragment
```java
public class FlutterFragment extends BoostFlutterFragment {
@Override
public String getContainerUrl() {
return "sample://firstPage";
}
@Override
public Map getContainerUrlParams() {
Map<String,String> params = new HashMap<>();
params.put("key","value");
return params;
}
}
```
## 使用Flutter Boost在dart代码打开页面。 ## boost集成
Dart
```java 集成请看boost的Examples
FlutterBoost.singleton
.open("sample://flutterFragmentPage")
```
# 问题反馈群(钉钉群)
## 使用Flutter Boost在dart代码关闭页面。 <img width="200" src="https://img.alicdn.com/tfs/TB1JSzVeYY1gK0jSZTEXXXDQVXa-892-1213.jpg">
```java
FlutterBoost.singleton.close(uniqueId);
```
# Examples
更详细的使用例子请参考Demo
# 许可证 # 许可证
该项目根据MIT许可证授权 - 有关详细信息,请参阅[LICENSE.md](LICENSE.md)文件 该项目根据MIT许可证授权 - 有关详细信息,请参阅[LICENSE.md](LICENSE.md)文件
<a name="Acknowledgments"> </a> <a name="Acknowledgments"> </a>
# 问题反馈群(钉钉群)
<img width="200" src="https://img.alicdn.com/tfs/TB1JSzVeYY1gK0jSZTEXXXDQVXa-892-1213.jpg">
## 关于我们 ## 关于我们
......
...@@ -26,6 +26,7 @@ android { ...@@ -26,6 +26,7 @@ android {
buildToolsVersion '27.0.3' buildToolsVersion '27.0.3'
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 28
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
lintOptions { lintOptions {
...@@ -34,9 +35,12 @@ android { ...@@ -34,9 +35,12 @@ android {
} }
dependencies { 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.alibaba:fastjson:1.2.41'
implementation 'com.android.support:support-v4:26.1.0'
implementation 'com.android.support:appcompat-v7:26.1.0'
} }
ext { ext {
......
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.idlefish.flutterboost"> package="com.idlefish.flutterboost">
<application>
<activity android:name="com.idlefish.flutterboost.containers.BoostFlutterDefaultActivity" />
</application>
</manifest> </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 { ...@@ -69,14 +69,13 @@ public class ContainerRecord implements IContainerRecord {
@Override @Override
public void onCreate() { public void onCreate() {
Utils.assertCallOnMainThread(); Utils.assertCallOnMainThread();
BoostEngineProvider.assertEngineRunning();
if (mState != STATE_UNKNOW) { if (mState != STATE_UNKNOW) {
Debuger.exception("state error"); Debuger.exception("state error");
} }
mState = STATE_CREATED; mState = STATE_CREATED;
mContainer.getBoostFlutterView().onResume(); // mContainer.getBoostFlutterView().onResume();
mProxy.create(); mProxy.create();
} }
...@@ -92,9 +91,10 @@ public class ContainerRecord implements IContainerRecord { ...@@ -92,9 +91,10 @@ public class ContainerRecord implements IContainerRecord {
mManager.pushRecord(this); mManager.pushRecord(this);
mProxy.appear();
mContainer.getBoostFlutterView().onAttach(); mContainer.getBoostFlutterView().onAttach();
mProxy.appear();
} }
@Override @Override
...@@ -129,15 +129,15 @@ public class ContainerRecord implements IContainerRecord { ...@@ -129,15 +129,15 @@ public class ContainerRecord implements IContainerRecord {
mProxy.destroy(); mProxy.destroy();
mContainer.getBoostFlutterView().onDestroy(); // mContainer.getBoostFlutterView().onDestroy();
mManager.removeRecord(this); mManager.removeRecord(this);
mManager.setContainerResult(this,-1,-1,null); mManager.setContainerResult(this,-1,-1,null);
if (!mManager.hasContainerAppear()) { if (!mManager.hasContainerAppear()) {
mContainer.getBoostFlutterView().onPause(); // mContainer.getBoostFlutterView().onPause();
mContainer.getBoostFlutterView().onStop(); // mContainer.getBoostFlutterView().onStop();
} }
} }
...@@ -154,44 +154,45 @@ public class ContainerRecord implements IContainerRecord { ...@@ -154,44 +154,45 @@ public class ContainerRecord implements IContainerRecord {
map.put("name", mContainer.getContainerUrl()); map.put("name", mContainer.getContainerUrl());
map.put("uniqueId", mUniqueId); map.put("uniqueId", mUniqueId);
FlutterBoost.singleton().channel().sendEvent("lifecycle", map); FlutterBoost.instance().channel().sendEvent("lifecycle", map);
mContainer.getBoostFlutterView().onBackPressed(); // mContainer.getBoostFlutterView().onBackPressed();
} }
@Override @Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
mContainer.getBoostFlutterView().onRequestPermissionsResult(requestCode, permissions, grantResults);
} }
@Override @Override
public void onNewIntent(Intent intent) { public void onNewIntent(Intent intent) {
mContainer.getBoostFlutterView().onNewIntent(intent);
} }
@Override @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
mContainer.getBoostFlutterView().onActivityResult(requestCode, resultCode, data);
} }
@Override @Override
public void onContainerResult(int requestCode, int resultCode, Map<String, Object> result) { public void onContainerResult(int requestCode, int resultCode, Map<String, Object> result) {
mManager.setContainerResult(this, requestCode,resultCode, result); mManager.setContainerResult(this, requestCode,resultCode, result);
} }
@Override @Override
public void onUserLeaveHint() { public void onUserLeaveHint() {
mContainer.getBoostFlutterView().onUserLeaveHint();
} }
@Override @Override
public void onTrimMemory(int level) { public void onTrimMemory(int level) {
mContainer.getBoostFlutterView().onTrimMemory(level);
} }
@Override @Override
public void onLowMemory() { public void onLowMemory() {
mContainer.getBoostFlutterView().onLowMemory();
} }
...@@ -252,7 +253,7 @@ public class ContainerRecord implements IContainerRecord { ...@@ -252,7 +253,7 @@ public class ContainerRecord implements IContainerRecord {
args.put("pageName", url); args.put("pageName", url);
args.put("params", params); args.put("params", params);
args.put("uniqueId", uniqueId); 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) { public void invokeChannelUnsafe(String method, String url, Map params, String uniqueId) {
...@@ -260,7 +261,7 @@ public class ContainerRecord implements IContainerRecord { ...@@ -260,7 +261,7 @@ public class ContainerRecord implements IContainerRecord {
args.put("pageName", url); args.put("pageName", url);
args.put("params", params); args.put("params", params);
args.put("uniqueId", uniqueId); args.put("uniqueId", uniqueId);
FlutterBoost.singleton().channel().invokeMethodUnsafe(method, args); FlutterBoost.instance().channel().invokeMethodUnsafe(method, args);
} }
} }
......
...@@ -60,7 +60,7 @@ public class Debuger { ...@@ -60,7 +60,7 @@ public class Debuger {
public static boolean isDebug(){ public static boolean isDebug(){
try { try {
return FlutterBoost.singleton().platform().isDebug(); return FlutterBoost.instance().platform().isDebug();
}catch (Throwable t){ }catch (Throwable t){
return false; return false;
} }
......
This diff is collapsed.
...@@ -25,7 +25,6 @@ package com.idlefish.flutterboost; ...@@ -25,7 +25,6 @@ package com.idlefish.flutterboost;
import android.content.Context; import android.content.Context;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.SparseArray;
import com.idlefish.flutterboost.interfaces.IContainerManager; import com.idlefish.flutterboost.interfaces.IContainerManager;
import com.idlefish.flutterboost.interfaces.IContainerRecord; import com.idlefish.flutterboost.interfaces.IContainerRecord;
...@@ -41,7 +40,6 @@ import java.util.LinkedHashMap; ...@@ -41,7 +40,6 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.Stack; import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;
public class FlutterViewContainerManager implements IContainerManager { public class FlutterViewContainerManager implements IContainerManager {
...@@ -84,6 +82,11 @@ public class FlutterViewContainerManager implements IContainerManager { ...@@ -84,6 +82,11 @@ public class FlutterViewContainerManager implements IContainerManager {
void removeRecord(IContainerRecord record) { void removeRecord(IContainerRecord record) {
mRecordStack.remove(record); mRecordStack.remove(record);
mRecordMap.remove(record.getContainer()); mRecordMap.remove(record.getContainer());
// if(mRecordMap.isEmpty()){
//
// }
} }
void setContainerResult(IContainerRecord record,int requestCode, int resultCode, Map<String,Object> result) { void setContainerResult(IContainerRecord record,int requestCode, int resultCode, Map<String,Object> result) {
...@@ -111,9 +114,9 @@ public class FlutterViewContainerManager implements IContainerManager { ...@@ -111,9 +114,9 @@ public class FlutterViewContainerManager implements IContainerManager {
} }
void openContainer(String url, Map<String, Object> urlParams, Map<String, Object> exts,OnResult onResult) { 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) { if(context == null) {
context = FlutterBoost.singleton().platform().getApplication(); context = FlutterBoost.instance().platform().getApplication();
} }
if(urlParams == null) { if(urlParams == null) {
...@@ -128,11 +131,13 @@ public class FlutterViewContainerManager implements IContainerManager { ...@@ -128,11 +131,13 @@ public class FlutterViewContainerManager implements IContainerManager {
final String uniqueId = ContainerRecord.genUniqueId(url); final String uniqueId = ContainerRecord.genUniqueId(url);
urlParams.put(IContainerRecord.UNIQ_KEY,uniqueId); urlParams.put(IContainerRecord.UNIQ_KEY,uniqueId);
IContainerRecord currentTopRecord = getCurrentTopRecord();
if(onResult != null) { 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) { IContainerRecord closeContainer(String uniqueId, Map<String, Object> result,Map<String,Object> exts) {
...@@ -148,7 +153,7 @@ public class FlutterViewContainerManager implements IContainerManager { ...@@ -148,7 +153,7 @@ public class FlutterViewContainerManager implements IContainerManager {
Debuger.exception("closeContainer can not find uniqueId:" + uniqueId); Debuger.exception("closeContainer can not find uniqueId:" + uniqueId);
} }
FlutterBoost.singleton().platform().closeContainer(targetRecord,result,exts); FlutterBoost.instance().platform().closeContainer(targetRecord,result,exts);
return targetRecord; return targetRecord;
} }
......
package com.idlefish.flutterboost; 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.IContainerRecord;
import com.idlefish.flutterboost.interfaces.IFlutterEngineProvider;
import com.idlefish.flutterboost.interfaces.IPlatform;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Map; import java.util.Map;
import io.flutter.embedding.android.FlutterView;
import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.PluginRegistry;
public abstract class Platform implements IPlatform { public abstract class Platform {
@Override public abstract Application getApplication();
public boolean isDebug() {
return false; public abstract void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts);
}
public abstract int whenEngineStart();
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) { 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); record.getContainer().finishContainer(result);
} }
@Override
public IFlutterEngineProvider engineProvider() {
return new BoostEngineProvider();
}
@Override public void registerPlugins(PluginRegistry mRegistry) {
public void registerPlugins(PluginRegistry registry) {
try { if(pluginsRegister!=null){
Class clz = Class.forName("io.flutter.plugins.GeneratedPluginRegistrant"); pluginsRegister.registerPlugins(mRegistry);
Method method = clz.getDeclaredMethod("registerWith",PluginRegistry.class);
method.invoke(null,registry);
}catch (Throwable t){
throw new RuntimeException(t);
} }
}
@Override if (lifecycleListener!= null) {
public int whenEngineStart() { lifecycleListener.onPluginsRegistered();
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; ...@@ -35,14 +35,14 @@ import android.view.View;
import android.view.Window; import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import com.alibaba.fastjson.JSON;
import java.io.BufferedReader; import java.io.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
public class Utils { public class Utils {
...@@ -263,7 +263,7 @@ public class Utils { ...@@ -263,7 +263,7 @@ public class Utils {
return; return;
} }
String [] arr = new String[]{"mLastSrvView", "mServedView", "mNextServedView"}; String [] arr = new String[]{"mLastSrvView","mServedView", "mNextServedView"};
Field f = null; Field f = null;
Object obj_get = null; Object obj_get = null;
for (int i = 0;i < arr.length;i ++) { for (int i = 0;i < arr.length;i ++) {
...@@ -272,21 +272,71 @@ public class Utils { ...@@ -272,21 +272,71 @@ public class Utils {
f = imm.getClass().getDeclaredField(param); f = imm.getClass().getDeclaredField(param);
if (f.isAccessible() == false) { if (f.isAccessible() == false) {
f.setAccessible(true); f.setAccessible(true);
} // author: sodino mail:sodino@qq.com }
obj_get = f.get(imm); obj_get = f.get(imm);
if (obj_get != null && obj_get instanceof View) { if (obj_get != null && obj_get instanceof View) {
View v_get = (View) obj_get; View v_get = (View) obj_get;
if (v_get.getContext() == destContext) { // 被InputMethodManager持有引用的context是想要目标销毁的 if (v_get.getContext() == destContext) {
f.set(imm, null); // 置空,破坏掉path to gc节点 f.set(imm, null);
} else { } else {
// 不是想要目标销毁的,即为又进了另一层界面了,不要处理,避免影响原逻辑,也就不用继续for循环了
break; break;
} }
} }
}catch(Throwable t){ }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; ...@@ -12,6 +12,23 @@ import android.view.inputmethod.InputMethodManager;
import io.flutter.embedding.engine.systemchannels.TextInputChannel; import io.flutter.embedding.engine.systemchannels.TextInputChannel;
import io.flutter.plugin.common.ErrorLogResult; import io.flutter.plugin.common.ErrorLogResult;
import io.flutter.plugin.common.MethodChannel; 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 { class XInputConnectionAdaptor extends BaseInputConnection {
private final View mFlutterView; private final View mFlutterView;
...@@ -20,10 +37,9 @@ class XInputConnectionAdaptor extends BaseInputConnection { ...@@ -20,10 +37,9 @@ class XInputConnectionAdaptor extends BaseInputConnection {
private final Editable mEditable; private final Editable mEditable;
private int mBatchCount; private int mBatchCount;
private InputMethodManager mImm; private InputMethodManager mImm;
private final Layout mLayout;
private static final MethodChannel.Result logger = @SuppressWarnings("deprecation")
new ErrorLogResult("FlutterTextInput");
public XInputConnectionAdaptor( public XInputConnectionAdaptor(
View view, View view,
int client, int client,
...@@ -36,6 +52,9 @@ class XInputConnectionAdaptor extends BaseInputConnection { ...@@ -36,6 +52,9 @@ class XInputConnectionAdaptor extends BaseInputConnection {
this.textInputChannel = textInputChannel; this.textInputChannel = textInputChannel;
mEditable = editable; mEditable = editable;
mBatchCount = 0; 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); mImm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
} }
...@@ -126,12 +145,25 @@ class XInputConnectionAdaptor extends BaseInputConnection { ...@@ -126,12 +145,25 @@ class XInputConnectionAdaptor extends BaseInputConnection {
return result; 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 @Override
public boolean sendKeyEvent(KeyEvent event) { public boolean sendKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) { if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) { if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
int selStart = Selection.getSelectionStart(mEditable); int selStart = clampIndexToEditable(Selection.getSelectionStart(mEditable), mEditable);
int selEnd = Selection.getSelectionEnd(mEditable); int selEnd = clampIndexToEditable(Selection.getSelectionEnd(mEditable), mEditable);
if (selEnd > selStart) { if (selEnd > selStart) {
// Delete the selection. // Delete the selection.
Selection.setSelection(mEditable, selStart); Selection.setSelection(mEditable, selStart);
...@@ -139,10 +171,33 @@ class XInputConnectionAdaptor extends BaseInputConnection { ...@@ -139,10 +171,33 @@ class XInputConnectionAdaptor extends BaseInputConnection {
updateEditingState(); updateEditingState();
return true; return true;
} else if (selStart > 0) { } else if (selStart > 0) {
// Delete to the left of the cursor. // Delete to the left/right of the cursor depending on direction of text.
int newSel = Math.max(selStart - 1, 0); // TODO(garyq): Explore how to obtain per-character direction. The
Selection.setSelection(mEditable, newSel); // isRTLCharAt() call below is returning blanket direction assumption
mEditable.delete(newSel, selStart); // 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(); updateEditingState();
return true; 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
...@@ -25,7 +25,7 @@ package com.idlefish.flutterboost.interfaces; ...@@ -25,7 +25,7 @@ package com.idlefish.flutterboost.interfaces;
import android.app.Activity; import android.app.Activity;
import com.idlefish.flutterboost.BoostFlutterView; import com.idlefish.flutterboost.containers.FlutterSplashView;
import java.util.Map; import java.util.Map;
...@@ -41,7 +41,7 @@ public interface IFlutterViewContainer { ...@@ -41,7 +41,7 @@ public interface IFlutterViewContainer {
* provide a flutter view * provide a flutter view
* @return * @return
*/ */
BoostFlutterView getBoostFlutterView(); FlutterSplashView getBoostFlutterView();
/** /**
* call to destroy the container * 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);
}
This diff is collapsed.
...@@ -45,6 +45,8 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe ...@@ -45,6 +45,8 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Map params = new HashMap(); Map params = new HashMap();
params.put("test1","v_test1");
params.put("test2","v_test2");
//Add some params if needed. //Add some params if needed.
if (v == mOpenNative) { if (v == mOpenNative) {
PageRouter.openPageByUrl(this, PageRouter.NATIVE_PAGE_URL , params); PageRouter.openPageByUrl(this, PageRouter.NATIVE_PAGE_URL , params);
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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