当前位置:网站首页>An efficient flutter hybrid stack management scheme with zero intrusion, you deserve it!
An efficient flutter hybrid stack management scheme with zero intrusion, you deserve it!
2022-06-29 04:38:00 【Light up your street lamp】
In the actual work scenario , It's hard for us to start from scratch with pure Flutter To build a project , That's why ,Native+Flutter The jump management of hybrid stack is the first problem we have to consider in hybrid development , Because it's hard to guarantee that we won't encounter the following situations .

So how to do technical selection puzzles many students who want to be mixed , After all Flutter Our ecology is not very mature , There are not many ready-made solutions and wheels , And it's not necessarily easy to use , Or the resource occupation is too high , Or it's too invasive . Fortunately, after these days of exploration , Summed up a set of plans , For your reference , Communicate with each other .
According to international practice , Let me first introduce some solutions and existing problems in the current market .
This article is based on Flutter edition :2.2 & Platform:Android
1.Google official ( Multi engine solutions )
That is, one new... At a time FlutterEngine To render Widget Trees . although Flutter 2.0 Later creation FlutterEngine The cost of is greatly reduced , But it still hasn't solved every problem FlutterEngine It's a single isolate, if necessary Flutter① and Flutter② If you interact with data , It will be very troublesome . We also cannot guarantee that there will be no data interaction between them , therefore Pass.
2. The famous idle fish flutter_boost( Single engine solution )
flutter_boost Recently released 3.0 Of bate edition , Abandoned 2.0 Version intrusion into the engine ( Fabulous !), But there are still many problems :
①: High up, not closed issues The contradiction between not responding in time ( You can understand , There's still work to do ).
②: Complex design , In case of use problems , By changing flutter_boost The cost of source code solution is very high .
③:flutter The coupling degree is high , I have to use flutter_boost A series of Route Related tools and Widget To achieve the effect of mixed stack jump , If I want to change the frame later , The changes to the code will be massive .
So he succeeded in persuading me to retreat .
3. Hello, bike team flutter_thrio( Single engine solution )
The advantages and disadvantages of the library have been described in detail by the author , No more details here , Interested friends can go into the portal and check it in person .
4. It's the team's Isolate Reuse scheme and Tencent Xinyue team TRouter programme
It is a pity that , At present, these two schemes are not open source , But it is likely that the byte team's solution is quite intrusive .
Since there is no ready-made plan , Then roll up your sleeves and make one , Let's start with a demonstration of the effect of hybrid stack jump :
Project address : github.com/wangkunhui/…

Now let's implement the above functions .
First , To determine the ultimate goal , Then step by step towards the goal to improve :
Goal one : Reuse FlutterEngine, Avoid additional resource overhead and FlutterEngine Communication costs between .
Goal two :Flutter Widget The jump between does not need to pass Native Layer control (flutter_boost need ).
Goal three : Every open Flutter Can and can only manage their own internal stack , At present Flutter Of Widget After all out of the stack , Quit current Flutter.
Goal four : Support Flutter Open with parameters Native page ( The return value can also support , But it hasn't been added yet ).
The model diagram designed according to these objectives is as follows :

The above figure is opened cross in turn 5 individual Activity Schematic diagram of stack information , It can be divided into three columns , The specific explanation is as follows :
On the left is Activity Stack information of , The second and fourth of them are mounted Flutter, And opened several Flutter Of Widget.
In the middle is FlutterEngine Medium Widget Stack diagram , Because reuse FlutterEngine Why ,Widget A To Widget P All exist in a stack , At the bottom is a blank HostWidget, Not responsible for any business logic , Its purpose is to ensure that the business above it is related to Widget Can be normally Pop Out of the stack ( because Flutter Navigator The last Widget It can't be Pop Of ).
On the right is the middle FlutterEngine in Widget Pipeline information description of stack , Those are those. Widget It's related to those HostActivity Example of , In order to be able to control my Widget When performing a stack withdrawal operation , Know where to retreat Widget The need when finish Drop the associated Activity.
If the above description is not very intuitive , Let me give you a chestnut to illustrate , I am here HostActivity example 2 Open two Flutter page ,Widget O and Widget P, When these two Widget After you finish your task , You have to exit , When I quit Widget P when , The interface becomes Widget O, That's fine , Then I continue to quit WIdget O, If there is no special treatment ,FlutterEngine Will show Widget N, That's clearly not what we want . Our expected result is to finish fall HostActivity example 2, Exhibition NativeOneActivity, I want to do that , You need to think of FlutterEngine The stack is different HostActivity The... Shown in the example Widget Make a special distinction .
Okay , The theoretical work has been done , Then there's a happy Coding time , The list of tasks we face is as follows :
One 、 How to FlutterEngine Reuse ?
Two 、FlutterActivity、FlutterFragment still FlutterView?
3、 ... and 、 How to listen FlutterEngine Stack change information ?
Four 、 How to synchronize the monitored stack information to the host Activity example ?
5、 ... and 、 How to deal with the host Activity and Flutter Page synchronization ?
The first question is , Instantiate first FlutterEngine:
/**
* initialization FlutterEngine
* @param context Context
* @param block Initialization status callback 0 No initialization required 1 Start initializing 2 Initialization done
*/
@Synchronized
fun initFlutterEngine(context: Context, block: (status: Int) -> Unit) {
// Determine that the cache already exists
if (!FlutterEngineCache.getInstance().contains(FLUTTER_ENGINE)) {
block(1)
// initialization FlutterEngine
var engine = FlutterEngine(context.applicationContext)
// initialization BasicMessageChannel
messageChannel = BasicMessageChannel(
engine.dartExecutor,
FLUTTER_MIX_STACK_CHANNEL,
StringCodec.INSTANCE
)
// Add message listening
messageChannel?.setMessageHandler(messageHandler)
this.initCallback = block
// Began to run dart Code
engine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
// cache FlutterEngine
FlutterEngineCache.getInstance().put(FLUTTER_ENGINE, engine)
} else {
block(0)
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
FlutterEngineCache It's sealed for us FlutterEngine Cache function of , But how to use this cache ?
Official FlutterActivity There is one in the class withCachedEngine Static method of , It can help us get the information using cache Intent example , Can also inherit FlutterActivity rewrite getCachedEngineId Method , achieve FlutterEngine The effect of reuse , Direct start FlutterActivity It can be loaded Flutter page , Very simple and convenient , But if there are some slightly complex scenes ,FlutterActivity Some are not enough .
The second question is ,Flutter 2.0 It provides us with three Flutter Containers , Namely FlutterActivity(FlutterFragmentActivity)、FlutterFragment and FlutterView, To meet our use in different scenarios , From the richness of these three class annotation documents , It is highly recommended that we directly use FlutterActivity Of . Simply look at the source code and you'll find , Whether it's FlutterActivity still FLutterFragment, It's all based on FlutterView Realized . If the project has a specified base class, it needs to inherit or implement native UI+Flutter UI The situation of , Or want to warm up earlier Flutter Of Widget,FlutterView It will undoubtedly be more flexible , So here I choose to use the ordinary Activity+FlutterView, The pseudocode is as follows :
class HostActivity : AppCompatActivity(){
var flutterEngine // From the cache FlutterEngine
var flutterChannel // BasicMessageChannel There will be a special introduction below
var flutterView
onCreate(){
// stay onCreate At the beginning of the method call , Just preheat in advance FlutterEngine, You can make Widget Load more smoothly
flutterEngine.lifecycleChannel.appIsResumed()
// Also in onCreate Send a message to FluuterEngine, Replace what we want to load in advance Widget, The method has a transition effect
flutterChannel.sendMessage(routePath)
super.onCreate()
setContentView(flutterView = createFlutterView())
}
onResume(){
super()
flutterEngine.lifecycleChannel.appIsResumed()
}
onPause(){
super()
flutterEngine.lifecycleChannel.appIsPaused()
}
onDestroy(){
super()
flutterView.detachFromFlutterEngine()
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
Third question , monitor FlutterEngine in Widget Changes in stack information , stay MaterialApp When initializing , There is one navigatorObservers Parameters of , Support us to add navigator Change information for (Navigator yes Flutter Page switching class provided ).
void main() {
// It's used here Get Framework to demonstrate , But it's not necessary
var getApp = GetMaterialApp(
initialRoute: RouterMapper.ROUTER_HOME,
getPages: [
GetPage(
name: RouterMapper.ROUTER_HOST, // blank HostView
page: () => Host(),
transition: Transition.rightToLeft),
...
],
navigatorObservers: [RouteNavigatorObserver()],
);
runApp(getApp);
RouterHelper.registerApp(); // Register message listener here
}
class RouteNavigatorObserver extends NavigatorObserver {
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
// Yes Widget Push It can be downloaded from route In order to get name Information and sync to Native
}
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
// Yes Widget Out of the stack It can be downloaded from route In order to get name Information and sync to Native
}
@override
void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {
// new Widget Replaced the previous Widget It can be downloaded from newRoute&oldRoute In order to get name Information and sync to Native
}
@override
void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) {
// One Widget By remove 了 , This is generally not recommended
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
By inheritance NavigatorObserver Class and register to navigatorObservers in , We have achieved the right FlutterEngine The middle road is monitored by information changes , So let's move on to the next question , How do we synchronize messages to Native?
** Fourth question ,**Flutter There are three ways to communicate with Native Interactive data , Namely BasicMessageChannel,MethodChannel and EventChannel, The first two can transfer data in both directions ,EventChannel Only support Native to Flutter To transfer data , A notification of a system message .MethodChannel Used to pass method calls ,BasicMessageChannel Used to transfer binary data , More suitable for our trial scenario , And then we need to Flutter and Native Stratified BasicMessageChannel For data interaction .
Then let's separate in Flutter and Native Layer creation BasicMessageChannel:
//Flutter
class RouterHelper {
// Register message channel
static final _routerMessageChannel =
BasicMessageChannel<String?>("flutter_router_channel", StringCodec());
static registerApp() {
// Prevent the following empty exception
WidgetsFlutterBinding.ensureInitialized();
// Register message monitor
_routerMessageChannel.setMessageHandler((String? message) async {
if (message != null) {
//native Message from layer
}
});
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
//Kotlin
fun initMessageChannel(){
var messageChannel = BasicMessageChannel(
engine.dartExecutor,
"flutter_router_channel",
StringCodec.INSTANCE)
messageChannel.setMessageHandler{ message, reply ->
// Handle Flutter Message sent
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
The message is sent by BasicMessageChannel Provided send Method realization , In the above question , We've been listening Navigator To judge Flutter Page change information in , Next, in the monitoring method , Send the corresponding information to Native Layer for processing , stay Native Layer, we can abstract out an interface , Give Way HostActivity Implement this interface , Through interface oriented programming , Distribute messages to currently active HostActivity in . and HostActivity A stack is maintained to record Flutter Changes in stack information .
/**
* flutter Route change callback
*/
interface RouteCallback {
//flutter route Stack message
fun onPush(route: String)
//flutter route Out of stack message
fun onPop(route: String)
//flutter route Stack replacement message
fun onReplace(newRoute: String, oldRoute: String)
//flutter route Removed messages
fun onRemove(route: String)
//flutter Apply to open Native Message for page
fun routeNative(nativeRoute: String, params: HashMap<String, Any>? = null)
//flutter route Out of stack message
fun getLifecycleOwner(): LifecycleOwner
}
class HostActivity : AppCompatActivity(), RouteCallback {
// Record flutter Routing stack information
private val routeStack: Stack<String> by lazy {
Stack()
}
//TODO RouteCallback How to implement the interface , In the method routeStack Perform stack in and stack out operations
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
Last question , When we need the user to return ,Widget The page should be similar to HostActivity Keep in sync , For example HostActivity Only one... Was opened Widget, So this Widget When you quit ,HostActivity We have to synchronize finish fall , Even if FlutterEngine There are other Widget. We can go through HostActivity Maintained in routeStack To achieve .
override fun onBackPressed() {
// Judge the length of the current stack , If at present HostActivity The length of the stack is less than or equal to 1, that Activity Will be finsih
if (routeStack.size <= 1) {
super.onBackPressed()
flutterEngine.navigationChannel.popRoute()
}
// Normal execution Navigator Of pop operation
else {
flutterEngine.navigationChannel.popRoute()
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
Of course, there is another problem to pay attention to , There are some Widget The return of is not handled by the return key , Then we need to onPop Methods for some special treatment :
override fun onPop(route: String) {
// Normally handle out of stack logic
if (routeStack.isEmpty() && !isFinishing) {
// If it's all out of the stack , Is the current HostActivity Your business has been completed
finish()
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
such , Can handle HostActivity And open in the host Widget Association , To advance and retreat together , The main logic is not responsible for the implementation , There is no need to use any third-party framework , This gives you a lot of flexibility , Especially for Flutter Come on ,Flutter Our ecology is not very mature , Avoid using overly intrusive frameworks , It also leaves enough space for future technical iterations .
Shortcomings and shortcomings
Although through to Flutter Monitoring and passing of information changes in the inner stack BasicMessageChannel Message synchronization to do a good job in hybrid stack management , But there are still some problems to be solved .
1:Flutter and Native The return value of the page ,OneActivity Opened a HostActivity And want to be in HostActivity Give... When you quit OneActivity A return value , This function has not been implemented in the example code , Continue to improve the framework when you have time .
2: Cross domain return The problem of , This “ Cross domain ” It's a name I defined myself , If I open two HostActivity, the second HostActivity adopt Navigator Of remove Method wants to close the first HostActivity Open in Widget, This can be done , Because these two HostActivity Use the same FlutterEngine, If this happens , That will make the first HostActivity There is a problem with the display of the page . Actually, I was thinking , The ideal hybrid management framework should be to open each HostActivity As a WebView To look at , Want to be in the second WebView Close the first... In WebView The page opened in , This is obviously not appropriate . Whether this cross domain return needs to be supported , It is also a problem worth considering .
Last
Xiaobian collected some on the Internet Android Develop relevant learning documents 、 Interview questions 、Android Core notes and other documents , I hope it can help you learn and improve , If you need reference, you can directly access it .

边栏推荐
- Memo pattern
- Daily practice - February 15, 2022
- Remediation for Unsafe Cryptographic Encryption
- 1018 hammer scissors cloth
- Experience sharing of system analysts in preparing for exams: phased and focused
- 【HackTheBox】dancing(SMB)
- 没遇到过这三个问题都不好意思说用过Redis
- Analysis of moudo Network Library
- [code random entry - hash table] T15, sum of three numbers - double pointer + sort
- Mysql 中的 mvcc原理
猜你喜欢

Basic use of JSX

How to create robots Txt file?

Open source demo| you draw and I guess -- make your life more interesting

IDEA修改jvm内存

Mysql 中的 mvcc原理

How to test electronic components with a multimeter

Visitor pattern

Remediation for Unsafe Cryptographic Encryption

ROS URDF model is parsed into KDL tree

SEAttention 通道注意力机制
随机推荐
LabVIEW displays Unicode characters
Mediator pattern
JDBC learning
20年秦皇岛D - Exam Results(二分+思维,附易错数据)
Daily practice - February 15, 2022
力扣解法汇总324-摆动排序 II
系统分析师备考经验分享:分阶段、分重点
Go Foundation (I)
Multi machine LAN office artifact rustdesk use push!!!
JVM memory tuning method
Rapid development project -vscode plug-in
网传广东一名学生3次考上北大,3年共赚200万元奖金
Matlab直接求贝塞尔函数的导函数
[code random entry - hash table] T15, sum of three numbers - double pointer + sort
Remediation for Unsafe Cryptographic Encryption
The last week! Summary of pre competition preparation for digital model American Games
BERT和ViT简介
Canoe- how to parse messages and display information in the trace window (use of program node and structure type system variables)
ROS TF coordinate transformation Library & rewrite to create high frequency coordinate transformation broadcaster
Remediation for Unsafe Cryptographic Encryption