使用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 engine(可直接使用)
持續更新中...