來源: Android 團隊 - 餘自然android
本文主要解決3個問題:ios
這裏,咱們以Flutter Module建立一個Flutter工程(flutter),而後run起來,就能夠在.android/Flutter/build/outouts/aar文件夾下面獲得這個aar git
這裏之因此以Flutter Module模式開發,而不是Flutter Application,就是爲了獲得這個aar。 Flutter Module模式下自動生成的.android文件夾下,纔會有這個Flutter文件夾,Flutter Application則沒有。 這樣的話,咱們才能夠借用Flutter已經有的生成aar的gradle腳本,否則還得本身去寫gradle打包腳本,很容易踩到坑裏就爬不起來了。github
而後咱們再另開一個窗口,新建一個Android工程(flutter_container),將這個aar複製過去 bash
這裏須要注意的一個問題,由於Flutter自己緣由,致使複製出來的aar裏面缺乏icudtl.dat文件,須要咱們本身手動複製這個icudtl.dat文件到assets/flutter_shared目錄下。app
怎麼獲得這個icudtl.dat文件呢,很簡單,解壓Flutter工程生成的默認apk便可獲得 less
而後,咱們就須要在宿主Android工程裏面,創建接收Flutter的Activity了。這裏能夠借鑑Flutter工程的.android/app目錄,核心就是兩個:async
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
FlutterMain.startInitialization(this);
}
}
複製代碼
/**
* debug模式原生跳轉到flutter界面會出現白屏,release包就不會出現白屏了
*/
public class MainFlutterActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}
複製代碼
這樣之後,咱們就能夠跳轉這個MainFlutterActivity,實如今Android工程裏面進入Flutter工程的默認頁面了。ide
上面只是簡單集成了Flutter,可是咱們知道,咱們從Android工程裏面跳轉Flutter,確定是須要選擇性的跳轉指定頁面的,不可能只是簡單的跳轉默認頁面就完了,因此,這裏須要用到Flutter的靜態路由了。佈局
修改Flutter工程的main.dart,定義了兩個指定頁面的路由:homePage、channelPage
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: TestPage(),
//這種方式不能傳遞參數,主要是方便原生調用
routes: <String, WidgetBuilder> {
'homePage': (BuildContext context) => new HomePage(),
'channelPage': (BuildContext context) => new ChannelPage(),
},
);
}
}
複製代碼
而後在宿主Android工程下,添加指定頁面的容器Activity,經過Flutter.createView來獲取指定頁面的View
注意,這裏的HomeFlutterActivity只須要繼承AppCompatActivity 便可,不須要繼承FlutterActivity了。
public class HomeFlutterActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FlutterView homePage = Flutter.createView(
this,
getLifecycle(),
"homePage"
);
setContentView(homePage);
}
}
複製代碼
這樣之後,咱們就能夠跳轉這個HomeFlutterActivity,實如今Android工程裏面進入Flutter工程的指定頁面了。
上面雖然可以跳轉指定頁面了,可是很顯然,有一個很大的問題:不能傳遞參數。
這是Flutter的靜態路由的一個很大的弊端,雖然經過動態路由能夠傳遞參數和接收返回值,可是動態路由無法給原生調用。
Navigator.of(context)
.push<String>(new MaterialPageRoute(builder: (context) {
return new NextPage(params);
})).then((String value) {
setState(() {
params = value;
});
});
複製代碼
有一個Flutter的路由庫:Fluro,能夠實現靜態路由傳參,例如這樣:
傳參
var bodyJson = '{"user":1281,"pass":3041}';
router.navigateTo(context, '/home/$bodyJson');
複製代碼
接收
Router router = new Router();
void main() {
router.define('/home/:data', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, dynamic> params) {
return new FluroHomePage(params['data'][0]);
}));
runApp(MyApp());
}
複製代碼
可是,這種方式在Flutter內部還行,卻沒法給原生調用,在原生裏面經過Flutter.createView的時候,是無法使用Fluro的,只能是默認的路由。
調研了不少方案,最後,沒有辦法了,只好採用最笨的方法:經過MethodChannel來傳遞參數。
這裏須要注意的是MethodChannel的調用,應該FlutterView已經建立完成,因此須要經過flutterView.post(new Runnable())來執行了,直接執行是不會傳參給Flutter的。
原生調用
MethodChannel channel = new MethodChannel(flutterView, CHANNEL);
channel.invokeMethod("invokeFlutterMethod", "hello,flutter", new MethodChannel.Result() {
@Override
public void success(@Nullable Object o) {
Log.i("flutter","1.原生調用invokeFlutterMethod-success:"+o.toString());
}
@Override
public void error(String s, @Nullable String s1, @Nullable Object o) {
Log.i("flutter","1.原生調用invokeFlutterMethod-error");
}
@Override
public void notImplemented() {
Log.i("flutter","1.原生調用invokeFlutterMethod-notImplemented");
}
});
複製代碼
Flutter執行
platform.setMethodCallHandler((handler) {
Future<String> future=Future((){
switch (handler.method) {
case "invokeFlutterMethod":
String args = handler.arguments;
print("2.Flutter執行invokeFlutterMethod:${args}");
return "this is flutter result";
}
});
return future;
});
複製代碼
Flutter調用
print("3.Flutter調用invokeNativeMethod");
int result =
await platform.invokeMethod("invokeNativeMethod", "hello,native");
print("5.收到原生執行結果:${result}");
複製代碼
原生執行
channel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
switch (call.method) {
case "invokeNativeMethod":
String args = (String) call.arguments;
Log.i("flutter","4.原生執行invokeNativeMethod:"+args);
result.success(200);
break;
default:
}
}
});
複製代碼
最後貼一下這個傳參頁面的完整代碼吧,主要就是跑了一下:
Android:
public class ChannelFlutterActivity extends AppCompatActivity {
private static final String CHANNEL = "com.ezbuy.flutter";
FlutterView flutterView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_channel);
FrameLayout frFlutter = findViewById(R.id.fr_flutter);
flutterView = getFlutterView("channelPage");
frFlutter.addView(flutterView);
flutterView.post(new Runnable() {
@Override
public void run() {
initMethodChannel(flutterView);
}
});
}
public FlutterView initMethodChannel(FlutterView flutterView) {
MethodChannel channel = new MethodChannel(flutterView, CHANNEL);
//1.原生調用Flutter方法
channel.invokeMethod("invokeFlutterMethod", "hello,flutter", new MethodChannel.Result() {
@Override
public void success(@Nullable Object o) {
Log.i("flutter","1.原生調用invokeFlutterMethod-success:"+o.toString());
}
@Override
public void error(String s, @Nullable String s1, @Nullable Object o) {
Log.i("flutter","1.原生調用invokeFlutterMethod-error");
}
@Override
public void notImplemented() {
Log.i("flutter","1.原生調用invokeFlutterMethod-notImplemented");
}
});
Log.i("flutter","1.原生調用invokeFlutterMethod");
//4.Flutter調用原生方法的監聽
channel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
switch (call.method) {
case "invokeNativeMethod":
String args = (String) call.arguments;
Log.i("flutter","4.原生執行invokeNativeMethod:"+args);
result.success(200);
break;
default:
}
}
});
return flutterView;
}
public FlutterView getFlutterView(String initialRoute) {
return Flutter.createView(
this,
getLifecycle(),
initialRoute
);
}
}
複製代碼
Flutter
class ChannelPage extends StatefulWidget {
ChannelPage();
@override
_ChannelPageState createState() => _ChannelPageState();
}
class _ChannelPageState extends State<ChannelPage> {
static const platform = const MethodChannel('com.ezbuy.flutter');
String data;
@override
void initState() {
super.initState();
data ="默認data";
initChannel();
}
@override
Widget build(BuildContext context) {
//必須用Scaffold包裹
return Scaffold(body: new Center(child: new Text(data)));
}
void initChannel() {
platform.setMethodCallHandler((handler) {
Future<String> future=Future((){
switch (handler.method) {
case "invokeFlutterMethod":
String args = handler.arguments;
print("2.Flutter執行invokeFlutterMethod:${args}");
setState(() {
data = "2.Flutter執行invokeFlutterMethod:${args}";
});
invokeNativeMethod();
return "this is flutter result";
}
});
return future;
});
}
void invokeNativeMethod() async {
print("3.Flutter調用invokeNativeMethod");
int result =
await platform.invokeMethod("invokeNativeMethod", "hello,native");
print("5.收到原生執行結果:${result}");
}
}
複製代碼
對啦,咱們這節說的是將Flutter以View級別嵌套在一個Android的Activity裏面,其實很簡單了啊,由於咱們經過Flutter.createView建立出來的View和普通的View並無什麼太大的區別,直接addView就能夠了,沒啥特別操做,好比這個ChannelFlutterActivity,我用的佈局文件就是以下所示:
最後的執行效果就是:
個人Flutter工程依賴了shared_preferences插件,致使報錯:
緣由是:Flutter工程導出成aar的時候,沒有包含插件裏面的原生代碼。
解決方案有2種,網上說是不用默認的生成aar的方式,用fataar-gradle-plugin來讓生成的flutter.aar直接包含嵌套的插件工程的aar,這就須要修改Flutter工程的.android/Flutter/build.gradle文件了。我試過,結果報了循環依賴的錯誤,就放棄了,你們若是這個方案走通了,歡迎告知我具體步驟。
個人解決方案:這裏我採起了一個簡單粗暴直接的方案,直接找到插件的aar,將它也複製到宿主Android工程了。這個插件的aar在這裏:
複製到這裏:
可是這個方案的弊端就是,之後每個插件,你都須要複製一下,後期的維護成本是有點高的。不像fataar是一勞永逸,只有flutter.aar這一份aar的。尤爲是後期確定會將aar作成遠程依賴,而再也不是直接發覆制過去,那維護成本就更高了些。
經過上文能夠看到,其實Flutter集成到Android項目仍是挺方便的(除了FlutterView傳參有點麻煩)。至於Flutter如何集成到ios項目,我尚未實踐過,還須要和ios的同事探索,若是你在集成到ios項目的過程當中,填了哪些坑,有哪些經驗總結,歡迎和咱們交流。