最近接手一个移动端应用,要为其android版本扩展支持调用lua脚本解析,而且最好同时能支持luasocket。如果只是希望在android下支持lua标准库的使用,那么androLua这个开源项目就可以解决这个问题。然而在为其扩展支持三方库,如luasocket时,遇到了一些问题,经过一翻折腾,最终解决了这个问题,把折腾的过程记录下来,方便有其他相同需求的人少走弯路。


基础知识

Lua是一门用标准C编写的动态脚本语言,如果希望在android上使用,则需要解决两个问题。第一,需要用JNI为Lua的C库进行封装,这样才可能在Java中使用。第二,由于Android系统开发所特有的系统环境限制,Lua三方库的动态加载机制和lua脚本模块的导入机制将不能正常运行,需要进行特殊处理。

对于第一个问题,LuaJava这个开源项目提供了Java调用Lua C代码的JNI封装接口。

对于第二个问题,分为两个方面。一方面是lua脚本模块的加载,由于android系统和普通的Linux环境并不相同,如果按照Lua标准的模块查找机制,将无法找到相关的模块文件。AndroLua解决了这个问题,它通过把lua脚本模块放到android应用的asserts目录下,在运行时通过资源类打开这些文件,用dostring来执行lua代码。另一方面是lua三方库如luasocket /luaJson等共享库的路径查找机制,在android系统下无法正常使用。本文主要解决第二个问题的第二个方面。


让lua能支持三方库的调用思考

第一,使用Java调用共享库的方式,把三方库编译成so文件,然后再封装一层JNI接口,通过java代码使用。但这种方式还不如直接用Java实现相关功能,在Lua中调用来的简单。

第二,利用lua自己的so模块加载机制,直接加载三方库。最初,我是希望走这条路的,因为这样不需要修改lua的源代码,但一直未能成功。其方法思想是把三方库编译的单独so文件打到apk包里,这样安装应用后,so文件会有一个路径。这样只需要修改luaconf.h文件,把CPATH的配置修改为so文件的路径,然后用ndk重新编译luaJava。这种方法确实有一定的效果,当我用这种方式把luasocket加载进去后,可以成功调用一次操作,操作返回后应用就崩溃了,查看logcat输出的日志,在调用共享库方法后出现段错误。一度以为是luasocket的实现有缺陷,希望能解决源码的问题,花费了不少无用功。后来,随着我对lua C API了解的增加,这条捷径被我放弃了。决定采用下面的方法。

第三,直接把luasocket代码静态编译到lua中,也就是说socket将成为lua的标准库,无须动态加载,这样luajava最终编译出来只有一个so文件,其中包含了lua/luasocket的功能。


具体实现步骤如下

1 目录结构整理

在jni目录下创建3个子目录,分别存放lua / luasocket / luajava 的源码

2 修改jni/lua/linit.c源码

将luasocket导出加载函数注册到标准库加载函数中,此外增加两个头文件的包含和模块名宏定义

#define linit_c#define LUA_LIB#include "lua.h"#include "lualib.h"#include "lauxlib.h"#include "luasocket.h"#include "mime.h"#define LUA_SOCKETLIBNAME "socket"#define LUA_MIMELIBNAME "mime"static const luaL_Reg lualibs[] = {  {"", luaopen_base},  {LUA_LOADLIBNAME, luaopen_package},  {LUA_TABLIBNAME, luaopen_table},  {LUA_IOLIBNAME, luaopen_io},  {LUA_OSLIBNAME, luaopen_os},  {LUA_STRLIBNAME, luaopen_string},  {LUA_MATHLIBNAME, luaopen_math},  {LUA_DBLIBNAME, luaopen_debug},  {LUA_MIMELIBNAME,luaopen_mime_core},  {LUA_SOCKETLIBNAME,luaopen_socket_core},  {NULL, NULL}};

3 修改ndk的构建文件

创建luasocket/Android.mk将luasocket先编译为静态库,ndk的项目文件和Make的语法一样,只是增加了一些宏和函数,不再此做介绍

LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE    := socketLOCAL_C_INCLUDES += $(LOCAL_PATH)/../lua/LOCAL_SRC_FILES := luasocket.c timeout.c buffer.c io.c auxiliar.c options.c inet.c tcp.c udp.c except.c select.c usocket.c mime.cinclude $(BUILD_STATIC_LIBRARY)
修改luajava/Android.mk将编译的lua和luasocket静态库一起编译为共享库
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_C_INCLUDES += $(LOCAL_PATH)/../luaLOCAL_C_INCLUDES += $(LOCAL_PATH)/../luasocketLOCAL_MODULE     := luajavaLOCAL_SRC_FILES  := luajava.cLOCAL_STATIC_LIBRARIES := liblua libsocketLOCAL_LDLIBS    := -ldl -lminclude $(BUILD_SHARED_LIBRARY)

4 修改socket.lua mime.lua http.lua源代码

在原始的luasocket中,共享库这一层生成了socket/core.so mime/core.so,而在lua模块这一层导出了socket.http socket.url socket.lua mime.lua这4个重要的模块。其中socket.lua中加载socket/core.so,导出了socket模块,mime.lua加载mime/core.so导出了mime模块。

由于android中只能把所有.lua文件存放在assert目录下,且不能有层次结构。因而创建模块时,要把http/url模块从socket中提出来,形成单独的模块。另外luasocket内部注册模块时,把模块名定义为socket和mime,为了不修改luasocket的代码,因而依旧保留了共享库的模块名为socket/mime。这样就和上层lua脚本模块重名,造成冲突。需要修改lua脚本的模块名,分别修改为socketlua.lua,mimelua.lua。这样只需要修改各lua文件的头部模块变量初始化部分的代码即可,各修改部分的代码如下:

socketlua.lua(原socket.lua)文件头部

local base = _Glocal string = require("string")local math = require("math")local socket = require("socket")module("socket")
mimelua.lua(原mime.lua)文件头部
local base = _Glocal ltn12 = require("ltn12")local mime = require("mime")local io = require("io")local string = require("string")module("mime")
http.lua(原socket/http.lua)文件头部
require("socketlua")require("mimelua")local socket = _G.socketlocal mime = _G.mimelocal base = _Glocal url = require("url")local ltn12 = require("ltn12")local string = require("string")local table = require("table")module("http")
url.lua(原socket/url)文件头部
local string = require("string")local base = _Glocal table = require("table")module("url")

5 应用

修改后,在lua中需要使用哪个模块就可以直接引用哪个模块了。下面是个小示例,它用flvxz网站提供的视频解析功能来解析一些主流网站的视频下载地址:

local http=require('http')local url =require('url')local mime=require('mime') local json=require('json')local base = _Gmodule("flvxz")local flvxz_parse_json = function(jobj)    for k,item in base.pairs(jobj) do        files = item['files']        if #files == 1 then           if  files[1]['ftype']=="mp4" then               return files[1]['furl']            end        end    endend        flvxz_parse_url = function(url)        local api="http://api.flvxz.com/jsonp/purejson/url/";        local surl = base.string.gsub(url,"://",":##");        local eurl = mime.b64(surl);        local b,c,h = http.request(api..eurl);        local succ,data = base.pcall(json.decode,b)        if not succ then            return nil;        end        return flvxz_parse_json(data)endlocal test = function()  local urls= {        "http://v.youku.com/v_show/id_XNTUzMDQzODky.html",        "http://www.tudou.com/programs/view/YDn_zTq_8gI/",        "http://www.iqiyi.com/v_19rrh3v2vw.html",        "http://www.letv.com/ptv/vplay/20047225.html"       }  for k,url in base.pairs(urls) do     base.print(flvxz_parse_url(url),url)     base.print("-------------\n")  endend--test();

相关链接

Luajava http://keplerproject.org/luajava/

androLua https://github.com/mkottman/AndroLua/




更多相关文章

  1. Android自定义进度条颜色
  2. Android里webviewActivity一般功能实现
  3. Android(安卓)实现阅读pdf格式的文件和android 7.0以上版本出现F
  4. Android(安卓)UI 之WaterFall瀑布流效果
  5. Android(安卓)Zip文件解压缩代码
  6. Android中运用Pull解析器读取XML文件
  7. POCO C++库在Android(安卓)平台上集成(3) (集成成功)
  8. android音频视频播放器
  9. Android(安卓)framebuffer 截屏原理

随机推荐

  1. Android(安卓)Fragment页打开相册
  2. Android利用Ksoap2连接webservice 源码
  3. Android(安卓)Sample NotePad学习二
  4. ubuntu 搭建android编译环境
  5. Android——Activity生命周期
  6. Android(安卓)GPS获得经纬度并得到该坐标
  7. Android(安卓)http POST
  8. Android(安卓)- 保持在底部的按钮栏,上面
  9. android EditText 监听
  10. android如何获取后台正在运行的service