程序猿日常
flutter填坑——现有Android项目和现有iOS项目引入同一个Flutter项目
一.现有Android项目 引入 Flutter 项目(源码Module方法引入)
项目目录
flutter_module是AndLangBase(Android现有项目)和iOSLangBase(iOS现有项目)引用的同一个flutter module
1.Android Stdio升级至4.0.1版本
2.现有项目升级AndroidX
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
classpath 'com.android.tools.build:gradle:3.5.3'
//butterknife版本升级
classpath 'com.jakewharton:butterknife-gradle-plugin:9.0.0-rc2'
使用自动升级AndroidX功能 Refactor->Migrate to AndroidX
3.创建Flutter Module(flutter_module)
参考链接[https://flutter.cn/docs/development/add-to-app](https://flutter.cn/docs/development/add-to-app)
4.跳转Flutter页面报错
Add-to-app mode got NullPointerException when use BackgroundMode.transparent
解决:
不使用backgroundMode,注视掉下面代码,跳转flutter页面成功
.backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
5.Flutter继续热更新开发
在flutter module项目的目录下执行命令flutter attach,之后运行app项目,成功之后如下图所示,更新flutter代码时,只有在命令行输入R,就可以实现热更新
6.原生跳转Flutter页面
两种方式:
1.FlutterEngineCache 在application里提前注册
public static final String FLUTTER_ENGINE_ID="AndLangFlutter";
public static void initFlutter(Context context){
// Instantiate a FlutterEngine.
FlutterEngine flutterEngine = new FlutterEngine(context);
// Configure an initial route.
flutterEngine.getNavigationChannel().setInitialRoute("/");
// Start executing Dart code to pre-warm the FlutterEngine.
flutterEngine.getDartExecutor().executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
);
// Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment.
FlutterEngineCache
.getInstance()
.put(FLUTTER_ENGINE_ID, flutterEngine);
}
之后调用方法(注:只能跳转至setInitialRoute的路由地址,无法修改路由)
activity.startActivity(
FlutterActivity
.withCachedEngine(FLUTTER_ENGINE_ID)
.build(activity)
);
2.withNewEngine 跳转
activity.startActivity(
FlutterActivity
.withNewEngine()
.initialRoute(route)
.build(activity)
);
工具类FlutterUtil
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.android.FlutterActivityLaunchConfigs;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterEngineCache;
import io.flutter.embedding.engine.dart.DartExecutor;
public class FlutterUtil {
public static final String FLUTTER_ENGINE_ID="AndLangFlutter";
public static void initFlutter(Context context){
// Instantiate a FlutterEngine.
FlutterEngine flutterEngine = new FlutterEngine(context);
// Configure an initial route.
flutterEngine.getNavigationChannel().setInitialRoute("/");
// Start executing Dart code to pre-warm the FlutterEngine.
flutterEngine.getDartExecutor().executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
);
// Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment.
FlutterEngineCache
.getInstance()
.put(FLUTTER_ENGINE_ID, flutterEngine);
}
public static void startFlutterActivity(Activity activity,String route){
if(!BBCUtil.isEmpty(route)){
activity.startActivity(
new FlutterActivity.NewEngineIntentBuilder(FlutterSelfActivity.class)
.initialRoute(route)
.build(activity)
);
}else {
activity.startActivity(
FlutterSelfActivity
.withCachedEngine(FLUTTER_ENGINE_ID)
.build(activity)
);
}
}
}
FlutterSelfActivity 是继承FlutterActivity的用于flutter与Android进行交互
需要在AndroidManifest.xml中注册FlutterSelfActivity
FlutterSelfActivity这里采用的kotlin 代码如下:
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.example.test.andlang.util.LogUtil
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
import lombok.NonNull
class FlutterSelfActivity : FlutterActivity() {
private val CHANNEL = "com.lang.shop"
var methodChannel_callFlutter: MethodChannel? = null
@SuppressLint("NewApi")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
methodChannel_callFlutter = MethodChannel(flutterEngine!!.dartExecutor.binaryMessenger, CHANNEL)
setcallHandler()
}
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
//flutter 调用 原生方法
fun setcallHandler() {
methodChannel_callFlutter!!.setMethodCallHandler(){
call, result ->
LogUtil.d("0.0","flutter 调用原生方法:"+call.method+",参数:"+call.arguments)
if ("goBack" == call.method){
finish()
}else{
result.notImplemented()
}
}
}
}
二.现有iOS项目 引入 Flutter 项目(源码Module方法引入)
1.引入之前Android生成的Flutter Module
在 Podfile
中添加下面代码:
flutter_application_path = '../my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
每个需要集成 Flutter 的 [Podfile target][],执行 install_all_flutter_pods(flutter_application_path)
:
target 'MyApp' do
install_all_flutter_pods(flutter_application_path)
end
运行 pod install
这时候可能会报错
[!] No podspec found for `Flutter` in `../flutter_module/.ios/Flutter/engine`
解决办法:
在AndroidStdio中把Flutter Module选择iOS设备运行一次,再pod install 即可解决。
2.踩坑
运行报错: Could not parse callback cache, aborting restore
AppDelegate : FlutterAppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions
return true;
改为:
return [super application:application didFinishLaunchingWithOptions:launchOptions];
3.原生跳转Flutter页面 同Android
工具类FlutterUtil
#import "AppDelegate.h"
#import "FlutterUtil.h"
#import <Flutter/Flutter.h>
#import "FlutterVC.h"
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>
static NSString *FLUTTER_ENGINE_ID=@"iOSLangFlutter";
@implementation FlutterUtil
//获取url中的key的值
+(void)initFlutter{
((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine= [[FlutterEngine alloc] initWithName:FLUTTER_ENGINE_ID];
FlutterEngine *flutterEngine =
((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
// Runs the default Dart entrypoint with a default Flutter route.
[[flutterEngine navigationChannel] invokeMethod:@"setInitialRoute"arguments:@"/"];
[flutterEngine run];
// Used to connect plugins (only if you have plugins with iOS platform code).
[GeneratedPluginRegistrant registerWithRegistry:flutterEngine];
}
+(void)pushFlutterVC:(UINavigationController *)vc withRoute:(NSString *)route{
FlutterVC *flutterViewController =nil;
if(!IsStrEmpty(route)){
//设置flutter 路由
// flutterViewController=[[FlutterVC alloc]init];
// [flutterViewController setInitialRoute:route];
// flutterViewController.splashScreenView=[[UIView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)];
// [GeneratedPluginRegistrant registerWithRegistry:[flutterViewController pluginRegistry]];
FlutterEngine *flutterEngine =
((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
flutterViewController = [[FlutterVC alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
[flutterViewController pushRoute:route];
}else{
FlutterEngine *flutterEngine =
((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
flutterViewController = [[FlutterVC alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
}
//导航控制器入栈的方式切换页面
[vc pushViewController:flutterViewController animated:YES];
//模态切换的方式切换页面
//[vc presentViewController:flutterViewController animated:YES completion:nil];
}
@end
FlutterVC 是继承FlutterViewController的用于flutter与iOS进行交互
代码如下:
#import "FlutterVC.h"
@interface FlutterVC ()
@end
@implementation FlutterVC
- (instancetype)init{
[self initMethodChannel];
return [super init];
}
- (instancetype)initWithEngine:(FlutterEngine*)engine
nibName:(nullable NSString*)nibName
bundle:(nullable NSBundle*)nibBundle{
[self initMethodChannel];
return [super initWithEngine:engine nibName:nibName bundle:nibBundle];
}
-(void)initMethodChannel{
FlutterEngine *flutterEngine =
((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
FlutterMethodChannel *messageChannel = [FlutterMethodChannel methodChannelWithName:@"com.lang.shop" binaryMessenger:flutterEngine.binaryMessenger];
//创建flutter 回调iOS的channel对象
[messageChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
// call.method 获取 flutter 给回到的方法名,要匹配到 channelName 对应的多个 发送方法名,一般需要判断区分
// call.arguments 获取到 flutter 给到的参数,(比如跳转到另一个页面所需要参数)
// result 是给flutter的回调, 该回调只能使用一次
NSLog(@"flutter 调用原生方法 method=%@ \narguments = %@", call.method, call.arguments);
// method和WKWebView里面JS交互很像
if ([call.method isEqualToString:@"goBack"]) {
[self.navigationController popViewControllerAnimated:YES];
}else{
//调用了未知方法,则报告该方法
result(FlutterMethodNotImplemented);
}
}];
}
@end
三.看下flutter_module的代码
1.main.dart文件部分代码
import 'package:flutter/material.dart';
import 'dart:ui';
import 'package:flutter_module/common/LogUtil.dart';
import 'package:flutter_module/common/RouteUtil.dart';
void main() {
LogUtil.showLog("原生传递的参数:"+window.defaultRouteName);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or press Run > Flutter Hot Reload in a Flutter IDE). Notice that the
// counter didn't reset back to zero; the application is not restarted.
primarySwatch: Colors.blue,
),
initialRoute:"/",
routes:RouteUtil.initRouteMap(context),
home:initRoute(window.defaultRouteName),
);
}
}
方法initRoute中处理接收到Native的参数,返回对应的Widget
2.Flutter页面退出返回到Native页面
static void exitFlutter(BuildContext context){
if(Platform.isAndroid) {
// SystemChannels.platform.invokeMethod('SystemNavigator.pop');
}else {
// exit(0);
// 创建一个给native的channel (类似iOS的通知)
}
nativeFun('goBack', '');
}
static void nativeFun(String funName,String params){
const methodChannel = const MethodChannel(Const.NATIVE_CHANNEL_NAME);
methodChannel.invokeMethod(funName, params);
}
nativeFun方法调用原生方法goBack进行返回
四.flutter module源码不显示了
在项目里.idea目录下modules.xml中添加module标签 flutter_***为自己的flutter module名称
<module fileurl="file://$PROJECT_DIR$/../flutter_***/flutter_***.iml" filepath="$PROJECT_DIR$/../flutter_***/flutter_***.iml" />