Flutter通用基础库flutter_luakit_plugin

使用flutter_luakit_plugin做为基础库开发Flutter应用

文章开头咱们先开门见山给出使用flutter_luakit_plugin做为基础库开发和普通flutter的区别。因为flutter定位是便携UI包,flutter提供的基础库功能是不足以知足复杂数据的app应用的,通常flutter开发模式以下图所示,当flutter知足不了咱们的需求的时候,使用methodchannel和eventchannel调用native接口。java

image

而使用flutter_luakit_plugin做为基础库的开发模式以下图所示,用lua来写逻辑层代码,用flutter写UI代码。luakit 提供了丰富的功能支持,能够支持大部分app的逻辑层开发,包括数据库orm,线程管理,http请求,异步socket,定时器,通知,json等等。用户只须要写dart代码和lua代码,不须要写oc、swift或java、kotlin代码,从而大幅提高代码的一致性(全部运行代码都是跨平台的)android

image

flutter_luakit_plugin由来

Flutter诞生的时候我很兴奋,由于我对跨平台开发UI的见解一直是不看好的,最主要的缘由是没法得到体验一致性,可是Flutter前无古人的解决了这个问题,真正作到一端开发的UI,不管多复杂,在另外一端是能够获得一致的体验的,作不到这点的跨平台UI方案实际上并无达到跨平台节省工做量的效果,Flutter作到了。ios

Flutter1.0.0 发布了,我认为移动端跨平台开发所须要全部元素都已经齐备了,咱们尝试使用Flutter作一些功能,一个版本以后咱们总结了一些问题。git

  • Flutter是一套UI解决方案,但一个功能除了UI,还须要不少支持,网络请求,长链接,短链接,数据库,线程控制等等,这些方面Flutter生态中提供得比较差,没有ios 或者android那么多成熟的解决方案。Flutter 为了克服这问题,提供了一个解决方案,利用methodchannel和eventchannel调用ios和android的接口,利用原生成熟的方案作底层逻辑支撑。咱们一开始也是这样解决,但后续的麻烦也来了,因为methodchannel和eventchannel实现的方法是不跨平台的,Flutter从ios和android获得的数据的格式,事件调用的时机等,两个平台的实现是不同的,基本不可能彻底统一,能够这样说,一个功能在一个端能跑通,在另外一个端第一次跑必定跑不通,而后就要花大量的时间进行调试,适配,这样作以后跨平台的优点荡然无存,你们就会不断扯皮。相信我,下面的对话会成为大家的平常。github

    ios开发:“大家android写的界面ios跑不起来”web

    Android 开发:“咱们android能跑啊,iOS接口写得不对吧”objective-c

    ios开发:“哪里不对,android写的界面,android帮忙调吧”sql

    Android 开发:“我又不是ios开发,我怎么调”数据库

  • 当一个已有的app要接入flutter,必然会产生一种状况,就是flutter体系里面的数据和逻辑,跟外部原生app的逻辑是不通的,简单说明一下,就是flutter写的业务逻辑一般是用dart语言写的,咱们在原生用object-c、swift或者java、kotlin写的代码是不能够脱离flutter的界面调用dart写的逻辑的,这种互通性的缺失,会致使不少数据联动作不到,譬如原生界面要现实一个flutter页面存下来的数据,或者原生界面要为flutter页面作一些预加载,这些都很不方便,主要是下图中,当flutter界面没调用时,从原生调用flutter接口是不容许的。json

image

以前我曾经开源一个纯逻辑层的跨平台解决方案luakit(附上luakit的起源),里面提供一个业务开发所须要的基本能力,包括网络请求,长链接,短链接,orm数据库,线程,通知机制等等,并且这些能力都是稳定的、跨平台并且通过实际业务验证过的方案。

作完一个版本纯flutter以后,我意识到能够用一种新的开发模式来进行flutter开发,这样能够避免我上面提到的两个问题,咱们团队立刻付诸实施,作了另外一个版本的flutter+luakit的尝试,即用flutter作界面,用lua来写逻辑,结构图以下。

image

新的方案开发效率获得极大的提高,不客气的说真正实现了跨平台,一个业务,从页面到逻辑,全部的代码一鼓作气所有由脚本完成(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...

image

搜索Luakit并安装Luakit插件而后重启androidstudio

image

配置lua项目

打开 Project Struture 窗口

image

选择 Modules、 Mark as Sources

image

添加调试器

选择 Edit Configurations ...

image

Select plus

image

添加Lua Remote(Mobdebug)

image

远程lua调试

在开始调试lua以前,咱们要在须要调试的lua文件加上下面一句lua代码。而后设上断点,便可调试。lua代码里面有两个参数,第一个是你调试用的电脑的ip地址,第二个是调试端口,默认是8172。

require("mobdebug").start("172.25.129.165", 8172)
复制代码

image

luakit的调试是经过socket来传递调试信息的,全部调试机器务必我电脑保持在同一网段,有时候可能作不到,这里咱们给出一下办法解决,咱们平常调试也是这样解决的。首先让你的手机开热点,而后你的电脑连上手机的热点,如今就能够保证你的手机和电脑是同一网段了,而后查看电脑的ip地址,填到lua代码上,就能够实现调试了。

image

image

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使用例子

持续更新中...