最近發佈了一個跨平臺的app開發框架Luakit 。那怎麼會想到作這樣一個東西呢?這要先說一下我參與過的一些項目,和在項目中接觸到的一些技術點和對項目開發體檢了,由於Luakit是集合了幾個重要技術才能作到用Lua腳原本實現跨平臺app開發的。java
我主要參與的項目是QQMail的IOS版。在2017年下半年,因爲機緣巧合,我參與開發了企業微信的一個分支版本,appstore上叫政務微信。QQMail的歷史比較悠久了,在QQMail項目裏咱們使用了兩項技術是比較特殊的,其餘項目團隊接觸得比較少,一個是Lua腳本化技術,一個是orm技術。而在政務微信開發過程當中是企業微信團隊的跨平臺開發技術給我留下很深印象,下面我首先簡單介紹這幾項技術。android
)來作的,只能在IOS上跑,不具有跨平臺的能力。QQMail裏面有幾個版本中,整個記事本模塊從底層邏輯層到界面渲染所有都用Lua來實現,腳本化過程當中咱們也克服了不少技術難點,例如如何在Lua腳本實現競爭式多線程,如何高效方便地在Lua環境實現數據存儲之類的這些業界難題,固然了,腳本化以後咱們也第一次吃到腳本化的甜頭,最大的優勢就是對代碼的掌控能力大大提高,一個是能夠隨時上線,另外就是能夠給不一樣的用戶下發不一樣的代碼。這個對於發現問題有很大的好處,當有用戶投訴的時候,給用戶下發特殊的debug代碼,基本沒有發現不了的問題。ios
深刻接觸這幾個框架後,我發現Lua跟chromium真是絕配,chromium提供跨平臺的消息循環機制能夠完美解決lua實現競爭式多線程的問題,在lua環境實現競爭式多線程(注意,不是單單線程安全)是使用lua開發的一個廣泛性的難題,cocos2d-x的lua-binding也沒解決這個問題,因此基於cocos2d-x lua版開發的遊戲也很難作到全腳本化,由於Lua只能單線程。有了Luakit後,這類問題都有解決方案了。而lua的內存管理機制也能夠很好的解決chromium用c++開發,內存管理和不適合函數式編程的最大的弊端,二者解合能夠產生很好的效果。有了lua的多線程模型後,參考GYDataCenter的實現原理,咱們能夠實現一套lua版的orm框架,GYDataCenter只能在ios使用,如今lua版的orm框架能夠具備跨平臺的特性。c++
Luakit提供的不少強大的功能,這些功能都是能夠跨平臺運行的,Luakit主要包括如下功能接口git
下面簡單介紹多線程接口,orm接口,http請求,異步socket接口和全局通知接口。github
多線程模型objective-c
如何在Lua實現競爭式多線程我會再發一篇文章專門講講,由於這個問題是Lua領域的廣泛存在的問題,有必定的技術意義。這裏我先簡單帶過一下實現思路,一個lua解析器自己是不具有多線程能力,甚至不是線程安全的,可是在服務器開發上已經有人嘗試起多條線程而後給每條線程配置獨立的Lua解析器,而後多條線程經過必定的數據通道傳輸數據,經過這樣的方式實現真正的多線程,可是這個思路一直沒有延伸到客戶端開發,主要緣由是由於客戶端一般把真正的線程隱藏起來,不管IOS或者android,都不能輕易地接觸真正的線程,可是因爲chromium提供了開源的線程模型,經過修改chromium的底層源碼,生成消息循環時的給每一個消息循環配置獨立的lua解析器,這樣最大的問題就獲得瞭解決,下面看一下Luakit 提供的多線程接口。sql
建立線程 ,demo code數據庫
-- Parma1 is the thread type ,there are five types of thread you can create. -- BusinessThreadUI -- BusinessThreadDB -- BusinessThreadLOGIC -- BusinessThreadFILE -- BusinessThreadIO -- Param2 is the thread name -- Result is new threadId which is the token you should hold to do further action local newThreadId = lua.thread.createThread(BusinessThreadLOGIC,"newThread")
異步調用方法,相似IOS gcd中的 dispatch_async , demo code編程
-- Parma1 is the threadId for which you want to perform method -- Parma2 is the modelName -- Parma3 is the methodName -- The result is just like you run the below code on a specified thread async -- require(modelName).methodName("params", 1.1, {1,2,3}, function (p) -- end) lua.thread.postToThread(threadId,modelName,methodName,"params", 1.1, {1,2,3}, function (p) -- do something here end)
同步調用方法,相似IOS gcd中的 dispatch_sync , demo code
-- Parma1 is the threadId for which you want to perform method -- Parma2 is the modelName -- Parma3 is the methodName -- The result is just like you run the below code on a specified thread sync -- local result = require(modelName).methodName("params", 1.1, {1,2,3}, function (p) -- end) local result = lua.thread.postToThreadSync(threadId,modelName,methodName,"params", 1.1, {1,2,3}, function (p) -- do something here end)
orm接口
orm 模型的實現方法是參考IOS orm 開源庫GYDataCenter的實現方法,GYDataCenter很依賴IOS gcd 的機制,Luakit中能夠用新的lua多線程接口取代,能夠作到一樣的效果,下面羅列一下 orm demo code
Luakit 提供的orm框架有以下特徵
定義數據模型, demo code
-- 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")
插入數據, demo code
local userTable = Table("user") local user = userTable({ username = "user1", password = "abc", time_create = os.time() }) user:save()
更新數據 demo code
local userTable = Table("user") local user = userTable.get:primaryKey({"user1"}):first() user.password = "efg" user.time_create = os.time() user:save()
select 數據,demo code
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 -----------")
聯表查詢,demo code
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 ")
http請求
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})
異步socket接口
Luakit 提供了非阻塞的socket調用接口, demo code
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()
通知接口
app開發中常常會遇到須要一對多的通知場景,例如ios有系統提供Notification Center 來提供,爲了跨平臺的實現通知,Luakit也提供通知接口
Lua register and post notification, demo code
lua.notification.createListener(function (l) local 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.notification.postNotification(3, { lua1 = "lua123", lua2 = "lua234" })
Android register and post notification, 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); HashMap<String, Integer> map = new HashMap<String, Integer>(); map.put("row", new Integer(2)); NotificationHelper.postNotification(3, map);
IOS register and post notification, 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); } post_notification(3, @{@"row":@(2)});
在騰訊我也接觸過很多項目(參與開發或者瞭解代碼),每一個項目都會發展出一套屬於本身的基礎架構,基礎架構的選擇一般都會根據本身原有的知識體系搭建,這個基本無一例外,習慣用chromium的團隊,全部由那個團隊創建的項目都會基於chromium作基礎架構,若是沒有特別熟悉的,就會使用原生提供的接口搭建本身的基礎架構,這個無可厚非,可是選擇完基礎架構後,基本上app的素質就已經定下來,能不能跨平臺,數據能不能支持orm,代碼能不能熱更新,全部這些基本能力都已經定下來了,後續加入團隊的人不管多牛都只是在原有基礎上添磚加瓦,修修補補,大動筋骨一般都有很大代價的。全部我認爲對項目的技術負責人來講,選擇什麼基礎架構這件事是再重要不過了,項目中後期花無數個晚上來解決不知從何查起的bug,對投訴無能爲力,沒有足夠的工具來快速響應,大量重複代碼,不斷反覆的bug,這些問題有可能看似是一個剛入職的工程師的疏忽或者設計不當,其實大部分的緣由從選擇基礎架構的時候已經註定了,往後代碼的複雜度,app具備的能力,早就已經定下了。
Luakit 是我暫時知道的最高效的基礎架構,由於它具備如下特色
最後,但願你們能夠多瞭解,試用Luakit ,有問題能夠發郵件到williamwen1986@gmail.com