日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

android lua sd卡,记Android层执行Lua脚本的一次实践

發布時間:2024/4/14 Android 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android lua sd卡,记Android层执行Lua脚本的一次实践 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

0. 前言

最近一直在寫Lua腳本,有時候出了問題,不知道是Lua層的問題,還是上游的問題,不知道從何下手。于是我學習了一點C/C++和JNI,把整個解析Lua腳本包、執行Lua腳本的流程全部都讀了一遍。熟悉了一遍之后,就萌生了自己封一個Android跑Lua腳本庫的想法。于是就有這篇博文。C/C++和Kotlin我都不熟,所以這次我主要用這兩種語言來寫(所以會很Java Style)。

1. 環境搭建

首先現在Lua官網下載Lua的源碼,我用的是5.3.5版本的。然后把源碼導入到Project中,寫好CMakeList:

# For more information about using CMake with Android Studio, read the

# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC

# or SHARED, and provides the relative paths to its source code.

# You can define multiple libraries, and CMake builds them for you.

# Gradle automatically packages shared libraries with your APK.

add_definitions(-Wno-deprecated)

add_library( # Sets the name of the library.

luabridge

# Sets the library as a shared library.

SHARED

# Provides a relative path to your source file(s).

src/main/jni/lua/lapi.c

src/main/jni/lua/lauxlib.c

src/main/jni/lua/lbaselib.c

src/main/jni/lua/lbitlib.c

src/main/jni/lua/lcode.c

src/main/jni/lua/lcorolib.c

src/main/jni/lua/lctype.c

src/main/jni/lua/ldblib.c

src/main/jni/lua/ldebug.c

src/main/jni/lua/ldo.c

src/main/jni/lua/ldump.c

src/main/jni/lua/lfunc.c

src/main/jni/lua/lgc.c

src/main/jni/lua/linit.c

src/main/jni/lua/liolib.c

src/main/jni/lua/llex.c

src/main/jni/lua/lmathlib.c

src/main/jni/lua/lmem.c

src/main/jni/lua/loadlib.c

src/main/jni/lua/lobject.c

src/main/jni/lua/lopcodes.c

src/main/jni/lua/loslib.c

src/main/jni/lua/lparser.c

src/main/jni/lua/lstate.c

src/main/jni/lua/lstring.c

src/main/jni/lua/lstrlib.c

src/main/jni/lua/ltable.c

src/main/jni/lua/ltablib.c

src/main/jni/lua/ltm.c

src/main/jni/lua/lua.c

#src/main/jni/lua/luac.c

src/main/jni/lua/lundump.c

src/main/jni/lua/lutf8lib.c

src/main/jni/lua/lvm.c

src/main/jni/lua/lzio.c)

# Searches for a specified prebuilt library and stores the path as a

# variable. Because CMake includes system libraries in the search path by

# default, you only need to specify the name of the public NDK library

# you want to add. CMake verifies that the library exists before

# completing its build.

find_library( # Sets the name of the path variable.

log-lib

# Specifies the name of the NDK library that

# you want CMake to locate.

log)

# Specifies libraries CMake should link to your target library. You

# can link multiple libraries, such as libraries you define in this

# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.

luabridge

# Links the target library to the log library

# included in the NDK.

${log-lib})

我要跑是 *.lua 類型的腳本,那就留下lua.c并刪掉luac.c,CMakeList里面也要跟著注釋掉。另外,因為我把Lua的源代碼導入進來當做一個庫,所以也不需要main方法了,把lua.c里面的main方法注釋掉。最后Rebuild一下Project就可以了。

2. Android單向調用Lua

先定一個小目標,Android層調用Lua層的函數,Lua層做一個加法后把結果返回給Android層。先寫好Lua腳本:

function test(a, b)

return a + b

end

這個Lua腳本很簡單,把傳過來的a和b相加后返回。現在我們可以開始考慮Native層的實現。在考慮實現之前,需要了解Lua虛擬棧和幾個Lua C API。

2.1. Lua虛擬棧

Lua層和Native層的數據交換是通過Lua虛擬棧來完成的。這個虛擬棧和普通的棧略有不同,它可以通過負值索引來訪問指定元素。如圖:

Lua虛擬棧

和普通的棧一樣,Lua虛擬棧同樣遵循先進后出原則,索引從下往上增加。不同的是Lua虛擬棧支持負值索引,使用負值索引可以自棧頂向下索引。

2.2. Lua C APIs

Lua提供了C APIs,方便Native層和Lua層之間的通訊。下面的Demo會用到這幾個C API。

lua_State *luaL_newstate (void);

新建一個Lua的context。

int luaL_loadbuffer (lua_State *L, const char *buff, size_t sz, const char *name);

編譯一個Lua chunk。如果編譯成功,它會把編譯結果包裝成一個函數,并把這個函數推入到棧中;否則,編譯失敗,它會把錯誤信息推入棧中。

參數

類型

說明

L

lua_State*

Lua的context

buff

const char*

需要加載的Lua腳本buffer

sz

size_t

Lua腳本buffer的長度

name

const char*

這個chunk的名稱,可空

int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc);

以安全模式調用一個函數,即使拋出異常也不會崩潰。當拋出異常時,如果errfunc為0,Lua虛擬機會把錯誤信息推入到Lua虛擬棧中,如果errfunc不為0,則錯誤處理會交由Lua虛擬棧中索引為errfunc的函數處理。執行結束后,Lua虛擬機會把參數以及調用的函數從棧中彈出。

參數

類型

說明

L

lua_State*

Lua的context

nargs

int

需要調用的函數的參數個數

nresults

int

需要調用的函數的返回結果個數

errfunc

int

錯誤處理函數在Lua虛擬棧中的索引,如果為0,錯誤信息會推入到Lua虛擬棧中

void lua_getglobal (lua_State *L, const char *name);

獲取名字為name的全局變量,并推入棧中。

參數

類型

說明

L

lua_State*

Lua的context

name

const char*

變量名稱

void lua_pushinteger (lua_State *L, lua_Integer n);

推入一個lua_Integer類型的數據到棧中

參數

類型

說明

L

lua_State*

Lua的context

n

lua_Integer

需要推入的數字

lua_Integer lua_tointeger (lua_State *L, int index);

將棧中的索引為index的元素轉lua_Integer并返回

參數

類型

說明

L

lua_State*

Lua的context

index

int

指定元素在棧中的索引

除了這些C API,其他的介紹及其用法可以查看官網的說明。

通過理解Lua虛擬棧和了解一些Lua C API,我們就可以實現一個簡單的Native層調用Lua層函數的功能。

jint startScript(JNIEnv* env, jobject obj, jstring jLuaStr, jint a, jint b) {

// 創建一個lua context

lua_State* luaContext = lua_newstate();

// 初始化lua lib

luaL_openlibs(luaContext);

const char* cLuaStr = env->GetStringUTFChars(jLuaStr, NULL);

// 加載buff到內存

int loadStatus = luaL_loadbuffer(luaContext, cLuaStr, strlen(cLuaStr), NULL);

if (LUA_OK != loadStatus) {

const char *szError = luaL_checkstring(luaContext, -1);

Log_e(LOG_TAG, "%s", szError);

return -1;

}

env->ReleaseStringUTFChars(jLuaStr, cLuaStr);

int callStatus = lua_pcall(luaContext, 0, LUA_MULTRET, 0);

if (LUA_OK != callStatus) {

const char *szError = luaL_checkstring(luaContext, -1);

Log_e(LOG_TAG, "%s", szError);

return -1;

}

// 獲取test方法

lua_getglobal(luaContext, "test");

if (LUA_TFUNCTION != lua_type(luaContext, -1)) {

Log_d(LOG_TAG, "can not found func : %s", "test");

return false;

}

// 推入參數

lua_pushinteger(luaContext, a);

lua_pushinteger(luaContext, b);

// 執行test方法

int callTestStatus = lua_pcall(luaContext, 2, 1, 0);

if(LUA_OK == callTestStatus) {

int ret = lua_tointeger(luaContext, 1)

return ret;

} else {

const char* errMsg = lua_tostring(luaContext, 1)

Log_e(LOG_TAG, "%s", errMsg);

return -1;

}

}

流程如注釋。在這一個過程中,Lua虛擬棧的內容變化如圖,從luaL_loadbuffer開始:

Lua虛擬棧內容變化

首先,經過luaL_loadbuffer之后,luaL_loadbuffer會把傳過來的*.lua文件的buffer作為一個Lua Chunk,接著編譯它。編譯完后,把編譯結果包裝成一個function并推入Lua虛擬棧中。經過lua_pcall后,Lua虛擬機會把所執行的function及其參數從Lua虛擬棧中彈出。接著,通過lua_getglobal獲取Lua層的全局變量「test」,lua_getglobal會把這個變量的值推入Lua虛擬棧中。函數已經準備好,再經過lua_pushinteger(a)和lua_pushinteger(b)后,函數和參數都已經順序推入了,調用lua_pcall的先決條件已經滿足。接下來,調用lua_pcall后,Lua虛擬機會根據調用lua_pcall是傳入的nresults,將結果推入Lua虛擬棧中。最后,我們只需要lua_tointeger(index)來獲取執行結果,返回給Android層即可。可以看到,自始至終,Lua虛擬棧充當一個數據交換的橋梁,是一個十分重要的角色。

接下來,只需要在Native層Register一下NativeMethods,并在Android層聲明一下native方法就可以使用了。

class LuaExecutor {

init {

System.loadLibrary("luabridge")

}

external fun startScript(luaString: String): Boolean

}

然而,上面的實現只有啟動腳本的功能。在實際中,我們總不可能啟動腳本之后,就沒有對腳本執行流程有一點控制吧。因此有必要加一個停止腳本的功能。如何停止正在執行的腳本?先來看看Lua提供的C API:

int luaL_error (lua_State *L, const char *fmt, ...);

拋出一個異常,錯誤信息為fmt。

參數

類型

說明

L

lua_State*

Lua的context

fmt

const char*

錯誤信息

int lua_sethook (lua_State *L, lua_Hook f, int mask, int count);

設置一個鉤子函數。

參數

類型

說明

L

lua_State*

Lua的context

f

lua_Hook

鉤子函數,包含需要執行的語句

mask

int

指定被調用的時機,取值為常量LUA_MASKCALL,LUA_MASKRET,LUA_MASKLINE和LUA_MASKCOUNT的按位或。

mask取值

說明

LUA_MASKCALL

代表鉤子函數f會在進入任意函數后執行

LUA_MASKRET

代表鉤子函數在退出任意函數前執行

LUA_MASKLINE

代表鉤子函數f會在執行函數內一行代碼前執行

LUA_MASKCOUNT

代表鉤子函數f會在lua解釋器執行了count條指令后執行

有了這兩個C API,腳本的停止功能就可以實現了:

void stopLuaHooker(lua_State *L, lua_Debug *ar) {

luaL_error(L, "quit Lua");

}

void forceStopLua(lua_State *L) {

int mask = LUA_MASKCOUNT;

lua_sethook(L, &stopLuaHooker, mask, 1);

}

當我們調用forceStopLua時,會為Lua腳本的執行設置一個鉤子函數,這個鉤子函數的執行時機是:lua_sethook執行之后,Lua解釋器執行完一條指令時。也就是說,我們在Lua層代碼執行到任意地方時調用forceStopLua后,Lua解釋器會在執行完一條指令后,接著執行stopLuaHooker,進而執行lua_error,拋出異常,腳本即終止。因此,腳本的啟動和停止的功能已經實現好了,封到一個類里,叫做LuaEngine:

#ifndef ANDROIDLUA_LUAENGINE_H

#define ANDROIDLUA_LUAENGINE_H

#include

#include

#include

#include "lua/lua.hpp"

#include "utils/Log.h"

#include "JniManager.h"

#define LOG_TAG "LuaEngine"

class LuaEngine {

public:

LuaEngine();

virtual ~LuaEngine();

lua_State *getScriptContext() {

return mScriptContext;

}

bool startScript(jstring jBuff, const char *functionName);

bool isScriptRunning() {

return scriptRunning;

}

bool stopScript();

private:

lua_State *mScriptContext;

bool scriptRunning;

bool loadBuff(jstring jBuff);

bool runLuaFunction(const char *functionName);

};

void quitLuaThread(lua_State *L);

void quitLuaThreadHooker(lua_State *L, lua_Debug *ar);

#endif //ANDROIDLUA_LUAENGINE_H

#include "LuaEngine.h"

LuaEngine::LuaEngine() {

mScriptContext = luaL_newstate();

scriptRunning = false;

}

LuaEngine::~LuaEngine() {

if (isScriptRunning()) {

stopScript();

}

mScriptContext = nullptr;

}

bool LuaEngine::startScript(jstring jBuff, const char *functionName) {

scriptRunning = true;

luaL_openlibs(mScriptContext);

if (this->loadBuff(jBuff)) {

Log_d(LOG_TAG, "script start running..");

bool success = this->runLuaFunction(functionName);

scriptRunning = false;

return success;

} else {

scriptRunning = false;

return false;

}

}

bool LuaEngine::stopScript() {

if (scriptRunning) {

quitLuaThread(mScriptContext);

scriptRunning = false;

return true;

} else {

Log_d(LOG_TAG, "script is Not running");

return false;

}

}

bool LuaEngine::loadBuff(jstring jBuff) {

// 讀取buff

JNIEnv *env;

JniManager::getInstance()->getJvm()->GetEnv((void **) &env, JNI_VERSION_1_6);

const char *cBuff = env->GetStringUTFChars(jBuff, nullptr);

if (LUA_OK != luaL_loadbuffer(mScriptContext, cBuff, strlen(cBuff), NULL)) {

const char *szError = luaL_checkstring(mScriptContext, -1);

Log_e(LOG_TAG, "%s", szError);

return false;

}

// 加載buff到內存

if (LUA_OK != lua_pcall(mScriptContext, 0, LUA_MULTRET, 0)) {

const char *szError = luaL_checkstring(mScriptContext, -1);

Log_e(LOG_TAG, "%s", szError);

return false;

}

env->ReleaseStringUTFChars(jBuff, cBuff);

env->DeleteGlobalRef(jBuff);

return true;

}

bool LuaEngine::runLuaFunction(const char *functionName) {

// 獲取errorFunc

// 錯誤由__TRACKBACK__來處理,可以用來打印錯誤信息,

// __TRACKBACK__函數需要自己定義在lua腳本中

lua_getglobal(mScriptContext, "__TRACKBACK__");

if (lua_type(mScriptContext, -1) != LUA_TFUNCTION) {

Log_d(LOG_TAG, "can not found errorFunc : __TRACKBACK__");

return false;

}

int errfunc = lua_gettop(mScriptContext);

// 獲取指定的方法

lua_getglobal(mScriptContext, functionName);

if (lua_type(mScriptContext, -1) != LUA_TFUNCTION) {

Log_d(LOG_TAG, "can not found func : %s", functionName);

return false;

}

// 跑指定的方法

return LUA_OK == lua_pcall(mScriptContext, 0, 0, errfunc);

}

void quitLuaThread(lua_State *L) {

int mask = LUA_MASKCOUNT;

lua_sethook(L, &quitLuaThreadHooker, mask, 1);

}

void quitLuaThreadHooker(lua_State *L, lua_Debug *ar) {

luaL_error(L, "quit Lua");

}

3. Lua單向調用Android

前面的實現,只允許Android層調用Lua的方法,而Lua層并不能調用Android層的方法。可不可以在Lua層調用Android層的方法?答案是可以的。一個思路是,Lua層調用Native層的方法,Native層再通過反射調用Android層的方法。先看看Lua層是怎么調用Native層的方法。Lua提供了一個C API:lua_register,它的原型是:

void lua_register (lua_State *L, const char *name, lua_CFunction f);

注冊一個CFunction。

參數

類型

說明

L

lua_State*

Lua的context

name

const char*

Lua層全局變量的名稱

f

lua_CFunction

C函數。原型是:int functionXXX(lua_State* L);其返回值的意義代表返回結果的個數。

我們可以用這個C API實現Lua層調用Native層的方法:

lua_register(mScriptContext, "getString" , getString);

int getString(lua_State *L) {

const char *cStr = "String From C Layer";

lua_pushstring(L, cStr);

return 1;

}

上面的代碼很簡單,先注冊一個名字為getString的全局變量,指向C函數getString。C函數getString中,先聲明并分配一個字符串cStr,再把這個字符串推入到Lua棧中,并返回結果個數。因此,在Lua層,如果執行getString(),則會得到字符串"String From C Layer",Lua層就可以調用Native層的方法了。

然后看看Native層調用Android層的方法。代碼如下:

int getString(lua_State *L) {

JNIEnv* env;

g_pJvm->GetEnv((void **) &env, JNI_VERSION_1_6);

jclass clazz = env->FindClass("com/zspirytus/androidlua/shell/ShellBridge");

if (!clazz) {

Log_d(LOG_TAG, "class not found!");

return 0;

}

jmethodID methodId = env->GetStaticMethodID(clazz, "getStringFromKotlinLayer", "()Ljava/lang/String;");

if (!methodId) {

Log_d(LOG_TAG, "method %s not found!", "getStringFromStaticJavaMethod");

return 0;

}

jstring jStr = (jstring) env->CallStaticObjectMethod(clazz, methodId);

const char *cStr = env->GetStringUTFChars(jStr, NULL);

lua_pushstring(L, cStr);

env->ReleaseStringUTFChars(jStr, cStr);

env->DeleteLocalRef(jStr);

return 1;

}

解釋一下,首先通過在JNI_OnLoad保存下來的JavaVM指針指針獲得Jni的環境變量,再用Jni的環境變量找到class和method,最后通過env、class和method反射調用Android層的方法獲得返回的jstring,轉成C-style的string后推入lua棧中,釋放資源,并返回結果個數。

在Android層,留下一個方法以供調用:

@Keep

object ShellBridge {

private val TAG = ShellBridge.javaClass.simpleName

@Keep

@JvmStatic

fun getStringFromKotlinLayer(): String {

return "String From Android Layer"

}

}

至此,Android層與Lua層的交互已經實現了。

4. 避免ANR

然而上面的實現可能會導致ANR,原因在于Lua腳本的執行可能是耗時的。如果Lua腳本的執行時間超過5秒,必然ANR。一個解決方法是,把Lua腳本的執行放到子線程當中。這個子線程應當給Native層管理比較好,還是Android層管理比較好?我個人覺得放在Native層比較好,這樣Android層就不需要專為執行Lua腳本而新建和管理線程,代碼就不會太復雜;即使Native層的邏輯比較復雜,編好了so,一般就會當做一個庫來使用,而不會去動它。所以,還是在Native層創建和管理線程。

pthread_create是Unix、Linux等系統創建線程的函數,它的原型是:

int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);

參數

類型

說明

tidp

pthread_t *restrict

線程ID

attr

const pthread_attr_t*restrict

線程屬性,默認為NULL

*(*start_rtn)(void *)

void

運行在新線程的函數

*restrict arg

void

start_rtn的所需參數

因此,我們可以把執行Lua腳本的邏輯移到新線程中:

void startWork() {

pthread_create(&mThreadId, NULL, &startWorkInner, (void*)this);

}

void stopWork() {

stopScript();

mThreadId = 0;

}

void* startWorkInner(void *args) {

startScript();

return nullptr;

}

這樣,startScript()就運行在新線程中,就不會有ANR的風險。我們把它封到一個類中,叫LuaTask,一次Lua腳本的開始與結束,都由這個類來管理。

#ifndef ANDROIDLUA_LUATASK_H

#define ANDROIDLUA_LUATASK_H

#include

#include

#include

#include "LuaEngine.h"

class LuaTask {

public:

LuaTask(jstring jBuff);

virtual ~LuaTask();

void startWork();

void stopWork();

bool isRunning();

private:

static void *startWorkInner(void *args);

private:

jstring mLuaBuff;

pthread_t mThreadId;

LuaEngine *mLuaEngine;

};

#endif //ANDROIDLUA_LUATASK_H

#include "LuaTask.h"

LuaTask::LuaTask(jstring jBuff) {

mLuaBuff = jBuff;

mLuaEngine = new LuaEngine();

mThreadId = 0;

}

LuaTask::~LuaTask() {

delete mLuaEngine;

}

void LuaTask::startWork() {

pthread_create(&mThreadId, NULL, &LuaTask::startWorkInner, (void*)this);

}

void LuaTask::stopWork() {

mLuaEngine->stopScript();

mThreadId = 0;

}

void* LuaTask::startWorkInner(void *args) {

LuaTask* task = (LuaTask*) args;

task->mLuaEngine->startScript(task->mLuaBuff, "main");

return nullptr;

}

bool LuaTask::isRunning() {

return mThreadId != 0;

}

但是,這是我們新創建的線程,還沒有attach到JavaVM。如果沒有attach到JavaVM,就會找不到JNIEnv,所以必須要attach到JavaVM,這樣才能拿到JavaVM的JNI環境變量,從而可以調用到Android層的方法。因此startWorkInner要改進一下:

void* startWorkInner(void *args) {

JNIEnv* env = nullptr;

JavaVMAttachArgs args{JNI_VERSION_1_6, nullptr, nullptr};

g_pJvm->AttachCurrentThread(&env, &args);

startScript()

g_pJvm->DetachCurrentThread();

return nullptr;

}

線程退出之前,記得要和JavaVM detach一下,這樣線程才能正常退出。

5. 運行腳本包

至此,我們完成了能夠隨時開始、停止,出錯能打印堆棧信息的執行Lua腳本功能。但實際上,我們不可能只跑單個腳本,并且腳本可能需要一些資源文件。因此我們一般會把腳本和資源文件打包成一個腳本包。在運行腳本之前,先解包,把腳本解析出來后再運行。

所以這個解析腳本的邏輯放在Native層還是Android層?我個人覺得放在Android層比較好。有兩點原因:

腳本包格式不確定,Native層不可能為每種情況進行適配,既然如此那就交由使用者來解析。

單一職責的原則,Native層負責還是只負責一種功能比較好。而且為解析腳本包而重新編譯一個so文件又太小題大做,所以解析的任務就交給使用者吧。

既然提到腳本包,我就簡單談談我的實現。我的實現是把lua腳本和資源文件一起壓縮成一個zip文件,在zip文件中有一個config文件,里面寫好了所有lua腳本的相對路徑。在解析的時候,先在內存中把config解壓出來,讀出所有lua腳本的相對路徑,然后在內存中把所有lua腳本文件都解壓出來后,拼接起來,在交給Native層運行。至于資源文件,根據腳本的運行情況進行動態解壓。我簡單的封裝了一下:

private external fun startScript(luaString: String): Boolean

external fun stopScript(): Boolean

external fun isScriptRunning(): Boolean

fun runScriptPkg(scriptPkg: File, configFile: String) {

mThreadPool?.execute {

val start = System.currentTimeMillis()

initScriptPkg(scriptPkg)

val zipFile = ZipFile(scriptPkg)

val config = ZipFileUtils.getFileContentFromZipFile(zipFile, configFile)

val luaScriptPaths = config.split("\r\n")

val luaScript = ZipFileUtils.getFilesContentFromZipFile(zipFile, luaScriptPaths)

Log.d("USE_TIME", "${System.currentTimeMillis() - start} ms")

mHandler?.post {

startScript(luaScript)

}

}

}

object ZipFileUtils {

fun getFileContentFromZipFile(zipFile: ZipFile, targetFile: String): String {

var ins: InputStream? = null

try {

val ze = zipFile.getEntry(targetFile)

return if (ze != null) {

ins = zipFile.getInputStream(ze)

FileUtils.readInputStream(ins)

} else {

""

}

} finally {

ins?.close()

}

}

fun getFilesContentFromZipFile(zipFile: ZipFile, targetFiles: List): String {

val stringBuilder = StringBuilder()

targetFiles.filter { it.isNotEmpty() and it.isNotBlank() }.forEach {

val content = getFileContentFromZipFile(zipFile, it)

stringBuilder.append(content).append('\n')

}

return stringBuilder.toString()

}

}

object FileUtils {

fun readInputStream(ins: InputStream): String {

return ins.bufferedReader().use(BufferedReader::readText)

}

}

至此,我們在原有功能的基礎上,增加了跑腳本包的功能。完整的代碼可以看倉庫。

6. 總結

Android層調用Lua層方法

Lua層調用Android層方法

7. 感想

Android跑Lua腳本這個過程其實是很簡單的,不是主要難點。這次主要卡住的地方是在JNI部分,因為我發現我所了解的C語言語法太古老了,跟不上現在的C語言。雖然我的C語言的代碼量也不多,加上我對JNI的一些編程規范不太了解,所以一路磕磕絆絆,但是總算是寫出來了。Kotlin和C/C++還是要多熟悉熟悉,多練練。

總結

以上是生活随笔為你收集整理的android lua sd卡,记Android层执行Lua脚本的一次实践的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 成人免费观看视频大全 | 91成人免费版 | 亚洲欧美在线综合 | 久久久久a | 夜夜嗨av一区二区三区免费区 | 毛茸茸多毛bbb毛多视频 | 3o一40一50一6o女人毛片 | 国产麻豆剧传媒精品国产 | 国产一区二区色 | 日韩国产电影 | 美女福利视频 | 亚洲经典一区二区三区 | 欧美精品卡一卡二 | 夜夜爽夜夜叫夜夜高潮漏水 | 91av中文字幕 | 国产视频中文字幕 | 国产精品99久久久久久久久 | 日本啊啊视频 | 小罗莉极品一线天在线 | 精久久久久久久 | 91桃色在线| 激情五月婷婷网 | 高清免费av | 一级片在线免费观看 | 一级艳片新婚之夜 | 在线视频免费观看 | 国产精品mv| 久草福利| 亚洲欧美国产精品专区久久 | 天天天天色| 亚洲综合成人亚洲 | 高潮喷水一区二区三区 | 久久er99热精品一区二区介绍 | 精品1区2区 | 日韩少妇内射免费播放18禁裸乳 | 日本在线中文字幕专区 | 午夜视频在线免费看 | 韩国三级hd中文字幕的背景音乐 | 日本中文字幕久久 | 白白色在线观看 | 亚洲图片自拍偷拍区 | 亚洲深夜福利 | 误杀1电影免费观看高清完整版 | 天天影视色 | 国产第8页 | 日韩伦理av | www.日| 日本午夜视频 | 国产精品午夜在线观看 | 欧美77777 | 国产一级色 | 奇米中文字幕 | 1级黄色大片| 欧美高清另类 | 九九九色 | 国产真实乱人偷精品 | 国内老熟妇对白xxxxhd | 国产男人的天堂 | 欧美亚洲精品天堂 | 超碰97在线免费观看 | 久久精品导航 | 在线成人av网站 | 亚洲免费视频网站 | 久久婷婷综合色丁香五月 | 热久久中文 | 欧美亚洲国产另类 | 欧美日韩视频无码一区二区三 | 91伦理 | 日日爽| 麻豆视频在线观看免费网站 | 91手机在线观看 | 国产美女被遭强高潮免费网站 | 天天激情 | 日本在线视频免费观看 | 中文字幕乱轮 | 国产美女被草 | 波多野结衣黄色片 | wwwxxxx欧美 | 中文字幕国产 | 亚洲校园激情 | 久久久久久久久久久免费 | 日本簧片在线观看 | 国产99久久久国产精品成人免费 | www.国产视频 | 亚洲欧美日韩中文字幕在线观看 | 日本一区二区三区成人 | 激情一级片| h片免费在线观看 | 自拍偷拍亚洲天堂 | 国产精品久久999 | 国产在线不卡一区 | 美腿丝袜亚洲色图 | 新红楼梦2005锦江版高清在线观看 | 国产精品网址 | 一级 黄 色 片69| 在线va视频 | 青青国产在线观看 | 亚洲黄色免费在线观看 | 中文字幕免费在线观看视频 |