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 ...@@ -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,12 @@ The main changes are as following: ...@@ -9,6 +9,12 @@ 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
The version of the flutter SDK requires v1.9.1+hotfixes, or it will compile error.
### 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
......
...@@ -5,42 +5,16 @@ ...@@ -5,42 +5,16 @@
<a href="https://mp.weixin.qq.com/s?__biz=MzU4MDUxOTI5NA==&mid=2247484367&idx=1&sn=fcbc485f068dae5de9f68d52607ea08f&chksm=fd54d7deca235ec86249a9e3714ec18be8b2d6dc580cae19e4e5113533a6c5b44dfa5813c4c3&scene=0&subscene=131&clicktime=1551942425&ascene=7&devicetype=android-28&version=2700033b&nettype=ctnet&abtest_cookie=BAABAAoACwASABMABAAklx4AVpkeAMSZHgDWmR4AAAA%3D&lang=zh_CN&pass_ticket=1qvHqOsbLBHv3wwAcw577EHhNjg6EKXqTfnOiFbbbaw%3D&wx_header=1">中文介绍</a> <a href="https://mp.weixin.qq.com/s?__biz=MzU4MDUxOTI5NA==&mid=2247484367&idx=1&sn=fcbc485f068dae5de9f68d52607ea08f&chksm=fd54d7deca235ec86249a9e3714ec18be8b2d6dc580cae19e4e5113533a6c5b44dfa5813c4c3&scene=0&subscene=131&clicktime=1551942425&ascene=7&devicetype=android-28&version=2700033b&nettype=ctnet&abtest_cookie=BAABAAoACwASABMABAAklx4AVpkeAMSZHgDWmR4AAAA%3D&lang=zh_CN&pass_ticket=1qvHqOsbLBHv3wwAcw577EHhNjg6EKXqTfnOiFbbbaw%3D&wx_header=1">中文介绍</a>
</p> </p>
# Latest News
Currently, version 1.9 of flutter is supported.
flutter branch:v1.9.1-hotfixes
FlutterBoost branch:feature/flutter_1.9_upgrade
FlutterBoost for androidx branch:feature/flutter_1.9_androidx_upgrade
```java
flutter_boost:
git:
url: 'https://github.com/alibaba/flutter_boost.git'
ref: 'feature/flutter_1.9_upgrade'
```
dingding group:
<img width="200" src="https://img.alicdn.com/tfs/TB1JSzVeYY1gK0jSZTEXXXDQVXa-892-1213.jpg">
# Release Note # Release Note
Please checkout the release note for the latest 0.1.54 to see changes [0.1.54 release note](https://github.com/alibaba/flutter_boost/releases) Please checkout the release note for the latest 0.1.60 to see changes [0.1.60 release note](https://github.com/alibaba/flutter_boost/releases)
# FlutterBoost # FlutterBoost
A next-generation Flutter-Native hybrid solution. FlutterBoost is a Flutter plugin which enables hybrid integration of Flutter for your existing native apps with minimum efforts.The philosophy of FlutterBoost is to use Flutter as easy as using a WebView. Managing Native pages and Flutter pages at the same time is non-trivial in an existing App. FlutterBoost takes care of page resolution for you. The only thing you need to care about is the name of the page(usually could be an URL).  A next-generation Flutter-Native hybrid solution. FlutterBoost is a Flutter plugin which enables hybrid integration of Flutter for your existing native apps with minimum efforts.The philosophy of FlutterBoost is to use Flutter as easy as using a WebView. Managing Native pages and Flutter pages at the same time is non-trivial in an existing App. FlutterBoost takes care of page resolution for you. The only thing you need to care about is the name of the page(usually could be an URL). 
<a name="bf647454"></a> <a name="bf647454"></a>
# Prerequisites # Prerequisites
You need to add Flutter to your project before moving on.The version of the flutter SDK requires v1.5.4-hotfixes, or it will compile error. You need to add Flutter to your project before moving on.The version of the flutter SDK requires v1.9.1+hotfixes, or it will compile error.
# Getting Started # Getting Started
...@@ -49,17 +23,19 @@ You need to add Flutter to your project before moving on.The version of the flut ...@@ -49,17 +23,19 @@ You need to add Flutter to your project before moving on.The version of the flut
Open you pubspec.yaml and add the following line to dependencies: Open you pubspec.yaml and add the following line to dependencies:
```java support branch
flutter_boost: ^0.1.54 ```json
flutter_boost:
git:
url: 'https://github.com/alibaba/flutter_boost.git'
ref: '0.1.60'
``` ```
androidx branch
or you could rely directly on a Github project tag, for example(recommended) ```json
```java
flutter_boost: flutter_boost:
git: git:
url: 'https://github.com/alibaba/flutter_boost.git' url: 'https://github.com/alibaba/flutter_boost.git'
ref: '0.1.54' ref: 'feature/flutter_1.9_androidx_upgrade'
``` ```
...@@ -68,31 +44,45 @@ flutter_boost: ...@@ -68,31 +44,45 @@ flutter_boost:
Add init code to you App Add init code to you App
```dart ```dart
void main() => runApp(MyApp()); void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget { class MyApp extends StatefulWidget {
@override @override
_MyAppState createState() => _MyAppState(); _MyAppState createState() => _MyAppState();
} }
class _MyAppState extends State<MyApp> { class _MyAppState extends State<MyApp> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
///register page widget builders,the key is pageName FlutterBoost.singleton.registerPageBuilders({
FlutterBoost.singleton.registerPageBuilders({ 'first': (pageName, params, _) => FirstRouteWidget(),
'sample://firstPage': (pageName, params, _) => FirstRouteWidget(), 'second': (pageName, params, _) => SecondRouteWidget(),
'sample://secondPage': (pageName, params, _) => SecondRouteWidget(), 'tab': (pageName, params, _) => TabRouteWidget(),
}); 'platformView': (pageName, params, _) => PlatformRouteWidget(),
'flutterFragment': (pageName, params, _) => FragmentRouteWidget(params),
} 'flutterPage': (pageName, params, _) {
print("flutterPage params:$params");
@override
Widget build(BuildContext context) => MaterialApp( return FlutterRouteWidget(params:params);
title: 'Flutter Boost example', },
builder: FlutterBoost.init(), ///init container manager });
home: Container()); }
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Boost example',
builder: FlutterBoost.init(postPush: _onRoutePushed),
home: Container());
}
void _onRoutePushed(
String pageName, String uniqueId, Map params, Route route, Future _) {
}
} }
``` ```
...@@ -101,9 +91,11 @@ class _MyAppState extends State<MyApp> { ...@@ -101,9 +91,11 @@ class _MyAppState extends State<MyApp> {
Note: You need to add libc++ into "Linked Frameworks and Libraries" Note: You need to add libc++ into "Linked Frameworks and Libraries"
### objective-c:
Use FLBFlutterAppDelegate as the superclass of your AppDelegate Use FLBFlutterAppDelegate as the superclass of your AppDelegate
```objc ```objectivec
@interface AppDelegate : FLBFlutterAppDelegate <UIApplicationDelegate> @interface AppDelegate : FLBFlutterAppDelegate <UIApplicationDelegate>
@end @end
``` ```
...@@ -111,37 +103,48 @@ Use FLBFlutterAppDelegate as the superclass of your AppDelegate ...@@ -111,37 +103,48 @@ Use FLBFlutterAppDelegate as the superclass of your AppDelegate
Implement FLBPlatform protocol methods for your App. Implement FLBPlatform protocol methods for your App.
```objc
@interface PlatformRouterImp : NSObject<FLBPlatform>
```objectivec
@interface PlatformRouterImp : NSObject<FLBPlatform>
@property (nonatomic,strong) UINavigationController *navigationController; @property (nonatomic,strong) UINavigationController *navigationController;
+ (PlatformRouterImp *)sharedRouter;
@end @end
@implementation PlatformRouterImp @implementation PlatformRouterImp
- (void)openPage:(NSString *)name #pragma mark - Boost 1.5
params:(NSDictionary *)params - (void)open:(NSString *)name
animated:(BOOL)animated urlParams:(NSDictionary *)params
completion:(void (^)(BOOL))completion exts:(NSDictionary *)exts
completion:(void (^)(BOOL))completion
{ {
if([params[@"present"] boolValue]){ BOOL animated = [exts[@"animated"] boolValue];
FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new; FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
[vc setName:name params:params]; [vc setName:name params:params];
[self.navigationController presentViewController:vc animated:animated completion:^{}]; [self.navigationController pushViewController:vc animated:animated];
}else{ if(completion) completion(YES);
FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
[vc setName:name params:params];
[self.navigationController pushViewController:vc animated:animated];
}
} }
- (void)present:(NSString *)name
urlParams:(NSDictionary *)params
exts:(NSDictionary *)exts
completion:(void (^)(BOOL))completion
{
BOOL animated = [exts[@"animated"] boolValue];
FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
[vc setName:name params:params];
[self.navigationController presentViewController:vc animated:animated completion:^{
if(completion) completion(YES);
}];
}
- (void)closePage:(NSString *)uid animated:(BOOL)animated params:(NSDictionary *)params completion:(void (^)(BOOL))completion - (void)close:(NSString *)uid
result:(NSDictionary *)result
exts:(NSDictionary *)exts
completion:(void (^)(BOOL))completion
{ {
BOOL animated = [exts[@"animated"] boolValue];
animated = YES;
FLBFlutterViewContainer *vc = (id)self.navigationController.presentedViewController; FLBFlutterViewContainer *vc = (id)self.navigationController.presentedViewController;
if([vc isKindOfClass:FLBFlutterViewContainer.class] && [vc.uniqueIDString isEqual: uid]){ if([vc isKindOfClass:FLBFlutterViewContainer.class] && [vc.uniqueIDString isEqual: uid]){
[vc dismissViewControllerAnimated:animated completion:^{}]; [vc dismissViewControllerAnimated:animated completion:^{}];
...@@ -149,7 +152,6 @@ Implement FLBPlatform protocol methods for your App. ...@@ -149,7 +152,6 @@ Implement FLBPlatform protocol methods for your App.
[self.navigationController popViewControllerAnimated:animated]; [self.navigationController popViewControllerAnimated:animated];
} }
} }
@end @end
``` ```
...@@ -158,59 +160,144 @@ Implement FLBPlatform protocol methods for your App. ...@@ -158,59 +160,144 @@ Implement FLBPlatform protocol methods for your App.
Initialize FlutterBoost with FLBPlatform at the beginning of your App, such as AppDelegate. Initialize FlutterBoost with FLBPlatform at the beginning of your App, such as AppDelegate.
```objc ```objc
PlatformRouterImp *router = [PlatformRouterImp new]; PlatformRouterImp *router = [PlatformRouterImp new];
[FlutterBoostPlugin.sharedInstance startFlutterWithPlatform:router [FlutterBoostPlugin.sharedInstance startFlutterWithPlatform:router
onStart:^(FlutterEngine *engine) { onStart:^(FlutterEngine *engine) {
}]; }];
``` ```
### swift:
init
```swift
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
) -> Bool {
let router = PlatformRouterImp.init();
FlutterBoostPlugin.sharedInstance()?.startFlutter(with: router, onStart: { (engine) in
});
self.window = UIWindow.init(frame: UIScreen.main.bounds)
let viewController = ViewController.init()
let navi = UINavigationController.init(rootViewController: viewController)
self.window.rootViewController = navi
self.window.makeKeyAndVisible()
return true;//super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
```
Implement FLBPlatform protocol methods for your App.
```swift
class PlatformRouterImp: NSObject, FLBPlatform {
func open(_ url: String, urlParams: [AnyHashable : Any], exts: [AnyHashable : Any], completion: @escaping (Bool) -> Void) {
var animated = false;
if exts["animated"] != nil{
animated = exts["animated"] as! Bool;
}
let vc = FLBFlutterViewContainer.init();
vc.setName(url, params: urlParams);
self.navigationController().pushViewController(vc, animated: animated);
completion(true);
}
func present(_ url: String, urlParams: [AnyHashable : Any], exts: [AnyHashable : Any], completion: @escaping (Bool) -> Void) {
var animated = false;
if exts["animated"] != nil{
animated = exts["animated"] as! Bool;
}
let vc = FLBFlutterViewContainer.init();
vc.setName(url, params: urlParams);
navigationController().present(vc, animated: animated) {
completion(true);
};
}
func close(_ uid: String, result: [AnyHashable : Any], exts: [AnyHashable : Any], completion: @escaping (Bool) -> Void) {
var animated = false;
if exts["animated"] != nil{
animated = exts["animated"] as! Bool;
}
let presentedVC = self.navigationController().presentedViewController;
let vc = presentedVC as? FLBFlutterViewContainer;
if vc?.uniqueIDString() == uid {
vc?.dismiss(animated: animated, completion: {
completion(true);
});
}else{
self.navigationController().popViewController(animated: animated);
}
}
func navigationController() -> UINavigationController {
let delegate = UIApplication.shared.delegate as! AppDelegate
let navigationController = delegate.window?.rootViewController as! UINavigationController
return navigationController;
}
}
```
## Integration with Android code. ## Integration with Android code.
Init FlutterBoost in Application.onCreate()  Init FlutterBoost in Application.onCreate() 
```java ```java
public class MyApplication extends FlutterApplication { public class MyApplication extends Application {
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
FlutterBoostPlugin.init(new IPlatform() { INativeRouter router =new INativeRouter() {
@Override
public Application getApplication() {
return MyApplication.this;
}
@Override @Override
public boolean isDebug() { public void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts) {
return true; String assembleUrl=Utils.assembleUrl(url,urlParams);
PageRouter.openPageByUrl(context,assembleUrl, urlParams);
} }
};
FlutterBoost.BoostLifecycleListener lifecycleListener= new FlutterBoost.BoostLifecycleListener() {
@Override @Override
public void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts) { public void onEngineCreated() {
//native open url
} }
@Override @Override
public IFlutterEngineProvider engineProvider() { public void onPluginsRegistered() {
return new BoostEngineProvider(){ MethodChannel mMethodChannel = new MethodChannel( FlutterBoost.instance().engineProvider().getDartExecutor(), "methodChannel");
@Override Log.e("MyApplication","MethodChannel create");
public BoostFlutterEngine createEngine(Context context) { TextPlatformViewPlugin.register(FlutterBoost.instance().getPluginRegistry().registrarFor("TextPlatformViewPlugin"));
return new BoostFlutterEngine(context, new DartExecutor.DartEntrypoint(
context.getResources().getAssets(),
FlutterMain.findAppBundlePath(context),
"main"),"/");
}
};
} }
@Override @Override
public int whenEngineStart() { public void onEngineDestroy() {
return ANY_ACTIVITY_CREATED;
} }
};
Platform platform= new FlutterBoost
.ConfigBuilder(this,router)
.isDebug(true)
.whenEngineStart(FlutterBoost.ConfigBuilder.ANY_ACTIVITY_CREATED)
.renderMode(FlutterView.RenderMode.texture)
.lifecycleListener(lifecycleListener)
.build();
FlutterBoost.instance().init(platform);
});
} }
}
``` ```
...@@ -250,40 +337,52 @@ However, in this way, you cannot get the page data result after the page finishe ...@@ -250,40 +337,52 @@ However, in this way, you cannot get the page data result after the page finishe
Android Android
```java ```java
public class FlutterPageActivity extends BoostFlutterActivity { public class PageRouter {
public final static Map<String, String> pageName = new HashMap<String, String>() {{
@Override
public String getContainerUrl() {
//specify the page name register in FlutterBoost
return "sample://firstPage";
}
@Override put("first", "first");
public Map getContainerUrlParams() { put("second", "second");
//params of the page put("tab", "tab");
Map<String,String> params = new HashMap<>();
params.put("key","value");
return params;
}
}
```
or put("sample://flutterPage", "flutterPage");
}};
```java public static final String NATIVE_PAGE_URL = "sample://nativePage";
public static final String FLUTTER_PAGE_URL = "sample://flutterPage";
public static final String FLUTTER_FRAGMENT_PAGE_URL = "sample://flutterFragmentPage";
public class FlutterFragment extends BoostFlutterFragment { public static boolean openPageByUrl(Context context, String url, Map params) {
@Override return openPageByUrl(context, url, params, 0);
public String getContainerUrl() {
return "flutterFragment";
} }
@Override public static boolean openPageByUrl(Context context, String url, Map params, int requestCode) {
public Map getContainerUrlParams() {
Map<String,String> params = new HashMap<>(); String path = url.split("\\?")[0];
params.put("tag",getArguments().getString("tag"));
return params; Log.i("openPageByUrl",path);
try {
if (pageName.containsKey(path)) {
Intent intent = BoostFlutterActivity.withNewEngine().url(pageName.get(path)).params(params)
.backgroundMode(BoostFlutterActivity.BackgroundMode.opaque).build(context);
context.startActivity(intent);
} else if (url.startsWith(FLUTTER_FRAGMENT_PAGE_URL)) {
context.startActivity(new Intent(context, FlutterFragmentPageActivity.class));
return true;
} else if (url.startsWith(NATIVE_PAGE_URL)) {
context.startActivity(new Intent(context, NativePageActivity.class));
return true;
} else {
return false;
}
} catch (Throwable t) {
return false;
}
return false;
} }
} }
``` ```
...@@ -316,6 +415,12 @@ Please see the example for details. ...@@ -316,6 +415,12 @@ Please see the example for details.
This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details
# Problem feedback group( dingding group)
<img width="200" src="https://img.alicdn.com/tfs/TB1JSzVeYY1gK0jSZTEXXXDQVXa-892-1213.jpg">
## 关于我们 ## 关于我们
阿里巴巴-闲鱼技术是国内最早也是最大规模线上运行Flutter的团队。 阿里巴巴-闲鱼技术是国内最早也是最大规模线上运行Flutter的团队。
......
...@@ -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.60的release note 确认变更,[0.1.60 release note](https://github.com/alibaba/flutter_boost/releases)
# FlutterBoost # FlutterBoost
...@@ -34,7 +13,8 @@ flutter boost androidx 分支是:feature/flutter_1.9_androidx_upgrade ...@@ -34,7 +13,8 @@ flutter boost androidx 分支是:feature/flutter_1.9_androidx_upgrade
# 前置条件 # 前置条件
在继续之前,您需要将Flutter集成到你现有的项目中。flutter sdk 的版本需要 v1.5.4-hotfixes,否则会编译失败.
在继续之前,您需要将Flutter集成到你现有的项目中。flutter sdk 的版本需要 v1.9.1-hotfixes,否则会编译失败.
# 安装 # 安装
...@@ -42,49 +22,67 @@ flutter boost androidx 分支是:feature/flutter_1.9_androidx_upgrade ...@@ -42,49 +22,67 @@ flutter boost androidx 分支是:feature/flutter_1.9_androidx_upgrade
打开pubspec.yaml并将以下行添加到依赖项: 打开pubspec.yaml并将以下行添加到依赖项:
support分支
```json ```json
flutter_boost: ^0.1.54
```
或者可以直接依赖github的项目的版本,Tag,pub发布会有延迟,推荐直接依赖Github项目 flutter_boost:
git:
url: 'https://github.com/alibaba/flutter_boost.git'
ref: '0.1.60'
```java ```
androidx分支
```json
flutter_boost: flutter_boost:
git: git:
url: 'https://github.com/alibaba/flutter_boost.git' url: 'https://github.com/alibaba/flutter_boost.git'
ref: '0.1.54' ref: 'feature/flutter_1.9_androidx_upgrade'
``` ```
## Dart代码的集成 ## Dart代码的集成
将init代码添加到App App 将init代码添加到App App
```dart ```dart
void main() => runApp(MyApp()); void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget { class MyApp extends StatefulWidget {
@override @override
_MyAppState createState() => _MyAppState(); _MyAppState createState() => _MyAppState();
} }
class _MyAppState extends State<MyApp> { class _MyAppState extends State<MyApp> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
///register page widget builders,the key is pageName FlutterBoost.singleton.registerPageBuilders({
FlutterBoost.singleton.registerPageBuilders({ 'first': (pageName, params, _) => FirstRouteWidget(),
'sample://firstPage': (pageName, params, _) => FirstRouteWidget(), 'second': (pageName, params, _) => SecondRouteWidget(),
'sample://secondPage': (pageName, params, _) => SecondRouteWidget(), 'tab': (pageName, params, _) => TabRouteWidget(),
}); 'platformView': (pageName, params, _) => PlatformRouteWidget(),
'flutterFragment': (pageName, params, _) => FragmentRouteWidget(params),
} 'flutterPage': (pageName, params, _) {
print("flutterPage params:$params");
@override
Widget build(BuildContext context) => MaterialApp( return FlutterRouteWidget(params:params);
title: 'Flutter Boost example', },
builder: FlutterBoost.init(), ///init container manager });
home: Container()); }
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Boost example',
builder: FlutterBoost.init(postPush: _onRoutePushed),
home: Container());
}
void _onRoutePushed(
String pageName, String uniqueId, Map params, Route route, Future _) {
}
} }
``` ```
...@@ -92,6 +90,8 @@ class _MyAppState extends State<MyApp> { ...@@ -92,6 +90,8 @@ class _MyAppState extends State<MyApp> {
注意:需要将libc++ 加入 "Linked Frameworks and Libraries" 中。 注意:需要将libc++ 加入 "Linked Frameworks and Libraries" 中。
### objective-c:
使用FLBFlutterAppDelegate作为AppDelegate的超类 使用FLBFlutterAppDelegate作为AppDelegate的超类
```objectivec ```objectivec
...@@ -104,35 +104,45 @@ class _MyAppState extends State<MyApp> { ...@@ -104,35 +104,45 @@ class _MyAppState extends State<MyApp> {
```objectivec ```objectivec
@interface PlatformRouterImp : NSObject<FLBPlatform> @interface PlatformRouterImp : NSObject<FLBPlatform>
@property (nonatomic,strong) UINavigationController *navigationController; @property (nonatomic,strong) UINavigationController *navigationController;
+ (PlatformRouterImp *)sharedRouter;
@end @end
@implementation PlatformRouterImp @implementation PlatformRouterImp
- (void)openPage:(NSString *)name #pragma mark - Boost 1.5
params:(NSDictionary *)params - (void)open:(NSString *)name
animated:(BOOL)animated urlParams:(NSDictionary *)params
completion:(void (^)(BOOL))completion exts:(NSDictionary *)exts
completion:(void (^)(BOOL))completion
{ {
if([params[@"present"] boolValue]){ BOOL animated = [exts[@"animated"] boolValue];
FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new; FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
[vc setName:name params:params]; [vc setName:name params:params];
[self.navigationController presentViewController:vc animated:animated completion:^{}]; [self.navigationController pushViewController:vc animated:animated];
}else{ if(completion) completion(YES);
FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
[vc setName:name params:params];
[self.navigationController pushViewController:vc animated:animated];
}
} }
- (void)present:(NSString *)name
urlParams:(NSDictionary *)params
exts:(NSDictionary *)exts
completion:(void (^)(BOOL))completion
{
BOOL animated = [exts[@"animated"] boolValue];
FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
[vc setName:name params:params];
[self.navigationController presentViewController:vc animated:animated completion:^{
if(completion) completion(YES);
}];
}
- (void)closePage:(NSString *)uid animated:(BOOL)animated params:(NSDictionary *)params completion:(void (^)(BOOL))completion - (void)close:(NSString *)uid
result:(NSDictionary *)result
exts:(NSDictionary *)exts
completion:(void (^)(BOOL))completion
{ {
BOOL animated = [exts[@"animated"] boolValue];
animated = YES;
FLBFlutterViewContainer *vc = (id)self.navigationController.presentedViewController; FLBFlutterViewContainer *vc = (id)self.navigationController.presentedViewController;
if([vc isKindOfClass:FLBFlutterViewContainer.class] && [vc.uniqueIDString isEqual: uid]){ if([vc isKindOfClass:FLBFlutterViewContainer.class] && [vc.uniqueIDString isEqual: uid]){
[vc dismissViewControllerAnimated:animated completion:^{}]; [vc dismissViewControllerAnimated:animated completion:^{}];
...@@ -140,66 +150,150 @@ class _MyAppState extends State<MyApp> { ...@@ -140,66 +150,150 @@ class _MyAppState extends State<MyApp> {
[self.navigationController popViewControllerAnimated:animated]; [self.navigationController popViewControllerAnimated:animated];
} }
} }
@end @end
``` ```
在应用程序开头使用FLBPlatform初始化FlutterBoost。 在应用程序开头使用FLBPlatform初始化FlutterBoost。
```objc ```objc
PlatformRouterImp *router = [PlatformRouterImp new]; PlatformRouterImp *router = [PlatformRouterImp new];
[FlutterBoostPlugin.sharedInstance startFlutterWithPlatformrouter [FlutterBoostPlugin.sharedInstance startFlutterWithPlatform:router
onStart^FlutterEngine *engine{ onStart:^(FlutterEngine *engine) {
}]; }];
``` ```
### swift:
初始化
```swift
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
) -> Bool {
let router = PlatformRouterImp.init();
FlutterBoostPlugin.sharedInstance()?.startFlutter(with: router, onStart: { (engine) in
});
self.window = UIWindow.init(frame: UIScreen.main.bounds)
let viewController = ViewController.init()
let navi = UINavigationController.init(rootViewController: viewController)
self.window.rootViewController = navi
self.window.makeKeyAndVisible()
return true;//super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
```
为您的应用程序实现FLBPlatform协议方法。
```swift
class PlatformRouterImp: NSObject, FLBPlatform {
func open(_ url: String, urlParams: [AnyHashable : Any], exts: [AnyHashable : Any], completion: @escaping (Bool) -> Void) {
var animated = false;
if exts["animated"] != nil{
animated = exts["animated"] as! Bool;
}
let vc = FLBFlutterViewContainer.init();
vc.setName(url, params: urlParams);
self.navigationController().pushViewController(vc, animated: animated);
completion(true);
}
func present(_ url: String, urlParams: [AnyHashable : Any], exts: [AnyHashable : Any], completion: @escaping (Bool) -> Void) {
var animated = false;
if exts["animated"] != nil{
animated = exts["animated"] as! Bool;
}
let vc = FLBFlutterViewContainer.init();
vc.setName(url, params: urlParams);
navigationController().present(vc, animated: animated) {
completion(true);
};
}
func close(_ uid: String, result: [AnyHashable : Any], exts: [AnyHashable : Any], completion: @escaping (Bool) -> Void) {
var animated = false;
if exts["animated"] != nil{
animated = exts["animated"] as! Bool;
}
let presentedVC = self.navigationController().presentedViewController;
let vc = presentedVC as? FLBFlutterViewContainer;
if vc?.uniqueIDString() == uid {
vc?.dismiss(animated: animated, completion: {
completion(true);
});
}else{
self.navigationController().popViewController(animated: animated);
}
}
func navigationController() -> UINavigationController {
let delegate = UIApplication.shared.delegate as! AppDelegate
let navigationController = delegate.window?.rootViewController as! UINavigationController
return navigationController;
}
}
```
## Android代码集成。 ## Android代码集成。
在Application.onCreate()中初始化FlutterBoost 在Application.onCreate()中初始化FlutterBoost
```java ```java
public class MyApplication extends FlutterApplication { public class MyApplication extends Application {
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
FlutterBoostPlugin.init(new IPlatform() { INativeRouter router =new INativeRouter() {
@Override @Override
public Application getApplication() { public void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts) {
return MyApplication.this; String assembleUrl=Utils.assembleUrl(url,urlParams);
PageRouter.openPageByUrl(context,assembleUrl, urlParams);
} }
@Override };
public boolean isDebug() {
return true;
}
FlutterBoost.BoostLifecycleListener lifecycleListener= new FlutterBoost.BoostLifecycleListener() {
@Override @Override
public void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts) { public void onEngineCreated() {
PageRouter.openPageByUrl(context,url,urlParams,requestCode);
} }
@Override @Override
public IFlutterEngineProvider engineProvider() { public void onPluginsRegistered() {
return new BoostEngineProvider(){ MethodChannel mMethodChannel = new MethodChannel( FlutterBoost.instance().engineProvider().getDartExecutor(), "methodChannel");
@Override Log.e("MyApplication","MethodChannel create");
public BoostFlutterEngine createEngine(Context context) { TextPlatformViewPlugin.register(FlutterBoost.instance().getPluginRegistry().registrarFor("TextPlatformViewPlugin"));
return new BoostFlutterEngine(context, new DartExecutor.DartEntrypoint(
context.getResources().getAssets(),
FlutterMain.findAppBundlePath(context),
"main"),"/");
}
};
} }
@Override @Override
public int whenEngineStart() { public void onEngineDestroy() {
return ANY_ACTIVITY_CREATED;
} }
}); };
Platform platform= new FlutterBoost
.ConfigBuilder(this,router)
.isDebug(true)
.whenEngineStart(FlutterBoost.ConfigBuilder.ANY_ACTIVITY_CREATED)
.renderMode(FlutterView.RenderMode.texture)
.lifecycleListener(lifecycleListener)
.build();
FlutterBoost.instance().init(platform);
} }
}
``` ```
# 基本用法 # 基本用法
...@@ -236,40 +330,52 @@ public class MyApplication extends FlutterApplication { ...@@ -236,40 +330,52 @@ public class MyApplication extends FlutterApplication {
Android Android
```java ```java
public class FlutterPageActivity extends BoostFlutterActivity { public class PageRouter {
public final static Map<String, String> pageName = new HashMap<String, String>() {{
@Override
public String getContainerUrl() {
//specify the page name register in FlutterBoost
return "sample://firstPage";
}
@Override put("first", "first");
public Map getContainerUrlParams() { put("second", "second");
//params of the page put("tab", "tab");
Map<String,String> params = new HashMap<>();
params.put("key","value");
return params;
}
}
```
或者用Fragment put("sample://flutterPage", "flutterPage");
}};
```java public static final String NATIVE_PAGE_URL = "sample://nativePage";
public class FlutterFragment extends BoostFlutterFragment { public static final String FLUTTER_PAGE_URL = "sample://flutterPage";
public static final String FLUTTER_FRAGMENT_PAGE_URL = "sample://flutterFragmentPage";
@Override public static boolean openPageByUrl(Context context, String url, Map params) {
public String getContainerUrl() { return openPageByUrl(context, url, params, 0);
return "sample://firstPage";
} }
@Override public static boolean openPageByUrl(Context context, String url, Map params, int requestCode) {
public Map getContainerUrlParams() {
Map<String,String> params = new HashMap<>(); String path = url.split("\\?")[0];
params.put("key","value");
return params; Log.i("openPageByUrl",path);
try {
if (pageName.containsKey(path)) {
Intent intent = BoostFlutterActivity.withNewEngine().url(pageName.get(path)).params(params)
.backgroundMode(BoostFlutterActivity.BackgroundMode.opaque).build(context);
context.startActivity(intent);
} else if (url.startsWith(FLUTTER_FRAGMENT_PAGE_URL)) {
context.startActivity(new Intent(context, FlutterFragmentPageActivity.class));
return true;
} else if (url.startsWith(NATIVE_PAGE_URL)) {
context.startActivity(new Intent(context, NativePageActivity.class));
return true;
} else {
return false;
}
} catch (Throwable t) {
return false;
}
return false;
} }
} }
``` ```
......
...@@ -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 android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.Surface;
import android.view.View;
import com.idlefish.flutterboost.interfaces.IContainerRecord;
import com.idlefish.flutterboost.interfaces.IStateListener;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import io.flutter.app.FlutterPluginRegistry;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.editing.TextInputPlugin;
import io.flutter.plugin.platform.PlatformViewRegistry;
import io.flutter.view.FlutterMain;
import io.flutter.view.FlutterView;
import io.flutter.view.TextureRegistry;
public class BoostFlutterEngine extends FlutterEngine {
protected final Context mContext;
protected final BoostPluginRegistry mBoostPluginRegistry;
protected final DartExecutor.DartEntrypoint mEntrypoint;
protected final String mInitRoute;
private final FakeRender mFakeRender;
protected WeakReference<Activity> mCurrentActivityRef;
public BoostFlutterEngine(@NonNull Context context) {
this(context, null, null);
}
public BoostFlutterEngine(@NonNull Context context, DartExecutor.DartEntrypoint entrypoint, String initRoute) {
super(context);
mContext = context.getApplicationContext();
mBoostPluginRegistry = new BoostPluginRegistry(this, context);
if (entrypoint != null) {
mEntrypoint = entrypoint;
} else {
mEntrypoint = defaultDartEntrypoint(context);
}
if (initRoute != null) {
mInitRoute = initRoute;
} else {
mInitRoute = defaultInitialRoute(context);
}
FlutterJNI flutterJNI = null;
try {
Field field = FlutterEngine.class.getDeclaredField("flutterJNI");
field.setAccessible(true);
flutterJNI = (FlutterJNI) field.get(this);
} catch (Throwable t) {
try {
for(Field field:FlutterEngine.class.getDeclaredFields()) {
field.setAccessible(true);
Object o = field.get(this);
if(o instanceof FlutterJNI) {
flutterJNI = (FlutterJNI)o;
}
}
if(flutterJNI == null) {
throw new RuntimeException("FlutterJNI not found");
}
}catch (Throwable it){
Debuger.exception(it);
}
}
mFakeRender = new FakeRender(flutterJNI);
}
public void startRun(@Nullable Activity activity) {
mCurrentActivityRef = new WeakReference<>(activity);
if (!getDartExecutor().isExecutingDart()) {
Debuger.log("engine start running...");
getNavigationChannel().setInitialRoute(mInitRoute);
getDartExecutor().executeDartEntrypoint(mEntrypoint);
final IStateListener stateListener = FlutterBoost.sInstance.mStateListener;
if (stateListener != null) {
stateListener.onEngineStarted(this);
}
FlutterBoost.singleton().platform().registerPlugins(mBoostPluginRegistry);
if(activity != null) {
FlutterRenderer.ViewportMetrics metrics = new FlutterRenderer.ViewportMetrics();
metrics.devicePixelRatio = activity.getResources().getDisplayMetrics().density;
final View decor = activity.getWindow().getDecorView();
if(decor != null) {
metrics.width = decor.getWidth();
metrics.height = decor.getHeight();
}
if (metrics.width <= 0 || metrics.height <= 0) {
metrics.width = Utils.getMetricsWidth(activity);
metrics.height = Utils.getMetricsHeight(activity);
}
metrics.paddingTop = Utils.getStatusBarHeight(activity);
metrics.paddingRight = 0;
metrics.paddingBottom = 0;
metrics.paddingLeft = 0;
metrics.viewInsetTop = 0;
metrics.viewInsetRight = 0;
metrics.viewInsetBottom = 0;
metrics.viewInsetLeft = 0;
getRenderer().setViewportMetrics(metrics);
}
}
}
protected DartExecutor.DartEntrypoint defaultDartEntrypoint(Context context) {
return new DartExecutor.DartEntrypoint(
context.getResources().getAssets(),
FlutterMain.findAppBundlePath(context),
"main");
}
protected String defaultInitialRoute(Context context) {
return "/";
}
public BoostPluginRegistry getBoostPluginRegistry() {
return mBoostPluginRegistry;
}
public boolean isRunning() {
return getDartExecutor().isExecutingDart();
}
@NonNull
@Override
public FlutterRenderer getRenderer() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
boolean hit = false;
for (StackTraceElement st : stackTrace) {
if (st.getMethodName().equals("sendViewportMetricsToFlutter")) {
hit = true;
break;
}
}
if (hit) {
return mFakeRender;
} else {
return super.getRenderer();
}
}
public class BoostPluginRegistry extends FlutterPluginRegistry {
private final FlutterEngine mEngine;
public BoostPluginRegistry(FlutterEngine engine, Context context) {
super(engine, context);
mEngine = engine;
}
public PluginRegistry.Registrar registrarFor(String pluginKey) {
return new BoostRegistrar(mEngine, super.registrarFor(pluginKey));
}
}
public class BoostRegistrar implements PluginRegistry.Registrar {
private final PluginRegistry.Registrar mRegistrar;
private final FlutterEngine mEngine;
BoostRegistrar(FlutterEngine engine, PluginRegistry.Registrar registrar) {
mRegistrar = registrar;
mEngine = engine;
}
@Override
public Activity activity() {
Activity activity;
IContainerRecord record;
record = FlutterBoost.singleton().containerManager().getCurrentTopRecord();
if (record == null) {
record = FlutterBoost.singleton().containerManager().getLastGenerateRecord();
}
if (record == null) {
activity = FlutterBoost.singleton().currentActivity();
} else {
activity = record.getContainer().getContextActivity();
}
if (activity == null && mCurrentActivityRef != null) {
activity = mCurrentActivityRef.get();
}
if (activity == null) {
throw new RuntimeException("current has no valid Activity yet");
}
return activity;
}
@Override
public Context context() {
return mRegistrar.context();
}
@Override
public Context activeContext() {
return mRegistrar.activeContext();
}
@Override
public BinaryMessenger messenger() {
return mEngine.getDartExecutor();
}
@Override
public TextureRegistry textures() {
return mEngine.getRenderer();
}
@Override
public PlatformViewRegistry platformViewRegistry() {
return mRegistrar.platformViewRegistry();
}
@Override
public FlutterView view() {
throw new RuntimeException("should not use!!!");
}
@Override
public String lookupKeyForAsset(String s) {
return mRegistrar.lookupKeyForAsset(s);
}
@Override
public String lookupKeyForAsset(String s, String s1) {
return mRegistrar.lookupKeyForAsset(s, s1);
}
@Override
public PluginRegistry.Registrar publish(Object o) {
return mRegistrar.publish(o);
}
@Override
public PluginRegistry.Registrar addRequestPermissionsResultListener(PluginRegistry.RequestPermissionsResultListener requestPermissionsResultListener) {
return mRegistrar.addRequestPermissionsResultListener(requestPermissionsResultListener);
}
@Override
public PluginRegistry.Registrar addActivityResultListener(PluginRegistry.ActivityResultListener activityResultListener) {
return mRegistrar.addActivityResultListener(activityResultListener);
}
@Override
public PluginRegistry.Registrar addNewIntentListener(PluginRegistry.NewIntentListener newIntentListener) {
return mRegistrar.addNewIntentListener(newIntentListener);
}
@Override
public PluginRegistry.Registrar addUserLeaveHintListener(PluginRegistry.UserLeaveHintListener userLeaveHintListener) {
return mRegistrar.addUserLeaveHintListener(userLeaveHintListener);
}
@Override
public PluginRegistry.Registrar addViewDestroyListener(PluginRegistry.ViewDestroyListener viewDestroyListener) {
return mRegistrar.addViewDestroyListener(viewDestroyListener);
}
}
private boolean viewportMetricsEqual(FlutterRenderer.ViewportMetrics a, FlutterRenderer.ViewportMetrics b) {
return a != null && b != null &&
a.height == b.height &&
a.width == b.width &&
a.devicePixelRatio == b.devicePixelRatio &&
a.paddingBottom == b.paddingBottom &&
a.paddingLeft == b.paddingLeft &&
a.paddingRight == b.paddingRight &&
a.paddingTop == b.paddingTop &&
a.viewInsetLeft == b.viewInsetLeft &&
a.viewInsetRight == b.viewInsetRight &&
a.viewInsetTop == b.viewInsetTop &&
a.viewInsetBottom == b.viewInsetBottom;
}
class FakeRender extends FlutterRenderer {
private ViewportMetrics last;
public FakeRender(FlutterJNI flutterJNI) {
super(flutterJNI);
}
@Override
public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) {
if (viewportMetrics.width > 0 && viewportMetrics.height > 0 /*&& !viewportMetricsEqual(last, viewportMetrics)*/) {
last = viewportMetrics;
Debuger.log("setViewportMetrics w:" + viewportMetrics.width + " h:" + viewportMetrics.height);
super.setViewportMetrics(viewportMetrics);
}
}
@Override
public void attachToRenderSurface(@NonNull RenderSurface renderSurface) {
Debuger.exception("should never called!");
}
@Override
public void detachFromRenderSurface() {
Debuger.exception("should never called!");
}
@Override
public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
Debuger.exception("should never called!");
}
@Override
public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
Debuger.exception("should never called!");
}
@Override
public SurfaceTextureEntry createSurfaceTexture() {
Debuger.exception("should never called!");
return null;
}
@Override
public void surfaceCreated(Surface surface) {
Debuger.exception("should never called!");
}
@Override
public void surfaceChanged(int width, int height) {
Debuger.exception("should never called!");
}
@Override
public void surfaceDestroyed() {
Debuger.exception("should never called!");
}
@Override
public Bitmap getBitmap() {
Debuger.exception("should never called!");
return null;
}
@Override
public void dispatchPointerDataPacket(ByteBuffer buffer, int position) {
Debuger.exception("should never called!");
}
@Override
public boolean isSoftwareRenderingEnabled() {
Debuger.exception("should never called!");
return false;
}
@Override
public void setAccessibilityFeatures(int flags) {
Debuger.exception("should never called!");
}
@Override
public void setSemanticsEnabled(boolean enabled) {
Debuger.exception("should never called!");
}
@Override
public void dispatchSemanticsAction(int id, int action, ByteBuffer args, int argsPosition) {
Debuger.exception("should never called!");
}
}
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Alibaba Group
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.idlefish.flutterboost;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.view.ViewCompat;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityNodeProvider;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.idlefish.flutterboost.interfaces.IStateListener;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
import io.flutter.embedding.android.FlutterView;
import io.flutter.plugin.platform.PlatformPlugin;
import io.flutter.view.AccessibilityBridge;
public class BoostFlutterView extends FrameLayout {
private BoostFlutterEngine mFlutterEngine;
private XFlutterView mFlutterView;
private PlatformPlugin mPlatformPlugin;
private Bundle mArguments;
private RenderingProgressCoverCreator mRenderingProgressCoverCreator;
private View mRenderingProgressCover;
private final List<OnFirstFrameRenderedListener> mFirstFrameRenderedListeners = new LinkedList<>();
private boolean mEngineAttached = false;
private boolean mNeedSnapshotWhenDetach = false;
private SnapshotView mSnapshot;
private final io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener mOnFirstFrameRenderedListener =
new io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener() {
@Override
public void onFirstFrameRendered() {
Debuger.log("BoostFlutterView onFirstFrameRendered");
if(mRenderingProgressCover != null && mRenderingProgressCover.getParent() != null) {
((ViewGroup)mRenderingProgressCover.getParent()).removeView(mRenderingProgressCover);
}
if(mNeedSnapshotWhenDetach) {
mSnapshot.dismissSnapshot(BoostFlutterView.this);
}
final Object[] listeners = mFirstFrameRenderedListeners.toArray();
for (Object obj : listeners) {
((OnFirstFrameRenderedListener) obj).onFirstFrameRendered(BoostFlutterView.this);
}
}
};
private final ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
ViewCompat.requestApplyInsets(mFlutterView);
}
};
public BoostFlutterView(Context context, BoostFlutterEngine engine, Bundle args, RenderingProgressCoverCreator creator) {
super(context);
mFlutterEngine = engine;
mArguments = args;
mRenderingProgressCoverCreator = creator;
init();
}
private void init() {
if (mFlutterEngine == null) {
mFlutterEngine = createFlutterEngine(getContext());
}
if (mArguments == null) {
mArguments = new Bundle();
}
mPlatformPlugin = new PlatformPlugin((Activity) getContext(), mFlutterEngine.getPlatformChannel());
mFlutterView = new XFlutterView(getContext(), getRenderMode(), getTransparencyMode());
addView(mFlutterView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
mSnapshot = new SnapshotView(getContext());
if(mRenderingProgressCoverCreator != null) {
mRenderingProgressCover = mRenderingProgressCoverCreator
.createRenderingProgressCover(getContext());
}else{
mRenderingProgressCover = createRenderingProgressCorver();
}
if(mRenderingProgressCover != null) {
addView(mRenderingProgressCover, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
mFlutterView.addOnFirstFrameRenderedListener(mOnFirstFrameRenderedListener);
mFlutterEngine.startRun((Activity)getContext());
final IStateListener stateListener = FlutterBoost.sInstance.mStateListener;
if(stateListener != null) {
stateListener.onFlutterViewInited(mFlutterEngine,this);
}
checkAssert();
}
private void checkAssert(){
try {
Method method = FlutterView.class.getDeclaredMethod("sendViewportMetricsToFlutter");
if(method == null) {
throw new Exception("method: FlutterView.sendViewportMetricsToFlutter not found!");
}
}catch (Throwable t){
Debuger.exception(t);
}
}
protected View createRenderingProgressCorver(){
FrameLayout frameLayout = new FrameLayout(getContext());
frameLayout.setBackgroundColor(Color.WHITE);
LinearLayout linearLayout = new LinearLayout(getContext());
linearLayout.setOrientation(LinearLayout.VERTICAL);
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.gravity = Gravity.CENTER;
frameLayout.addView(linearLayout,layoutParams);
ProgressBar progressBar = new ProgressBar(getContext());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER_HORIZONTAL;
linearLayout.addView(progressBar,params);
TextView textView = new TextView(getContext());
params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER_HORIZONTAL;
textView.setText("Frame Rendering...");
linearLayout.addView(textView,params);
return frameLayout;
}
protected BoostFlutterEngine createFlutterEngine(Context context) {
return FlutterBoost.singleton().engineProvider().provideEngine(context);
}
public void addFirstFrameRendered(OnFirstFrameRenderedListener listener) {
mFirstFrameRenderedListeners.add(listener);
}
public void removeFirstFrameRendered(OnFirstFrameRenderedListener listener) {
mFirstFrameRenderedListeners.remove(listener);
}
protected FlutterView.RenderMode getRenderMode() {
String renderModeName = mArguments.getString("flutterview_render_mode", FlutterView.RenderMode.surface.name());
return FlutterView.RenderMode.valueOf(renderModeName);
}
protected FlutterView.TransparencyMode getTransparencyMode() {
String transparencyModeName = mArguments.getString("flutterview_transparency_mode", FlutterView.TransparencyMode.transparent.name());
return FlutterView.TransparencyMode.valueOf(transparencyModeName);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
ViewCompat.requestApplyInsets(this);
getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalLayoutListener);
onDetach();
flutterEngine.getPluginRegistry().getPlatformViewsController().onFlutterViewDestroyed();
}
public BoostFlutterEngine getEngine(){
return mFlutterEngine;
}
public void onResume() {
Debuger.log("BoostFlutterView onResume");
// mFlutterEngine.getLifecycleChannel().appIsResumed();
}
// public void onPostResume() {
// Debuger.log("BoostFlutterView onPostResume");
// mPlatformPlugin.onPostResume();
// }
public void onPause() {
Debuger.log("BoostFlutterView onPause");
// mFlutterEngine.getLifecycleChannel().appIsInactive();
}
public void onStop() {
Debuger.log("BoostFlutterView onStop");
// mFlutterEngine.getLifecycleChannel().appIsPaused();
}
public void onAttach() {
Debuger.log("BoostFlutterView onAttach");
final IStateListener stateListener = FlutterBoost.sInstance.mStateListener;
if(stateListener != null) {
stateListener.beforeEngineAttach(mFlutterEngine,this);
}
mFlutterView.attachToFlutterEngine(mFlutterEngine);
mEngineAttached = true;
if(stateListener != null) {
stateListener.afterEngineAttached(mFlutterEngine,this);
}
}
public void toggleSnapshot() {
mSnapshot.toggleSnapshot(this);
}
public void toggleAttach() {
if(mEngineAttached) {
onDetach();
}else{
onAttach();
}
}
public void onDetach() {
Debuger.log("BoostFlutterView onDetach");
if(mNeedSnapshotWhenDetach) {
mSnapshot.showSnapshot(BoostFlutterView.this);
}
final IStateListener stateListener = FlutterBoost.sInstance.mStateListener;
if(stateListener != null) {
stateListener.beforeEngineDetach(mFlutterEngine,this);
}
mFlutterView.detachFromFlutterEngine();
mEngineAttached = false;
if(stateListener != null) {
stateListener.afterEngineDetached(mFlutterEngine,this);
}
}
public void onDestroy() {
Debuger.log("BoostFlutterView onDestroy");
mFlutterView.removeOnFirstFrameRenderedListener(mOnFirstFrameRenderedListener);
mFlutterView.release();
}
//混合栈的返回和原来Flutter的返回逻辑不同
public void onBackPressed() {
// Debuger.log("onBackPressed()");
// if (mFlutterEngine != null) {
// mFlutterEngine.getNavigationChannel().popRoute();
// } else {
// Debuger.log("Invoked onBackPressed() before BoostFlutterView was attached to an Activity.");
// }
}
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (mFlutterEngine != null) {
mFlutterEngine.getPluginRegistry().onRequestPermissionsResult(requestCode, permissions, grantResults);
} else {
Debuger.log("onRequestPermissionResult() invoked before BoostFlutterView was attached to an Activity.");
}
}
public void onNewIntent(Intent intent) {
if (mFlutterEngine != null) {
mFlutterEngine.getPluginRegistry().onNewIntent(intent);
} else {
Debuger.log("onNewIntent() invoked before BoostFlutterView was attached to an Activity.");
}
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mFlutterEngine != null) {
mFlutterEngine.getPluginRegistry().onActivityResult(requestCode, resultCode, data);
} else {
Debuger.log("onActivityResult() invoked before BoostFlutterView was attached to an Activity.");
}
}
public void onUserLeaveHint() {
if (mFlutterEngine != null) {
mFlutterEngine.getPluginRegistry().onUserLeaveHint();
} else {
Debuger.log("onUserLeaveHint() invoked before BoostFlutterView was attached to an Activity.");
}
}
public void onTrimMemory(int level) {
if (mFlutterEngine != null) {
if (level == 10) {
mFlutterEngine.getSystemChannel().sendMemoryPressureWarning();
}
} else {
Debuger.log("onTrimMemory() invoked before BoostFlutterView was attached to an Activity.");
}
}
public void onLowMemory() {
mFlutterEngine.getSystemChannel().sendMemoryPressureWarning();
}
public static class Builder {
private Context context;
private BoostFlutterEngine engine;
private FlutterView.RenderMode renderMode;
private FlutterView.TransparencyMode transparencyMode;
private RenderingProgressCoverCreator renderingProgressCoverCreator;
public Builder(Context ctx) {
this.context = ctx;
renderMode = FlutterView.RenderMode.surface;
transparencyMode = FlutterView.TransparencyMode.transparent;
}
public Builder flutterEngine(BoostFlutterEngine engine) {
this.engine = engine;
return this;
}
public Builder renderMode(FlutterView.RenderMode renderMode) {
this.renderMode = renderMode;
return this;
}
public Builder renderingProgressCoverCreator(RenderingProgressCoverCreator creator) {
this.renderingProgressCoverCreator = creator;
return this;
}
public Builder transparencyMode(FlutterView.TransparencyMode transparencyMode) {
this.transparencyMode = transparencyMode;
return this;
}
public BoostFlutterView build() {
Bundle args = new Bundle();
args.putString("flutterview_render_mode", renderMode != null ? renderMode.name() : FlutterView.RenderMode.surface.name());
args.putString("flutterview_transparency_mode", transparencyMode != null ? transparencyMode.name() : FlutterView.TransparencyMode.transparent.name());
return new BoostFlutterView(context, engine, args,renderingProgressCoverCreator);
}
}
public interface OnFirstFrameRenderedListener {
void onFirstFrameRendered(BoostFlutterView view);
}
public interface RenderingProgressCoverCreator {
View createRenderingProgressCover(Context context);
}
}
package com.idlefish.flutterboost;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.PluginRegistry;
import java.util.*;
public class BoostPluginRegistry implements PluginRegistry {
private static final String TAG = "ShimPluginRegistry";
private final FlutterEngine flutterEngine;
private final Map<String, Object> pluginMap = new HashMap();
private final BoostRegistrarAggregate shimRegistrarAggregate;
public BoostRegistrarAggregate getRegistrarAggregate() {
return shimRegistrarAggregate;
}
public BoostPluginRegistry(FlutterEngine flutterEngine) {
this.flutterEngine = flutterEngine;
this.shimRegistrarAggregate = new BoostRegistrarAggregate();
this.flutterEngine.getPlugins().add(this.shimRegistrarAggregate);
}
public Registrar registrarFor(String pluginKey) {
Log.v("ShimPluginRegistry", "Creating plugin Registrar for '" + pluginKey + "'");
if (this.pluginMap.containsKey(pluginKey)) {
throw new IllegalStateException("Plugin key " + pluginKey + " is already in use");
} else {
this.pluginMap.put(pluginKey, (Object) null);
BoostRegistrar registrar = new BoostRegistrar(pluginKey, this.pluginMap);
this.shimRegistrarAggregate.addPlugin(registrar);
return registrar;
}
}
public boolean hasPlugin(String pluginKey) {
return this.pluginMap.containsKey(pluginKey);
}
public Object valuePublishedByPlugin(String pluginKey) {
return this.pluginMap.get(pluginKey);
}
public static class BoostRegistrarAggregate implements FlutterPlugin, ActivityAware {
private final Set<BoostRegistrar> shimRegistrars;
private FlutterPluginBinding flutterPluginBinding;
private ActivityPluginBinding activityPluginBinding;
public ActivityPluginBinding getActivityPluginBinding() {
return activityPluginBinding;
}
private BoostRegistrarAggregate() {
this.shimRegistrars = new HashSet();
}
public void addPlugin(BoostRegistrar shimRegistrar) {
this.shimRegistrars.add(shimRegistrar);
if (this.flutterPluginBinding != null) {
shimRegistrar.onAttachedToEngine(this.flutterPluginBinding);
}
if (this.activityPluginBinding != null) {
shimRegistrar.onAttachedToActivity(this.activityPluginBinding);
}
}
public void onAttachedToEngine(FlutterPluginBinding binding) {
this.flutterPluginBinding = binding;
Iterator var2 = this.shimRegistrars.iterator();
while (var2.hasNext()) {
BoostRegistrar shimRegistrar = (BoostRegistrar) var2.next();
shimRegistrar.onAttachedToEngine(binding);
}
}
public void onDetachedFromEngine(FlutterPluginBinding binding) {
Iterator var2 = this.shimRegistrars.iterator();
while (var2.hasNext()) {
BoostRegistrar shimRegistrar = (BoostRegistrar) var2.next();
shimRegistrar.onDetachedFromEngine(binding);
}
this.flutterPluginBinding = null;
}
public void onAttachedToActivity(ActivityPluginBinding binding) {
this.activityPluginBinding = binding;
Iterator var2 = this.shimRegistrars.iterator();
while (var2.hasNext()) {
BoostRegistrar shimRegistrar = (BoostRegistrar) var2.next();
shimRegistrar.onAttachedToActivity(binding);
}
}
public void onDetachedFromActivityForConfigChanges() {
Iterator var1 = this.shimRegistrars.iterator();
while (var1.hasNext()) {
BoostRegistrar shimRegistrar = (BoostRegistrar) var1.next();
shimRegistrar.onDetachedFromActivity();
}
this.activityPluginBinding = null;
}
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
Iterator var2 = this.shimRegistrars.iterator();
while (var2.hasNext()) {
BoostRegistrar shimRegistrar = (BoostRegistrar) var2.next();
shimRegistrar.onReattachedToActivityForConfigChanges(binding);
}
}
public void onDetachedFromActivity() {
Iterator var1 = this.shimRegistrars.iterator();
while (var1.hasNext()) {
BoostRegistrar shimRegistrar = (BoostRegistrar) var1.next();
shimRegistrar.onDetachedFromActivity();
}
}
}
}
package com.idlefish.flutterboost;
import android.app.Activity;
import android.content.Context;
import android.support.annotation.NonNull;
import io.flutter.Log;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.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;
} }
......
/*
* 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; package com.idlefish.flutterboost;
import android.app.Activity; import android.app.Activity;
import android.app.Application; import android.app.Application;
import android.content.Intent; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.NonNull;
import com.idlefish.flutterboost.interfaces.*;
import com.idlefish.flutterboost.interfaces.IContainerManager; import io.flutter.embedding.android.FlutterView;
import com.idlefish.flutterboost.interfaces.IContainerRecord; import io.flutter.embedding.engine.FlutterEngine;
import com.idlefish.flutterboost.interfaces.IFlutterEngineProvider; import io.flutter.embedding.engine.FlutterShellArgs;
import com.idlefish.flutterboost.interfaces.IFlutterViewContainer; import io.flutter.embedding.engine.dart.DartExecutor;
import com.idlefish.flutterboost.interfaces.IPlatform; import io.flutter.plugin.common.PluginRegistry;
import com.idlefish.flutterboost.interfaces.IStateListener; import io.flutter.view.FlutterMain;
import java.lang.reflect.Method;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class FlutterBoost { public class FlutterBoost {
private Platform mPlatform;
private FlutterViewContainerManager mManager;
private FlutterEngine mEngine;
private Activity mCurrentActiveActivity;
private PluginRegistry mRegistry;
static FlutterBoost sInstance = null; static FlutterBoost sInstance = null;
public static synchronized void init(IPlatform platform) { private long FlutterPostFrameCallTime = 0;
if (sInstance == null) {
sInstance = new FlutterBoost(platform);
}
if (platform.whenEngineStart() == IPlatform.IMMEDIATELY) { public long getFlutterPostFrameCallTime() {
sInstance.mEngineProvider return FlutterPostFrameCallTime;
.provideEngine(platform.getApplication()) }
.startRun(null);
} public void setFlutterPostFrameCallTime(long FlutterPostFrameCallTime) {
this.FlutterPostFrameCallTime = FlutterPostFrameCallTime;
} }
public static FlutterBoost singleton() { public static FlutterBoost instance() {
if (sInstance == null) { if (sInstance == null) {
throw new RuntimeException("FlutterBoost not init yet"); sInstance = new FlutterBoost();
} }
return sInstance; return sInstance;
} }
private final IPlatform mPlatform; public void init(Platform platform) {
private final FlutterViewContainerManager mManager;
private final IFlutterEngineProvider mEngineProvider;
IStateListener mStateListener;
Activity mCurrentActiveActivity;
private FlutterBoost(IPlatform platform) {
mPlatform = platform; mPlatform = platform;
mManager = new FlutterViewContainerManager(); mManager = new FlutterViewContainerManager();
IFlutterEngineProvider provider = platform.engineProvider(); platform.getApplication().registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
if(provider == null) {
provider = new BoostEngineProvider();
}
mEngineProvider = provider;
platform.getApplication().registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks());
BoostChannel.addActionAfterRegistered(new BoostChannel.ActionAfterRegistered() {
@Override @Override
public void onChannelRegistered(BoostChannel channel) { public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
channel.addMethodCallHandler(new BoostMethodHandler()); mCurrentActiveActivity = activity;
if (mPlatform.whenEngineStart() == ConfigBuilder.ANY_ACTIVITY_CREATED) {
doInitialFlutter();
}
} }
});
}
public IFlutterEngineProvider engineProvider() { @Override
return sInstance.mEngineProvider; public void onActivityStarted(Activity activity) {
} if (mCurrentActiveActivity == null) {
Debuger.log("Application entry foreground");
if (createEngine() != null) {
HashMap<String, String> map = new HashMap<>();
map.put("type", "foreground");
channel().sendEvent("lifecycle", map);
}
}
mCurrentActiveActivity = activity;
}
public IContainerManager containerManager() { @Override
return sInstance.mManager; public void onActivityResumed(Activity activity) {
} mCurrentActiveActivity = activity;
}
public IPlatform platform() { @Override
return sInstance.mPlatform; public void onActivityPaused(Activity activity) {
}
public BoostChannel channel() { }
return BoostChannel.singleton();
}
public Activity currentActivity() { @Override
return sInstance.mCurrentActiveActivity; public void onActivityStopped(Activity activity) {
} if (mCurrentActiveActivity == activity) {
Debuger.log("Application entry background");
if (createEngine() != null) {
HashMap<String, String> map = new HashMap<>();
map.put("type", "background");
channel().sendEvent("lifecycle", map);
}
mCurrentActiveActivity = null;
}
}
public IFlutterViewContainer findContainerById(String id) { @Override
return mManager.findContainerById(id); public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
public void setStateListener(@Nullable IStateListener listener){ }
mStateListener = listener;
}
class ActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks { @Override
@Override public void onActivityDestroyed(Activity activity) {
public void onActivityCreated(Activity activity, Bundle savedInstanceState) { if (mCurrentActiveActivity == activity) {
if (platform().whenEngineStart() == IPlatform.ANY_ACTIVITY_CREATED) { Debuger.log("Application entry background");
sInstance.mEngineProvider
.provideEngine(activity) if (createEngine() != null) {
.startRun(activity); HashMap<String, String> map = new HashMap<>();
map.put("type", "background");
channel().sendEvent("lifecycle", map);
}
mCurrentActiveActivity = null;
}
} }
});
if (mPlatform.whenEngineStart() == ConfigBuilder.IMMEDIATELY) {
doInitialFlutter();
} }
@Override
public void onActivityStarted(Activity activity) {
if (mCurrentActiveActivity == null) {
Debuger.log("Application entry foreground");
if (mEngineProvider.tryGetEngine() != null) { }
HashMap<String, String> map = new HashMap<>();
map.put("type", "foreground"); public void doInitialFlutter() {
channel().sendEvent("lifecycle",map);
}
} if (mEngine != null) return;
mCurrentActiveActivity = activity;
FlutterEngine flutterEngine = createEngine();
if (mPlatform.lifecycleListener != null) {
mPlatform.lifecycleListener.onEngineCreated();
}
if (flutterEngine.getDartExecutor().isExecutingDart()) {
return;
} }
@Override if (mPlatform.initialRoute() != null) {
public void onActivityResumed(Activity activity) { flutterEngine.getNavigationChannel().setInitialRoute(mPlatform.initialRoute());
mCurrentActiveActivity = activity;
} }
DartExecutor.DartEntrypoint entrypoint = new DartExecutor.DartEntrypoint(
FlutterMain.findAppBundlePath(),
"main"
);
flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
mRegistry = new BoostPluginRegistry(createEngine());
mPlatform.registerPlugins(mRegistry);
}
public static class ConfigBuilder {
public static final String DEFAULT_DART_ENTRYPOINT = "main";
public static final String DEFAULT_INITIAL_ROUTE = "/";
public static int IMMEDIATELY = 0; //立即启动引擎
public static int ANY_ACTIVITY_CREATED = 1; //当有任何Activity创建时,启动引擎
public static int FLUTTER_ACTIVITY_CREATED = 2; //当有flutterActivity创建时,启动引擎
public static int APP_EXit = 0; //所有flutter Activity destory 时,销毁engine
public static int All_FLUTTER_ACTIVITY_DESTROY = 1; //所有flutter Activity destory 时,销毁engine
private String dartEntrypoint = DEFAULT_DART_ENTRYPOINT;
private String initialRoute = DEFAULT_INITIAL_ROUTE;
private int whenEngineStart = ANY_ACTIVITY_CREATED;
private int whenEngineDestory = APP_EXit;
private boolean isDebug = false;
private FlutterView.RenderMode renderMode = FlutterView.RenderMode.texture;
@Override private Application mApp;
public void onActivityPaused(Activity activity) {
private INativeRouter router = null;
private BoostLifecycleListener lifecycleListener;
private BoostPluginsRegister boostPluginsRegister;
public ConfigBuilder(Application app, INativeRouter router) {
this.router = router;
this.mApp = app;
} }
@Override public ConfigBuilder renderMode(FlutterView.RenderMode renderMode) {
public void onActivityStopped(Activity activity) { this.renderMode = renderMode;
if (mCurrentActiveActivity == activity) { return this;
Debuger.log("Application entry background"); }
if (mEngineProvider.tryGetEngine() != null) { public ConfigBuilder dartEntrypoint(@NonNull String dartEntrypoint) {
HashMap<String, String> map = new HashMap<>(); this.dartEntrypoint = dartEntrypoint;
map.put("type", "background"); return this;
channel().sendEvent("lifecycle",map);
}
mCurrentActiveActivity = null;
}
} }
@Override public ConfigBuilder initialRoute(@NonNull String initialRoute) {
public void onActivitySaveInstanceState(Activity activity, Bundle outState) { this.initialRoute = initialRoute;
return this;
}
public ConfigBuilder isDebug(boolean isDebug) {
this.isDebug = isDebug;
return this;
} }
@Override public ConfigBuilder whenEngineStart(int whenEngineStart) {
public void onActivityDestroyed(Activity activity) { this.whenEngineStart = whenEngineStart;
if (mCurrentActiveActivity == activity) { return this;
Debuger.log("Application entry background"); }
if (mEngineProvider.tryGetEngine() != null) { public ConfigBuilder whenEngineDestory(int whenEngineDestory) {
HashMap<String, String> map = new HashMap<>(); this.whenEngineDestory = whenEngineDestory;
map.put("type", "background"); return this;
channel().sendEvent("lifecycle",map);
}
mCurrentActiveActivity = null;
}
} }
}
class BoostMethodHandler implements MethodChannel.MethodCallHandler { public ConfigBuilder lifecycleListener(BoostLifecycleListener lifecycleListener) {
this.lifecycleListener = lifecycleListener;
return this;
}
public ConfigBuilder pluginsRegister(BoostPluginsRegister boostPluginsRegister) {
this.boostPluginsRegister = boostPluginsRegister;
return this;
}
public Platform build() {
@Override Platform platform = new Platform() {
public void onMethodCall(MethodCall methodCall, final MethodChannel.Result result) {
switch (methodCall.method) {
case "pageOnStart":
{
Map<String, Object> pageInfo = new HashMap<>();
try { public Application getApplication() {
IContainerRecord record = mManager.getCurrentTopRecord(); return ConfigBuilder.this.mApp;
}
if (record == null) { public boolean isDebug() {
record = mManager.getLastGenerateRecord();
}
if(record != null) { return ConfigBuilder.this.isDebug;
pageInfo.put("name", record.getContainer().getContainerUrl()); }
pageInfo.put("params", record.getContainer().getContainerUrlParams());
pageInfo.put("uniqueId", record.uniqueId());
}
result.success(pageInfo); @Override
} catch (Throwable t) { public String initialRoute() {
result.error("no flutter page found!",t.getMessage(),t); return ConfigBuilder.this.initialRoute;
}
} }
break;
case "openPage": public void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts) {
{ router.openContainer(context, url, urlParams, requestCode, exts);
try {
Map<String,Object> params = methodCall.argument("urlParams");
Map<String,Object> exts = methodCall.argument("exts");
String url = methodCall.argument("url");
mManager.openContainer(url, params, exts, new FlutterViewContainerManager.OnResult() {
@Override
public void onResult(Map<String, Object> rlt) {
if (result != null) {
result.success(rlt);
}
}
});
}catch (Throwable t){
result.error("open page error",t.getMessage(),t);
}
} }
break;
case "closePage":
{ public int whenEngineStart() {
try { return ConfigBuilder.this.whenEngineStart;
String uniqueId = methodCall.argument("uniqueId");
Map<String,Object> resultData = methodCall.argument("result");
Map<String,Object> exts = methodCall.argument("exts");
mManager.closeContainer(uniqueId, resultData,exts);
result.success(true);
}catch (Throwable t){
result.error("close page error",t.getMessage(),t);
}
} }
break;
case "onShownContainerChanged": @Override
{ public int whenEngineDestroy() {
try { return ConfigBuilder.this.whenEngineDestory;
String newId = methodCall.argument("newName");
String oldId = methodCall.argument("oldName");
mManager.onShownContainerChanged(newId,oldId);
result.success(true);
}catch (Throwable t){
result.error("onShownContainerChanged",t.getMessage(),t);
}
} }
break;
default: public FlutterView.RenderMode renderMode() {
{ return ConfigBuilder.this.renderMode;
result.notImplemented();
} }
} };
platform.lifecycleListener = this.lifecycleListener;
platform.pluginsRegister=this.boostPluginsRegister;
return platform;
}
}
public IContainerManager containerManager() {
return sInstance.mManager;
}
public Platform platform() {
return sInstance.mPlatform;
}
public FlutterBoostPlugin channel() {
return FlutterBoostPlugin.singleton();
}
public Activity currentActivity() {
return sInstance.mCurrentActiveActivity;
}
public IFlutterViewContainer findContainerById(String id) {
return mManager.findContainerById(id);
}
public PluginRegistry getPluginRegistry() {
return mRegistry;
}
private FlutterEngine createEngine() {
if (mEngine == null) {
FlutterMain.startInitialization(mPlatform.getApplication());
FlutterShellArgs flutterShellArgs = new FlutterShellArgs(new String[0]);
FlutterMain.ensureInitializationComplete(
mPlatform.getApplication().getApplicationContext(), flutterShellArgs.toArray());
mEngine = new FlutterEngine(mPlatform.getApplication().getApplicationContext());
} }
return mEngine;
}
public FlutterEngine engineProvider() {
return mEngine;
} }
public static void setBoostResult(Activity activity, HashMap result) {
Intent intent = new Intent(); public void boostDestroy() {
if (result != null) { if (mEngine != null) {
intent.putExtra(IFlutterViewContainer.RESULT_KEY, result); mEngine.destroy();
}
if (mPlatform.lifecycleListener != null) {
mPlatform.lifecycleListener.onEngineDestroy();
} }
activity.setResult(Activity.RESULT_OK, intent); mEngine = null;
mRegistry = null;
mCurrentActiveActivity = null;
} }
}
public interface BoostLifecycleListener {
void onEngineCreated();
void onPluginsRegistered();
void onEngineDestroy();
}
public interface BoostPluginsRegister {
void registerPlugins(PluginRegistry mRegistry);
}
}
...@@ -2,64 +2,54 @@ package com.idlefish.flutterboost; ...@@ -2,64 +2,54 @@ package com.idlefish.flutterboost;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.idlefish.flutterboost.interfaces.IStateListener; import com.idlefish.flutterboost.interfaces.IContainerRecord;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashMap; import java.util.*;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.PluginRegistry;
public class BoostChannel { public class FlutterBoostPlugin {
private static BoostChannel sInstance; private static FlutterBoostPlugin sInstance;
private final MethodChannel mMethodChannel; private final MethodChannel mMethodChannel;
private final Set<MethodChannel.MethodCallHandler> mMethodCallHandlers = new HashSet<>(); private final Set<MethodChannel.MethodCallHandler> mMethodCallHandlers = new HashSet<>();
private final Map<String,Set<EventListener>> mEventListeners = new HashMap<>(); private final Map<String, Set<EventListener>> mEventListeners = new HashMap<>();
private static final Set<ActionAfterRegistered> sActions = new HashSet<>(); private static final Set<ActionAfterRegistered> sActions = new HashSet<>();
public static BoostChannel singleton() { public static FlutterBoostPlugin singleton() {
if (sInstance == null) { if (sInstance == null) {
throw new RuntimeException("BoostChannel not register yet"); throw new RuntimeException("FlutterBoostPlugin not register yet");
} }
return sInstance; return sInstance;
} }
public static void addActionAfterRegistered(ActionAfterRegistered action) { public static void addActionAfterRegistered(ActionAfterRegistered action) {
if(action == null) return; if (action == null) return;
if(sInstance == null) { if (sInstance == null) {
sActions.add(action); sActions.add(action);
}else{ } else {
action.onChannelRegistered(sInstance); action.onChannelRegistered(sInstance);
} }
} }
public static void registerWith(PluginRegistry.Registrar registrar) { public static void registerWith(PluginRegistry.Registrar registrar) {
sInstance = new BoostChannel(registrar); sInstance = new FlutterBoostPlugin(registrar);
for(ActionAfterRegistered a : sActions) { for (ActionAfterRegistered a : sActions) {
a.onChannelRegistered(sInstance); a.onChannelRegistered(sInstance);
} }
if(FlutterBoost.sInstance != null) {
final IStateListener stateListener = FlutterBoost.sInstance.mStateListener;
if (stateListener != null) {
stateListener.onChannelRegistered(registrar, sInstance);
}
}
sActions.clear(); sActions.clear();
} }
private BoostChannel(PluginRegistry.Registrar registrar){ private FlutterBoostPlugin(PluginRegistry.Registrar registrar) {
mMethodChannel = new MethodChannel(registrar.messenger(), "flutter_boost"); mMethodChannel = new MethodChannel(registrar.messenger(), "flutter_boost");
mMethodChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() { mMethodChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
...@@ -78,26 +68,29 @@ public class BoostChannel { ...@@ -78,26 +68,29 @@ public class BoostChannel {
} }
} }
if(listeners != null) { if (listeners != null) {
for(Object o:listeners) { for (Object o : listeners) {
((EventListener)o).onEvent(name,args); ((EventListener) o).onEvent(name, args);
} }
} }
}else{ } else {
Object[] handlers; Object[] handlers;
synchronized (mMethodCallHandlers) { synchronized (mMethodCallHandlers) {
handlers = mMethodCallHandlers.toArray(); handlers = mMethodCallHandlers.toArray();
} }
for(Object o:handlers) { for (Object o : handlers) {
((MethodChannel.MethodCallHandler)o).onMethodCall(methodCall,result); ((MethodChannel.MethodCallHandler) o).onMethodCall(methodCall, result);
} }
} }
} }
}); });
addMethodCallHandler(new BoostMethodHandler());
} }
public void invokeMethodUnsafe(final String name,Serializable args){ public void invokeMethodUnsafe(final String name, Serializable args) {
invokeMethod(name, args, new MethodChannel.Result() { invokeMethod(name, args, new MethodChannel.Result() {
@Override @Override
public void success(@Nullable Object o) { public void success(@Nullable Object o) {
...@@ -106,17 +99,17 @@ public class BoostChannel { ...@@ -106,17 +99,17 @@ public class BoostChannel {
@Override @Override
public void error(String s, @Nullable String s1, @Nullable Object o) { public void error(String s, @Nullable String s1, @Nullable Object o) {
Debuger.log("invoke method "+name+" error:"+s+" | "+s1); Debuger.log("invoke method " + name + " error:" + s + " | " + s1);
} }
@Override @Override
public void notImplemented() { public void notImplemented() {
Debuger.log("invoke method "+name+" notImplemented"); Debuger.log("invoke method " + name + " notImplemented");
} }
}); });
} }
public void invokeMethod(final String name,Serializable args){ public void invokeMethod(final String name, Serializable args) {
invokeMethod(name, args, new MethodChannel.Result() { invokeMethod(name, args, new MethodChannel.Result() {
@Override @Override
public void success(@Nullable Object o) { public void success(@Nullable Object o) {
...@@ -125,18 +118,18 @@ public class BoostChannel { ...@@ -125,18 +118,18 @@ public class BoostChannel {
@Override @Override
public void error(String s, @Nullable String s1, @Nullable Object o) { public void error(String s, @Nullable String s1, @Nullable Object o) {
Debuger.exception("invoke method "+name+" error:"+s+" | "+s1); Debuger.exception("invoke method " + name + " error:" + s + " | " + s1);
} }
@Override @Override
public void notImplemented() { public void notImplemented() {
Debuger.exception("invoke method "+name+" notImplemented"); Debuger.exception("invoke method " + name + " notImplemented");
} }
}); });
} }
public void invokeMethod(final String name,Serializable args,MethodChannel.Result result){ public void invokeMethod(final String name, Serializable args, MethodChannel.Result result) {
if("__event__".equals(name)) { if ("__event__".equals(name)) {
Debuger.exception("method name should not be __event__"); Debuger.exception("method name should not be __event__");
} }
...@@ -144,7 +137,7 @@ public class BoostChannel { ...@@ -144,7 +137,7 @@ public class BoostChannel {
} }
public void addMethodCallHandler(MethodChannel.MethodCallHandler handler) { public void addMethodCallHandler(MethodChannel.MethodCallHandler handler) {
synchronized (mMethodCallHandlers){ synchronized (mMethodCallHandlers) {
mMethodCallHandlers.add(handler); mMethodCallHandlers.add(handler);
} }
} }
...@@ -156,29 +149,29 @@ public class BoostChannel { ...@@ -156,29 +149,29 @@ public class BoostChannel {
} }
public void addEventListener(String name, EventListener listener) { public void addEventListener(String name, EventListener listener) {
synchronized (mEventListeners){ synchronized (mEventListeners) {
Set<EventListener> set = mEventListeners.get(name); Set<EventListener> set = mEventListeners.get(name);
if(set == null) { if (set == null) {
set = new HashSet<>(); set = new HashSet<>();
} }
set.add(listener); set.add(listener);
mEventListeners.put(name,set); mEventListeners.put(name, set);
} }
} }
public void removeEventListener(EventListener listener) { public void removeEventListener(EventListener listener) {
synchronized (mEventListeners) { synchronized (mEventListeners) {
for(Set<EventListener> set:mEventListeners.values()) { for (Set<EventListener> set : mEventListeners.values()) {
set.remove(listener); set.remove(listener);
} }
} }
} }
public void sendEvent(String name,Map args){ public void sendEvent(String name, Map args) {
Map event = new HashMap(); Map event = new HashMap();
event.put("name",name); event.put("name", name);
event.put("arguments",args); event.put("arguments", args);
mMethodChannel.invokeMethod("__event__",event); mMethodChannel.invokeMethod("__event__", event);
} }
public interface EventListener { public interface EventListener {
...@@ -186,6 +179,90 @@ public class BoostChannel { ...@@ -186,6 +179,90 @@ public class BoostChannel {
} }
public interface ActionAfterRegistered { public interface ActionAfterRegistered {
void onChannelRegistered(BoostChannel channel); void onChannelRegistered(FlutterBoostPlugin channel);
}
class BoostMethodHandler implements MethodChannel.MethodCallHandler {
@Override
public void onMethodCall(MethodCall methodCall, final MethodChannel.Result result) {
FlutterViewContainerManager mManager = (FlutterViewContainerManager) FlutterBoost.instance().containerManager();
switch (methodCall.method) {
case "pageOnStart": {
Map<String, Object> pageInfo = new HashMap<>();
try {
IContainerRecord record = mManager.getCurrentTopRecord();
if (record == null) {
record = mManager.getLastGenerateRecord();
}
if (record != null) {
pageInfo.put("name", record.getContainer().getContainerUrl());
pageInfo.put("params", record.getContainer().getContainerUrlParams());
pageInfo.put("uniqueId", record.uniqueId());
}
result.success(pageInfo);
FlutterBoost.instance().setFlutterPostFrameCallTime(new Date().getTime());
} catch (Throwable t) {
result.error("no flutter page found!", t.getMessage(), t);
}
}
break;
case "openPage": {
try {
Map<String, Object> params = methodCall.argument("urlParams");
Map<String, Object> exts = methodCall.argument("exts");
String url = methodCall.argument("url");
mManager.openContainer(url, params, exts, new FlutterViewContainerManager.OnResult() {
@Override
public void onResult(Map<String, Object> rlt) {
if (result != null) {
result.success(rlt);
}
}
});
} catch (Throwable t) {
result.error("open page error", t.getMessage(), t);
}
}
break;
case "closePage": {
try {
String uniqueId = methodCall.argument("uniqueId");
Map<String, Object> resultData = methodCall.argument("result");
Map<String, Object> exts = methodCall.argument("exts");
mManager.closeContainer(uniqueId, resultData, exts);
result.success(true);
} catch (Throwable t) {
result.error("close page error", t.getMessage(), t);
}
}
break;
case "onShownContainerChanged": {
try {
String newId = methodCall.argument("newName");
String oldId = methodCall.argument("oldName");
mManager.onShownContainerChanged(newId, oldId);
result.success(true);
} catch (Throwable t) {
result.error("onShownContainerChanged", t.getMessage(), t);
}
}
break;
default: {
result.notImplemented();
}
}
}
} }
} }
...@@ -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,13 @@ public class FlutterViewContainerManager implements IContainerManager { ...@@ -84,6 +82,13 @@ 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()){
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) { void setContainerResult(IContainerRecord record,int requestCode, int resultCode, Map<String,Object> result) {
...@@ -111,9 +116,9 @@ public class FlutterViewContainerManager implements IContainerManager { ...@@ -111,9 +116,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 +133,13 @@ public class FlutterViewContainerManager implements IContainerManager { ...@@ -128,11 +133,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 +155,7 @@ public class FlutterViewContainerManager implements IContainerManager { ...@@ -148,7 +155,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 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) { 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 { try {
Class clz = Class.forName("io.flutter.plugins.GeneratedPluginRegistrant"); Class clz = Class.forName("io.flutter.plugins.GeneratedPluginRegistrant");
Method method = clz.getDeclaredMethod("registerWith",PluginRegistry.class); Method method = clz.getDeclaredMethod("registerWith", PluginRegistry.class);
method.invoke(null,registry); method.invoke(null, mRegistry);
}catch (Throwable t){ } catch (Throwable t) {
throw new RuntimeException(t); Log.i("flutterboost.platform",t.toString());
} }
}
@Override if(pluginsRegister!=null){
public int whenEngineStart() { pluginsRegister.registerPlugins(mRegistry);
return ANY_ACTIVITY_CREATED; }
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; ...@@ -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
package com.idlefish.flutterboost; package com.idlefish.flutterboost;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Activity; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration; import android.content.res.Configuration;
//import android.graphics.Insets;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Build; import android.os.Build;
import android.os.LocaleList; import android.os.LocaleList;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi; import android.support.annotation.RequiresApi;
import android.support.annotation.VisibleForTesting;
import android.support.v4.view.ViewCompat;
import android.text.format.DateFormat; import android.text.format.DateFormat;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View;
import android.view.WindowInsets; import android.view.WindowInsets;
import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeProvider; import android.view.accessibility.AccessibilityNodeProvider;
...@@ -22,24 +25,46 @@ import android.view.inputmethod.EditorInfo; ...@@ -22,24 +25,46 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnection;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set;
import io.flutter.embedding.android.AndroidKeyProcessor; import io.flutter.Log;
import io.flutter.embedding.android.AndroidTouchProcessor; import io.flutter.embedding.android.*;
import io.flutter.embedding.android.FlutterSurfaceView;
import io.flutter.embedding.android.FlutterTextureView;
import io.flutter.embedding.android.FlutterView;
import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener; import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
import io.flutter.plugin.editing.TextInputPlugin; import io.flutter.plugin.editing.TextInputPlugin;
import io.flutter.plugin.platform.PlatformPlugin; import io.flutter.plugin.platform.PlatformViewsController;
import io.flutter.view.AccessibilityBridge; import io.flutter.view.AccessibilityBridge;
/**
* Displays a Flutter UI on an Android device.
* <p>
* A {@code FlutterView}'s UI is painted by a corresponding {@link FlutterEngine}.
* <p>
* A {@code FlutterView} can operate in 2 different {@link RenderMode}s:
* <ol>
* <li>{@link RenderMode#surface}, which paints a Flutter UI to a {@link android.view.SurfaceView}.
* This mode has the best performance, but a {@code FlutterView} in this mode cannot be positioned
* between 2 other Android {@code View}s in the z-index, nor can it be animated/transformed.
* Unless the special capabilities of a {@link android.graphics.SurfaceTexture} are required,
* developers should strongly prefer this render mode.</li>
* <li>{@link RenderMode#texture}, which paints a Flutter UI to a {@link android.graphics.SurfaceTexture}.
* This mode is not as performant as {@link RenderMode#surface}, but a {@code FlutterView} in this
* mode can be animated and transformed, as well as positioned in the z-index between 2+ other
* Android {@code Views}. Unless the special capabilities of a {@link android.graphics.SurfaceTexture}
* are required, developers should strongly prefer the {@link RenderMode#surface} render mode.</li>
* </ol>
* See <a>https://source.android.com/devices/graphics/arch-tv#surface_or_texture</a> for more
* information comparing {@link android.view.SurfaceView} and {@link android.view.TextureView}.
*/
public class XFlutterView extends FrameLayout { public class XFlutterView extends FrameLayout {
private static final String TAG = "XFlutterView"; private static final String TAG = "FlutterView";
// Behavior configuration of this FlutterView. // Behavior configuration of this FlutterView.
@NonNull @NonNull
...@@ -50,10 +75,14 @@ public class XFlutterView extends FrameLayout { ...@@ -50,10 +75,14 @@ public class XFlutterView extends FrameLayout {
// Internal view hierarchy references. // Internal view hierarchy references.
@Nullable @Nullable
private FlutterRenderer.RenderSurface renderSurface; private FlutterRenderer.RenderSurface renderSurface;
private final Set<OnFirstFrameRenderedListener> onFirstFrameRenderedListeners = new HashSet<>();
private boolean didRenderFirstFrame;
// Connections to a Flutter execution context. // Connections to a Flutter execution context.
@Nullable @Nullable
private FlutterEngine flutterEngine; private FlutterEngine flutterEngine;
@NonNull
private final Set<FlutterView.FlutterEngineAttachmentListener> flutterEngineAttachmentListeners = new HashSet<>();
// Components that process various types of Android View input and events, // Components that process various types of Android View input and events,
// possibly storing intermediate state, and communicating those events to Flutter. // possibly storing intermediate state, and communicating those events to Flutter.
...@@ -69,6 +98,8 @@ public class XFlutterView extends FrameLayout { ...@@ -69,6 +98,8 @@ public class XFlutterView extends FrameLayout {
@Nullable @Nullable
private AccessibilityBridge accessibilityBridge; private AccessibilityBridge accessibilityBridge;
private boolean hasAddFirstFrameRenderedListener=false;
// Directly implemented View behavior that communicates with Flutter. // Directly implemented View behavior that communicates with Flutter.
private final FlutterRenderer.ViewportMetrics viewportMetrics = new FlutterRenderer.ViewportMetrics(); private final FlutterRenderer.ViewportMetrics viewportMetrics = new FlutterRenderer.ViewportMetrics();
...@@ -79,17 +110,51 @@ public class XFlutterView extends FrameLayout { ...@@ -79,17 +110,51 @@ public class XFlutterView extends FrameLayout {
} }
}; };
private final OnFirstFrameRenderedListener onFirstFrameRenderedListener = new OnFirstFrameRenderedListener() {
@Override
public void onFirstFrameRendered() {
didRenderFirstFrame = true;
for (OnFirstFrameRenderedListener listener : onFirstFrameRenderedListeners) {
listener.onFirstFrameRendered();
}
}
};
/**
* Constructs a {@code FlutterView} programmatically, without any XML attributes.
* <p>
* <ul>
* <li>{@link #renderMode} defaults to {@link RenderMode#surface}.</li>
* <li>{@link #transparencyMode} defaults to {@link TransparencyMode#opaque}.</li>
* </ul>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/
public XFlutterView(@NonNull Context context) { public XFlutterView(@NonNull Context context) {
this(context, null, null, null); this(context, null, null, null);
} }
/**
* Constructs a {@code FlutterView} programmatically, without any XML attributes,
* and allows selection of a {@link #renderMode}.
* <p>
* {@link #transparencyMode} defaults to {@link TransparencyMode#opaque}.
* <p>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/
public XFlutterView(@NonNull Context context, @NonNull FlutterView.RenderMode renderMode) { public XFlutterView(@NonNull Context context, @NonNull FlutterView.RenderMode renderMode) {
this(context, null, renderMode, null); this(context, null, renderMode, null);
} }
/**
* Constructs a {@code FlutterView} programmatically, without any XML attributes,
* assumes the use of {@link RenderMode#surface}, and allows selection of a {@link #transparencyMode}.
* <p>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/
public XFlutterView(@NonNull Context context, @NonNull FlutterView.TransparencyMode transparencyMode) { public XFlutterView(@NonNull Context context, @NonNull FlutterView.TransparencyMode transparencyMode) {
this(context, null, FlutterView.RenderMode.surface, transparencyMode); this(context, null, FlutterView.RenderMode.surface, transparencyMode);
} }
...@@ -97,6 +162,9 @@ public class XFlutterView extends FrameLayout { ...@@ -97,6 +162,9 @@ public class XFlutterView extends FrameLayout {
/** /**
* Constructs a {@code FlutterView} programmatically, without any XML attributes, and allows * Constructs a {@code FlutterView} programmatically, without any XML attributes, and allows
* a selection of {@link #renderMode} and {@link #transparencyMode}. * a selection of {@link #renderMode} and {@link #transparencyMode}.
* <p>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/ */
public XFlutterView(@NonNull Context context, @NonNull FlutterView.RenderMode renderMode, @NonNull FlutterView.TransparencyMode transparencyMode) { public XFlutterView(@NonNull Context context, @NonNull FlutterView.RenderMode renderMode, @NonNull FlutterView.TransparencyMode transparencyMode) {
this(context, null, renderMode, transparencyMode); this(context, null, renderMode, transparencyMode);
...@@ -104,9 +172,11 @@ public class XFlutterView extends FrameLayout { ...@@ -104,9 +172,11 @@ public class XFlutterView extends FrameLayout {
/** /**
* Constructs a {@code FlutterSurfaceView} in an XML-inflation-compliant manner. * Constructs a {@code FlutterSurfaceView} in an XML-inflation-compliant manner.
* * <p>
* // TODO(mattcarroll): expose renderMode in XML when build system supports R.attr * {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/ */
// TODO(mattcarroll): expose renderMode in XML when build system supports R.attr
public XFlutterView(@NonNull Context context, @Nullable AttributeSet attrs) { public XFlutterView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, null, null); this(context, attrs, null, null);
} }
...@@ -121,18 +191,18 @@ public class XFlutterView extends FrameLayout { ...@@ -121,18 +191,18 @@ public class XFlutterView extends FrameLayout {
} }
private void init() { private void init() {
Log.d(TAG, "Initializing FlutterView"); Log.v(TAG, "Initializing FlutterView");
switch (renderMode) { switch (renderMode) {
case surface: case surface:
Log.d(TAG, "Internally creating a FlutterSurfaceView."); Log.v(TAG, "Internally using a FlutterSurfaceView.");
FlutterSurfaceView flutterSurfaceView = new FlutterSurfaceView(getContext(), transparencyMode == FlutterView.TransparencyMode.transparent); FlutterSurfaceView flutterSurfaceView = new FlutterSurfaceView(getContext(), transparencyMode == FlutterView.TransparencyMode.transparent);
renderSurface = flutterSurfaceView; renderSurface = flutterSurfaceView;
addView(flutterSurfaceView); addView(flutterSurfaceView);
break; break;
case texture: case texture:
Log.d(TAG, "Internally creating a FlutterTextureView."); Log.v(TAG, "Internally using a FlutterTextureView.");
FlutterTextureView flutterTextureView = new FlutterTextureView(getContext()); XFlutterTextureView flutterTextureView = new XFlutterTextureView(getContext());
renderSurface = flutterTextureView; renderSurface = flutterTextureView;
addView(flutterTextureView); addView(flutterTextureView);
break; break;
...@@ -143,12 +213,32 @@ public class XFlutterView extends FrameLayout { ...@@ -143,12 +213,32 @@ public class XFlutterView extends FrameLayout {
setFocusableInTouchMode(true); setFocusableInTouchMode(true);
} }
/**
* Returns true if an attached {@link FlutterEngine} has rendered at least 1 frame to this
* {@code FlutterView}.
* <p>
* Returns false if no {@link FlutterEngine} is attached.
* <p>
* This flag is specific to a given {@link FlutterEngine}. The following hypothetical timeline
* demonstrates how this flag changes over time.
* <ol>
* <li>{@code flutterEngineA} is attached to this {@code FlutterView}: returns false</li>
* <li>{@code flutterEngineA} renders its first frame to this {@code FlutterView}: returns true</li>
* <li>{@code flutterEngineA} is detached from this {@code FlutterView}: returns false</li>
* <li>{@code flutterEngineB} is attached to this {@code FlutterView}: returns false</li>
* <li>{@code flutterEngineB} renders its first frame to this {@code FlutterView}: returns true</li>
* </ol>
*/
public boolean hasRenderedFirstFrame() {
return didRenderFirstFrame;
}
/** /**
* Adds the given {@code listener} to this {@code FlutterView}, to be notified upon Flutter's * Adds the given {@code listener} to this {@code FlutterView}, to be notified upon Flutter's
* first rendered frame. * first rendered frame.
*/ */
public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
renderSurface.addOnFirstFrameRenderedListener(listener); onFirstFrameRenderedListeners.add(listener);
} }
/** /**
...@@ -156,7 +246,7 @@ public class XFlutterView extends FrameLayout { ...@@ -156,7 +246,7 @@ public class XFlutterView extends FrameLayout {
* {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}. * {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}.
*/ */
public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
renderSurface.removeOnFirstFrameRenderedListener(listener); onFirstFrameRenderedListeners.remove(listener);
} }
//------- Start: Process View configuration that Flutter cares about. ------ //------- Start: Process View configuration that Flutter cares about. ------
...@@ -168,9 +258,11 @@ public class XFlutterView extends FrameLayout { ...@@ -168,9 +258,11 @@ public class XFlutterView extends FrameLayout {
* change, device language change, device text scale factor change, etc. * change, device language change, device text scale factor change, etc.
*/ */
@Override @Override
protected void onConfigurationChanged(Configuration newConfig) { protected void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig);
Log.v(TAG, "Configuration changed. Sending locales and user settings to Flutter.");
try { try {
sendLocalesToFlutter(newConfig); sendLocalesToFlutter(newConfig);
sendUserSettingsToFlutter(); sendUserSettingsToFlutter();
}catch (Throwable e){ }catch (Throwable e){
...@@ -194,6 +286,9 @@ public class XFlutterView extends FrameLayout { ...@@ -194,6 +286,9 @@ public class XFlutterView extends FrameLayout {
@Override @Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
super.onSizeChanged(width, height, oldWidth, oldHeight); super.onSizeChanged(width, height, oldWidth, oldHeight);
Log.v(TAG, "Size changed. Sending Flutter new viewport metrics. FlutterView was "
+ oldWidth + " x " + oldHeight
+ ", it is now " + width + " x " + height);
viewportMetrics.width = width; viewportMetrics.width = width;
viewportMetrics.height = height; viewportMetrics.height = height;
sendViewportMetricsToFlutter(); sendViewportMetricsToFlutter();
...@@ -212,7 +307,12 @@ public class XFlutterView extends FrameLayout { ...@@ -212,7 +307,12 @@ public class XFlutterView extends FrameLayout {
@Override @Override
@TargetApi(20) @TargetApi(20)
@RequiresApi(20) @RequiresApi(20)
public final WindowInsets onApplyWindowInsets(WindowInsets insets) { // The annotations to suppress "InlinedApi" and "NewApi" lints prevent lint warnings
// caused by usage of Android Q APIs. These calls are safe because they are
// guarded.
@SuppressLint({"InlinedApi", "NewApi"})
@NonNull
public final WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) {
WindowInsets newInsets = super.onApplyWindowInsets(insets); WindowInsets newInsets = super.onApplyWindowInsets(insets);
// Status bar (top) and left/right system insets should partially obscure the content (padding). // Status bar (top) and left/right system insets should partially obscure the content (padding).
...@@ -226,6 +326,23 @@ public class XFlutterView extends FrameLayout { ...@@ -226,6 +326,23 @@ public class XFlutterView extends FrameLayout {
viewportMetrics.viewInsetRight = 0; viewportMetrics.viewInsetRight = 0;
viewportMetrics.viewInsetBottom = insets.getSystemWindowInsetBottom(); viewportMetrics.viewInsetBottom = insets.getSystemWindowInsetBottom();
viewportMetrics.viewInsetLeft = 0; viewportMetrics.viewInsetLeft = 0;
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Insets systemGestureInsets = insets.getSystemGestureInsets();
// viewportMetrics.systemGestureInsetTop = systemGestureInsets.top;
// viewportMetrics.systemGestureInsetRight = systemGestureInsets.right;
// viewportMetrics.systemGestureInsetBottom = systemGestureInsets.bottom;
// viewportMetrics.systemGestureInsetLeft = systemGestureInsets.left;
// }
Log.v(TAG, "Updating window insets (onApplyWindowInsets()):\n"
+ "Status bar insets: Top: " + viewportMetrics.paddingTop
+ ", Left: " + viewportMetrics.paddingLeft + ", Right: " + viewportMetrics.paddingRight + "\n"
+ "Keyboard insets: Bottom: " + viewportMetrics.viewInsetBottom
+ ", Left: " + viewportMetrics.viewInsetLeft + ", Right: " + viewportMetrics.viewInsetRight
+ "System Gesture Insets - Left: " + viewportMetrics.systemGestureInsetLeft + ", Top: " + viewportMetrics.systemGestureInsetTop
+ ", Right: " + viewportMetrics.systemGestureInsetRight + ", Bottom: " + viewportMetrics.viewInsetBottom);
sendViewportMetricsToFlutter(); sendViewportMetricsToFlutter();
return newInsets; return newInsets;
...@@ -240,7 +357,7 @@ public class XFlutterView extends FrameLayout { ...@@ -240,7 +357,7 @@ public class XFlutterView extends FrameLayout {
*/ */
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
protected boolean fitSystemWindows(Rect insets) { protected boolean fitSystemWindows(@NonNull Rect insets) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
// Status bar, left/right system insets partially obscure content (padding). // Status bar, left/right system insets partially obscure content (padding).
viewportMetrics.paddingTop = insets.top; viewportMetrics.paddingTop = insets.top;
...@@ -253,6 +370,13 @@ public class XFlutterView extends FrameLayout { ...@@ -253,6 +370,13 @@ public class XFlutterView extends FrameLayout {
viewportMetrics.viewInsetRight = 0; viewportMetrics.viewInsetRight = 0;
viewportMetrics.viewInsetBottom = insets.bottom; viewportMetrics.viewInsetBottom = insets.bottom;
viewportMetrics.viewInsetLeft = 0; viewportMetrics.viewInsetLeft = 0;
Log.v(TAG, "Updating window insets (fitSystemWindows()):\n"
+ "Status bar insets: Top: " + viewportMetrics.paddingTop
+ ", Left: " + viewportMetrics.paddingLeft + ", Right: " + viewportMetrics.paddingRight + "\n"
+ "Keyboard insets: Bottom: " + viewportMetrics.viewInsetBottom
+ ", Left: " + viewportMetrics.viewInsetLeft + ", Right: " + viewportMetrics.viewInsetRight);
sendViewportMetricsToFlutter(); sendViewportMetricsToFlutter();
return true; return true;
} else { } else {
...@@ -276,7 +400,8 @@ public class XFlutterView extends FrameLayout { ...@@ -276,7 +400,8 @@ public class XFlutterView extends FrameLayout {
* rather than spread that logic throughout this {@code FlutterView}. * rather than spread that logic throughout this {@code FlutterView}.
*/ */
@Override @Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) { @Nullable
public InputConnection onCreateInputConnection(@NonNull EditorInfo outAttrs) {
if (!isAttachedToFlutterEngine()) { if (!isAttachedToFlutterEngine()) {
return super.onCreateInputConnection(outAttrs); return super.onCreateInputConnection(outAttrs);
} }
...@@ -284,6 +409,21 @@ public class XFlutterView extends FrameLayout { ...@@ -284,6 +409,21 @@ public class XFlutterView extends FrameLayout {
return textInputPlugin.createInputConnection(this, outAttrs); return textInputPlugin.createInputConnection(this, outAttrs);
} }
/**
* Allows a {@code View} that is not currently the input connection target to invoke commands on
* the {@link android.view.inputmethod.InputMethodManager}, which is otherwise disallowed.
* <p>
* Returns true to allow non-input-connection-targets to invoke methods on
* {@code InputMethodManager}, or false to exclusively allow the input connection target to invoke
* such methods.
*/
@Override
public boolean checkInputConnectionProxy(View view) {
return flutterEngine != null
? flutterEngine.getPlatformViewsController().checkInputConnectionProxy(view)
: super.checkInputConnectionProxy(view);
}
/** /**
* Invoked when key is released. * Invoked when key is released.
* *
...@@ -292,13 +432,13 @@ public class XFlutterView extends FrameLayout { ...@@ -292,13 +432,13 @@ public class XFlutterView extends FrameLayout {
* software keyboard is used, though a software keyboard may choose to invoke * software keyboard is used, though a software keyboard may choose to invoke
* this method in some situations. * this method in some situations.
* *
* {@link KeyEvent}s are sent from Android to Flutter. {@link AndroidKeyProcessor} * {@link KeyEvent}s are sent from Android to Flutter. {@link }
* may do some additional work with the given {@link KeyEvent}, e.g., combine this * may do some additional work with the given {@link KeyEvent}, e.g., combine this
* {@code keyCode} with the previous {@code keyCode} to generate a unicode combined * {@code keyCode} with the previous {@code keyCode} to generate a unicode combined
* character. * character.
*/ */
@Override @Override
public boolean onKeyUp(int keyCode, KeyEvent event) { public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
if (!isAttachedToFlutterEngine()) { if (!isAttachedToFlutterEngine()) {
return super.onKeyUp(keyCode, event); return super.onKeyUp(keyCode, event);
} }
...@@ -315,13 +455,13 @@ public class XFlutterView extends FrameLayout { ...@@ -315,13 +455,13 @@ public class XFlutterView extends FrameLayout {
* software keyboard is used, though a software keyboard may choose to invoke * software keyboard is used, though a software keyboard may choose to invoke
* this method in some situations. * this method in some situations.
* *
* {@link KeyEvent}s are sent from Android to Flutter. {@link AndroidKeyProcessor} * {@link KeyEvent}s are sent from Android to Flutter. {@link }
* may do some additional work with the given {@link KeyEvent}, e.g., combine this * may do some additional work with the given {@link KeyEvent}, e.g., combine this
* {@code keyCode} with the previous {@code keyCode} to generate a unicode combined * {@code keyCode} with the previous {@code keyCode} to generate a unicode combined
* character. * character.
*/ */
@Override @Override
public boolean onKeyDown(int keyCode, KeyEvent event) { public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
if (!isAttachedToFlutterEngine()) { if (!isAttachedToFlutterEngine()) {
return super.onKeyDown(keyCode, event); return super.onKeyDown(keyCode, event);
} }
...@@ -337,7 +477,7 @@ public class XFlutterView extends FrameLayout { ...@@ -337,7 +477,7 @@ public class XFlutterView extends FrameLayout {
* method forwards all {@link MotionEvent} data from Android to Flutter. * method forwards all {@link MotionEvent} data from Android to Flutter.
*/ */
@Override @Override
public boolean onTouchEvent(MotionEvent event) { public boolean onTouchEvent(@NonNull MotionEvent event) {
if (!isAttachedToFlutterEngine()) { if (!isAttachedToFlutterEngine()) {
return super.onTouchEvent(event); return super.onTouchEvent(event);
} }
...@@ -362,7 +502,7 @@ public class XFlutterView extends FrameLayout { ...@@ -362,7 +502,7 @@ public class XFlutterView extends FrameLayout {
* method forwards all {@link MotionEvent} data from Android to Flutter. * method forwards all {@link MotionEvent} data from Android to Flutter.
*/ */
@Override @Override
public boolean onGenericMotionEvent(MotionEvent event) { public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
boolean handled = isAttachedToFlutterEngine() && androidTouchProcessor.onGenericMotionEvent(event); boolean handled = isAttachedToFlutterEngine() && androidTouchProcessor.onGenericMotionEvent(event);
return handled ? true : super.onGenericMotionEvent(event); return handled ? true : super.onGenericMotionEvent(event);
} }
...@@ -379,7 +519,7 @@ public class XFlutterView extends FrameLayout { ...@@ -379,7 +519,7 @@ public class XFlutterView extends FrameLayout {
* processed here for accessibility purposes. * processed here for accessibility purposes.
*/ */
@Override @Override
public boolean onHoverEvent(MotionEvent event) { public boolean onHoverEvent(@NonNull MotionEvent event) {
if (!isAttachedToFlutterEngine()) { if (!isAttachedToFlutterEngine()) {
return super.onHoverEvent(event); return super.onHoverEvent(event);
} }
...@@ -395,6 +535,7 @@ public class XFlutterView extends FrameLayout { ...@@ -395,6 +535,7 @@ public class XFlutterView extends FrameLayout {
//-------- Start: Accessibility ------- //-------- Start: Accessibility -------
@Override @Override
@Nullable
public AccessibilityNodeProvider getAccessibilityNodeProvider() { public AccessibilityNodeProvider getAccessibilityNodeProvider() {
if (accessibilityBridge != null && accessibilityBridge.isAccessibilityEnabled()) { if (accessibilityBridge != null && accessibilityBridge.isAccessibilityEnabled()) {
return accessibilityBridge; return accessibilityBridge;
...@@ -406,12 +547,8 @@ public class XFlutterView extends FrameLayout { ...@@ -406,12 +547,8 @@ public class XFlutterView extends FrameLayout {
} }
} }
// TODO(mattcarroll): Confer with Ian as to why we need this method. Delete if possible, otherwise add comments. // TODO(mattcarroll): Confer with Ian as to why we need this method. Delete if possible, otherwise add comments.
private void resetWillNotDraw(boolean isAccessibilityEnabled, boolean isTouchExplorationEnabled) { private void resetWillNotDraw(boolean isAccessibilityEnabled, boolean isTouchExplorationEnabled) {
if(flutterEngine==null) return;
if(flutterEngine.getRenderer()==null) return;
if (!flutterEngine.getRenderer().isSoftwareRenderingEnabled()) { if (!flutterEngine.getRenderer().isSoftwareRenderingEnabled()) {
setWillNotDraw(!(isAccessibilityEnabled || isTouchExplorationEnabled)); setWillNotDraw(!(isAccessibilityEnabled || isTouchExplorationEnabled));
} else { } else {
...@@ -431,9 +568,11 @@ public class XFlutterView extends FrameLayout { ...@@ -431,9 +568,11 @@ public class XFlutterView extends FrameLayout {
* See {@link #detachFromFlutterEngine()} for information on how to detach from a * See {@link #detachFromFlutterEngine()} for information on how to detach from a
* {@link FlutterEngine}. * {@link FlutterEngine}.
*/ */
public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
Log.d(TAG, "attachToFlutterEngine()"); public void attachToFlutterEngine(
@NonNull FlutterEngine flutterEngine
) {
Log.d(TAG, "Attaching to a FlutterEngine: " + flutterEngine);
if (isAttachedToFlutterEngine()) { if (isAttachedToFlutterEngine()) {
if (flutterEngine == this.flutterEngine) { if (flutterEngine == this.flutterEngine) {
// We are already attached to this FlutterEngine // We are already attached to this FlutterEngine
...@@ -442,76 +581,81 @@ public class XFlutterView extends FrameLayout { ...@@ -442,76 +581,81 @@ public class XFlutterView extends FrameLayout {
} }
// Detach from a previous FlutterEngine so we can attach to this new one. // Detach from a previous FlutterEngine so we can attach to this new one.
Log.d(TAG, "Currently attached to a different engine. Detaching."); Log.d(TAG, "Currently attached to a different engine. Detaching and then attaching"
+ " to new engine.");
detachFromFlutterEngine(); detachFromFlutterEngine();
} }
this.flutterEngine = flutterEngine; this.flutterEngine = flutterEngine;
// initialize PlatformViewsController
this.flutterEngine.getPluginRegistry().getPlatformViewsController().attach(getContext(),flutterEngine.getRenderer(),flutterEngine.getDartExecutor());
// Instruct our FlutterRenderer that we are now its designated RenderSurface. // Instruct our FlutterRenderer that we are now its designated RenderSurface.
this.flutterEngine.getRenderer().attachToRenderSurface(renderSurface); FlutterRenderer flutterRenderer = this.flutterEngine.getRenderer();
didRenderFirstFrame = flutterRenderer.hasRenderedFirstFrame();
if(!hasAddFirstFrameRenderedListener){
flutterRenderer.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
hasAddFirstFrameRenderedListener=true;
}
flutterRenderer.attachToRenderSurface(renderSurface);
// Initialize various components that know how to process Android View I/O // Initialize various components that know how to process Android View I/O
// in a way that Flutter understands. // in a way that Flutter understands.
if(textInputPlugin==null){ if(textInputPlugin==null){
textInputPlugin = new XTextInputPlugin( textInputPlugin = new XTextInputPlugin(
this, this,
flutterEngine.getTextInputChannel() flutterEngine.getTextInputChannel(),
this.flutterEngine.getPlatformViewsController()
); );
} }
textInputPlugin.setTextInputMethodHandler(); textInputPlugin.setTextInputMethodHandler();
textInputPlugin.getInputMethodManager().restartInput(this); textInputPlugin.getInputMethodManager().restartInput(this);
androidKeyProcessor = new XAndroidKeyProcessor(
this.androidKeyProcessor = new XAndroidKeyProcessor(
this.flutterEngine.getKeyEventChannel(), this.flutterEngine.getKeyEventChannel(),
textInputPlugin textInputPlugin
); );
this.androidTouchProcessor = new AndroidTouchProcessor(this.flutterEngine.getRenderer());
this.accessibilityBridge = new AccessibilityBridge(
this,
flutterEngine.getAccessibilityChannel(),
(AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE),
getContext().getContentResolver(),
this.flutterEngine.getPlatformViewsController()
);
accessibilityBridge.setOnAccessibilityChangeListener(onAccessibilityChangeListener);
resetWillNotDraw(
accessibilityBridge.isAccessibilityEnabled(),
accessibilityBridge.isTouchExplorationEnabled()
);
// Connect AccessibilityBridge to the PlatformViewsController within the FlutterEngine.
androidTouchProcessor = new AndroidTouchProcessor(this.flutterEngine.getRenderer()); // This allows platform Views to hook into Flutter's overall accessibility system.
this.flutterEngine.getPlatformViewsController().attachAccessibilityBridge(accessibilityBridge);
if(accessibilityBridge==null){
accessibilityBridge = new AccessibilityBridge(
this,
flutterEngine.getAccessibilityChannel(),
(AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE),
getContext().getContentResolver(),
// TODO(mattcaroll): plumb the platform views controller to the accessibility bridge.
// https://github.com/flutter/flutter/issues/29618
null
);
accessibilityBridge.setOnAccessibilityChangeListener(onAccessibilityChangeListener);
resetWillNotDraw(
accessibilityBridge.isAccessibilityEnabled(),
accessibilityBridge.isTouchExplorationEnabled()
);
}
// Inform the Android framework that it should retrieve a new InputConnection // Inform the Android framework that it should retrieve a new InputConnection
// now that an engine is attached. // now that an engine is attached.
// TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin // TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin
textInputPlugin.getInputMethodManager().restartInput(this);
// Push View and Context related information from Android to Flutter. // Push View and Context related information from Android to Flutter.
sendUserSettingsToFlutter(); sendUserSettingsToFlutter();
sendLocalesToFlutter(getResources().getConfiguration()); sendLocalesToFlutter(getResources().getConfiguration());
sendViewportMetricsToFlutter(); sendViewportMetricsToFlutter();
}
public void release(){
if(accessibilityBridge!=null){ // Notify engine attachment listeners of the attachment.
accessibilityBridge.release(); for (FlutterView.FlutterEngineAttachmentListener listener : flutterEngineAttachmentListeners) {
listener.onFlutterEngineAttachedToFlutterView(flutterEngine);
} }
textInputPlugin.release();
// If the first frame has already been rendered, notify all first frame listeners.
// Do this after all other initialization so that listeners don't inadvertently interact
// with a FlutterView that is only partially attached to a FlutterEngine.
// if (didRenderFirstFrame) {
// onFirstFrameRenderedListener.onFirstFrameRendered();
// }
} }
/** /**
...@@ -525,45 +669,83 @@ public class XFlutterView extends FrameLayout { ...@@ -525,45 +669,83 @@ public class XFlutterView extends FrameLayout {
* {@link FlutterEngine}. * {@link FlutterEngine}.
*/ */
public void detachFromFlutterEngine() { public void detachFromFlutterEngine() {
Log.d(TAG, "detachFromFlutterEngine()"); Log.d(TAG, "Detaching from a FlutterEngine: " + flutterEngine);
if (!isAttachedToFlutterEngine()) { if (!isAttachedToFlutterEngine()) {
Log.d(TAG, "Not attached to an engine. Doing nothing."); Log.d(TAG, "Not attached to an engine. Doing nothing.");
return; return;
} }
Log.d(TAG, "Detaching from Flutter Engine");
// detach platformviews in page in case memory leak // Notify engine attachment listeners of the detachment.
flutterEngine.getPluginRegistry().getPlatformViewsController().detach(); for (FlutterView.FlutterEngineAttachmentListener listener : flutterEngineAttachmentListeners) {
listener.onFlutterEngineDetachedFromFlutterView();
}
// Disconnect the FlutterEngine's PlatformViewsController from the AccessibilityBridge.
flutterEngine.getPlatformViewsController().detachAccessibiltyBridge();
// Disconnect and clean up the AccessibilityBridge.
accessibilityBridge.release();
accessibilityBridge = null;
// Inform the Android framework that it should retrieve a new InputConnection // Inform the Android framework that it should retrieve a new InputConnection
// now that the engine is detached. The new InputConnection will be null, which // now that the engine is detached. The new InputConnection will be null, which
// signifies that this View does not process input (until a new engine is attached). // signifies that this View does not process input (until a new engine is attached).
// TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin // TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin
// textInputPlugin.getInputMethodManager().restartInput(this);
// Instruct our FlutterRenderer that we are no longer interested in being its RenderSurface. // Instruct our FlutterRenderer that we are no longer interested in being its RenderSurface.
// this.textInputPlugin.getInputMethodManager().restartInput(this); FlutterRenderer flutterRenderer = flutterEngine.getRenderer();
flutterEngine.getRenderer().detachFromRenderSurface(); // didRenderFirstFrame = false;
flutterRenderer.removeOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
flutterRenderer.detachFromRenderSurface();
flutterEngine = null; flutterEngine = null;
// TODO(mattcarroll): clear the surface when JNI doesn't blow up
// if (isSurfaceAvailableForRendering) {
// Canvas canvas = surfaceHolder.lockCanvas();
// canvas.drawColor(Color.RED);
// surfaceHolder.unlockCanvasAndPost(canvas);
// }
} }
public void release(){
if(textInputPlugin!=null){
textInputPlugin.release();
}
}
private boolean isAttachedToFlutterEngine() { /**
* Returns true if this {@code FlutterView} is currently attached to a {@link FlutterEngine}.
*/
@VisibleForTesting
public boolean isAttachedToFlutterEngine() {
return flutterEngine != null; return flutterEngine != null;
} }
/**
* Returns the {@link FlutterEngine} to which this {@code FlutterView} is currently attached,
* or null if this {@code FlutterView} is not currently attached to a {@link FlutterEngine}.
*/
@VisibleForTesting
@Nullable
public FlutterEngine getAttachedFlutterEngine() {
return flutterEngine;
}
/**
* attached to/detaches from a {@link FlutterEngine}.
*/
@VisibleForTesting
public void addFlutterEngineAttachmentListener(@NonNull FlutterView.FlutterEngineAttachmentListener listener) {
flutterEngineAttachmentListeners.add(listener);
}
/**
*/
@VisibleForTesting
public void removeFlutterEngineAttachmentListener(@NonNull FlutterView.FlutterEngineAttachmentListener listener) {
flutterEngineAttachmentListeners.remove(listener);
}
/** /**
* Send the current {@link Locale} configuration to Flutter. * Send the current {@link Locale} configuration to Flutter.
* *
* FlutterEngine must be non-null when this method is invoked. * FlutterEngine must be non-null when this method is invoked.
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
private void sendLocalesToFlutter(Configuration config) { private void sendLocalesToFlutter(@NonNull Configuration config) {
List<Locale> locales = new ArrayList<>(); List<Locale> locales = new ArrayList<>();
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
LocaleList localeList = config.getLocales(); LocaleList localeList = config.getLocales();
...@@ -590,19 +772,18 @@ public class XFlutterView extends FrameLayout { ...@@ -590,19 +772,18 @@ public class XFlutterView extends FrameLayout {
*/ */
private void sendUserSettingsToFlutter() { private void sendUserSettingsToFlutter() {
if(flutterEngine!=null&&flutterEngine.getSettingsChannel()!=null){ if(flutterEngine!=null&&flutterEngine.getSettingsChannel()!=null){
flutterEngine.getSettingsChannel().startMessage() flutterEngine.getSettingsChannel().startMessage()
.setTextScaleFactor(getResources().getConfiguration().fontScale) .setTextScaleFactor(getResources().getConfiguration().fontScale)
.setUse24HourFormat(DateFormat.is24HourFormat(getContext())) .setUse24HourFormat(DateFormat.is24HourFormat(getContext()))
.send(); .send();
} }
} }
// TODO(mattcarroll): consider introducing a system channel for this communication instead of JNI // TODO(mattcarroll): consider introducing a system channel for this communication instead of JNI
private void sendViewportMetricsToFlutter() { private void sendViewportMetricsToFlutter() {
Log.d(TAG, "sendViewportMetricsToFlutter()");
if (!isAttachedToFlutterEngine()) { if (!isAttachedToFlutterEngine()) {
Log.w(TAG, "Tried to send viewport metrics from Android to Flutter but this FlutterView was not attached to a FlutterEngine."); Log.w(TAG, "Tried to send viewport metrics from Android to Flutter but this "
+ "FlutterView was not attached to a FlutterEngine.");
return; return;
} }
......
...@@ -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;
import android.app.Activity;
import android.app.ActivityManager.TaskDescription;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.HapticFeedbackConstants;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.Window;
import java.util.List;
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
import io.flutter.plugin.common.ActivityLifecycleListener;
public class XPlatformPlugin implements ActivityLifecycleListener {
public static final int DEFAULT_SYSTEM_UI = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
private Activity activity;
private final PlatformChannel platformChannel;
private PlatformChannel.SystemChromeStyle currentTheme;
private int mEnabledOverlays;
private final PlatformChannel.PlatformMessageHandler mPlatformMessageHandler = new PlatformChannel.PlatformMessageHandler() {
@Override
public void playSystemSound(@NonNull PlatformChannel.SoundType soundType) {
XPlatformPlugin.this.playSystemSound(soundType);
}
@Override
public void vibrateHapticFeedback(@NonNull PlatformChannel.HapticFeedbackType feedbackType) {
XPlatformPlugin.this.vibrateHapticFeedback(feedbackType);
}
@Override
public void setPreferredOrientations(int androidOrientation) {
setSystemChromePreferredOrientations(androidOrientation);
}
@Override
public void setApplicationSwitcherDescription(@NonNull PlatformChannel.AppSwitcherDescription description) {
setSystemChromeApplicationSwitcherDescription(description);
}
@Override
public void showSystemOverlays(@NonNull List<PlatformChannel.SystemUiOverlay> overlays) {
setSystemChromeEnabledSystemUIOverlays(overlays);
}
@Override
public void restoreSystemUiOverlays() {
restoreSystemChromeSystemUIOverlays();
}
@Override
public void setSystemUiOverlayStyle(@NonNull PlatformChannel.SystemChromeStyle systemUiOverlayStyle) {
setSystemChromeSystemUIOverlayStyle(systemUiOverlayStyle);
}
@Override
public void popSystemNavigator() {
XPlatformPlugin.this.popSystemNavigator();
}
@Override
public CharSequence getClipboardData(@Nullable PlatformChannel.ClipboardContentFormat format) {
return XPlatformPlugin.this.getClipboardData(format);
}
@Override
public void setClipboardData(@NonNull String text) {
XPlatformPlugin.this.setClipboardData(text);
}
};
public XPlatformPlugin(Activity activity, PlatformChannel platformChannel) {
this.activity = activity;
this.platformChannel = platformChannel;
this.platformChannel.setPlatformMessageHandler(mPlatformMessageHandler);
mEnabledOverlays = DEFAULT_SYSTEM_UI;
}
private void playSystemSound(PlatformChannel.SoundType soundType) {
if (soundType == PlatformChannel.SoundType.CLICK) {
View view = activity.getWindow().getDecorView();
view.playSoundEffect(SoundEffectConstants.CLICK);
}
}
private void vibrateHapticFeedback(PlatformChannel.HapticFeedbackType feedbackType) {
View view = activity.getWindow().getDecorView();
switch (feedbackType) {
case STANDARD:
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
break;
case LIGHT_IMPACT:
view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
break;
case MEDIUM_IMPACT:
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
break;
case HEAVY_IMPACT:
// HapticFeedbackConstants.CONTEXT_CLICK from API level 23.
view.performHapticFeedback(6);
break;
case SELECTION_CLICK:
view.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
break;
}
}
private void setSystemChromePreferredOrientations(int androidOrientation) {
activity.setRequestedOrientation(androidOrientation);
}
private void setSystemChromeApplicationSwitcherDescription(PlatformChannel.AppSwitcherDescription description) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return;
}
// Linter refuses to believe we're only executing this code in API 28 unless we use distinct if blocks and
// hardcode the API 28 constant.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P && Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
activity.setTaskDescription(new TaskDescription(description.label));
}
if (Build.VERSION.SDK_INT >= 28) {
TaskDescription taskDescription = new TaskDescription(description.label, 0, description.color);
activity.setTaskDescription(taskDescription);
}
}
private void setSystemChromeEnabledSystemUIOverlays(List<PlatformChannel.SystemUiOverlay> overlaysToShow) {
// Start by assuming we want to hide all system overlays (like an immersive game).
int enabledOverlays = DEFAULT_SYSTEM_UI
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
if (overlaysToShow.size() == 0) {
enabledOverlays |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
}
// Re-add any desired system overlays.
for (int i = 0; i < overlaysToShow.size(); ++i) {
PlatformChannel.SystemUiOverlay overlayToShow = overlaysToShow.get(i);
switch (overlayToShow) {
case TOP_OVERLAYS:
enabledOverlays &= ~View.SYSTEM_UI_FLAG_FULLSCREEN;
break;
case BOTTOM_OVERLAYS:
enabledOverlays &= ~View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
enabledOverlays &= ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
break;
}
}
mEnabledOverlays = enabledOverlays;
updateSystemUiOverlays();
}
private void updateSystemUiOverlays(){
activity.getWindow().getDecorView().setSystemUiVisibility(mEnabledOverlays);
if (currentTheme != null) {
setSystemChromeSystemUIOverlayStyle(currentTheme);
}
}
private void restoreSystemChromeSystemUIOverlays() {
updateSystemUiOverlays();
}
private void setSystemChromeSystemUIOverlayStyle(PlatformChannel.SystemChromeStyle systemChromeStyle) {
Window window = activity.getWindow();
View view = window.getDecorView();
int flags = view.getSystemUiVisibility();
// You can change the navigation bar color (including translucent colors)
// in Android, but you can't change the color of the navigation buttons until Android O.
// LIGHT vs DARK effectively isn't supported until then.
// Build.VERSION_CODES.O
if (Build.VERSION.SDK_INT >= 26) {
if (systemChromeStyle.systemNavigationBarIconBrightness != null) {
switch (systemChromeStyle.systemNavigationBarIconBrightness) {
case DARK:
//View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
flags |= 0x10;
break;
case LIGHT:
flags &= ~0x10;
break;
}
}
if (systemChromeStyle.systemNavigationBarColor != null) {
window.setNavigationBarColor(systemChromeStyle.systemNavigationBarColor);
}
}
// Build.VERSION_CODES.M
if (Build.VERSION.SDK_INT >= 23) {
if (systemChromeStyle.statusBarIconBrightness != null) {
switch (systemChromeStyle.statusBarIconBrightness) {
case DARK:
// View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
flags |= 0x2000;
break;
case LIGHT:
flags &= ~0x2000;
break;
}
}
if (systemChromeStyle.statusBarColor != null) {
window.setStatusBarColor(systemChromeStyle.statusBarColor);
}
}
if (systemChromeStyle.systemNavigationBarDividerColor != null) {
// Not availible until Android P.
// window.setNavigationBarDividerColor(systemNavigationBarDividerColor);
}
view.setSystemUiVisibility(flags);
currentTheme = systemChromeStyle;
}
private void popSystemNavigator() {
activity.finish();
}
private CharSequence getClipboardData(PlatformChannel.ClipboardContentFormat format) {
ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = clipboard.getPrimaryClip();
if (clip == null)
return null;
if (format == null || format == PlatformChannel.ClipboardContentFormat.PLAIN_TEXT) {
return clip.getItemAt(0).coerceToText(activity);
}
return null;
}
private void setClipboardData(String text) {
ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("text label?", text);
clipboard.setPrimaryClip(clip);
}
@Override
public void onPostResume() {
updateSystemUiOverlays();
}
public void release(){
this.activity=null;
}
}
\ No newline at end of file
...@@ -11,20 +11,23 @@ import android.view.inputmethod.BaseInputConnection; ...@@ -11,20 +11,23 @@ import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.systemchannels.TextInputChannel; import io.flutter.embedding.engine.systemchannels.TextInputChannel;
import io.flutter.view.FlutterView; import io.flutter.plugin.platform.PlatformViewsController;
/** /**
* Android implementation of the text input plugin. * Android implementation of the text input plugin.
*/ */
public class XTextInputPlugin { public class XTextInputPlugin {
@NonNull @NonNull
private View mView; private View mView;
@NonNull @NonNull
private final InputMethodManager mImm; private final InputMethodManager mImm;
@NonNull @NonNull
private int mClient = 0; private final TextInputChannel textInputChannel;
@NonNull
private InputTarget inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0);
@Nullable @Nullable
private TextInputChannel.Configuration configuration; private TextInputChannel.Configuration configuration;
@Nullable @Nullable
...@@ -32,17 +35,29 @@ public class XTextInputPlugin { ...@@ -32,17 +35,29 @@ public class XTextInputPlugin {
private boolean mRestartInputPending; private boolean mRestartInputPending;
@Nullable @Nullable
private InputConnection lastInputConnection; private InputConnection lastInputConnection;
private TextInputChannel textInputChannel; @NonNull
public XTextInputPlugin(View view, TextInputChannel mTextInputChannel) { private PlatformViewsController platformViewsController;
// When true following calls to createInputConnection will return the cached lastInputConnection if the input
// target is a platform view. See the comments on lockPlatformViewInputConnection for more details.
private boolean isInputConnectionLocked;
public XTextInputPlugin(View view, @NonNull TextInputChannel textInputChannel, @NonNull PlatformViewsController platformViewsController) {
mView = view; mView = view;
mImm = (InputMethodManager) view.getContext().getSystemService( mImm = (InputMethodManager) view.getContext().getSystemService(
Context.INPUT_METHOD_SERVICE); Context.INPUT_METHOD_SERVICE);
textInputChannel=mTextInputChannel; this.textInputChannel = textInputChannel;
this.platformViewsController = platformViewsController;
// this.platformViewsController.attachTextInputPlugin(this);
}
public void release() {
mView = null;
} }
public void setTextInputMethodHandler( ){ public void setTextInputMethodHandler() {
textInputChannel.setTextInputMethodHandler(new TextInputChannel.TextInputMethodHandler() { textInputChannel.setTextInputMethodHandler(new TextInputChannel.TextInputMethodHandler() {
@Override @Override
public void show() { public void show() {
...@@ -59,6 +74,11 @@ public class XTextInputPlugin { ...@@ -59,6 +74,11 @@ public class XTextInputPlugin {
setTextInputClient(textInputClientId, configuration); setTextInputClient(textInputClientId, configuration);
} }
@Override
public void setPlatformViewClient(int platformViewId) {
setPlatformViewTextInputClient(platformViewId);
}
@Override @Override
public void setEditingState(TextInputChannel.TextEditState editingState) { public void setEditingState(TextInputChannel.TextEditState editingState) {
setTextInputEditingState(mView, editingState); setTextInputEditingState(mView, editingState);
...@@ -70,15 +90,46 @@ public class XTextInputPlugin { ...@@ -70,15 +90,46 @@ public class XTextInputPlugin {
} }
}); });
} }
public void release(){
mView=null;
}
@NonNull @NonNull
public InputMethodManager getInputMethodManager() { public InputMethodManager getInputMethodManager() {
return mImm; return mImm;
} }
/***
* Use the current platform view input connection until unlockPlatformViewInputConnection is called.
*
* The current input connection instance is cached and any following call to @{link createInputConnection} returns
* the cached connection until unlockPlatformViewInputConnection is called.
*
* This is a no-op if the current input target isn't a platform view.
*
* This is used to preserve an input connection when moving a platform view from one virtual display to another.
*/
public void lockPlatformViewInputConnection() {
if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW) {
isInputConnectionLocked = true;
}
}
/**
* Unlocks the input connection.
* <p>
* See also: @{link lockPlatformViewInputConnection}.
*/
public void unlockPlatformViewInputConnection() {
isInputConnectionLocked = false;
}
/**
* Detaches the text input plugin from the platform views controller.
* <p>
* The TextInputPlugin instance should not be used after calling this.
*/
public void destroy() {
platformViewsController.detachTextInputPlugin();
}
private static int inputTypeFromTextInputType( private static int inputTypeFromTextInputType(
TextInputChannel.InputType type, TextInputChannel.InputType type,
boolean obscureText, boolean obscureText,
...@@ -107,6 +158,8 @@ public class XTextInputPlugin { ...@@ -107,6 +158,8 @@ public class XTextInputPlugin {
textType |= InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS; textType |= InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
} else if (type.type == TextInputChannel.TextInputType.URL) { } else if (type.type == TextInputChannel.TextInputType.URL) {
textType |= InputType.TYPE_TEXT_VARIATION_URI; textType |= InputType.TYPE_TEXT_VARIATION_URI;
} else if (type.type == TextInputChannel.TextInputType.VISIBLE_PASSWORD) {
textType |= InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
} }
if (obscureText) { if (obscureText) {
...@@ -129,8 +182,16 @@ public class XTextInputPlugin { ...@@ -129,8 +182,16 @@ public class XTextInputPlugin {
} }
public InputConnection createInputConnection(View view, EditorInfo outAttrs) { public InputConnection createInputConnection(View view, EditorInfo outAttrs) {
if (mClient == 0) { if (inputTarget.type == InputTarget.Type.NO_TARGET) {
lastInputConnection = null; lastInputConnection = null;
return null;
}
if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW) {
if (isInputConnectionLocked) {
return lastInputConnection;
}
lastInputConnection = platformViewsController.getPlatformViewById(inputTarget.id).onCreateInputConnection(outAttrs);
return lastInputConnection; return lastInputConnection;
} }
...@@ -159,7 +220,7 @@ public class XTextInputPlugin { ...@@ -159,7 +220,7 @@ public class XTextInputPlugin {
XInputConnectionAdaptor connection = new XInputConnectionAdaptor( XInputConnectionAdaptor connection = new XInputConnectionAdaptor(
view, view,
mClient, inputTarget.id,
textInputChannel, textInputChannel,
mEditable mEditable
); );
...@@ -175,24 +236,53 @@ public class XTextInputPlugin { ...@@ -175,24 +236,53 @@ public class XTextInputPlugin {
return lastInputConnection; return lastInputConnection;
} }
/**
* Clears a platform view text input client if it is the current input target.
* <p>
* This is called when a platform view is disposed to make sure we're not hanging to a stale input
* connection.
*/
public void clearPlatformViewClient(int platformViewId) {
if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW && inputTarget.id == platformViewId) {
inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0);
hideTextInput(mView);
mImm.restartInput(mView);
mRestartInputPending = false;
}
}
private void showTextInput(View view) { private void showTextInput(View view) {
if(view==null) return;
view.requestFocus(); view.requestFocus();
mImm.showSoftInput(view, 0); mImm.showSoftInput(view, 0);
} }
private void hideTextInput(View view) { private void hideTextInput(View view) {
// Note: a race condition may lead to us hiding the keyboard here just after a platform view has shown it.
// This can only potentially happen when switching focus from a Flutter text field to a platform view's text
// field(by text field here I mean anything that keeps the keyboard open).
// See: https://github.com/flutter/flutter/issues/34169
mImm.hideSoftInputFromWindow(view.getApplicationWindowToken(), 0); mImm.hideSoftInputFromWindow(view.getApplicationWindowToken(), 0);
} }
private void setTextInputClient(int client, TextInputChannel.Configuration configuration) { private void setTextInputClient(int client, TextInputChannel.Configuration configuration) {
mClient = client; inputTarget = new InputTarget(InputTarget.Type.FRAMEWORK_CLIENT, client);
this.configuration = configuration; this.configuration = configuration;
mEditable = Editable.Factory.getInstance().newEditable(""); mEditable = Editable.Factory.getInstance().newEditable("");
// setTextInputClient will be followed by a call to setTextInputEditingState. // setTextInputClient will be followed by a call to setTextInputEditingState.
// Do a restartInput at that time. // Do a restartInput at that time.
mRestartInputPending = true; mRestartInputPending = true;
unlockPlatformViewInputConnection();
}
private void setPlatformViewTextInputClient(int platformViewId) {
// We need to make sure that the Flutter view is focused so that no imm operations get short circuited.
// Not asking for focus here specifically manifested in a but on API 28 devices where the platform view's
// request to show a keyboard was ignored.
mView.requestFocus();
inputTarget = new InputTarget(InputTarget.Type.PLATFORM_VIEW, platformViewId);
mImm.restartInput(mView);
mRestartInputPending = false;
} }
private void applyStateToSelection(TextInputChannel.TextEditState state) { private void applyStateToSelection(TextInputChannel.TextEditState state) {
...@@ -222,6 +312,45 @@ public class XTextInputPlugin { ...@@ -222,6 +312,45 @@ public class XTextInputPlugin {
} }
private void clearTextInputClient() { private void clearTextInputClient() {
mClient = 0; if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW) {
// Focus changes in the framework tree have no guarantees on the order focus nodes are notified. A node
// that lost focus may be notified before or after a node that gained focus.
// When moving the focus from a Flutter text field to an AndroidView, it is possible that the Flutter text
// field's focus node will be notified that it lost focus after the AndroidView was notified that it gained
// focus. When this happens the text field will send a clearTextInput command which we ignore.
// By doing this we prevent the framework from clearing a platform view input client(the only way to do so
// is to set a new framework text client). I don't see an obvious use case for "clearing" a platform views
// text input client, and it may be error prone as we don't know how the platform view manages the input
// connection and we probably shouldn't interfere.
// If we ever want to allow the framework to clear a platform view text client we should probably consider
// changing the focus manager such that focus nodes that lost focus are notified before focus nodes that
// gained focus as part of the same focus event.
return;
}
inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0);
unlockPlatformViewInputConnection();
}
static private class InputTarget {
enum Type {
NO_TARGET,
// InputConnection is managed by the TextInputPlugin, and events are forwarded to the Flutter framework.
FRAMEWORK_CLIENT,
// InputConnection is managed by an embedded platform view.
PLATFORM_VIEW
}
public InputTarget(@NonNull Type type, int id) {
this.type = type;
this.id = id;
}
@NonNull
Type type;
// The ID of the input target.
//
// For framework clients this is the framework input connection client ID.
// For platform views this is the platform view's ID.
int id;
} }
} }
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Alibaba Group
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.idlefish.flutterboost.containers; package com.idlefish.flutterboost.containers;
import android.app.Activity; import android.app.Activity;
import android.arch.lifecycle.Lifecycle;
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.LifecycleRegistry;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.support.annotation.NonNull;
import android.os.Looper; import android.support.annotation.Nullable;
import android.view.Gravity; import android.view.*;
import android.view.KeyEvent; import android.widget.*;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.idlefish.flutterboost.BoostFlutterEngine;
import com.idlefish.flutterboost.BoostFlutterView;
import com.idlefish.flutterboost.FlutterBoost; import com.idlefish.flutterboost.FlutterBoost;
import com.idlefish.flutterboost.Utils; import com.idlefish.flutterboost.XFlutterView;
import com.idlefish.flutterboost.interfaces.IFlutterViewContainer; import io.flutter.Log;
import com.idlefish.flutterboost.interfaces.IOperateSyncer; import io.flutter.embedding.android.DrawableSplashScreen;
import io.flutter.embedding.android.FlutterView;
import io.flutter.embedding.android.SplashScreen;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.plugin.platform.PlatformPlugin;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import io.flutter.embedding.android.FlutterView; public class BoostFlutterActivity extends Activity
import io.flutter.plugin.platform.PlatformPlugin; implements FlutterActivityAndFragmentDelegate.Host,
LifecycleOwner {
public abstract class BoostFlutterActivity extends Activity implements IFlutterViewContainer { private static final String TAG = "NewBoostFlutterActivity";
protected BoostFlutterEngine mFlutterEngine; // Meta-data arguments, processed from manifest XML.
protected BoostFlutterView mFlutterView; protected static final String SPLASH_SCREEN_META_DATA_KEY = "io.flutter.embedding.android.SplashScreenDrawable";
protected IOperateSyncer mSyncer; protected static final String NORMAL_THEME_META_DATA_KEY = "io.flutter.embedding.android.NormalTheme";
private Handler mHandler = new Handler(Looper.getMainLooper()); // Intent extra arguments.
protected static final String EXTRA_DART_ENTRYPOINT = "dart_entrypoint";
protected static final String EXTRA_BACKGROUND_MODE = "background_mode";
protected static final String EXTRA_DESTROY_ENGINE_WITH_ACTIVITY = "destroy_engine_with_activity";
@Override protected static final String EXTRA_URL = "url";
protected void onCreate(Bundle savedInstanceState) { protected static final String EXTRA_PARAMS = "params";
super.onCreate(savedInstanceState);
configureWindowForTransparency();
mSyncer = FlutterBoost.singleton().containerManager().generateSyncer(this); // Default configuration.
protected static final String DEFAULT_BACKGROUND_MODE = BackgroundMode.opaque.name();
mFlutterEngine = createFlutterEngine(); public static Intent createDefaultIntent(@NonNull Context launchContext) {
mFlutterView = createFlutterView(mFlutterEngine); return withNewEngine().build(launchContext);
}
setContentView(mFlutterView); public static NewEngineIntentBuilder withNewEngine() {
return new NewEngineIntentBuilder(BoostFlutterActivity.class);
}
mSyncer.onCreate();
configureStatusBarForFullscreenFlutterExperience(); public static class NewEngineIntentBuilder {
private final Class<? extends BoostFlutterActivity> activityClass;
private String backgroundMode = DEFAULT_BACKGROUND_MODE;
private String url = "";
private Map params = new HashMap();
protected NewEngineIntentBuilder(@NonNull Class<? extends BoostFlutterActivity> activityClass) {
this.activityClass = activityClass;
}
public NewEngineIntentBuilder url(@NonNull String url) {
this.url = url;
return this;
}
public NewEngineIntentBuilder params(@NonNull Map params) {
this.params = params;
return this;
}
public NewEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
this.backgroundMode = backgroundMode.name();
return this;
}
public Intent build(@NonNull Context context) {
SerializableMap serializableMap = new SerializableMap();
serializableMap.setMap(params);
return new Intent(context, activityClass)
.putExtra(EXTRA_BACKGROUND_MODE, backgroundMode)
.putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, false)
.putExtra(EXTRA_URL, url)
.putExtra(EXTRA_PARAMS, serializableMap);
}
}
public static class SerializableMap implements Serializable {
private Map<String, Object> map;
public Map<String, Object> getMap() {
return map;
}
public void setMap(Map<String, Object> map) {
this.map = map;
}
}
private FlutterActivityAndFragmentDelegate delegate;
@NonNull
private LifecycleRegistry lifecycle;
public BoostFlutterActivity() {
lifecycle = new LifecycleRegistry(this);
} }
@Override @Override
public void onAttachedToWindow() { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onAttachedToWindow(); switchLaunchThemeForNormalTheme();
mHandler.post(new Runnable() {
@Override super.onCreate(savedInstanceState);
public void run() {
configureStatusBarForFullscreenFlutterExperience(); lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
delegate = new FlutterActivityAndFragmentDelegate(this);
delegate.onAttach(this);
configureWindowForTransparency();
setContentView(createFlutterView());
configureStatusBarForFullscreenFlutterExperience();
}
private void switchLaunchThemeForNormalTheme() {
try {
ActivityInfo activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
if (activityInfo.metaData != null) {
int normalThemeRID = activityInfo.metaData.getInt(NORMAL_THEME_META_DATA_KEY, -1);
if (normalThemeRID != -1) {
setTheme(normalThemeRID);
}
} else {
Log.d(TAG, "Using the launch theme as normal theme.");
} }
}); } catch (PackageManager.NameNotFoundException exception) {
Log.e(TAG, "Could not read meta-data for FlutterActivity. Using the launch theme as normal theme.");
}
}
@Nullable
@Override
public SplashScreen provideSplashScreen() {
Drawable manifestSplashDrawable = getSplashScreenFromManifest();
if (manifestSplashDrawable != null) {
return new DrawableSplashScreen(manifestSplashDrawable, ImageView.ScaleType.CENTER, 500L);
} else {
return null;
}
} }
protected void configureWindowForTransparency() { /**
if (isBackgroundTransparent()) { * Returns a {@link Drawable} to be used as a splash screen as requested by meta-data in the
* {@code AndroidManifest.xml} file, or null if no such splash screen is requested.
* <p>
* See {@link #SPLASH_SCREEN_META_DATA_KEY} for the meta-data key to be used in a
* manifest file.
*/
@Nullable
@SuppressWarnings("deprecation")
private Drawable getSplashScreenFromManifest() {
try {
ActivityInfo activityInfo = getPackageManager().getActivityInfo(
getComponentName(),
PackageManager.GET_META_DATA | PackageManager.GET_ACTIVITIES
);
Bundle metadata = activityInfo.metaData;
Integer splashScreenId = metadata != null ? metadata.getInt(SPLASH_SCREEN_META_DATA_KEY) : null;
return splashScreenId != null
? Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP
? getResources().getDrawable(splashScreenId, getTheme())
: getResources().getDrawable(splashScreenId)
: null;
} catch (PackageManager.NameNotFoundException e) {
// This is never expected to happen.
return null;
}
}
/**
* Sets this {@code Activity}'s {@code Window} background to be transparent, and hides the status
* bar, if this {@code Activity}'s desired {@link BackgroundMode} is {@link BackgroundMode#transparent}.
* <p>
* For {@code Activity} transparency to work as expected, the theme applied to this {@code Activity}
* must include {@code <item name="android:windowIsTranslucent">true</item>}.
*/
private void configureWindowForTransparency() {
BackgroundMode backgroundMode = getBackgroundMode();
if (backgroundMode == BackgroundMode.transparent) {
getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
getWindow().setFlags( getWindow().setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
...@@ -105,7 +221,15 @@ public abstract class BoostFlutterActivity extends Activity implements IFlutterV ...@@ -105,7 +221,15 @@ public abstract class BoostFlutterActivity extends Activity implements IFlutterV
} }
} }
protected void configureStatusBarForFullscreenFlutterExperience() { @NonNull
protected View createFlutterView() {
return delegate.onCreateView(
null /* inflater */,
null /* container */,
null /* savedInstanceState */);
}
private void configureStatusBarForFullscreenFlutterExperience() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow(); Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
...@@ -113,158 +237,269 @@ public abstract class BoostFlutterActivity extends Activity implements IFlutterV ...@@ -113,158 +237,269 @@ public abstract class BoostFlutterActivity extends Activity implements IFlutterV
window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI); window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
} }
Utils.setStatusBarLightMode(this,true);
}
protected BoostFlutterEngine createFlutterEngine(){
return FlutterBoost.singleton().engineProvider().provideEngine(this);
} }
protected BoostFlutterView createFlutterView(BoostFlutterEngine engine){ protected XFlutterView getFlutterView() {
BoostFlutterView.Builder builder = new BoostFlutterView.Builder(this); return delegate.getFlutterView();
return builder.flutterEngine(engine)
.renderMode(FlutterView.RenderMode.texture)
.transparencyMode(isBackgroundTransparent() ?
FlutterView.TransparencyMode.transparent :
FlutterView.TransparencyMode.opaque)
.renderingProgressCoverCreator(new BoostFlutterView.RenderingProgressCoverCreator() {
@Override
public View createRenderingProgressCover(Context context) {
return BoostFlutterActivity.this.createRenderingProgressCover();
}
})
.build();
}
protected boolean isBackgroundTransparent(){
return false;
} }
protected View createRenderingProgressCover(){ @Override
FrameLayout frameLayout = new FrameLayout(this); protected void onStart() {
super.onStart();
LinearLayout linearLayout = new LinearLayout(this); lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START);
linearLayout.setOrientation(LinearLayout.VERTICAL); delegate.onStart();
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.gravity = Gravity.CENTER;
frameLayout.addView(linearLayout,layoutParams);
ProgressBar progressBar = new ProgressBar(this);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER_HORIZONTAL;
linearLayout.addView(progressBar,params);
TextView textView = new TextView(this);
params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER_HORIZONTAL;
textView.setText("Frame Rendering...");
linearLayout.addView(textView,params);
return frameLayout;
} }
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
mSyncer.onAppear(); lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
mFlutterEngine.getLifecycleChannel().appIsResumed(); delegate.onResume();
}
@Override
public void onPostResume() {
super.onPostResume();
delegate.onPostResume();
} }
@Override @Override
protected void onPause() { protected void onPause() {
mSyncer.onDisappear();
super.onPause(); super.onPause();
mFlutterEngine.getLifecycleChannel().appIsInactive(); delegate.onPause();
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
}
@Override
protected void onStop() {
super.onStop();
delegate.onStop();
} }
@Override @Override
protected void onDestroy() { protected void onDestroy() {
Utils.fixInputMethodManagerLeak(this);
mSyncer.onDestroy();
super.onDestroy(); super.onDestroy();
delegate.onDestroyView();
delegate.onDetach();
// lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
} }
@Override @Override
public void onBackPressed() { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mSyncer.onBackPressed(); delegate.onActivityResult(requestCode, resultCode, data);
} }
@Override @Override
protected void onNewIntent(Intent intent) { protected void onNewIntent(@NonNull Intent intent) {
// TODO(mattcarroll): change G3 lint rule that forces us to call super
super.onNewIntent(intent); super.onNewIntent(intent);
mSyncer.onNewIntent(intent); delegate.onNewIntent(intent);
} }
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { public void onBackPressed() {
super.onActivityResult(requestCode, resultCode, data); delegate.onBackPressed();
mSyncer.onActivityResult(requestCode,resultCode,data); }
Map<String,Object> result = null;
if(data != null) {
Serializable rlt = data.getSerializableExtra(RESULT_KEY);
if(rlt instanceof Map) {
result = (Map<String,Object>)rlt;
}
}
mSyncer.onContainerResult(requestCode,resultCode,result); @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
delegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
} }
@Override @Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { public void onUserLeaveHint() {
super.onRequestPermissionsResult(requestCode, permissions, grantResults); delegate.onUserLeaveHint();
mSyncer.onRequestPermissionsResult(requestCode, permissions, grantResults);
} }
@Override @Override
public void onTrimMemory(int level) { public void onTrimMemory(int level) {
super.onTrimMemory(level); super.onTrimMemory(level);
mSyncer.onTrimMemory(level); delegate.onTrimMemory(level);
} }
/**
* {@link FlutterActivityAndFragmentDelegate.Host} method that is used by
* {@link FlutterActivityAndFragmentDelegate} to obtain a {@code Context} reference as
* needed.
*/
@Override @Override
public void onLowMemory() { @NonNull
super.onLowMemory(); public Context getContext() {
mSyncer.onLowMemory(); return this;
} }
/**
* {@link FlutterActivityAndFragmentDelegate.Host} method that is used by
* {@link FlutterActivityAndFragmentDelegate} to obtain an {@code Activity} reference as
* needed. This reference is used by the delegate to instantiate a {@link FlutterView},
* a {@link PlatformPlugin}, and to determine if the {@code Activity} is changing
* configurations.
*/
@Override @Override
protected void onUserLeaveHint() { @NonNull
super.onUserLeaveHint(); public Activity getActivity() {
mSyncer.onUserLeaveHint(); return this;
} }
/**
* {@link FlutterActivityAndFragmentDelegate.Host} method that is used by
* {@link FlutterActivityAndFragmentDelegate} to obtain a {@code Lifecycle} reference as
* needed. This reference is used by the delegate to provide Flutter plugins with access
* to lifecycle events.
*/
@Override @Override
public Activity getContextActivity() { @NonNull
return this; public Lifecycle getLifecycle() {
return lifecycle;
} }
/**
* {@link FlutterActivityAndFragmentDelegate.Host} method that is used by
* {@link FlutterActivityAndFragmentDelegate} to obtain Flutter shell arguments when
* initializing Flutter.
*/
@NonNull
@Override @Override
public BoostFlutterView getBoostFlutterView() { public FlutterShellArgs getFlutterShellArgs() {
return mFlutterView; return FlutterShellArgs.fromIntent(getIntent());
} }
/**
* Returns true if Flutter is running in "debug mode", and false otherwise.
* <p>
* Debug mode allows Flutter to operate with hot reload and hot restart. Release mode does not.
*/
private boolean isDebuggable() {
return (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
/**
* {@link FlutterActivityAndFragmentDelegate.Host} method that is used by
* {@link FlutterActivityAndFragmentDelegate} to obtain the desired {@link FlutterView.RenderMode}
* that should be used when instantiating a {@link FlutterView}.
*/
@NonNull
@Override @Override
public void finishContainer(Map<String,Object> result) { public FlutterView.RenderMode getRenderMode() {
if(result != null) { return getBackgroundMode() == BackgroundMode.opaque
FlutterBoost.setBoostResult(this,new HashMap<>(result)); ? FlutterView.RenderMode.surface
finish(); : FlutterView.RenderMode.texture;
}else{ }
finish();
/**
* {@link FlutterActivityAndFragmentDelegate.Host} method that is used by
* {@link FlutterActivityAndFragmentDelegate} to obtain the desired
* {@link FlutterView.TransparencyMode} that should be used when instantiating a
* {@link FlutterView}.
*/
@NonNull
@Override
public FlutterView.TransparencyMode getTransparencyMode() {
return getBackgroundMode() == BackgroundMode.opaque
? FlutterView.TransparencyMode.opaque
: FlutterView.TransparencyMode.transparent;
}
/**
* The desired window background mode of this {@code Activity}, which defaults to
* {@link BackgroundMode#opaque}.
*/
@NonNull
protected BackgroundMode getBackgroundMode() {
if (getIntent().hasExtra(EXTRA_BACKGROUND_MODE)) {
return BackgroundMode.valueOf(getIntent().getStringExtra(EXTRA_BACKGROUND_MODE));
} else {
return BackgroundMode.opaque;
} }
} }
/**
* Hook for subclasses to easily provide a custom {@link FlutterEngine}.
* <p>
* This hook is where a cached {@link FlutterEngine} should be provided, if a cached
* {@link FlutterEngine} is desired.
*/
@Nullable
@Override
public FlutterEngine provideFlutterEngine(@NonNull Context context) {
// No-op. Hook for subclasses.
return FlutterBoost.instance().engineProvider();
}
/**
* Hook for subclasses to obtain a reference to the {@link FlutterEngine} that is owned
* by this {@code FlutterActivity}.
*/
@Nullable
protected FlutterEngine getFlutterEngine() {
return delegate.getFlutterEngine();
}
@Nullable
@Override
public PlatformPlugin providePlatformPlugin(@Nullable Activity activity, @NonNull FlutterEngine flutterEngine) {
if (activity != null) {
return new PlatformPlugin(getActivity(), flutterEngine.getPlatformChannel());
} else {
return null;
}
}
/**
* Hook for subclasses to easily configure a {@code FlutterEngine}, e.g., register
* plugins.
* <p>
* This method is called after {@link #provideFlutterEngine(Context)}.
*/
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
// No-op. Hook for subclasses.
}
@Override
public boolean shouldAttachEngineToActivity() {
return true;
}
@Override @Override
public void onContainerShown() {} public String getContainerUrl() {
if (getIntent().hasExtra(EXTRA_URL)) {
return getIntent().getStringExtra(EXTRA_URL);
}
return "";
}
@Override @Override
public void onContainerHidden() {} public Map getContainerUrlParams() {
if (getIntent().hasExtra(EXTRA_PARAMS)) {
SerializableMap serializableMap = (SerializableMap) getIntent().getSerializableExtra(EXTRA_PARAMS);
return serializableMap.getMap();
}
Map<String, String> params = new HashMap<>();
return params;
}
/**
* The mode of the background of a {@code FlutterActivity}, either opaque or transparent.
*/
public enum BackgroundMode {
/**
* Indicates a FlutterActivity with an opaque background. This is the default.
*/
opaque,
/**
* Indicates a FlutterActivity with a transparent background.
*/
transparent
}
} }
package com.idlefish.flutterboost.containers;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import java.util.HashMap;
import java.util.Map;
public class BoostFlutterDefaultActivity extends BoostFlutterActivity {
@Override
public String getContainerUrl() {
return getIntent().getStringExtra("url");
}
@Override
public Map getContainerUrlParams() {
return (Map)(getIntent().getSerializableExtra("params"));
}
private static Intent intent(Context context, String url, HashMap<String, Object> params) {
final Intent intent = new Intent(context, BoostFlutterDefaultActivity.class);
intent.putExtra("url", url);
intent.putExtra("params", params);
return intent;
}
public static void open(Context context, String url, HashMap<String, Object> params) {
context.startActivity(intent(context, url, params));
}
public static void open(Activity activity, String url, HashMap<String, Object> params, int requestCode) {
activity.startActivityForResult(intent(activity, url, params), requestCode);
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Alibaba Group
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.idlefish.flutterboost.containers;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import com.idlefish.flutterboost.BoostFlutterEngine;
import com.idlefish.flutterboost.BoostFlutterView;
import com.idlefish.flutterboost.FlutterBoost;
import com.idlefish.flutterboost.Utils;
import com.idlefish.flutterboost.interfaces.IFlutterViewContainer;
import com.idlefish.flutterboost.interfaces.IOperateSyncer;
import java.util.Map;
import io.flutter.embedding.android.FlutterView;
import io.flutter.plugin.platform.PlatformPlugin;
abstract public class BoostFlutterFragment extends Fragment implements IFlutterViewContainer {
protected BoostFlutterEngine mFlutterEngine;
protected BoostFlutterView mFlutterView;
protected IOperateSyncer mSyncer;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mSyncer = FlutterBoost.singleton().containerManager().generateSyncer(this);
mFlutterEngine = createFlutterEngine();
mFlutterView = createFlutterView(mFlutterEngine);
mSyncer.onCreate();
return mFlutterView;
}
protected BoostFlutterEngine createFlutterEngine(){
return FlutterBoost.singleton().engineProvider().provideEngine(getContext());
}
protected BoostFlutterView createFlutterView(BoostFlutterEngine engine){
BoostFlutterView.Builder builder = new BoostFlutterView.Builder(getContextActivity());
return builder.flutterEngine(engine)
.renderMode(FlutterView.RenderMode.texture)
.transparencyMode(FlutterView.TransparencyMode.opaque)
.build();
}
@Override
public void onResume() {
super.onResume();
mSyncer.onAppear();
mFlutterEngine.getLifecycleChannel().appIsResumed();
}
@Override
public void onPause() {
mSyncer.onDisappear();
super.onPause();
mFlutterEngine.getLifecycleChannel().appIsInactive();
}
@Override
public void onDestroy() {
mSyncer.onDestroy();
super.onDestroy();
}
public void onBackPressed() {
mSyncer.onBackPressed();
}
public void onNewIntent(Intent intent) {
mSyncer.onNewIntent(intent);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
mSyncer.onActivityResult(requestCode,resultCode,data);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
mSyncer.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
public void onTrimMemory(int level) {
mSyncer.onTrimMemory(level);
}
@Override
public void onLowMemory() {
super.onLowMemory();
mSyncer.onLowMemory();
}
public void onUserLeaveHint() {
mSyncer.onUserLeaveHint();
}
@Override
public Activity getContextActivity() {
return getActivity();
}
@Override
public BoostFlutterView getBoostFlutterView() {
return mFlutterView;
}
@Override
public void finishContainer(Map<String,Object> result) {
getFragmentManager().popBackStack();
}
@Override
public void onContainerShown() {}
@Override
public void onContainerHidden() {}
}
package com.idlefish.flutterboost.containers;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.arch.lifecycle.Lifecycle;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import com.idlefish.flutterboost.BoostPluginRegistry;
import com.idlefish.flutterboost.FlutterBoost;
import com.idlefish.flutterboost.Utils;
import com.idlefish.flutterboost.XFlutterView;
import com.idlefish.flutterboost.interfaces.IFlutterViewContainer;
import com.idlefish.flutterboost.interfaces.IOperateSyncer;
import io.flutter.Log;
import io.flutter.app.FlutterActivity;
import io.flutter.embedding.android.*;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.platform.PlatformPlugin;
import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer {
private static final String TAG = "FlutterActivityAndFragmentDelegate";
@NonNull
private Host host;
@Nullable
private FlutterEngine flutterEngine;
@Nullable
private FlutterSplashView flutterSplashView;
@Nullable
private XFlutterView flutterView;
@Nullable
private PlatformPlugin platformPlugin;
private boolean isFlutterEngineFromHost;
protected IOperateSyncer mSyncer;
FlutterActivityAndFragmentDelegate(@NonNull Host host) {
this.host = host;
}
void release() {
this.host = null;
this.flutterEngine = null;
this.flutterView = null;
this.platformPlugin = null;
}
@Nullable
FlutterEngine getFlutterEngine() {
return flutterEngine;
}
XFlutterView getFlutterView() {
return flutterView;
}
void onAttach(@NonNull Context context) {
ensureAlive();
if (FlutterBoost.instance().platform().whenEngineStart() == FlutterBoost.ConfigBuilder.FLUTTER_ACTIVITY_CREATED) {
FlutterBoost.instance().doInitialFlutter();
}
// When "retain instance" is true, the FlutterEngine will survive configuration
// changes. Therefore, we create a new one only if one does not already exist.
if (flutterEngine == null) {
setupFlutterEngine();
}
// Regardless of whether or not a FlutterEngine already existed, the PlatformPlugin
// is bound to a specific Activity. Therefore, it needs to be created and configured
// every time this Fragment attaches to a new Activity.
// TODO(mattcarroll): the PlatformPlugin needs to be reimagined because it implicitly takes
// control of the entire window. This is unacceptable for non-fullscreen
// use-cases.
platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);
host.configureFlutterEngine(flutterEngine);
host.getActivity().getWindow().setFormat(PixelFormat.TRANSLUCENT);
}
private void setupFlutterEngine() {
Log.d(TAG, "Setting up FlutterEngine.");
// Second, defer to subclasses for a custom FlutterEngine.
flutterEngine = host.provideFlutterEngine(host.getContext());
if (flutterEngine != null) {
isFlutterEngineFromHost = true;
return;
}
// Our host did not provide a custom FlutterEngine. Create a FlutterEngine to back our
// FlutterView.
Log.d(TAG, "No preferred FlutterEngine was provided. Creating a new FlutterEngine for"
+ " this NewFlutterFragment.");
isFlutterEngineFromHost = false;
}
@SuppressLint("ResourceType")
@NonNull
View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Log.v(TAG, "Creating FlutterView.");
flutterEngine.getActivityControlSurface().attachToActivity(
host.getActivity(),
host.getLifecycle()
);
mSyncer = FlutterBoost.instance().containerManager().generateSyncer(this);
ensureAlive();
flutterView = new XFlutterView(host.getActivity(), FlutterBoost.instance().platform().renderMode(), host.getTransparencyMode());
flutterSplashView = new FlutterSplashView(host.getContext());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
flutterSplashView.setId(View.generateViewId());
} else {
// TODO(mattcarroll): Find a better solution to this ID. This is a random, static ID.
// It might conflict with other Views, and it means that only a single FlutterSplashView
// can exist in a View hierarchy at one time.
flutterSplashView.setId(486947586);
}
flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen());
mSyncer.onCreate();
return flutterSplashView;
}
void onStart() {
Log.v(TAG, "onStart()");
ensureAlive();
// We post() the code that attaches the FlutterEngine to our FlutterView because there is
// some kind of blocking logic on the native side when the surface is connected. That lag
// causes launching Activitys to wait a second or two before launching. By post()'ing this
// behavior we are able to move this blocking logic to after the Activity's launch.
// TODO(mattcarroll): figure out how to avoid blocking the MAIN thread when connecting a surface
}
void onResume() {
mSyncer.onAppear();
Log.v(TAG, "onResume()");
ensureAlive();
flutterEngine.getLifecycleChannel().appIsResumed();
BoostPluginRegistry registry = (BoostPluginRegistry) FlutterBoost.instance().getPluginRegistry();
ActivityPluginBinding binding = registry.getRegistrarAggregate().getActivityPluginBinding();
if (binding != null && (binding.getActivity() != this.host.getActivity())) {
flutterEngine.getActivityControlSurface().attachToActivity(
host.getActivity(),
host.getLifecycle()
);
}
}
void onPostResume() {
Log.v(TAG, "onPostResume()");
ensureAlive();
Utils.setStatusBarLightMode(host.getActivity(), true);
}
void onPause() {
Log.v(TAG, "onPause()");
ensureAlive();
mSyncer.onDisappear();
flutterEngine.getLifecycleChannel().appIsInactive();
}
void onStop() {
Log.v(TAG, "onStop()");
ensureAlive();
}
void onDestroyView() {
Log.v(TAG, "onDestroyView()");
mSyncer.onDestroy();
ensureAlive();
BoostPluginRegistry registry = (BoostPluginRegistry) FlutterBoost.instance().getPluginRegistry();
ActivityPluginBinding binding = registry.getRegistrarAggregate().getActivityPluginBinding();
if (binding != null && (binding.getActivity() == this.host.getActivity())) {
registry.getRegistrarAggregate().onDetachedFromActivityForConfigChanges();
flutterEngine.getActivityControlSurface().detachFromActivityForConfigChanges();
}
flutterView.release();
}
void onDetach() {
Log.v(TAG, "onDetach()");
ensureAlive();
// Null out the platformPlugin to avoid a possible retain cycle between the plugin, this Fragment,
// and this Fragment's Activity.
if (platformPlugin != null) {
platformPlugin.destroy();
platformPlugin = null;
}
Utils.fixInputMethodManagerLeak(host.getActivity());
}
void onBackPressed() {
mSyncer.onBackPressed();
ensureAlive();
}
void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
mSyncer.onRequestPermissionsResult(requestCode, permissions, grantResults);
ensureAlive();
if (flutterEngine != null) {
Log.v(TAG, "Forwarding onRequestPermissionsResult() to FlutterEngine:\n"
+ "requestCode: " + requestCode + "\n"
+ "permissions: " + Arrays.toString(permissions) + "\n"
+ "grantResults: " + Arrays.toString(grantResults));
flutterEngine.getActivityControlSurface().onRequestPermissionsResult(requestCode, permissions, grantResults);
} else {
Log.w(TAG, "onRequestPermissionResult() invoked before NewFlutterFragment was attached to an Activity.");
}
}
void onNewIntent(@NonNull Intent intent) {
mSyncer.onNewIntent(intent);
ensureAlive();
if (flutterEngine != null) {
Log.v(TAG, "Forwarding onNewIntent() to FlutterEngine.");
flutterEngine.getActivityControlSurface().onNewIntent(intent);
} else {
Log.w(TAG, "onNewIntent() invoked before NewFlutterFragment was attached to an Activity.");
}
}
void onActivityResult(int requestCode, int resultCode, Intent data) {
mSyncer.onActivityResult(requestCode, resultCode, data);
Map<String, Object> result = null;
if (data != null) {
Serializable rlt = data.getSerializableExtra(RESULT_KEY);
if (rlt instanceof Map) {
result = (Map<String, Object>) rlt;
}
}
mSyncer.onContainerResult(requestCode, resultCode, result);
ensureAlive();
if (flutterEngine != null) {
Log.v(TAG, "Forwarding onActivityResult() to FlutterEngine:\n"
+ "requestCode: " + requestCode + "\n"
+ "resultCode: " + resultCode + "\n"
+ "data: " + data);
flutterEngine.getActivityControlSurface().onActivityResult(requestCode, resultCode, data);
} else {
Log.w(TAG, "onActivityResult() invoked before NewFlutterFragment was attached to an Activity.");
}
}
void onUserLeaveHint() {
ensureAlive();
if (flutterEngine != null) {
Log.v(TAG, "Forwarding onUserLeaveHint() to FlutterEngine.");
flutterEngine.getActivityControlSurface().onUserLeaveHint();
} else {
Log.w(TAG, "onUserLeaveHint() invoked before NewFlutterFragment was attached to an Activity.");
}
}
void onTrimMemory(int level) {
mSyncer.onTrimMemory(level);
ensureAlive();
if (flutterEngine != null) {
// Use a trim level delivered while the application is running so the
// framework has a chance to react to the notification.
if (level == TRIM_MEMORY_RUNNING_LOW) {
Log.v(TAG, "Forwarding onTrimMemory() to FlutterEngine. Level: " + level);
flutterEngine.getSystemChannel().sendMemoryPressureWarning();
}
} else {
Log.w(TAG, "onTrimMemory() invoked before NewFlutterFragment was attached to an Activity.");
}
}
void onLowMemory() {
Log.v(TAG, "Forwarding onLowMemory() to FlutterEngine.");
mSyncer.onLowMemory();
ensureAlive();
flutterEngine.getSystemChannel().sendMemoryPressureWarning();
}
/**
* Ensures that this delegate has not been {@link #release()}'ed.
* <p>
* An {@code IllegalStateException} is thrown if this delegate has been {@link #release()}'ed.
*/
private void ensureAlive() {
if (host == null) {
throw new IllegalStateException("Cannot execute method on a destroyed FlutterActivityAndFragmentDelegate.");
}
}
@Override
public Activity getContextActivity() {
return (Activity) this.host.getActivity();
}
@Override
public FlutterSplashView getBoostFlutterView() {
return this.flutterSplashView;
}
@Override
public void finishContainer(Map<String, Object> result) {
if (result != null) {
setBoostResult(this.host.getActivity(), new HashMap<>(result));
this.host.getActivity().finish();
} else {
this.host.getActivity().finish();
}
}
public void setBoostResult(Activity activity, HashMap result) {
Intent intent = new Intent();
if (result != null) {
intent.putExtra(IFlutterViewContainer.RESULT_KEY, result);
}
activity.setResult(Activity.RESULT_OK, intent);
}
@Override
public String getContainerUrl() {
return this.host.getContainerUrl();
}
@Override
public Map getContainerUrlParams() {
return this.host.getContainerUrlParams();
}
@Override
public void onContainerShown() {
}
@Override
public void onContainerHidden() {
}
/**
* The {@link FlutterActivity} or {@link FlutterFragment} that owns this
* {@code FlutterActivityAndFragmentDelegate}.
*/
/* package */ interface Host extends SplashScreenProvider, FlutterEngineProvider, FlutterEngineConfigurator {
/**
* Returns the {@link Context} that backs the host {@link Activity} or {@code Fragment}.
*/
@NonNull
Context getContext();
/**
* Returns the host {@link Activity} or the {@code Activity} that is currently attached
* to the host {@code Fragment}.
*/
@Nullable
Activity getActivity();
/**
* Returns the {@link Lifecycle} that backs the host {@link Activity} or {@code Fragment}.
*/
@NonNull
Lifecycle getLifecycle();
/**
* Returns the {@link FlutterShellArgs} that should be used when initializing Flutter.
*/
@NonNull
FlutterShellArgs getFlutterShellArgs();
/**
* Returns the {@link FlutterView.RenderMode} used by the {@link FlutterView} that
* displays the {@link FlutterEngine}'s content.
*/
@NonNull
FlutterView.RenderMode getRenderMode();
/**
* Returns the {@link FlutterView.TransparencyMode} used by the {@link FlutterView} that
* displays the {@link FlutterEngine}'s content.
*/
@NonNull
FlutterView.TransparencyMode getTransparencyMode();
@Nullable
SplashScreen provideSplashScreen();
/**
* Returns the {@link FlutterEngine} that should be rendered to a {@link FlutterView}.
* <p>
* If {@code null} is returned, a new {@link FlutterEngine} will be created automatically.
*/
@Nullable
FlutterEngine provideFlutterEngine(@NonNull Context context);
/**
* Hook for the host to create/provide a {@link PlatformPlugin} if the associated
* Flutter experience should control system chrome.
*/
@Nullable
PlatformPlugin providePlatformPlugin(@Nullable Activity activity, @NonNull FlutterEngine flutterEngine);
/**
* Hook for the host to configure the {@link FlutterEngine} as desired.
*/
void configureFlutterEngine(@NonNull FlutterEngine flutterEngine);
/**
* Returns true if the {@link FlutterEngine}'s plugin system should be connected to the
* host {@link Activity}, allowing plugins to interact with it.
*/
boolean shouldAttachEngineToActivity();
String getContainerUrl();
Map getContainerUrlParams();
}
}
package com.idlefish.flutterboost.containers;
import android.app.Activity;
import android.arch.lifecycle.Lifecycle;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.idlefish.flutterboost.FlutterBoost;
import com.idlefish.flutterboost.XFlutterView;
import io.flutter.embedding.android.*;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.plugin.platform.PlatformPlugin;
import java.util.HashMap;
import java.util.Map;
public class FlutterFragment extends Fragment implements FlutterActivityAndFragmentDelegate.Host {
private static final String TAG = "NewFlutterFragment";
/**
* The Dart entrypoint method name that is executed upon initialization.
*/
protected static final String ARG_DART_ENTRYPOINT = "dart_entrypoint";
/**
* Initial Flutter route that is rendered in a Navigator widget.
*/
protected static final String ARG_INITIAL_ROUTE = "initial_route";
/**
* Path to Flutter's Dart code.
*/
protected static final String ARG_APP_BUNDLE_PATH = "app_bundle_path";
/**
* Flutter shell arguments.
*/
protected static final String ARG_FLUTTER_INITIALIZATION_ARGS = "initialization_args";
/**
* {@link FlutterView.RenderMode} to be used for the {@link FlutterView} in this
* {@code NewFlutterFragment}
*/
protected static final String ARG_FLUTTERVIEW_RENDER_MODE = "flutterview_render_mode";
/**
* {@link FlutterView.TransparencyMode} to be used for the {@link FlutterView} in this
* {@code NewFlutterFragment}
*/
protected static final String ARG_FLUTTERVIEW_TRANSPARENCY_MODE = "flutterview_transparency_mode";
/**
* See {@link #shouldAttachEngineToActivity()}.
*/
protected static final String ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY = "should_attach_engine_to_activity";
/**
* the created {@code NewFlutterFragment}.
*/
protected static final String ARG_CACHED_ENGINE_ID = "cached_engine_id";
/**
* True if the {@link FlutterEngine} in the created {@code NewFlutterFragment} should be destroyed
* when the {@code NewFlutterFragment} is destroyed, false if the {@link FlutterEngine} should
* outlive the {@code NewFlutterFragment}.
*/
protected static final String ARG_DESTROY_ENGINE_WITH_FRAGMENT = "destroy_engine_with_fragment";
protected static final String EXTRA_URL = "url";
protected static final String EXTRA_PARAMS = "params";
/**
* Creates a {@code NewFlutterFragment} with a default configuration.
* <p>
* {@code NewFlutterFragment}'s default configuration creates a new {@link FlutterEngine} within
* the {@code NewFlutterFragment} and uses the following settings:
* <ul>
* <li>Dart entrypoint: "main"</li>
* <li>Initial route: "/"</li>
* <li>Render mode: surface</li>
* <li>Transparency mode: transparent</li>
* </ul>
* <p>
* To use a new {@link FlutterEngine} with different settings, use {@link #withNewEngine()}.
* <p>
* To use a cached {@link FlutterEngine} instead of creating a new one, use
*/
@NonNull
public static FlutterFragment createDefault() {
return new NewEngineFragmentBuilder().build();
}
/**
* Returns a {@link NewEngineFragmentBuilder} to create a {@code NewFlutterFragment} with a new
* {@link FlutterEngine} and a desired engine configuration.
*/
@NonNull
public static NewEngineFragmentBuilder withNewEngine() {
return new NewEngineFragmentBuilder();
}
public static class NewEngineFragmentBuilder {
private final Class<? extends FlutterFragment> fragmentClass;
private FlutterShellArgs shellArgs = null;
private FlutterView.RenderMode renderMode = FlutterView.RenderMode.surface;
private FlutterView.TransparencyMode transparencyMode = FlutterView.TransparencyMode.transparent;
private boolean shouldAttachEngineToActivity = true;
private String url = "";
private Map params = new HashMap();
/**
* Constructs a {@code NewEngineFragmentBuilder} that is configured to construct an instance of
* {@code NewFlutterFragment}.
*/
public NewEngineFragmentBuilder() {
fragmentClass = FlutterFragment.class;
}
/**
* Constructs a {@code NewEngineFragmentBuilder} that is configured to construct an instance of
* {@code subclass}, which extends {@code NewFlutterFragment}.
*/
public NewEngineFragmentBuilder(@NonNull Class<? extends FlutterFragment> subclass) {
fragmentClass = subclass;
}
/**
* Any special configuration arguments for the Flutter engine
*/
@NonNull
public NewEngineFragmentBuilder flutterShellArgs(@NonNull FlutterShellArgs shellArgs) {
this.shellArgs = shellArgs;
return this;
}
/**
* Render Flutter either as a {@link FlutterView.RenderMode#surface} or a
* {@link FlutterView.RenderMode#texture}. You should use {@code surface} unless
* you have a specific reason to use {@code texture}. {@code texture} comes with
* a significant performance impact, but {@code texture} can be displayed
* beneath other Android {@code View}s and animated, whereas {@code surface}
* cannot.
*/
@NonNull
public NewEngineFragmentBuilder renderMode(@NonNull FlutterView.RenderMode renderMode) {
this.renderMode = renderMode;
return this;
}
public NewEngineFragmentBuilder url(@NonNull String url) {
this.url = url;
return this;
}
public NewEngineFragmentBuilder params(@NonNull Map params) {
this.params = params;
return this;
}
/**
* Support a {@link FlutterView.TransparencyMode#transparent} background within {@link FlutterView},
* or force an {@link FlutterView.TransparencyMode#opaque} background.
* <p>
* See {@link FlutterView.TransparencyMode} for implications of this selection.
*/
@NonNull
public NewEngineFragmentBuilder transparencyMode(@NonNull FlutterView.TransparencyMode transparencyMode) {
this.transparencyMode = transparencyMode;
return this;
}
@NonNull
protected Bundle createArgs() {
Bundle args = new Bundle();
// TODO(mattcarroll): determine if we should have an explicit FlutterTestFragment instead of conflating.
if (null != shellArgs) {
args.putStringArray(ARG_FLUTTER_INITIALIZATION_ARGS, shellArgs.toArray());
}
BoostFlutterActivity.SerializableMap serializableMap = new BoostFlutterActivity.SerializableMap();
serializableMap.setMap(params);
args.putString(EXTRA_URL, url);
args.putSerializable(EXTRA_PARAMS, serializableMap);
args.putString(ARG_FLUTTERVIEW_RENDER_MODE, renderMode != null ? renderMode.name() : FlutterView.RenderMode.surface.name());
args.putString(ARG_FLUTTERVIEW_TRANSPARENCY_MODE, transparencyMode != null ? transparencyMode.name() : FlutterView.TransparencyMode.transparent.name());
args.putBoolean(ARG_DESTROY_ENGINE_WITH_FRAGMENT, true);
return args;
}
/**
* Constructs a new {@code NewFlutterFragment} (or a subclass) that is configured based on
* properties set on this {@code Builder}.
*/
@NonNull
public <T extends FlutterFragment> T build() {
try {
@SuppressWarnings("unchecked")
T frag = (T) fragmentClass.getDeclaredConstructor().newInstance();
if (frag == null) {
throw new RuntimeException("The NewFlutterFragment subclass sent in the constructor ("
+ fragmentClass.getCanonicalName() + ") does not match the expected return type.");
}
Bundle args = createArgs();
frag.setArguments(args);
return frag;
} catch (Exception e) {
throw new RuntimeException("Could not instantiate NewFlutterFragment subclass (" + fragmentClass.getName() + ")", e);
}
}
}
// Delegate that runs all lifecycle and OS hook logic that is common between
// FlutterActivity and NewFlutterFragment. See the FlutterActivityAndFragmentDelegate
// implementation for details about why it exists.
private FlutterActivityAndFragmentDelegate delegate;
protected XFlutterView getFlutterView() {
return delegate.getFlutterView();
}
public FlutterFragment() {
// Ensure that we at least have an empty Bundle of arguments so that we don't
// need to continually check for null arguments before grabbing one.
setArguments(new Bundle());
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
delegate = new FlutterActivityAndFragmentDelegate(this);
delegate.onAttach(context);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return delegate.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onStart() {
super.onStart();
delegate.onStart();
}
@Override
public void onResume() {
super.onResume();
delegate.onResume();
}
// TODO(mattcarroll): determine why this can't be in onResume(). Comment reason, or move if possible.
@ActivityCallThrough
public void onPostResume() {
delegate.onPostResume();
}
@Override
public void onPause() {
super.onPause();
delegate.onPause();
}
@Override
public void onStop() {
super.onStop();
delegate.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
delegate.onDestroyView();
}
@Override
public void onDetach() {
super.onDetach();
delegate.onDetach();
delegate.release();
delegate = null;
}
@ActivityCallThrough
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
delegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@ActivityCallThrough
public void onNewIntent(@NonNull Intent intent) {
delegate.onNewIntent(intent);
}
@ActivityCallThrough
public void onBackPressed() {
delegate.onBackPressed();
}
/**
* A result has been returned after an invocation of {@link Fragment#startActivityForResult(Intent, int)}.
* <p>
*
* @param requestCode request code sent with {@link Fragment#startActivityForResult(Intent, int)}
* @param resultCode code representing the result of the {@code Activity} that was launched
* @param data any corresponding return data, held within an {@code Intent}
*/
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
delegate.onActivityResult(requestCode, resultCode, data);
}
@ActivityCallThrough
public void onUserLeaveHint() {
delegate.onUserLeaveHint();
}
/**
* Callback invoked when memory is low.
* <p>
* This implementation forwards a memory pressure warning to the running Flutter app.
* <p>
*
* @param level level
*/
@ActivityCallThrough
public void onTrimMemory(int level) {
delegate.onTrimMemory(level);
}
/**
* Callback invoked when memory is low.
* <p>
* This implementation forwards a memory pressure warning to the running Flutter app.
*/
@Override
public void onLowMemory() {
super.onLowMemory();
delegate.onLowMemory();
}
@NonNull
private Context getContextCompat() {
return Build.VERSION.SDK_INT >= 23
? getContext()
: getActivity();
}
/**
* {@link FlutterActivityAndFragmentDelegate.Host} method that is used by
* {@link FlutterActivityAndFragmentDelegate} to obtain Flutter shell arguments when
* initializing Flutter.
*/
@Override
@NonNull
public FlutterShellArgs getFlutterShellArgs() {
String[] flutterShellArgsArray = getArguments().getStringArray(ARG_FLUTTER_INITIALIZATION_ARGS);
return new FlutterShellArgs(
flutterShellArgsArray != null ? flutterShellArgsArray : new String[]{}
);
}
/**
* Returns the desired {@link FlutterView.RenderMode} for the {@link FlutterView} displayed in
* this {@code NewFlutterFragment}.
* <p>
* Defaults to {@link FlutterView.RenderMode#surface}.
* <p>
* Used by this {@code NewFlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host}
*/
@Override
@NonNull
public FlutterView.RenderMode getRenderMode() {
String renderModeName = getArguments().getString(
ARG_FLUTTERVIEW_RENDER_MODE,
FlutterView.RenderMode.surface.name()
);
return FlutterView.RenderMode.valueOf(renderModeName);
}
/**
* Returns the desired {@link FlutterView.TransparencyMode} for the {@link FlutterView} displayed in
* this {@code NewFlutterFragment}.
* <p>
* Defaults to {@link FlutterView.TransparencyMode#transparent}.
* <p>
* Used by this {@code NewFlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host}
*/
@Override
@NonNull
public FlutterView.TransparencyMode getTransparencyMode() {
String transparencyModeName = getArguments().getString(
ARG_FLUTTERVIEW_TRANSPARENCY_MODE,
FlutterView.TransparencyMode.transparent.name()
);
return FlutterView.TransparencyMode.valueOf(transparencyModeName);
}
@Override
@Nullable
public SplashScreen provideSplashScreen() {
FragmentActivity parentActivity = getActivity();
if (parentActivity instanceof SplashScreenProvider) {
SplashScreenProvider splashScreenProvider = (SplashScreenProvider) parentActivity;
return splashScreenProvider.provideSplashScreen();
}
return null;
}
@Override
@Nullable
public FlutterEngine provideFlutterEngine(@NonNull Context context) {
return FlutterBoost.instance().engineProvider();
}
/**
* Hook for subclasses to obtain a reference to the {@link FlutterEngine} that is owned
* by this {@code FlutterActivity}.
*/
@Nullable
public FlutterEngine getFlutterEngine() {
return delegate.getFlutterEngine();
}
@Nullable
@Override
public PlatformPlugin providePlatformPlugin(@Nullable Activity activity, @NonNull FlutterEngine flutterEngine) {
if (activity != null) {
return new PlatformPlugin(getActivity(), flutterEngine.getPlatformChannel());
} else {
return null;
}
}
/**
* Configures a {@link FlutterEngine} after its creation.
* <p>
* This method is called after {@link #provideFlutterEngine(Context)}, and after the given
* {@link FlutterEngine} has been attached to the owning {@code FragmentActivity}. See
* {@link io.flutter.embedding.engine.plugins.activity.ActivityControlSurface#attachToActivity(Activity, Lifecycle)}.
* <p>
* It is possible that the owning {@code FragmentActivity} opted not to connect itself as
* an {@link io.flutter.embedding.engine.plugins.activity.ActivityControlSurface}. In that
* case, any configuration, e.g., plugins, must not expect or depend upon an available
* {@code Activity} at the time that this method is invoked.
* <p>
* The default behavior of this method is to defer to the owning {@code FragmentActivity}
* as a {@link FlutterEngineConfigurator}. Subclasses can override this method if the
* subclass needs to override the {@code FragmentActivity}'s behavior, or add to it.
* <p>
* Used by this {@code NewFlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host}
*/
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
FragmentActivity attachedActivity = getActivity();
if (attachedActivity instanceof FlutterEngineConfigurator) {
((FlutterEngineConfigurator) attachedActivity).configureFlutterEngine(flutterEngine);
}
}
/**
* See {@link NewEngineFragmentBuilder#shouldAttachEngineToActivity()} and
* <p>
* Used by this {@code NewFlutterFragment}'s {@link FlutterActivityAndFragmentDelegate}
*/
@Override
public boolean shouldAttachEngineToActivity() {
return true;
}
@Override
public String getContainerUrl() {
return getArguments().getString(EXTRA_URL);
}
@Override
public Map getContainerUrlParams() {
BoostFlutterActivity.SerializableMap serializableMap = (BoostFlutterActivity.SerializableMap) getArguments().getSerializable(EXTRA_PARAMS);
return serializableMap.getMap();
}
/**
* Annotates methods in {@code NewFlutterFragment} that must be called by the containing
* {@code Activity}.
*/
@interface ActivityCallThrough {
}
}
\ No newline at end of file
package com.idlefish.flutterboost.containers;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import com.idlefish.flutterboost.*;
import io.flutter.Log;
import io.flutter.embedding.android.FlutterView;
import io.flutter.embedding.android.SplashScreen;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
import java.util.Date;
/**
* {@code View} that displays a {@link SplashScreen} until a given {@link FlutterView}
* renders its first frame.
*/
public class FlutterSplashView extends FrameLayout {
private static String TAG = "FlutterSplashView";
private FlutterEngine mFlutterEngine;
@Nullable
private SplashScreen splashScreen;
@Nullable
private XFlutterView flutterView;
@Nullable
private View splashScreenView;
@Nullable
private Bundle splashScreenState;
@Nullable
private String transitioningIsolateId;
@Nullable
private String previousCompletedSplashIsolate;
private Handler handler = new Handler();
@NonNull
private final FlutterView.FlutterEngineAttachmentListener flutterEngineAttachmentListener = new FlutterView.FlutterEngineAttachmentListener() {
@Override
public void onFlutterEngineAttachedToFlutterView(@NonNull FlutterEngine engine) {
flutterView.removeFlutterEngineAttachmentListener(this);
}
@Override
public void onFlutterEngineDetachedFromFlutterView() {
}
};
@NonNull
private final OnFirstFrameRenderedListener onFirstFrameRenderedListener = new OnFirstFrameRenderedListener() {
int i = 0;
@Override
public void onFirstFrameRendered() {
if (FlutterBoost.instance().platform().whenEngineStart() == FlutterBoost.ConfigBuilder.FLUTTER_ACTIVITY_CREATED) {
long now = new Date().getTime();
long flutterPostFrameCallTime = FlutterBoost.instance().getFlutterPostFrameCallTime();
if (flutterPostFrameCallTime != 0 && (now - flutterPostFrameCallTime) > 800) {
if (splashScreen != null) {
transitionToFlutter();
}
return;
}
handler.postDelayed(new Runnable() {
@Override
public void run() {
onFirstFrameRenderedListener.onFirstFrameRendered();
}
}, 200);
} else {
if (splashScreen != null) {
transitionToFlutter();
}
}
}
};
@NonNull
private final Runnable onTransitionComplete = new Runnable() {
@Override
public void run() {
removeView(splashScreenView);
previousCompletedSplashIsolate = transitioningIsolateId;
}
};
public FlutterSplashView(@NonNull Context context) {
this(context, null, 0);
}
public FlutterSplashView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public FlutterSplashView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setSaveEnabled(true);
if (mFlutterEngine == null) {
mFlutterEngine = FlutterBoost.instance().engineProvider();
}
}
/**
* Displays the given {@code splashScreen} on top of the given {@code flutterView} until
* Flutter has rendered its first frame, then the {@code splashScreen} is transitioned away.
* <p>
* If no {@code splashScreen} is provided, this {@code FlutterSplashView} displays the
* given {@code flutterView} on its own.
*/
public void displayFlutterViewWithSplash(@NonNull XFlutterView flutterView, @Nullable SplashScreen splashScreen) {
// If we were displaying a previous FlutterView, remove it.
if (this.flutterView != null) {
this.flutterView.removeOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
removeView(this.flutterView);
}
// If we were displaying a previous splash screen View, remove it.
if (splashScreenView != null) {
removeView(splashScreenView);
}
// Display the new FlutterView.
this.flutterView = flutterView;
addView(flutterView);
this.splashScreen = splashScreen;
// Display the new splash screen, if needed.
if (splashScreen != null) {
splashScreenView = splashScreen.createSplashView(getContext(), splashScreenState);
splashScreenView.setBackgroundColor(Color.WHITE);
addView(this.splashScreenView);
flutterView.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
}
}
/**
* Returns true if a splash UI for a specific Flutter experience has already completed.
* <p>
* A "specific Flutter experience" is defined as any experience with the same Dart isolate
* ID. The purpose of this distinction is to prevent a situation where a user gets past a
* splash UI, rotates the device (or otherwise triggers a recreation) and the splash screen
* reappears.
* <p>
* An isolate ID is deemed reasonable as a key for a completion event because a Dart isolate
* cannot be entered twice. Therefore, a single Dart isolate cannot return to an "un-rendered"
* state after having previously rendered content.
*/
private boolean hasSplashCompleted() {
if (flutterView == null) {
throw new IllegalStateException("Cannot determine if splash has completed when no FlutterView "
+ "is set.");
}
if (!flutterView.isAttachedToFlutterEngine()) {
throw new IllegalStateException("Cannot determine if splash has completed when no "
+ "FlutterEngine is attached to our FlutterView. This question depends on an isolate ID "
+ "to differentiate Flutter experiences.");
}
// A null isolate ID on a non-null FlutterEngine indicates that the Dart isolate has not
// been initialized. Therefore, no frame has been rendered for this engine, which means
// no splash screen could have completed yet.
return flutterView.getAttachedFlutterEngine().getDartExecutor().getIsolateServiceId() != null
&& flutterView.getAttachedFlutterEngine().getDartExecutor().getIsolateServiceId().equals(previousCompletedSplashIsolate);
}
/**
* Transitions a splash screen to the Flutter UI.
* <p>
* This method requires that our FlutterView be attached to an engine, and that engine have
* a Dart isolate ID. It also requires that a {@code splashScreen} exist.
*/
private void transitionToFlutter() {
transitioningIsolateId = flutterView.getAttachedFlutterEngine().getDartExecutor().getIsolateServiceId();
Log.v(TAG, "Transitioning splash screen to a Flutter UI. Isolate: " + transitioningIsolateId);
splashScreen.transitionToFlutter(onTransitionComplete);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
handler.removeCallbacksAndMessages(null);
}
public void onAttach() {
Debuger.log("BoostFlutterView onAttach");
flutterView.attachToFlutterEngine(mFlutterEngine);
}
public void onDetach() {
Debuger.log("BoostFlutterView onDetach");
flutterView.detachFromFlutterEngine();
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Alibaba Group
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.idlefish.flutterboost.interfaces;
import android.content.Context;
import com.idlefish.flutterboost.BoostFlutterEngine;
/**
* a flutter engine provider
*/
public interface IFlutterEngineProvider {
/**
* create flutter engine, we just hold a single instance now
* @param context
* @return
*/
BoostFlutterEngine createEngine(Context context);
/**
* provide a flutter engine
* @param context
* @return
*/
BoostFlutterEngine provideEngine(Context context);
/**
* may return null
* @return
*/
BoostFlutterEngine tryGetEngine();
}
...@@ -25,7 +25,7 @@ package com.idlefish.flutterboost.interfaces; ...@@ -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);
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Alibaba Group
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.idlefish.flutterboost.interfaces;
import android.app.Application;
import android.content.Context;
import java.util.Map;
import io.flutter.plugin.common.PluginRegistry;
/**
* APIs that platform(Android App) must provide
*/
public interface IPlatform {
int IMMEDIATELY = 0; //立即启动引擎
int ANY_ACTIVITY_CREATED = 1; //当有任何Activity创建时,启动引擎
/**
* get current application
* @return
*/
Application getApplication();
/**
* debug or not
* @return
*/
boolean isDebug();
/**
* register plugins
* @return
*/
void registerPlugins(PluginRegistry registry);
void openContainer(Context context,String url,Map<String,Object> urlParams,int requestCode,Map<String,Object> exts);
void closeContainer(IContainerRecord record, Map<String,Object> result, Map<String,Object> exts);
IFlutterEngineProvider engineProvider();
/**
* @return
*
* IMMEDIATELY //立即
* ANY_ACTIVITY_CREATED //当有任何Activity创建的时候
* LAZY //懒加载,尽可能延后
*/
int whenEngineStart();
}
package com.idlefish.flutterboost.interfaces;
import com.idlefish.flutterboost.BoostChannel;
import com.idlefish.flutterboost.BoostFlutterEngine;
import com.idlefish.flutterboost.BoostFlutterView;
import io.flutter.plugin.common.PluginRegistry;
public interface IStateListener {
void onEngineCreated(BoostFlutterEngine engine);
void onEngineStarted(BoostFlutterEngine engine);
void onChannelRegistered(PluginRegistry.Registrar registrar, BoostChannel channel);
void onFlutterViewInited(BoostFlutterEngine engine, BoostFlutterView flutterView);
void beforeEngineAttach(BoostFlutterEngine engine, BoostFlutterView flutterView);
void afterEngineAttached(BoostFlutterEngine engine, BoostFlutterView flutterView);
void beforeEngineDetach(BoostFlutterEngine engine, BoostFlutterView flutterView);
void afterEngineDetached(BoostFlutterEngine engine, BoostFlutterView flutterView);
}
...@@ -25,7 +25,7 @@ apply plugin: 'com.android.application' ...@@ -25,7 +25,7 @@ apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android { android {
compileSdkVersion 26 compileSdkVersion 28
lintOptions { lintOptions {
disable 'InvalidPackage' disable 'InvalidPackage'
...@@ -35,7 +35,7 @@ android { ...@@ -35,7 +35,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.taobao.idlefish.flutterboostexample" applicationId "com.taobao.idlefish.flutterboostexample"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 26 targetSdkVersion 28
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
...@@ -53,11 +53,11 @@ android { ...@@ -53,11 +53,11 @@ android {
flutter { flutter {
source '../..' source '../..'
} }
dependencies { dependencies {
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.android.support:support-v4:26.1.0' implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:appcompat-v7:26.1.0'
} }
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.taobao.idlefish.flutterboostexample"> xmlns:tools="http://schemas.android.com/tools"
package="com.taobao.idlefish.flutterboostexample">
<!-- The INTERNET permission is required for development. Specifically, <!-- The INTERNET permission is required for development. Specifically,
flutter needs it to communicate with the running application flutter needs it to communicate with the running application
...@@ -33,24 +34,32 @@ ...@@ -33,24 +34,32 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".FlutterPageActivity" android:name="com.idlefish.flutterboost.containers.BoostFlutterActivity"
android:theme="@style/Theme.AppCompat" android:theme="@style/Theme.AppCompat"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"/> android:windowSoftInputMode="adjustResize" >
<meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/page_loading"/>
</activity>
<activity <activity
android:name=".FlutterFragmentPageActivity" android:name=".FlutterFragmentPageActivity"
android:theme="@style/Theme.AppCompat" android:theme="@style/Theme.AppCompat"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"/> android:windowSoftInputMode="adjustResize">
<meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/page_loading"/>
</activity>
<activity <activity
android:name=".NativePageActivity" android:name=".NativePageActivity"
android:theme="@style/Theme.AppCompat" android:theme="@style/Theme.AppCompat"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
android:windowSoftInputMode="adjustResize"/> android:windowSoftInputMode="adjustResize"/>
</application> </application>
</manifest> </manifest>
package com.taobao.idlefish.flutterboostexample;
import android.os.Bundle;
import android.support.annotation.Nullable;
import com.idlefish.flutterboost.containers.BoostFlutterFragment;
import java.util.HashMap;
import java.util.Map;
public class FlutterFragment extends BoostFlutterFragment {
@Override
public void setArguments(@Nullable Bundle args) {
if(args == null) {
args = new Bundle();
args.putString("tag","");
}
super.setArguments(args);
}
public void setTabTag(String tag) {
Bundle args = new Bundle();
args.putString("tag",tag);
super.setArguments(args);
}
@Override
public String getContainerUrl() {
return "flutterFragment";
}
@Override
public Map getContainerUrlParams() {
Map<String,String> params = new HashMap<>();
params.put("tag",getArguments().getString("tag"));
return params;
}
public static FlutterFragment instance(String tag){
FlutterFragment fragment = new FlutterFragment();
fragment.setTabTag(tag);
return fragment;
}
}
package com.taobao.idlefish.flutterboostexample; package com.taobao.idlefish.flutterboostexample;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
...@@ -9,10 +12,15 @@ import android.support.v7.app.AppCompatActivity; ...@@ -9,10 +12,15 @@ import android.support.v7.app.AppCompatActivity;
import android.view.View; import android.view.View;
import android.view.Window; import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.ImageView;
import com.idlefish.flutterboost.containers.FlutterFragment;
import io.flutter.embedding.android.DrawableSplashScreen;
import io.flutter.embedding.android.SplashScreen;
import io.flutter.embedding.android.SplashScreenProvider;
import io.flutter.plugin.platform.PlatformPlugin; import io.flutter.plugin.platform.PlatformPlugin;
public class FlutterFragmentPageActivity extends AppCompatActivity implements View.OnClickListener { public class FlutterFragmentPageActivity extends AppCompatActivity implements View.OnClickListener, SplashScreenProvider {
protected static final String SPLASH_SCREEN_META_DATA_KEY = "io.flutter.embedding.android.SplashScreenDrawable";
private FlutterFragment mFragment; private FlutterFragment mFragment;
...@@ -61,16 +69,18 @@ public class FlutterFragmentPageActivity extends AppCompatActivity implements Vi ...@@ -61,16 +69,18 @@ public class FlutterFragmentPageActivity extends AppCompatActivity implements Vi
if(mTab1 == v) { if(mTab1 == v) {
mTab1.setBackgroundColor(Color.YELLOW); mTab1.setBackgroundColor(Color.YELLOW);
mFragment = FlutterFragment.instance("tab1");
mFragment= new FlutterFragment.NewEngineFragmentBuilder().url("flutterFragment").build();
}else if(mTab2 == v) { }else if(mTab2 == v) {
mTab2.setBackgroundColor(Color.YELLOW); mTab2.setBackgroundColor(Color.YELLOW);
mFragment = FlutterFragment.instance("tab2"); mFragment= new FlutterFragment.NewEngineFragmentBuilder().url("flutterFragment").build();
}else if(mTab3 == v) { }else if(mTab3 == v) {
mTab3.setBackgroundColor(Color.YELLOW); mTab3.setBackgroundColor(Color.YELLOW);
mFragment = FlutterFragment.instance("tab3"); mFragment= new FlutterFragment.NewEngineFragmentBuilder().url("flutterFragment").build();
}else{ }else{
mTab4.setBackgroundColor(Color.YELLOW); mTab4.setBackgroundColor(Color.YELLOW);
mFragment = FlutterFragment.instance("tab4"); mFragment= new FlutterFragment.NewEngineFragmentBuilder().url("flutterFragment").build();
} }
getSupportFragmentManager() getSupportFragmentManager()
...@@ -84,4 +94,34 @@ public class FlutterFragmentPageActivity extends AppCompatActivity implements Vi ...@@ -84,4 +94,34 @@ public class FlutterFragmentPageActivity extends AppCompatActivity implements Vi
super.onResume(); super.onResume();
mTab1.performClick(); mTab1.performClick();
} }
@Nullable
@Override
public SplashScreen provideSplashScreen() {
Drawable manifestSplashDrawable = getSplashScreenFromManifest();
if (manifestSplashDrawable != null) {
return new DrawableSplashScreen(manifestSplashDrawable, ImageView.ScaleType.CENTER,500L);
} else {
return null;
}
}
private Drawable getSplashScreenFromManifest() {
try {
ActivityInfo activityInfo = getPackageManager().getActivityInfo(
getComponentName(),
PackageManager.GET_META_DATA | PackageManager.GET_ACTIVITIES
);
Bundle metadata = activityInfo.metaData;
Integer splashScreenId = metadata != null ? metadata.getInt(SPLASH_SCREEN_META_DATA_KEY) : null;
return splashScreenId != null
? Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP
? getResources().getDrawable(splashScreenId, getTheme())
: getResources().getDrawable(splashScreenId)
: null;
} catch (PackageManager.NameNotFoundException e) {
// This is never expected to happen.
return null;
}
}
} }
package com.taobao.idlefish.flutterboostexample;
import com.idlefish.flutterboost.containers.BoostFlutterActivity;
import java.util.HashMap;
import java.util.Map;
public class FlutterPageActivity extends BoostFlutterActivity {
/**
* 该方法返回当前Activity在Flutter层对应的name,
* 混合栈将会在flutter层根据这个名字,在注册的Route表中查找对应的Widget
*
* 在flutter层有注册函数:
* FlutterBoost.singleton.registerPageBuilders({
* 'first': (pageName, params, _) => FirstRouteWidget(),
* 'second': (pageName, params, _) => SecondRouteWidget(),
* ...
* });
*
* 该方法中返回的就是注册的key:first , second
*
* @return
*/
@Override
public String getContainerUrl() {
return "flutterPage";
}
/**
* 该方法返回的参数将会传递给上层的flutter对应的Widget
*
* 在flutter层有注册函数:
* FlutterBoost.singleton.registerPageBuilders({
* 'first': (pageName, params, _) => FirstRouteWidget(),
* 'second': (pageName, params, _) => SecondRouteWidget(),
* ...
* });
*
* 该方法返回的参数就会封装成上面的params
*
* @return
*/
@Override
public Map getContainerUrlParams() {
Map<String,String> params = new HashMap<>();
params.put("aaa","bbb");
return params;
}
}
...@@ -45,6 +45,8 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe ...@@ -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);
......
...@@ -3,66 +3,53 @@ package com.taobao.idlefish.flutterboostexample; ...@@ -3,66 +3,53 @@ package com.taobao.idlefish.flutterboostexample;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
import com.idlefish.flutterboost.BoostChannel; import android.util.Log;
import com.idlefish.flutterboost.BoostEngineProvider; import com.idlefish.flutterboost.*;
import com.idlefish.flutterboost.BoostFlutterEngine;
import com.idlefish.flutterboost.FlutterBoost;
import com.idlefish.flutterboost.Platform;
import com.idlefish.flutterboost.interfaces.IFlutterEngineProvider;
import java.util.Map; import java.util.Map;
import io.flutter.app.FlutterApplication; import com.idlefish.flutterboost.interfaces.INativeRouter;
import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.android.FlutterView;
import io.flutter.view.FlutterMain; import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MyApplication extends Application {
public class MyApplication extends FlutterApplication {
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
FlutterBoost.init(new Platform() { INativeRouter router =new INativeRouter() {
@Override @Override
public Application getApplication() { public void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts) {
return MyApplication.this; String assembleUrl=Utils.assembleUrl(url,urlParams);
PageRouter.openPageByUrl(context,assembleUrl, urlParams);
} }
@Override };
public boolean isDebug() {
return true;
}
@Override FlutterBoost.BoostPluginsRegister pluginsRegister= new FlutterBoost.BoostPluginsRegister(){
public void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts) {
PageRouter.openPageByUrl(context, url, urlParams, requestCode);
}
@Override @Override
public IFlutterEngineProvider engineProvider() { public void registerPlugins(PluginRegistry mRegistry) {
return new BoostEngineProvider() { GeneratedPluginRegistrant.registerWith(mRegistry);
@Override TextPlatformViewPlugin.register(mRegistry.registrarFor("TextPlatformViewPlugin"));
public BoostFlutterEngine createEngine(Context context) {
return new BoostFlutterEngine(context, new DartExecutor.DartEntrypoint(
context.getResources().getAssets(),
FlutterMain.findAppBundlePath(context),
"main"), "/");
}
};
} }
};
Platform platform= new FlutterBoost
.ConfigBuilder(this,router)
.isDebug(true)
.whenEngineStart(FlutterBoost.ConfigBuilder.ANY_ACTIVITY_CREATED)
.renderMode(FlutterView.RenderMode.texture)
.pluginsRegister(pluginsRegister)
.build();
FlutterBoost.instance().init(platform);
@Override
public int whenEngineStart() {
return ANY_ACTIVITY_CREATED;
}
});
BoostChannel.addActionAfterRegistered(new BoostChannel.ActionAfterRegistered() {
@Override
public void onChannelRegistered(BoostChannel channel) {
//platform view register should use FlutterPluginRegistry instread of BoostPluginRegistry
TextPlatformViewPlugin.register(FlutterBoost.singleton().engineProvider().tryGetEngine().getPluginRegistry());
}
});
} }
} }
...@@ -33,6 +33,9 @@ public class NativePageActivity extends AppCompatActivity implements View.OnClic ...@@ -33,6 +33,9 @@ public class NativePageActivity extends AppCompatActivity implements View.OnClic
@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");
if (v == mOpenNative) { if (v == mOpenNative) {
PageRouter.openPageByUrl(this, PageRouter.NATIVE_PAGE_URL,params); PageRouter.openPageByUrl(this, PageRouter.NATIVE_PAGE_URL,params);
} else if (v == mOpenFlutter) { } else if (v == mOpenFlutter) {
......
package com.taobao.idlefish.flutterboostexample; package com.taobao.idlefish.flutterboostexample;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.text.TextUtils; import android.util.Log;
import com.idlefish.flutterboost.containers.BoostFlutterActivity;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class PageRouter { public class PageRouter {
public final static Map<String, String> pageName = new HashMap<String, String>() {{
put("first", "first");
put("second", "second");
put("tab", "tab");
put("sample://flutterPage", "flutterPage");
}};
public static final String NATIVE_PAGE_URL = "sample://nativePage"; public static final String NATIVE_PAGE_URL = "sample://nativePage";
public static final String FLUTTER_PAGE_URL = "sample://flutterPage"; public static final String FLUTTER_PAGE_URL = "sample://flutterPage";
public static final String FLUTTER_FRAGMENT_PAGE_URL = "sample://flutterFragmentPage"; public static final String FLUTTER_FRAGMENT_PAGE_URL = "sample://flutterFragmentPage";
public static boolean openPageByUrl(Context context, String url,Map params) { public static boolean openPageByUrl(Context context, String url, Map params) {
return openPageByUrl(context, url,params, 0); return openPageByUrl(context, url, params, 0);
} }
public static boolean openPageByUrl(Context context, String url, Map params, int requestCode) { public static boolean openPageByUrl(Context context, String url, Map params, int requestCode) {
String path = url.split("\\?")[0];
Log.i("openPageByUrl",path);
try { try {
if (url.startsWith(FLUTTER_PAGE_URL)) { if (pageName.containsKey(path)) {
context.startActivity(new Intent(context, FlutterPageActivity.class)); Intent intent = BoostFlutterActivity.withNewEngine().url(pageName.get(path)).params(params)
.backgroundMode(BoostFlutterActivity.BackgroundMode.opaque).build(context);
if(context instanceof Activity){
Activity activity=(Activity)context;
activity.startActivityForResult(intent,requestCode);
}else{
context.startActivity(intent);
}
return true; return true;
} else if (url.startsWith(FLUTTER_FRAGMENT_PAGE_URL)) { } else if (url.startsWith(FLUTTER_FRAGMENT_PAGE_URL)) {
context.startActivity(new Intent(context, FlutterFragmentPageActivity.class)); context.startActivity(new Intent(context, FlutterFragmentPageActivity.class));
...@@ -27,9 +50,10 @@ public class PageRouter { ...@@ -27,9 +50,10 @@ public class PageRouter {
} else if (url.startsWith(NATIVE_PAGE_URL)) { } else if (url.startsWith(NATIVE_PAGE_URL)) {
context.startActivity(new Intent(context, NativePageActivity.class)); context.startActivity(new Intent(context, NativePageActivity.class));
return true; return true;
} else {
return false;
} }
return false;
} catch (Throwable t) { } catch (Throwable t) {
return false; return false;
} }
......
package com.taobao.idlefish.flutterboostexample; package com.taobao.idlefish.flutterboostexample;
import io.flutter.app.FlutterPluginRegistry; import io.flutter.app.FlutterPluginRegistry;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.StandardMessageCodec; import io.flutter.plugin.common.StandardMessageCodec;
public class TextPlatformViewPlugin { public class TextPlatformViewPlugin {
public static void register(FlutterPluginRegistry registry) { public static void register(PluginRegistry.Registrar registrar) {
registry.getPlatformViewsController().getRegistry().registerViewFactory("plugins.test/view", registrar.platformViewRegistry().registerViewFactory("plugins.test/view",
new TextPlatformViewFactory(StandardMessageCodec.INSTANCE)); new TextPlatformViewFactory(StandardMessageCodec.INSTANCE));
} }
} }
...@@ -3,12 +3,12 @@ ...@@ -3,12 +3,12 @@
// //
#import "GeneratedPluginRegistrant.h" #import "GeneratedPluginRegistrant.h"
#import <flutter_boost/BoostChannel.h> #import <flutter_boost/FlutterBoostPlugin.h>
@implementation GeneratedPluginRegistrant @implementation GeneratedPluginRegistrant
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry { + (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
[BoostChannel registerWithRegistrar:[registry registrarForPlugin:@"BoostChannel"]]; [FlutterBoostPlugin registerWithRegistrar:[registry registrarForPlugin:@"FlutterBoostPlugin"]];
} }
@end @end
...@@ -20,13 +20,13 @@ class _MyAppState extends State<MyApp> { ...@@ -20,13 +20,13 @@ class _MyAppState extends State<MyApp> {
'first': (pageName, params, _) => FirstRouteWidget(), 'first': (pageName, params, _) => FirstRouteWidget(),
'second': (pageName, params, _) => SecondRouteWidget(), 'second': (pageName, params, _) => SecondRouteWidget(),
'tab': (pageName, params, _) => TabRouteWidget(), 'tab': (pageName, params, _) => TabRouteWidget(),
'platformView': (pageName, params, _) => PlatformRouteWidget(),
'flutterFragment': (pageName, params, _) => FragmentRouteWidget(params), 'flutterFragment': (pageName, params, _) => FragmentRouteWidget(params),
///可以在native层通过 getContainerParams 来传递参数 ///可以在native层通过 getContainerParams 来传递参数
'flutterPage': (pageName, params, _) { 'flutterPage': (pageName, params, _) {
print("flutterPage params:$params"); print("flutterPage params:$params");
return FlutterRouteWidget(); return FlutterRouteWidget(params:params);
}, },
}); });
} }
......
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
typedef void TextViewCreatedCallback(TextViewController controller);
class TextView extends StatefulWidget {
const TextView({
Key key,
this.onTextViewCreated,
}) : super(key: key);
final TextViewCreatedCallback onTextViewCreated;
@override
State<StatefulWidget> createState() => _TextViewState();
}
class _TextViewState extends State<TextView> {
@override
Widget build(BuildContext context) {
if (defaultTargetPlatform == TargetPlatform.android) {
return AndroidView(
viewType: 'plugins.test/view',
onPlatformViewCreated: _onPlatformViewCreated,
);
}
return Text(
'$defaultTargetPlatform is not yet supported by the text_view plugin');
}
void _onPlatformViewCreated(int id) {
if (widget.onTextViewCreated == null) {
return;
}
widget.onTextViewCreated(new TextViewController._(id));
}
}
class TextViewController {
TextViewController._(int id)
: _channel = new MethodChannel('plugins.felix.angelov/textview_$id');
final MethodChannel _channel;
Future<void> setText(String text) async {
assert(text != null);
return _channel.invokeMethod('setText', text);
}
}
\ No newline at end of file
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_boost/flutter_boost.dart'; import 'package:flutter_boost/flutter_boost.dart';
import 'package:flutter_boost_example/platform_view.dart';
class FirstRouteWidget extends StatelessWidget { class FirstRouteWidget extends StatelessWidget {
@override @override
...@@ -14,7 +16,8 @@ class FirstRouteWidget extends StatelessWidget { ...@@ -14,7 +16,8 @@ class FirstRouteWidget extends StatelessWidget {
onPressed: () { onPressed: () {
print("open second page!"); print("open second page!");
FlutterBoost.singleton.open("second").then((Map value) { FlutterBoost.singleton.open("second").then((Map value) {
print("call me when page is finished. did recieve second route result $value"); print(
"call me when page is finished. did recieve second route result $value");
}); });
}, },
), ),
...@@ -66,90 +69,211 @@ class TabRouteWidget extends StatelessWidget { ...@@ -66,90 +69,211 @@ class TabRouteWidget extends StatelessWidget {
} }
} }
class FlutterRouteWidget extends StatelessWidget { class PlatformRouteWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title:Text("Platform Route"),
),
body: Center(
child: RaisedButton(
child: TextView(),
onPressed: () {
print("open second page!");
FlutterBoost.singleton.open("second").then((Map value) {
print(
"call me when page is finished. did recieve second route result $value");
});
},
),
),
);
}
}
class FlutterRouteWidget extends StatefulWidget {
FlutterRouteWidget({this.params,this.message});
final Map params;
final String message; final String message;
FlutterRouteWidget({this.message}); @override
_FlutterRouteWidgetState createState() => _FlutterRouteWidgetState();
}
class _FlutterRouteWidgetState extends State<FlutterRouteWidget> {
final TextEditingController _usernameController = TextEditingController();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final String message=widget.message;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
brightness:Brightness.light,
backgroundColor: Colors.white,
textTheme:new TextTheme(title: TextStyle(color: Colors.black)) ,
title: Text('flutter_boost_example'), title: Text('flutter_boost_example'),
), ),
body: Column( body: SingleChildScrollView(
crossAxisAlignment: CrossAxisAlignment.start, child:Container(
children: <Widget>[ margin: const EdgeInsets.all(24.0),
Container( child: Column(
margin: const EdgeInsets.only(top: 80.0), crossAxisAlignment: CrossAxisAlignment.start,
child: Text( children: <Widget>[
message ?? "This is a flutter activity", Container(
style: TextStyle(fontSize: 28.0, color: Colors.blue), margin: const EdgeInsets.only(top: 10.0,bottom: 20.0),
), child: Text(
alignment: AlignmentDirectional.center, message ?? "This is a flutter activity \n params:${widget.params}",
), style: TextStyle(fontSize: 28.0, color: Colors.blue),
Expanded(child: Container()), ),
InkWell( alignment: AlignmentDirectional.center,
child: Container( ),
padding: const EdgeInsets.all(8.0), // Expanded(child: Container()),
margin: const EdgeInsets.all(8.0), const CupertinoTextField(
color: Colors.yellow, prefix: Icon(
child: Text( CupertinoIcons.person_solid,
'open native page', color: CupertinoColors.lightBackgroundGray,
style: TextStyle(fontSize: 22.0, color: Colors.black), size: 28.0,
)), ),
padding: EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0),
clearButtonMode: OverlayVisibilityMode.editing,
textCapitalization: TextCapitalization.words,
autocorrect: false,
decoration: BoxDecoration(
border: Border(bottom: BorderSide(width: 0.0, color: CupertinoColors.inactiveGray)),
),
placeholder: 'Name',
),
InkWell(
child: Container(
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.all(8.0),
color: Colors.yellow,
child: Text(
'open native page',
style: TextStyle(fontSize: 22.0, color: Colors.black),
)),
///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。 ///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb ///例如:sample://nativePage?aaa=bbb
onTap: () => onTap: () => FlutterBoost.singleton
FlutterBoost.singleton.open("sample://nativePage", urlParams: { .open("sample://nativePage", urlParams: {
"query": {"aaa": "bbb"} "query": {"aaa": "bbb"}
}), }),
), ),
InkWell( InkWell(
child: Container( child: Container(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.all(8.0), margin: const EdgeInsets.all(8.0),
color: Colors.yellow, color: Colors.yellow,
child: Text( child: Text(
'open flutter page', 'open first',
style: TextStyle(fontSize: 22.0, color: Colors.black), style: TextStyle(fontSize: 22.0, color: Colors.black),
)), )),
///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。 ///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb ///例如:sample://nativePage?aaa=bbb
onTap: () => onTap: () => FlutterBoost.singleton
FlutterBoost.singleton.open("sample://flutterPage", urlParams: { .open("first", urlParams: {
"query": {"aaa": "bbb"} "query": {"aaa": "bbb"}
}), }),
), ),
InkWell( InkWell(
child: Container( child: Container(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.all(8.0), margin: const EdgeInsets.all(8.0),
color: Colors.yellow, color: Colors.yellow,
child: Text( child: Text(
'push flutter widget', 'open second',
style: TextStyle(fontSize: 22.0, color: Colors.black), style: TextStyle(fontSize: 22.0, color: Colors.black),
)), )),
onTap: () {
Navigator.push( ///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
context, MaterialPageRoute(builder: (_) => PushWidget())); ///例如:sample://nativePage?aaa=bbb
}, onTap: () => FlutterBoost.singleton
), .open("second", urlParams: {
InkWell( "query": {"aaa": "bbb"}
child: Container( }),
padding: const EdgeInsets.all(8.0), ),
margin: const EdgeInsets.all(8.0), InkWell(
color: Colors.yellow, child: Container(
child: Text( padding: const EdgeInsets.all(8.0),
'open flutter fragment page', margin: const EdgeInsets.all(8.0),
style: TextStyle(fontSize: 22.0, color: Colors.black), color: Colors.yellow,
)), child: Text(
onTap: () => 'open tab',
FlutterBoost.singleton.open("sample://flutterFragmentPage"), style: TextStyle(fontSize: 22.0, color: Colors.black),
), )),
],
///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb
onTap: () => FlutterBoost.singleton
.open("tab", urlParams: {
"query": {"aaa": "bbb"}
}),
),
InkWell(
child: Container(
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.all(8.0),
color: Colors.yellow,
child: Text(
'open flutter page',
style: TextStyle(fontSize: 22.0, color: Colors.black),
)),
///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb
onTap: () => FlutterBoost.singleton
.open("sample://flutterPage", urlParams: {
"query": {"aaa": "bbb"}
}),
),
InkWell(
child: Container(
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.all(8.0),
color: Colors.yellow,
child: Text(
'push flutter widget',
style: TextStyle(fontSize: 22.0, color: Colors.black),
)),
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (_) => PushWidget()));
},
),
InkWell(
child: Container(
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.all(8.0),
color: Colors.yellow,
child: Text(
'push Platform demo',
style: TextStyle(fontSize: 22.0, color: Colors.black),
)),
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (_) => PlatformRouteWidget()));
},
),
InkWell(
child: Container(
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.all(8.0),
color: Colors.yellow,
child: Text(
'open flutter fragment page',
style: TextStyle(fontSize: 22.0, color: Colors.black),
)),
onTap: () => FlutterBoost.singleton
.open("sample://flutterFragmentPage"),
),
],
),
),
), ),
); );
} }
......
...@@ -149,7 +149,7 @@ static NSUInteger kInstanceCounter = 0; ...@@ -149,7 +149,7 @@ static NSUInteger kInstanceCounter = 0;
- (void)setEnableForRunnersBatch:(BOOL)enable{ - (void)setEnableForRunnersBatch:(BOOL)enable{
//dummy function //dummy function
// NSLog(@"[DEBUG]- I did nothing, I am innocent"); NSLog(@"[DEBUG]- I did nothing, I am innocent");
} }
#pragma mark - Life circle methods #pragma mark - Life circle methods
...@@ -177,7 +177,7 @@ static NSUInteger kInstanceCounter = 0; ...@@ -177,7 +177,7 @@ static NSUInteger kInstanceCounter = 0;
} }
[super viewWillAppear:animated]; [super viewWillAppear:animated];
// //instead of calling [super viewWillAppear:animated];, call super's super //instead of calling [super viewWillAppear:animated];, call super's super
// struct objc_super target = { // struct objc_super target = {
// .super_class = class_getSuperclass([FlutterViewController class]), // .super_class = class_getSuperclass([FlutterViewController class]),
// .receiver = self, // .receiver = self,
...@@ -222,7 +222,7 @@ static NSUInteger kInstanceCounter = 0; ...@@ -222,7 +222,7 @@ static NSUInteger kInstanceCounter = 0;
params:_params params:_params
uniqueId:self.uniqueIDString]; uniqueId:self.uniqueIDString];
[super viewDidDisappear:animated]; [super viewDidDisappear:animated];
//// instead of calling [super viewDidDisappear:animated];, call super's super // instead of calling [super viewDidDisappear:animated];, call super's super
// struct objc_super target = { // struct objc_super target = {
// .super_class = class_getSuperclass([FlutterViewController class]), // .super_class = class_getSuperclass([FlutterViewController class]),
// .receiver = self, // .receiver = self,
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'container/boost_container.dart'; import 'container/boost_container.dart';
...@@ -60,6 +61,26 @@ class FlutterBoost { ...@@ -60,6 +61,26 @@ class FlutterBoost {
PrePushRoute prePush, PrePushRoute prePush,
PostPushRoute postPush}) { PostPushRoute postPush}) {
if(Platform.isAndroid){
WidgetsBinding.instance.addPostFrameCallback((_){
singleton.channel.invokeMethod<Map>('pageOnStart').then((Map pageInfo){
if (pageInfo == null || pageInfo.isEmpty) return;
if (pageInfo.containsKey("name") &&
pageInfo.containsKey("params") &&
pageInfo.containsKey("uniqueId")) {
ContainerCoordinator.singleton.nativeContainerDidShow(
pageInfo["name"], pageInfo["params"], pageInfo["uniqueId"]);
}
});
});
}
return (BuildContext context, Widget child) { return (BuildContext context, Widget child) {
assert(child is Navigator, 'child must be Navigator, what is wrong?'); assert(child is Navigator, 'child must be Navigator, what is wrong?');
......
name: flutter_boost name: flutter_boost
description: A next-generation Flutter-Native hybrid solution. FlutterBoost is a Flutter plugin which enables hybrid integration of Flutter for your existing native apps with minimum efforts. description: A next-generation Flutter-Native hybrid solution. FlutterBoost is a Flutter plugin which enables hybrid integration of Flutter for your existing native apps with minimum efforts.
version: 0.1.54 version: 0.1.60
author: Alibaba Xianyu author: Alibaba Xianyu
homepage: https://github.com/alibaba/flutter_boost homepage: https://github.com/alibaba/flutter_boost
...@@ -19,7 +19,7 @@ dependencies: ...@@ -19,7 +19,7 @@ dependencies:
flutter: flutter:
plugin: plugin:
androidPackage: com.idlefish.flutterboost androidPackage: com.idlefish.flutterboost
pluginClass: BoostChannel pluginClass: FlutterBoostPlugin
# To add assets to your plugin package, add an assets section, like this: # To add assets to your plugin package, add an assets section, like this:
# assets: # assets:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment