共计 10037 个字符,预计需要花费 26 分钟才能阅读完成。
使用 flutter_luakit_plugin 作为基础库开发 flutter 应用
文章开头我们先开门见山给出使用 flutter_luakit_plugin 作为基础库开发和普通 flutter 的区别。由于 flutter 定位是便携 UI 包,flutter 提供的基础库功能是不足以满足复杂数据的 app 应用的,一般 flutter 开发模式如下图所示,当 flutter 满足不了我们的需求的时候,使用 methodchannel 和 eventchannel 调用 native 接口。
而使用 flutter_luakit_plugin 作为基础库的开发模式如下图所示,用 lua 来写逻辑层代码,用 flutter 写 UI 代码。luakit 提供了丰富的功能支持,可以支持大部分 app 的逻辑层开发,包括数据库 orm,线程管理,http 请求,异步 socket,定时器,通知,json 等等。用户只需要写 dart 代码和 lua 代码,不需要写 oc、swift 或 java、kotlin 代码,从而大幅提升代码的一致性(所有运行代码都是跨平台的)。
flutter_luakit_plugin 由来
Flutter 诞生的时候我很兴奋,因为我对跨平台开发 UI 的看法一直是不看好的,最主要的原因是无法获得体验一致性,但是 Flutter 前无古人的解决了这个问题,真正做到一端开发的 UI,无论多复杂,在另一端是可以得到一致的体验的,做不到这点的跨平台 UI 方案实际上并没有达到跨平台节省工作量的效果,Flutter 做到了。
Flutter1.0.0 发布了,我认为移动端跨平台开发所需要所有元素都已经齐备了,我们尝试使用 Flutter 做一些功能,一个版本之后我们总结了一些问题。
Flutter 是一套 UI 解决方案,但一个功能除了 UI,还需要很多支持,网络请求,长连接,短连接,数据库,线程控制等等,这些方面 Flutter 生态中提供得比较差,没有 ios 或者 android 那么多成熟的解决方案。Flutter 为了克服这问题,提供了一个解决方案,利用 methodchannel 和 eventchannel 调用 ios 和 android 的接口,利用原生成熟的方案做底层逻辑支撑。我们一开始也是这样解决,但后续的麻烦也来了,由于 methodchannel 和 eventchannel 实现的方法是不跨平台的,Flutter 从 ios 和 android 得到的数据的格式,事件调用的时机等,两个平台的实现是不一样的,基本不可能完全统一,可以这样说,一个功能在一个端能跑通,在另一个端第一次跑一定跑不通,然后就要花大量的时间进行调试,适配,这样做之后跨平台的优势荡然无存, 大家就会不断扯皮。相信我,下面的对话会成为你们的日常。
ios 开发:“你们 android 写的界面 ios 跑不起来”
Android 开发:“我们 android 能跑啊,iOS 接口写得不对吧”
ios 开发:“哪里不对,android 写的界面,android 帮忙调吧”
Android 开发:“我又不是 ios 开发,我怎么调”
当一个已有的 app 要接入 flutter,必然会产生一种情况,就是 flutter 体系里面的数据和逻辑,跟外部原生 app 的逻辑是不通的,简单说明一下,就是 flutter 写的业务逻辑通常是用 dart 语言写的,我们在原生用 object-c、swift 或者 java、kotlin 写的代码是不可以脱离 flutter 的界面调用 dart 写的逻辑的,这种互通性的缺失,会导致很多数据联动做不到,譬如原生界面要现实一个 flutter 页面存下来的数据,或者原生界面要为 flutter 页面做一些预加载,这些都很不方便,主要是下图中,当 flutter 界面没调用时,从原生调用 flutter 接口是不允许的。
之前我曾经开源一个纯逻辑层的跨平台解决方案 luakit(附上 luakit 的起源),里面提供一个业务开发所需要的基本能力,包括网络请求,长连接,短连接,orm 数据库,线程,通知机制等等,而且这些能力都是稳定的、跨平台而且经过实际业务验证过的方案。
做完一个版本纯 flutter 之后,我意识到可以用一种新的开发模式来进行 flutter 开发,这样可以避免我上面提到的两个问题,我们团队马上付诸实施,做了另一个版本的 flutter+luakit 的尝试,即用 flutter 做界面,用 lua 来写逻辑,结构图如下。
新的方案开发效率得到极大的提升,不客气的说真正实现了跨平台,一个业务,从页面到逻辑,所有的代码一气呵成全部由脚本完成(dart+lua),完全不用 object-c、swift 或者 java、kotlin 来写逻辑,这样一个业务基本就可以无缝地从一端直接搬到另一端使用,所以我写了这篇文章来介绍我们团队的这个尝试,也把我们的成果 flutter_luakit_plugin 开源了出来,让这种开发模式帮助到更多 flutter 开发团队。
细说开发模式
下一步我们一起看看如何用 flutter 配合 lua 实现全部代码都是跨平台的。我们提供了一个 demo project,供大家参考。
dart 写界面在 demo 中所有的 ui 都写在了 main.dart, 当然在真实业务中肯定复杂很多,但是并不影响我们的开发模式。
dart 调用 lua 逻辑接口
FlutterLuakitPlugin.callLuaFun(“WeatherManager”, “getWeather”).then((dynamic d) {
print(“getWeather” + d.toString());
setState(() {
weathers = d;
});
});
上面这段代码的意思是调用 WeatherManager 的 lua 模块,里面提供的 getWeather 方法,然后把得到的数据以 future 的形式返回给 dart,上面的代码相当于调用下面一段 lua 代码
require(‘WeatherManager’).getWeather(function (d)
end)
然后剩下的事情就到 lua,在 lua 里面可以使用 luakit 提供的所有强大功能,一个 app 所需要的绝大部分的功能应该都提供了,而且我们还会不断扩展。
大家可能会担心 dart 和 lua 的数据格式转换问题,这个不用担心,所有细节在 flutter_luakit_plugin 都已经做好封装,使用者尽管像使用 dart 接口那样去使用 lua 接口即可。
在 lua 中实现所有的非 UI 逻辑这个 demo(WeatherManager.lua) 已经演示了如何使用 luakit 的相关功能,包括,网络,orm 数据库,多线程,数据解析,等等
如果实在有 flutter_luakit_plugin 没有支持的功能,可以走回 flutter 提供的 methodchannel 和 eventchannel 的方式实现
如何接入 flutter_luakit_plugin
经过了几个月磨合实践,我们团队已经把接入 flutter_luakit_plugin 的成本降到最低,可以说是非常方便接入了。我们已经把 flutter_luakit_plugin 发布到 flutter 官方的插件仓库。首先,要像其他 flutter 插件一样,在 pubspec.yaml 里面加上依赖,可参考 demo 配置
flutter_luakit_plugin: ^1.0.0
然后在 ios 项目的 podfile 加上 ios 的依赖,可参考 demo 配置
source ‘https://github.com/williamwen1986/LuakitPod.git’
source ‘https://github.com/williamwen1986/curl.git’
pod ‘curl’, ‘~> 1.0.0’
pod ‘LuakitPod’, ‘~> 1.0.13’
然后在 android 项目 app 的 build.gradle 文件加上 android 的依赖,可参考 demo 配置
repositories {
maven {url “https://jitpack.io”}
}
dependencies {
implementation ‘com.github.williamwen1986:LuakitJitpack:1.0.6’
}
最后,在需要使用的地方加上 import 就可以使用 lua 脚本了
import ‘package:flutter_luakit_plugin/flutter_luakit_plugin.dart’;
lua 脚本我们默认的执行根路径在 android 是 assets/lua,ios 默认的执行根路径是 Bundle 路径。
flutter_luakit_plugin 开发环境 IDE–AndroidStudio
flutter 官方推荐的 IDE 是 androidstudio 和 visual studio code。我们在开发中觉得 androidstudio 更好用,所有我们同步也开发了 luakit 的 androidstudio 插件,名字就叫 luakit。luakit 插件提供了以下的一些功能。
远程 lua 调试
查找函数使用
跳到函数定义
跳到文件
参数名字提示
代码自动补全
代码格式化
代码语法检查
标准 lua api 自动补全
luakit api 自动补全
大部分功能,跟其他 IDE 没太多差别,这里我就不细讲了,我重点讲一下远程 lua 调试功能,因为这个跟平时调试 ios 和 android 设备有点不一样,下面我们详细介绍 androidstudio luakit 插件的使用。
androidstudio 安装 luakit 插件
AndroidStudio->Preference..->Plugins->Browse reprositories…
搜索 Luakit 并安装 Luakit 插件然后重启 androidstudio
配置 lua 项目
打开 Project Struture 窗口
选择 Modules、Mark as Sources
添加调试器
选择 Edit Configurations …
Select plus
添加 Lua Remote(Mobdebug)
远程 lua 调试
在开始调试 lua 之前,我们要在需要调试的 lua 文件加上下面一句 lua 代码。然后设上断点,即可调试。lua 代码里面有两个参数,第一个是你调试用的电脑的 ip 地址,第二个是调试端口,默认是 8172。
require(“mobdebug”).start(“172.25.129.165”, 8172)
luakit 的调试是通过 socket 来传递调试信息的,所有调试机器务必我电脑保持在同一网段,有时候可能做不到,这里我们给出一下办法解决,我们日常调试也是这样解决的。首先让你的手机开热点,然后你的电脑连上手机的热点,现在就可以保证你的手机和电脑是同一网段了,然后查看电脑的 ip 地址,填到 lua 代码上,就可以实现调试了。
flutter_luakit_plugin 提供的 api 介绍
(1)数据库 orm 操作
这是 flutter_luakit_plugin 里面提供的一个强大的功能,也是 flutter 现在最缺的,简单高效的数据库操作,flutter_luakit_plugin 提供的数据库 orm 功能有以下特征
面向对象
自动创建和更新表结构
自带内部对象缓存
定时自动 transaction
线程安全,完全不用考虑线程问题
具体可参考 demo lua,下面只做简单介绍。
定义数据模型
— Add the define table to dbData.lua
— Luakit provide 7 colum types
— IntegerField to sqlite integer
— RealField to sqlite real
— BlobField to sqlite blob
— CharField to sqlite varchar
— TextField to sqlite text
— BooleandField to sqlite bool
— DateTimeField to sqlite integer
user = {
__dbname__ = “test.db”,
__tablename__ = “user”,
username = {“CharField”,{max_length = 100, unique = true, primary_key = true}},
password = {“CharField”,{max_length = 50, unique = true}},
age = {“IntegerField”,{null = true}},
job = {“CharField”,{max_length = 50, null = true}},
des = {“TextField”,{null = true}},
time_create = {“DateTimeField”,{null = true}}
},
— when you use, you can do just like below
local Table = require(‘orm.class.table’)
local userTable = Table(“user”)
插入数据
local userTable = Table(“user”)
local user = userTable({
username = “user1”,
password = “abc”,
time_create = os.time()
})
user:save()
更新数据
local userTable = Table(“user”)
local user = userTable.get:primaryKey({“user1”}):first()
user.password = “efg”
user.time_create = os.time()
user:save()
删除数据
local userTable = Table(“user”)
local user = userTable.get:primaryKey({“user1”}):first()
user:delete()
批量更新数据
local userTable = Table(“user”)
userTable.get:where({age__gt = 40}):update({age = 45})
批量删除数据
local userTable = Table(“user”)
userTable.get:where({age__gt = 40}):delete()
select 数据
local userTable = Table(“user”)
local users = userTable.get:all()
print(“select all ———–“)
local user = userTable.get:first()
print(“select first ———–“)
users = userTable.get:limit(3):offset(2):all()
print(“select limit offset ———–“)
users = userTable.get:order_by({desc(‘age’), asc(‘username’)}):all()
print(“select order_by ———–“)
users = userTable.get:where({age__lt = 30,
age__lte = 30,
age__gt = 10,
age__gte = 10,
username__in = {“first”, “second”, “creator”},
password__notin = {“testpasswd”, “new”, “hello”},
username__null = false
}):all()
print(“select where ———–“)
users = userTable.get:where({“scrt_tw”,30},”password = ? AND age < ?”):all()
print(“select where customs ———–“)
users = userTable.get:primaryKey({“first”,”randomusername”}):all()
print(“select primaryKey ———–“)
联表操作
local userTable = Table(“user”)
local newsTable = Table(“news”)
local user_group = newsTable.get:join(userTable):all()
print(“join foreign_key”)
user_group = newsTable.get:join(userTable,”news.create_user_id = user.username AND user.age < ?”, {20}):all()
print(“join where “)
user_group = newsTable.get:join(userTable,nil,nil,nil,{create_user_id = “username”, title = “username”}):all()
print(“join matchColumns “)
(2)通知机制
通知机制提供了一个低耦合的事件互通方法,即在原生或者 lua 或者 dart 注册消息,在任何地方抛出的消息都可以接收到。
Flutter 添加监听消息
void notify(dynamic d) {
}
FlutterLuakitPlugin.addLuaObserver(3, notify);
Flutter 取消监听
FlutterLuakitPlugin.removeLuaObserver(3, notify);
Flutter 抛消息
FlutterLuakitPlugin.postNotification(3, data);
lua 添加监听消息 demo code
local listener
lua_notification.createListener(function (l)
listener = l
listener:AddObserver(3,
function (data)
print(“lua Observer”)
if data then
for k,v in pairs(data) do
print(“lua Observer”..k..v)
end
end
end
)
end);
lua 抛消息 demo code
lua_notification.postNotification(3,
{
lua1 = “lua123”,
lua2 = “lua234″
})
ios 添加监听消息 demo code
_notification_observer.reset(new NotificationProxyObserver(self));
_notification_observer->AddObserver(3);
– (void)onNotification:(int)type data:(id)data
{
NSLog(@”object-c onNotification type = %d data = %@”, type , data);
}
ios 抛消息 demo code
post_notification(3, @{@”row”:@(2)});
android 添加监听消息 demo code
LuaNotificationListener listener = new LuaNotificationListener();
INotificationObserver observer = new INotificationObserver() {
@Override
public void onObserve(int type, Object info) {
HashMap<String, Integer> map = (HashMap<String, Integer>)info;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
Log.i(“business”, “android onObserve”);
Log.i(“business”, entry.getKey());
Log.i(“business”,””+entry.getValue());
}
}
};
listener.addObserver(3, observer);
android 抛消息 demo code
HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put(“row”, new Integer(2));
NotificationHelper.postNotification(3, map);
(3)http request
flutter 本身提供了 http 请求库 dio,不过当项目的逻辑接口想在 flutter,原生 native 都可用的情况下,flutter 写的逻辑代码就不太合适了,原因上文已经提到,原生 native 是不可以随意调用 flutter 代码的,所以遇到这种情况,只有 luakit 合适,lua 写的逻辑接口可以在所有地方调用,flutter、ios、android 都可以方便的使用 lua 代码,下面给出 luakit 提供的 http 接口,demo code。
— url , the request url
— isPost, boolean value represent post or get
— uploadContent, string value represent the post data
— uploadPath, string value represent the file path to post
— downloadPath, string value to tell where to save the response
— headers, tables to tell the http header
— socketWatcherTimeout, int value represent the socketTimeout
— onResponse, function value represent the response callback
— onProgress, function value represent the onProgress callback
lua_http.request({url = “http://tj.nineton.cn/Heart/index/all?city=CHSH000000”,
onResponse = function (response)
end})
(4)Async socket
异步 socket 长连接功能也是很多 app 开发所依赖的,flutter 只支持 websocket 协议,如果 app 想使用基础的 socket 协议,那就要使用 flutter_luakit_plugin 提供的 socket 功能了,使用也非常简单,demo code,在 callback 里面拿到数据后可以使用上文提到的通知机制把数据传回到 flutter 层。
local socket = lua_asyncSocket.create(“127.0.0.1”,4001)
socket.connectCallback = function (rv)
if rv >= 0 then
print(“Connected”)
socket:read()
end
end
socket.readCallback = function (str)
print(str)
timer = lua_timer.createTimer(0)
timer:start(2000,function ()
socket:write(str)
end)
socket:read()
end
socket.writeCallback = function (rv)
print(“write” .. rv)
end
socket:connect()
(5)json 解析
json 是最常用数据类型,使用可参考 demo
local t = cjson.decode(responseStr)
responseStr = cjson.encode(t)
(6) 定时器 timer
定时器也是项目开发中经常用到的一个功能,定时器我们在 orm 框架的 lua 源码里面有用到,demo
local _timer
_timer = lua_timer.createTimer(1)// 0 代表单次,1 代表重复
_timer:start(2000,function ()
end)
_timer:stop()
(7) 还有所有普通适合 lua 用的库都可以在 flutter_luakit_plugin 使用
flutter 技术积累相关链接
flutter 通用基础库 flutter_luakit_plugin
flutter_luakit_plugin 使用例子
《手把手教你编译 Flutter engine》
《手把手教你解决 Flutter engine 内存漏》
修复内存泄漏后的 flutter engine(可直接使用)
修复内存泄漏后的 flutter engine 使用例子
持续更新中 …