Flutter通用基礎庫flutter_luakit_plugin

williamwen1986發表於2019-01-08

使用flutter_luakit_plugin作為基礎庫開發Flutter應用

文章開頭我們先開門見山給出使用flutter_luakit_plugin作為基礎庫開發和普通flutter的區別。由於flutter定位是便攜UI包,flutter提供的基礎庫功能是不足以滿足複雜資料的app應用的,一般flutter開發模式如下圖所示,當flutter滿足不了我們的需求的時候,使用methodchannel和eventchannel呼叫native介面。

image

而使用flutter_luakit_plugin作為基礎庫的開發模式如下圖所示,用lua來寫邏輯層程式碼,用flutter寫UI程式碼。luakit 提供了豐富的功能支援,可以支援大部分app的邏輯層開發,包括資料庫orm,執行緒管理,http請求,非同步socket,定時器,通知,json等等。使用者只需要寫dart程式碼和lua程式碼,不需要寫oc、swift或java、kotlin程式碼,從而大幅提升程式碼的一致性(所有執行程式碼都是跨平臺的)

image

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介面是不允許的。

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

持續更新中...

相關文章