参考:
https://flutter.dev/docs/development/add-to-app/android/project-setup
配置architectures
Flutter currently only supports building ahead-of-time (AOT) compiled libraries for armeabi-v7a and arm64-v8a.
android {
//...
defaultConfig {
ndk {
// Filter for architectures supported by Flutter.
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
}
}
android {
//...
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
}
Option A - Depend on the Android Archive (AAR)
This option allows your team to build the host app without installing the Flutter SDK.
cd some/path/my_flutter
$ flutter build aar
执行完后会生成以下文件:
build/host/outputs/repo
└── com
└── example
└── my_flutter
├── flutter_release
│ ├── 1.0
│ │ ├── flutter_release-1.0.aar
│ │ ├── flutter_release-1.0.aar.md5
│ │ ├── flutter_release-1.0.aar.sha1
│ │ ├── flutter_release-1.0.pom
│ │ ├── flutter_release-1.0.pom.md5
│ │ └── flutter_release-1.0.pom.sha1
│ ├── maven-metadata.xml
│ ├── maven-metadata.xml.md5
│ └── maven-metadata.xml.sha1
├── flutter_profile
│ ├── ...
└── flutter_debug
└── ...
android {
// ...
}
repositories {
maven {
url 'some/path/my_flutter/build/host/outputs/repo'
// This is relative to the location of the build.gradle file
// if using a relative path.
}
maven {
url 'http://download.flutter.io'
}
}
dependencies {
// ...
debugImplementation 'com.example.flutter_module:flutter_debug:1.0'
profileImplementation 'com.example.flutter_module:flutter_profile:1.0'
releaseImplementation 'com.example.flutter_module:flutter_release:1.0'
}
Option B - Depend on the module’s source code
This option enables a one-step build for both your Android project and Flutter project. This option is convenient when you work on both parts simultaneously and rapidly iterate, but your team must install the Flutter SDK to build the host app.
include ':app' // assumed existing content
setBinding(new Binding([gradle: this])) // new
evaluate(new File( // new
settingsDir.parentFile, // new
'my_flutter/.android/include_flutter.groovy' // new
)) // new
dependencies {
implementation project(':flutter')
}
A Flutter screen can be added as a normal, opaque screen, or as a see-through, translucent screen.
// flutter升级后,view相关的api有所变化,推荐尽量使用FlutterActivity的方式
FlutterActivity使用如下:
/*
* 这种形式启动,不能重写getInitialRoute方法,否则会死循环直至栈溢出
*/
class FlutterActivityPractise : FlutterActivity() {
var initParams:String?=null
companion object{
val EXTRA_INITIAL_ROUTE = "initial_route"
private const val PAGE_ROUTE = "order_list"
fun withNewEngine(): NewEngineIntentBuilder {
return object : NewEngineIntentBuilder(FlutterActivityPractise::class.java) {
}
}
fun start(context: Context, initParams: String?) {
//createJsonTemplate(PAGE_ROUTE,"url","args-----")
val intent = withNewEngine().initialRoute(initParams.let {
if(TextUtils.isEmpty(initParams)) "/." else initParams!!
}).build(context)
context.startActivity(intent)
}
private fun createJsonTemplate(
pageRoute: String?,
url: String?,
args: String?
): String {
return JsonTemplate(
pageRoute, url, args
).toJson()
}
}
}
/*
* 这种形式启动,可以重写getInitialRoute方法,对参数进一步处理
*/
class FlutterActivityPractise2 : FlutterActivity() {
private var initParams: String? = null
companion object{
val EXTRA_INITIAL_ROUTE = "initial_route"
private const val PAGE_ROUTE = "order_list"
fun start(context:Context){
val intent = Intent(context, FlutterActivityPractise2::class.java)
intent.putExtra(EXTRA_INITIAL_ROUTE,
createJsonTemplate(
PAGE_ROUTE,
"url",
"args-----"
)
)
context.startActivity(intent)
}
private fun createJsonTemplate(
pageRoute: String?,
url: String?,
args: String?
): String {
return JsonTemplate(
pageRoute, url, args
).toJson()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initParams = intent.getStringExtra(EXTRA_INITIAL_ROUTE);
}
override fun getInitialRoute(): String {
return createJsonTemplate(
PAGE_ROUTE,
"url",
"args--xxx---"
)
}
}
companion object{
const val ENGINE_ID = "1"
}
override fun onCreate() {
super.onCreate()
val flutterEngine = FlutterEngine(this)
flutterEngine
.dartExecutor
.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
FlutterEngineCache.getInstance().put(ENGINE_ID, flutterEngine)
}
If an Activity is equally applicable for your application needs, consider using a FlutterActivity instead of a FlutterFragment, which is quicker and easier to use.
FlutterFragment allows developers to control the following details of the Flutter experience within the Fragment:
1.当使用预热引擎时,对1的设置无效。(思考:考虑启动完,主动向本地请求跳转配置,重新刷新页面)
2. flutter对Android fragment提供了2种渲染方式,默认是surface,另一种是texture,2者区别,
当我们向应用中添加FlutterFragment时,默认的renderMode是surface,所以当出现从flutterFragment向本地的页面跳转时,flutterFragment会覆盖在目标页面上面,这时候手动设置一下renderMode为texture 就好了。
下面是我总结的2种添加FlutterFragment的方式:
FlutterFragment.withNewEngine()
// .dartEntrypoint("mySpecialEntrypoint")
.renderMode(FlutterView.RenderMode.texture)
// .transparencyMode(FlutterView.TransparencyMode.opaque)
// .shouldAttachEngineToActivity(true)
.initialRoute("order_list")
.build()
class TestFlutterFragment : FlutterFragment(){
private var name:String? = "szm"
companion object{
fun withNewEngine():CusEngineFragmentBuilder{
return CusEngineFragmentBuilder(TestFlutterFragment::class.java)
}
}
class CusEngineFragmentBuilder(@NonNull subclass: Class):NewEngineFragmentBuilder(subclass){
var name:String?=null
fun name(name:String):CusEngineFragmentBuilder{
this.name = name
return this
}
override fun createArgs(): Bundle {
val bundle = super.createArgs()
bundle.putString("name",name)
return bundle
}
}
// 提供过度页面,可定制
override fun provideSplashScreen(): SplashScreen? {
// // Load the splash Drawable.
// val splash: Drawable = activity.getResources().getDrawable(R.drawable.my_splash)
//
// // Construct a DrawableSplashScreen with the loaded splash Drawable and
// // return it.
// return DrawableSplashScreen(splash)
return SplashScreenWithTransition()
}
}
### 使用
val fragmentManager: FragmentManager = supportFragmentManager
flutterFragment = fragmentManager
.findFragmentByTag(TAG_FLUTTER_FRAGMENT) as TestFlutterFragment?
if (flutterFragment == null) {
var newFlutterFragment = TestFlutterFragment.withNewEngine().name("sm")
.dartEntrypoint("mySpecialEntrypoint")
.renderMode(FlutterView.RenderMode.texture)
.transparencyMode(FlutterView.TransparencyMode.opaque)
.shouldAttachEngineToActivity(true)
.initialRoute("fragment_no_ext_args")
.build()
flutterFragment = newFlutterFragment
fragmentManager
.beginTransaction()
.add(
R.id.fragment_container,
flutterFragment as Fragment,
TAG_FLUTTER_FRAGMENT
)
.commit()
}
参考:https://flutter.dev/docs/development/add-to-app/ios/project-setup
Create a Flutter module
Embed the Flutter module in your existing application
There are two ways to embed Flutter in your existing application.
Note: Your app will not run on a simulator in Release mode because Flutter does not yet support output x86 ahead-of-time (AOT) binaries for your Dart code. You can run in Debug mode on a simulator or a real device, and Release on a real device.
This method requires every developer working on your project to have a locally installed version of the Flutter SDK. Simply build your application in Xcode to automatically run the script to embed your Dart and plugin code. This allows rapid iteration with the most up-to-date version of your Flutter module without running additional commands outside of Xcode.
假设目录结构如下,
some/path/
├── my_flutter/
│ └── .ios/
│ └── Flutter/
│ └── podhelper.rb
└── MyApp/
└── Podfile
命令行进入根目录,执行pod init,生成pod文件,然后执行pod install 会在根目录生成一些其他安装文件,其中有一个xxx.xcworkspace文件,我们通过这个文件打开工程
flutter_application_path = '../my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'MyApp' do
install_all_flutter_pods(flutter_application_path)
end
Note: When you change the Flutter plugin dependencies in my_flutter/pubspec.yaml, run flutter pub get in your Flutter module directory to refresh the list of plugins read by the podhelper.rb script. Then, run pod install again from in your application atsome/path/MyApp.
Alternatively, you can generate the necessary frameworks and embed them in your application by manually editing your existing Xcode project. You may do this if members of your team can’t locally install Flutter SDK and CocoaPods, or if you don’t want to use CocoaPods as a dependency manager in your existing applications. You must run flutter build ios-framework every time you make code changes in your Flutter module.
flutter build ios-framework --output=some/path/MyApp/Flutter/
执行完会生成以下文件
some/path/MyApp/
└── Flutter/
├── Debug/
│ ├── Flutter.framework
│ ├── App.framework
│ ├── FlutterPluginRegistrant.framework
│ └── example_plugin.framework (each plugin with iOS platform code is a separate framework)
├── Profile/
│ ├── Flutter.framework
│ ├── App.framework
│ ├── FlutterPluginRegistrant.framework
│ └── example_plugin.framework
└── Release/
├── Flutter.framework
├── App.framework
├── FlutterPluginRegistrant.framework
└── example_plugin.framework
Tip: With Xcode 11 installed, you can generate XCFrameworks instead of universal frameworks by adding the flags --xcframework --no-universal.
For example, you can drag the frameworks from some/path/MyApp/Flutter/Release/ in Finder into your targets’s build settings > General > Frameworks, Libraries, and Embedded Content. Then, select “Embed & Sign” from the drop-down list.
In the target’s build settings, add $(PROJECT_DIR)/Flutter/Release/ to your Framework Search Paths (FRAMEWORK_SEARCH_PATHS).
Tip: To embed the Debug version of the Flutter frameworks in your Debug build configuration and the Release version in your Release configuration, in your MyApp.xcodeproj/project.pbxproj, try replacing path = Flutter/Release/example.framework; with path = “Flutter/$(CONFIGURATION)/example.framework”; for all added frameworks. (Note the added ".)
You must also add ( P R O J E C T D I R ) / F l u t t e r / (PROJECT_DIR)/Flutter/ (PROJECTDIR)/Flutter/(CONFIGURATION) to your Framework Search Paths build setting.
flutter提供了三种 native和dart 通信的机制:
一般我们使用MethodChannel就好了,下面我对dart端的通信进行了封装,并且附上使用方式
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// 封装通信相关的
abstract class AbsState extends State{
EventChannel _eventChannel;
MethodChannel _methodChannel ;
BasicMessageChannel _basicMessageChannel ;
StreamSubscription _streamSubscription;
void eventChannelListen(streamName,void onData(T event),
{Function onError, void onDone(), bool cancelOnError}){
if(_eventChannel == null){
_eventChannel = EventChannel('EventChannelPlugin');
}
_streamSubscription = _eventChannel.receiveBroadcastStream(streamName).listen(onData);
}
Future methodChannelInvoke(String method, [ dynamic arguments ]) async {
if(_methodChannel == null){
_methodChannel = const MethodChannel('MethodChannelPlugin');
}
return _methodChannel.invokeMethod(method,arguments);
}
void setBasicMessageChannelHandler(Future handler(dynamic message),){
if(_basicMessageChannel == null){
_basicMessageChannel =
const BasicMessageChannel('BasicMessageChannelPlugin',StringCodec());
}
_basicMessageChannel.setMessageHandler(handler);
}
Future send(dynamic message) async {
if(_basicMessageChannel == null){
_basicMessageChannel =
const BasicMessageChannel('BasicMessageChannelPlugin',StringCodec());
}
return _basicMessageChannel.send(message);
}
@override
void dispose() {
if(_streamSubscription != null){
_streamSubscription.cancel();
_streamSubscription = null;
}
super.dispose();
}
}
### 使用
class _OrderListPageState extends AbsState {
@override
void initState() {
eventChannelListen("we", onData_,onError: onError_);
setBasicMessageChannelHandler((message)=>Future((){
setState(() {
msg = 'BasicMessageChannel:'+message;
});
print('----msg--$msg');
return "收到Native的消息:" + message;
}));
bindMethodHandler(handler:_handleNavigationInvocation);
super.initState();
}
Void testUse(){
// methodChannel
methodChannelInvoke("jumpToReceiptPage",data.orderSn);
// _basicMessageChannel
send(value);
}
}
#### EventChannel
class EventChannelPlugin : EventChannel.StreamHandler {
private var eventSink:EventChannel.EventSink? = null
override fun onListen(p0: Any?, p1: EventChannel.EventSink?) {
Log.e("listen--",p0.toString())
eventSink = p1
}
override fun onCancel(p0: Any?) {
eventSink = null
}
companion object{
fun registerWith(flutterView: BinaryMessenger):EventChannelPlugin{
var eventChannelPlugin = EventChannelPlugin()
EventChannel(flutterView, "EventChannelPlugin").setStreamHandler(eventChannelPlugin)
return eventChannelPlugin
}
}
fun send(params:Any){
eventSink?.success(params)
}
}
#### MethodChannel
public class MethodChannelPlugin implements MethodChannel.MethodCallHandler {
private IMethodMsgCallback iMethodMsgCallback;
public void setiMethodMsgCallback(IMethodMsgCallback iMethodMsgCallback) {
this.iMethodMsgCallback = iMethodMsgCallback;
}
public static MethodChannelPlugin registerWith(BinaryMessenger flutterView){
MethodChannel methodChannel = new MethodChannel(flutterView,"MethodChannelPlugin");
MethodChannelPlugin instance = new MethodChannelPlugin();
methodChannel.setMethodCallHandler(instance);
return instance;
}
@Override
public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
switch (methodCall.method){// 处理来自Dart的方法调用
case "send":
showMessage(methodCall.arguments);
result.success("MethodChannelPlugin收到:" + methodCall.arguments);
break;
case "commonArgs":
result.success(parseMapToJson());
break;
default:
// result.notImplemented();
if(iMethodCallBack != null){
iMethodCallBack.onMethodChannelCallBack(methodCall,result);
}
}
}
private void showMessage(Object arguments) {
// Toast.makeText(activity, arguments.toString(), Toast.LENGTH_SHORT).show();
if(iMethodMsgCallback != null){
iMethodMsgCallback.onMsg(arguments.toString());
}
}
private String parseMapToJson(){
Gson gson = new Gson();
HashMap params = ApnInit.getHeads();
String token = Configurator.getInstance().getConfiguration(ConfigKeys.TOKEN);
if (!TextUtils.isEmpty(token)) {
params.put("token", token);
}
return gson.toJson(params);
}
public interface IMethodMsgCallback{
void onMsg(String msg);
}
private IMethodCallBack iMethodCallBack;
public void setiMethodCallBack(IMethodCallBack iMethodCallBack) {
this.iMethodCallBack = iMethodCallBack;
}
public interface IMethodCallBack{
void onMethodChannelCallBack(MethodCall methodCall, MethodChannel.Result result);
}
}
#### BasicMessageChannel
class BasicMessageChannelPlugin(var flutterView: BinaryMessenger) :
BasicMessageChannel.MessageHandler {
var iReceiveCallBack:IReceiveCallBack?= null
override fun onMessage(p0: String?, p1: BasicMessageChannel.Reply) {
Log.e("listen--","BasicMessageChannelPlugin------$p0")
iReceiveCallBack?.onMessage(p0,p1)
}
private var messageChannelPlugin: BasicMessageChannel =
BasicMessageChannel(flutterView,"BasicMessageChannelPlugin",StringCodec.INSTANCE)
companion object{
fun registerWith(binaryMessenger: BinaryMessenger):BasicMessageChannelPlugin{
return BasicMessageChannelPlugin(binaryMessenger)
}
}
init {
messageChannelPlugin.setMessageHandler(this)
}
fun send(message:String,callBack:BasicMessageChannel.Reply?){
messageChannelPlugin.send(message,callBack)
}
public interface IReceiveCallBack{
fun onMessage(p0: String?, p1: BasicMessageChannel.Reply)
}
}
import UIKit
import Flutter
import SnapKit
class ViewController: UIViewController {
let nativeController = TestViewController()
override func viewDidLoad() {
super.viewDidLoad()
let nativeView = nativeController.view!
nativeView.backgroundColor = UIColor.blue
view.addSubview(nativeView)
nativeView.snp.makeConstraints({make in
make.top.left.equalToSuperview()
make.width.equalToSuperview()
make.height.equalToSuperview().dividedBy(2)
})
if let flutterEngine = (UIApplication.shared.delegate as? FlutterApp)?.flutterEngine {
let flutterViewController = FlutterViewController(nibName: nil, bundle: nil)
// let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
flutterViewController.modalPresentationStyle = .overFullScreen
// flutterViewController.setInitialRoute("fragment_no_ext_args")
flutterViewController.setInitialRoute("order_list")
view.addSubview(flutterViewController.view)
flutterViewController.view.snp.makeConstraints({
$0.left.equalToSuperview()
$0.top.equalTo(nativeView.snp_bottom)
$0.width.equalToSuperview()
$0.height.equalToSuperview().dividedBy(2)
})
self.addChild(flutterViewController)
//methodChannel
let methodChannel = FlutterMethodChannel(name: "MethodChannelPlugin", binaryMessenger: flutterViewController.binaryMessenger)
methodChannel.setMethodCallHandler { call, result in
switch call.method{
case "send":
self.nativeController.updateTvShow(txt: call.arguments as! String)
default:
print(call.method)
}
}
//FlutterBasicMessageChannel
let basicMessageChannel = FlutterBasicMessageChannel(name: "BasicMessageChannelPlugin", binaryMessenger: flutterViewController.binaryMessenger,codec: FlutterStringCodec.sharedInstance())
basicMessageChannel.setMessageHandler { (p0, reply) in
self.nativeController.updateTvShow(txt:p0 as! String)
}
//FlutterEventChannel
let eventChannel = FlutterEventChannel(name: "EventChannelPlugin", binaryMessenger: flutterViewController.binaryMessenger)
eventChannel.setStreamHandler(self)
nativeController.buttonClickBlock { (msg) in
if self.nativeController.useMethodChannel(){
basicMessageChannel.sendMessage(msg)
}else{
methodChannel.invokeMethod("send", arguments: msg)
}
}
}
}
}
extension ViewController: FlutterStreamHandler {
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
self.nativeController.updateTvShow(txt: arguments as! String)
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
return nil
}
}