本文基於 Lua 5.3.html
Lua 是一個輕量級腳本語言,經常使用於嵌入其餘語言做爲補充。關於更多Lua自己的問題不在本文討論範圍以內。
在 Android 中嵌入 Lua 優勢不少,藉助 Lua 腳本語言的優點,能夠輕鬆實現動態邏輯控制,應用能夠隨時從服務器讀取最新 Lua 腳本文件,在不更新應用的狀況下修改程序邏輯。
惋惜 Lua 官方只提供了 C API ,而 Android 主要使用 JAVA 做爲開發語言。咱們能夠藉助 JNI 來間接實如今 Android 中嵌入 Lua 。java
本身實現 JNI 是一件很費力的事情,還好有人已經造好了輪子叫作 Luajava ,而且又有人基於 Luajava 作了 Android 專用庫。網上流傳最廣的是 Androlua ,不過做者已經多年不維護了,對 Lua 的支持依然停留在5.1而且有一些bug,有人Fork了這個項目,並將其更新至 Lua 5.3 :New Androlua ,不過這個項目也存在一些問題,我修復了一下可是做者並無處理個人 pull request ,各位能夠直接使用我修復優化後的:Android-Lua. android
在修復bug的同時,我也添加了一些中文註釋,減小第一次接觸 Lua C API 朋友們的學習記憶成本。git
因爲最終須要調用 Lua C API,因此請先配置 NDK 開發環境。在 Android Studio 中打開 SDK Manager,切換到 SDK Tools 標籤頁,勾選CMake
、LLDB
、NDK
下載安裝之。
github
僅僅想實現 Android Lua 互相調用,不關心具體過程的,能夠直接添加依賴:implementation 'cc.chenhe:android-lua:1.0.2'
而後後邊的導入部分能夠跳過了。服務器
Clone github 項目到本地並用 Android Studio 打開。大體能夠看到下圖目錄結構。(因爲後期更新,結構不必定徹底相同)
其中 androidlua
是庫,app
是demo工程。庫中,lua
下是 Lua 解釋引擎,luajava
是 JNI 的有關代碼。*.mk
文件是NDK配置文件,詳情請參考Google NDK 文檔。app
你能夠將 androidlua
Module 導入本身工程做爲依賴庫使用。ide
此時咱們已經能夠在 Android 與 Lua 直接互相調用了。可是在開始以前,還要學習下 Lua C API 的有關東西,由於這是與 Lua 交互的基礎。函數
Lua 與 C 依靠一個虛擬的棧來完成數據交換。包括變量、函數、參數、返回值等在內的一切數據,都要放入棧中來共享。爲了調用方便,這個棧並非嚴格遵循棧的規則。從索引來看,棧底索引爲1,往上依次遞增。而棧頂索引是-1,往下依次遞減。所以,正負索引都是合法的,可是0不能夠。下面是棧的示意圖:
學習
Lua 提供了大量的 C API,與其餘語言的交互徹底依賴這些 API,下面的基礎教程中本文會介紹幾個基礎的,具體能夠查看Lua 官方手冊
這些函數均由 Lua 提供,在 Luajava 中被封裝在 LuaState
類下。
加載 Lua 標準庫,通常須要調用一下。
執行一段 Lua 腳本。
執行給定文件中的 Lua 腳本。
獲取當前棧的內容。
java 中對應函數是dumpStack()
,返回String,能夠直接輸出,便於調試。
將各類類型的數據壓入棧,以便將來使用。
將棧中指定索引處的值以xxx類型取出。
獲取 Lua 中的全局變量(包括函數),並壓入棧頂,以便將來使用。
參數就是要獲取的變量的名字。
獲取 Lua 中 某一 table 的元素,並壓入棧頂。
第一個參數是 table 在棧中的索引,第二個參數是要獲取元素的 key.
執行 Lua 函數。
第一個參數是此函數的參數個數,第二個是返回值個數,第三個是錯誤處理函數在棧中的索引。
終於要開始調用了,想一想還有點小激動呢~
爲了方便說明,咱們先定義一個腳本文件叫 test.lua
,而後將其放在assets
目錄下,由於這下面的文件不會被編譯。
可使用下面的函數來讀取 assets 中文件的內容:
public static String readAssetsTxt( Context context, String fileName ){ try { InputStream is = context.getAssets().open( fileName ); int size = is.available(); /* Read the entire asset into a local byte buffer. */ byte[] buffer = new byte[size]; is.read( buffer ); is.close(); /* Convert the buffer into a string. */ String text = new String( buffer, "utf-8" ); /* Finally stick the string into the text view. */ return(text); } catch ( IOException e ) { e.printStackTrace(); } return("err"); }
以前說了,依靠一個虛擬的棧來完成數據交換。那麼首先咱們固然要建立這個棧。
LuaState lua = LuaStateFactory.newLuaState(); //建立棧 lua.openLibs(); //加載標準庫 lua.close(); //養成良好習慣,在執行完畢後銷燬Lua棧。
咱們可使用LdoString()
來執行一段簡單的腳本。
String l = "local a = 1"; lua.LdoString(l);
這樣就執行了一個很簡單的腳本,他聲明瞭一個全局變量l
,值爲1.
固然,也可使用LdoFile()
來加載腳本文件,不過這須要你先把腳本複製到 SD 卡才行。由於在 APK 中是沒有「路徑」可言的。
首頁要說明的是,只有全局變量(非 local)才能夠讀取。
test.lua:
a = 111; t = { ["name"] = "Chenhe", [2] = 2222, }
android:
lua.getGlobal("a"); //獲取變量a並將值壓入棧 Log.i("a", lua.toInteger(-1) + ""); //以int類型取出棧頂的值(也就是a) lua.getGlobal("t"); //獲取變量t並壓入棧頂,此時table位於棧頂。 lua.getField(-1, "name"); //取出棧頂的table的name元素,壓入棧頂。 Log.i("t.name", lua.toString(-1)); //以string類型取出棧頂的值(也就是t.name) Log.i("dump",lua.dumpStack()); //輸出當前棧
運行後能夠看到log:
I/a: 111 I/t.name: Chenhe I/dump: 1: number = 111.0 2: table 3: string = 'Chenhe'
咱們已經成功讀取了 Lua 中的變量。
調用 Lua 函數的通常流程爲:
test.lua:
function test(a, b) return a + b, a - b; end
android:
lua.getGlobal( "test" ); //獲取函數併入棧 lua.pushInteger( 5 ); //壓入第一個參數a lua.pushInteger( 3 ); //壓入第二個參數b lua.pcall( 2, 2, 0 ); //執行函數,有2個參數,2個返回值,不執行錯誤處理。 Log.i( "r1", lua.toInteger( -2 ) + "" ); //輸出第一個返回值 Log.i( "r2", lua.toInteger( -1 ) + "" ); //輸出第二個返回值 Log.i( "dump", lua.dumpStack() );
運行後能夠看到log:
I/r1: 8 I/r2: 2 I/dump: 1: number = 8.0 2: number = 2.0
這就成功地執行了 Lua 函數,並取得2個返回值。而以前入棧的函數以及參數,在執行的時候 Lua 已經彈出了,因此最後棧裏只剩下2個返回值。
得益於 Luajava 以及 Androlua 的封裝,咱們能夠直接將對象做爲參數傳入,並在 Lua 中直接執行對象的成員函數。
test.lua:
function setText(tv,s) tv:setText("set by Lua."..s); tv:setTextSize(50); end
android:
lua.getGlobal("setText"); //獲取函數 lua.pushJavaObject(textView); //把TextView傳入 lua.pushString("Demo"); //傳入一個字符串 lua.pcall(2,0,0); //執行函數,有2個參數。
有時咱們須要在 android 中建立 Lua 變量,這樣就能夠在 Lua 中直接使用了。注意,這裏建立的變量都是全局的(非 local)。
test.lua:
function setText(tv) tv:setText("set by Lua."..s); --這裏的s變量由java注入 tv:setTextSize(50); end
android:
lua.pushString( "from java" ); //壓入欲注入變量的值 lua.setGlobal( "s" ); //壓入變量名 lua.getGlobal( "setText" ); //獲取Lua函數 lua.pushJavaObject( textView ); //壓入參數 lua.pcall( 1, 0, 0 ); //執行函數
能夠看到 Lua 成功調用了 Java 注入的變量s
.