yet another blog.

May 21 2016
lua与c相互调用的常用接口

lua与c交互主要通过栈,栈底为1,栈顶为-1,以下是其相互调用的常用接口:

c调lua

栈操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
int lua_gettop (lua_State *L);
返回栈顶元素的索引。 因为索引是从 1 开始编号的, 所以这个结果等于栈上的元素个数; 特别指出,0 表示栈为空。
void lua_settop (lua_State *L, int index); //相当于至栈顶HEAD指针
参数允许传入任何索引以及 0 。 它将把堆栈的栈顶设为这个索引。 如果新的栈顶比原来的大, 超出部分的新元素将被填为 nil 。 如果 index 为 0 , 把栈上所有元素移除。
void lua_pop (lua_State *L, int n);
从栈中弹出 n 个元素。
void lua_insert (lua_State *L, int index);
把栈顶元素移动到指定的有效索引处, 依次移动这个索引之上的元素。 不要用伪索引来调用这个函数, 因为伪索引没有真正指向栈上的位置。
void lua_remove (lua_State *L, int index);
从给定有效索引处移除一个元素, 把这个索引之上的所有元素移下来填补上这个空隙。 不能用伪索引来调用这个函数,因为伪索引并不指向真实的栈上的位置。
void lua_replace (lua_State *L, int index);
把栈顶元素放置到给定位置而不移动其它元素 (因此覆盖了那个位置处的值),然后将栈顶元素弹出。
void lua_pushvalue (lua_State *L, int index);
把栈上给定索引处的元素作一个副本压栈。
void lua_pushboolean (lua_State *L, int b);
把 b 作为一个布尔量压栈。
int lua_toboolean (lua_State *L, int index);
把给定索引处的 Lua 值转换为一个 C 中的布尔量( 0 或是 1 )。 和 Lua 中做的所有测试一样, lua_toboolean 会把任何不同于 false 和 nil 的值当作真返回; 否则就返回假。 (如果你想只接收真正的 boolean 值, 就需要使用 lua_isboolean 来测试值的类型。)
int lua_checkstack (lua_State *L, int n);
确保堆栈上至少有 n 个额外空位。 如果不能把堆栈扩展到相应的尺寸,函数返回假。 失败的原因包括将把栈扩展到比固定最大尺寸还大 (至少是几千个元素)或分配内存失败。 这个函数永远不会缩小堆栈; 如果堆栈已经比需要的大了,那么就保持原样。
lua_Number luaL_checknumber (lua_State *L, int arg);
检查函数的第 arg 个参数是否是一个 数字,并返回这个数字。
int lua_getglobal (lua_State *L, const char *name); //取全局变量并压栈
把全局变量 name 里的值压栈,返回该值的类型。
void lua_setglobal (lua_State *L, const char *name); //设置全局变量并出栈
从堆栈上弹出一个值,并将其设为全局变量 name 的新值。

table操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
void lua_createtable (lua_State *L, int narr, int nrec);
创建一张新的空表压栈。 参数 narr 建议了这张表作为序列使用时会有多少个元素; 参数 nrec 建议了这张表可能拥有多少序列之外的元素。 Lua 会使用这些建议来预分配这张新表。 如果你知道这张表用途的更多信息,预分配可以提高性能。 否则,你可以使用函数 lua_newtable 。
void lua_newtable (lua_State *L);
创建一张空表,并将其压栈。 它等价于 lua_createtable(L, 0, 0) 。
int lua_gettable (lua_State *L, int index); //取table里的值,在index处table栈顶k的值
把 t[k] 的值压栈, 这里的 t 是指索引指向的值, 而 k 则是栈顶放的值。
这个函数会弹出堆栈上的键,把结果放在栈上相同位置。 和在 Lua 中一样, 这个函数可能触发对应 "index" 事件的元方法 (参见 §2.4 )。
返回压入值的类型。
int lua_getfield (lua_State *L, int index, const char *k); // = lua_pushstring(L, key); lua_gettable(L, -2);
把 t[k] 的值压栈, 这里的 t 是索引指向的值。 在 Lua 中,这个函数可能触发对应 "index" 事件对应的元方法 (参见 §2.4 )。
函数将返回压入值的类型。
void lua_settable (lua_State *L, int index);
做一个等价于 t[k] = v 的操作, 这里 t 是给出的索引处的值, v 是栈顶的那个值, k 是栈顶之下的值。
这个函数会将键和值都弹出栈。 跟在 Lua 中一样,这个函数可能触发一个 "newindex" 事件的元方法 (参见 §2.4)。
void lua_setfield (lua_State *L, int index, const char *k);
做一个等价于 t[k] = v 的操作, 这里 t 是给出的索引处的值, 而 v 是栈顶的那个值。
这个函数将把这个值弹出栈。 跟在 Lua 中一样,这个函数可能触发一个 "newindex" 事件的元方法 (参见 §2.4)。
int lua_rawget (lua_State *L, int index);
类似于 lua_gettable , 但是作一次直接访问(不触发元方法)。
int lua_rawgeti (lua_State *L, int index, lua_Integer n);
把 t[n] 的值压栈, 这里的 t 是指给定索引处的表。 这是一次直接访问;就是说,它不会触发元方法。
void lua_rawset (lua_State *L, int index);
类似于 lua_settable , 但是是做一次直接赋值(不触发元方法)。
void lua_rawseti (lua_State *L, int index, lua_Integer i);
等价于 t[i] = v , 这里的 t 是指给定索引处的表, 而 v 是栈顶的值。
这个函数会将值弹出栈。 赋值是直接的;即不会触发元方法。
int lua_getmetatable (lua_State *L, int index);
如果该索引处的值有元表,则将其元表压栈,返回 1 。 否则不会将任何东西入栈,返回 0

调lua函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int lua_pcall (lua_State *L, int nargs, int nresults, int msgh);
以保护模式调用一个函数。
nargs 和 nresults 的含义与 lua_call 中的相同。 如果在调用过程中没有发生错误, lua_pcall 的行为和 lua_call 完全一致。 但是,如果有错误发生的话, lua_pcall 会捕获它, 然后把唯一的值(错误消息)压栈,然后返回错误码。 同 lua_call 一样, lua_pcall 总是把函数本身和它的参数从栈上移除。
如果 msgh 是 0 , 返回在栈顶的错误消息就和原始错误消息完全一致。 否则, msgh 就被当成是 错误处理函数 在栈上的索引位置。 (在当前的实现里,这个索引不能是伪索引。) 在发生运行时错误时, 这个函数会被调用而参数就是错误消息。 错误处理函数的返回值将被 lua_pcall 作为错误消息返回在堆栈上。
典型的用法中,错误处理函数被用来给错误消息加上更多的调试信息, 比如栈跟踪信息。 这些信息在 lua_pcall 返回后, 由于栈已经展开,所以收集不到了。
lua_pcall 函数会返回下列常数 (定义在 lua.h 内)中的一个:
LUA_OK (0): 成功。
LUA_ERRRUN: 运行时错误。
LUA_ERRMEM: 内存分配错误。对于这种错,Lua 不会调用错误处理函数。
LUA_ERRERR: 在运行错误处理函数时发生的错误。
LUA_ERRGCMM: 在运行 __gc 元方法时发生的错误。 (这个错误和被调用的函数无关。)
int lua_pcallk (lua_State *L,
int nargs,
int nresults,
int msgh,
lua_KContext ctx,
lua_KFunction k);
这个函数的行为和 lua_pcall 完全一致,只不过它还允许被调用的函数让出

lua调c

注册c函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
typedef int (*lua_CFunction) (lua_State *L); //每个函数有自己的局部私有栈
C 函数的类型。
为了正确的和 Lua 通讯, C 函数必须使用下列协议。 这个协议定义了参数以及返回值传递方法: C 函数通过 Lua 中的栈来接受参数, 参数以正序入栈(第一个参数首先入栈)。 因此,当函数开始的时候, lua_gettop(L) 可以返回函数收到的参数个数。 第一个参数(如果有的话)在索引 1 的地方, 而最后一个参数在索引 lua_gettop(L) 处。 当需要向 Lua 返回值的时候, C 函数只需要把它们以正序压到堆栈上(第一个返回值最先压入), 然后返回这些返回值的个数。 在这些返回值之下的,堆栈上的东西都会被 Lua 丢掉。 和 Lua 函数一样,从 Lua 中调用 C 函数也可以有很多返回值。
void lua_pushcfunction (lua_State *L, lua_CFunction f); : //注册c函数 lua_pushcfunction(L, I_sin); lua_setglobal(L, "mysin");
将一个 C 函数压栈。 这个函数接收一个 C 函数指针, 并将一个类型为 function 的 Lua 值压栈。 当这个栈顶的值被调用时,将触发对应的 C 函数。
注册到 Lua 中的任何函数都必须遵循正确的协议来接收参数和返回值 (参见 lua_CFunction )。
lua_pushcfunction 是作为一个宏定义出现的:
#define lua_pushcfunction(L,f) lua_pushcclosure(L,f,0)
void lua_register (lua_State *L, const char *name, lua_CFunction f); //注册c函数
把 C 函数 f 设到全局变量 name 中。 它通过一个宏定义:
#define lua_register(L,n,f) \
(lua_pushcfunction(L, f), lua_setglobal(L, n))
typedef struct luaL_Reg {
const char *name;
lua_CFunction func;
} luaL_Reg;
用于 luaL_setfuncs 注册函数的数组类型。 name 指函数名,func 是函数指针。 任何 luaL_Reg 数组必须以一对 name 与 func 皆为 NULL 结束。

c模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//in c
static int I_dir (lua_State *L) {
//
}
static const struct luaL_Reg mylib[] = {
{"dir", I_dir},
{NULL, NULL} //结尾
}
int luaopen_mylib (lua_State *L) {
luaL_register(L, "mylib", mylib);
return 1
}
// in lua
require "mylib"

package.searchers

用于 require 控制如何加载模块的表。

这张表内的每一项都是一个 查找器函数。 当查找一个模块时, require 按次序调用这些查找器, 并传入模块名(require 的参数)作为唯一的一个参数。 此函数可以返回另一个函数(模块的 加载器)加上另一个将传递给这个加载器的参数。 或是返回一个描述为何没有找到这个模块的字符串 (或是返回 nil 什么也不想说)。

Lua 用四个查找器函数初始化这张表。

第一个查找器就是简单的在 package.preload 表中查找加载器。

第二个查找器用于查找 Lua 库的加载库。 它使用储存在 package.path 中的路径来做查找工作。 查找过程和函数 package.searchpath 描述的一致。

第三个查找器用于查找 C 库的加载库。 它使用储存在 package.cpath 中的路径来做查找工作。 同样, 查找过程和函数 package.searchpath 描述的一致。 例如,如果 C 路径是这样一个字符串

"./?.so;./?.dll;/usr/local/?/init.so"

查找器查找模块 foo 会依次尝试打开文件 ./foo.so,./foo.dll, 以及 /usr/local/foo/init.so。 一旦它找到一个 C 库, 查找器首先使用动态链接机制连接该库。 然后尝试在该库中找到可以用作加载器的 C 函数。 这个 C 函数的名字是 “luaopen_” 紧接模块名的字符串, 其中字符串中所有的下划线都会被替换成点。 此外,如果模块名中有横线, 横线后面的部分(包括横线)都被去掉。 例如,如果模块名为 a.b.c-v2.1, 函数名就是 luaopen_a_b_c。

第四个搜索器是 一体化加载器。 它从 C 路径中查找指定模块的根名字。 例如,当请求 a.b.c 时, 它将查找 a 这个 C 库。 如果找得到,它会在里面找子模块的加载函数。 在我们的例子中,就是找 luaopen_a_b_c。 利用这个机制,可以把若干 C 子模块打包进单个库。 每个子模块都可以有原本的加载函数名。

除了第一个(预加载)搜索器外,每个搜索器都会返回 它找到的模块的文件名。 这和 package.searchpath 的返回值一样。 第一个搜索器没有返回值。

参考

http://cloudwu.github.io/lua53doc/manual.html

Jan 3 2016
用go写的格式化输出目录文件的小工具

这两天用markdown写去年的总结,然后有很多的照片想加进来,发现一个个手写路径比较麻烦,而直接拖进来只有少数编辑器支持,而且一堆图片拖进来的话还是乱序,比较麻烦。

于是用go写了个小程序,可以将目录下的文件按模板输出,还可以选择批量加入指定字符做分隔行。

1
2
3
4
5
6
7
8
9
Usage of listdir:
-b string
blank line by
-bn int
blank line number
-f string
format: xxx___xxx
-p string
path (default "./")

比如我这里就是将目录里的图片全部格式化成markdown的语法,并且每个中间插入3行<br />做换行,然后输出到s.md中

1
listdir -f "\![](___)" -b "<br />" -bn 3 > s.md

代码如下,也在gist上也放了一份。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package main
import (
"flag"
"fmt"
"io/ioutil"
"path/filepath"
"sort"
"strconv"
"strings"
)
const (
//PATTERN format by this pattern
PATTERN = "___"
)
var path = flag.String("p", "./", "path")
var format = flag.String("f", "", "format: xxx"+PATTERN+"xxx")
var blankLine = flag.String("b", "", "blank line by")
var blankLineNum = flag.Int("bn", 0, "blank line number")
var sortByNum = flag.Bool("sn", true, "sort by num")
type ByNum []string
func (paths ByNum) Len() int {
return len(paths)
}
func (paths ByNum) Swap(i, j int) {
paths[i], paths[j] = paths[j], paths[i]
}
func (paths ByNum) Less(i, j int) bool {
pathi := filepath.Base(paths[i])
pathj := filepath.Base(paths[j])
namei := strings.Split(pathi, ".")[0]
namej := strings.Split(pathj, ".")[0]
numi, _ := strconv.Atoi(namei)
numj, _ := strconv.Atoi(namej)
return numi < numj
}
func listDir(path *string) []string {
files, _ := ioutil.ReadDir(*path)
fileSlice := make([]string, len(files))
absPath, _ := filepath.Abs(*path)
for i, file := range files {
fileSlice[i] = absPath + "/" + file.Name()
}
if *sortByNum {
sort.Sort(ByNum(fileSlice))
} else {
sort.Strings(fileSlice)
}
return fileSlice
}
func formatStr(str *string) *string {
if len(*format) > 0 && strings.Contains(*format, PATTERN) {
replace := strings.Replace(*format, PATTERN, *str, -1)
return &replace
}
return str
}
func main() {
flag.Parse()
rets := listDir(path)
var ret string
for i := 0; i < len(rets); i++ {
ret = rets[i]
fmt.Println(*(formatStr(&ret)))
if i == len(rets)-1 {
break
}
for i := 0; i < *blankLineNum; i++ {
fmt.Println(*blankLine)
}
}
}

Oct 24 2015
通过shadowsocks搭建http代理

用shadowsocks科学上网有一阵了,感觉速度很快。这两天在家里用intellij安插件发现很慢,想着能否搭一个本地的http代理中转,将请求发到shadowsocks上,一搜果然有现成的方案,也很简单,这里记录下。

安装Polipo

1
brew install polipo

配置polipo到shadowsocks端口

1
2
service polipo stop
polipo socksParentProxy=localhost:1080

使用

1
2
3
4
5
6
7
8
9
各种app里配置http_proxy=localhost:8123
http_proxy=http://localhost:8123 brew update
http_proxy=http://localhost:8123 curl www.google.com
http_proxy=http://localhost:8123 wget www.google.com
git config --global http.proxy 127.0.0.1:8123

Sep 26 2015
React Native Android版核心层js-bridge实现浅析

这是之前在android版刚出来不久时在内部发的一篇浅析,现在同步上来,这之间,版本由于快速变动或多或少有些许出入,但大体思路暂时未变。

react native的android版于上周发布,然后花了点时间试了下,从几个demo的体验来看都挺流畅,具体环境搭建与基本介绍,可以看看官方的文档说明。
之后周末又花了点时间看了下内部的实现,这里主要来简单介绍下java层是如何与js绑定的。

整体架构上,可以分为java层<->c++层<->js层,其中java层作为入口,主要是对整体流程的控制,模块的配置。c++层是对JavaScriptCore接口的封装(每个应用自己背一个js解释器),直接执行js脚本并获取返回值。js层则是界面的布局与事件的响应处理。

启动

MoviesActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MoviesActivity extends Activity implements DefaultHardwareBackBtnHandler {
private ReactInstanceManager mReactInstanceManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setBundleAssetName("MoviesApp.android.bundle")
.setJSMainModuleName("Examples/Movies/MoviesApp.android")
.addPackage(new MainReactPackage())
.setUseDeveloperSupport(true)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
((ReactRootView) findViewById(R.id.react_root_view))
.startReactApplication(mReactInstanceManager, "MoviesApp", null);
}

入口上,主要是在相应使用React的Activity中加入一个ReactRootView,在Activity的onCreate中,通过ReactRootView启动React。这里的addPackage是配置应用所需的java模块与js模块,这个后面会细说。然后React的启动中主要会构造React的context,加载模块配置表,初始化CatalystInstance(java与js调用的高层接口) 与ReactBridge(jni接口封装),最后通过AppRegistry调用到js层的入口完成启动。

ReactInstanceManager.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void attachMeasuredRootViewToInstance(
ReactRootView rootView,
CatalystInstance catalystInstance) {
UiThreadUtil.assertOnUiThread();
// Reset view content as it's going to be populated by the application content from JS
rootView.removeAllViews();
rootView.setId(View.NO_ID);
UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class);
int rootTag = uiManagerModule.addMeasuredRootView(rootView);
@Nullable Bundle launchOptions = rootView.getLaunchOptions();
WritableMap initialProps = launchOptions != null
? Arguments.fromBundle(launchOptions)
: Arguments.createMap();
String jsAppModuleName = rootView.getJSModuleName();
WritableNativeMap appParams = new WritableNativeMap();
appParams.putDouble("rootTag", rootTag);
appParams.putMap("initialProps", initialProps);
catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
}

模块注册

React Native中,需要用户自己定义需要的配置ReactPackage,显式的声明哪些java类或js脚本是api,是两边都需要的。的然后在启动中注册后,会同时在java端与js端共用一份模块配置表,主要以{moduleID, methodID}定位到某个java模块或js模块的方法。

CoreModulesPackage.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/* package */ class CoreModulesPackage implements ReactPackage {
private final ReactInstanceManager mReactInstanceManager;
private final DefaultHardwareBackBtnHandler mHardwareBackBtnHandler;
CoreModulesPackage(
ReactInstanceManager reactInstanceManager,
DefaultHardwareBackBtnHandler hardwareBackBtnHandler) {
mReactInstanceManager = reactInstanceManager;
mHardwareBackBtnHandler = hardwareBackBtnHandler;
}
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext catalystApplicationContext) {
return Arrays.<NativeModule>asList(
new AnimationsDebugModule(
catalystApplicationContext,
mReactInstanceManager.getDevSupportManager().getDevSettings()),
new AndroidInfoModule(),
new DeviceEventManagerModule(catalystApplicationContext, mHardwareBackBtnHandler),
new ExceptionsManagerModule(mReactInstanceManager.getDevSupportManager()),
new Timing(catalystApplicationContext),
...
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Arrays.asList(
DeviceEventManagerModule.RCTDeviceEventEmitter.class,
JSTimersExecution.class,
RCTEventEmitter.class,
AppRegistry.class,
ReactNative.class,
DebugComponentOwnershipModule.RCTDebugComponentOwnership.class);
}
...

模块配置

NativeModuleRegistry.java
1
2
3
4
5
6
7
public class NativeModuleRegistry {
private final ArrayList<ModuleDefinition> mModuleTable;
private final Map<Class<NativeModule>, NativeModule> mModuleInstances;
private final String mModuleDescriptions;
private final ArrayList<OnBatchCompleteListener> mBatchCompleteListenerModules;
...

java模块注册表

JavaScriptModuleRegistry.java
1
2
3
4
5
/*package*/ class JavaScriptModuleRegistry {
private final HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> mModuleInstances;
...
}

js模块注册表

ToastModule.java
1
2
3
4
5
6
7
8
9
10
11
12
13
public class ToastModule extends ReactContextBaseJavaModule {
public ToastModule(ReactApplicationContext reactContext) {
super(reactContext);
}
...
@ReactMethod
public void show(String message, int duration) {
Toast.makeText(getReactApplicationContext(), message, duration).show();
}
}

java模块主要是通过@ReactMethod注解表明该方法为可以导出到js的方法

AppRegistry.java
1
2
3
4
5
6
/**
* JS module interface - main entry point for launching react application for a given key.
*/
public interface AppRegistry extends JavaScriptModule {
void runApplication(String appKey, WritableMap appParameters);
}
JavaScriptModuleRegistry.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static class JavaScriptModuleInvocationHandler implements InvocationHandler {
private final CatalystInstance mCatalystInstance;
private final JavaScriptModuleRegistration mModuleRegistration;
public JavaScriptModuleInvocationHandler(
CatalystInstance catalystInstance,
JavaScriptModuleRegistration moduleRegistration) {
mCatalystInstance = catalystInstance;
mModuleRegistration = moduleRegistration;
}
@Override
public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String tracingName = mModuleRegistration.getTracingName(method);
mCatalystInstance.callFunction(
mModuleRegistration.getModuleId(),
mModuleRegistration.getMethodId(method),
Arguments.fromJavaArgs(args),
tracingName);
return null;
}
}

js模块是继承JavaScriptModule的interface,与之对应的是在js侧有个同名的js脚本,运行时通过动态代理技术,生成实现,在模块配置表中查找到该js,然后发起调用。

CatalystInstance.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void initializeBridge(
JavaScriptExecutor jsExecutor,
NativeModuleRegistry registry,
JavaScriptModulesConfig jsModulesConfig,
JSBundleLoader jsBundleLoader) {
mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
Assertions.assertCondition(mBridge == null, "initializeBridge should be called once");
mBridge = new ReactBridge(
jsExecutor,
new NativeModulesReactCallback(),
mCatalystQueueConfiguration.getNativeModulesQueueThread());
mBridge.setGlobalVariable(
"__fbBatchedBridgeConfig",
buildModulesConfigJSONProperty(registry, jsModulesConfig));
jsBundleLoader.loadScript(mBridge);
}

将模块配置表作为全局变量导入到js层,

调用

java与js之前的调用,两边都是以{moduleID, methodID}的形式作为一个call,然后一端在之前的模块配置表里查找注册的模块与方法。

java调用js

是直接通过c++层对JavaScriptCore封装的接口调用到相应的脚本。

ReactBridge.java
1
2
3
4
5
6
public native void loadScriptFromAssets(AssetManager assetManager, String assetName);
public native void loadScriptFromNetworkCached(String sourceURL, @Nullable String tempFileName);
public native void callFunction(int moduleId, int methodId, NativeArray arguments);
public native void invokeCallback(int callbackID, NativeArray arguments);
public native void setGlobalVariable(String propertyName, String jsonEncodedArgument);
...

OnLoad.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void callFunction(JNIEnv* env, jobject obj, jint moduleId, jint methodId,
NativeArray::jhybridobject args) {
auto bridge = extractRefPtr<Bridge>(env, obj);
auto arguments = cthis(wrap_alias(args));
std::vector<folly::dynamic> call{
(double) moduleId,
(double) methodId,
std::move(arguments->array),
};
try {
bridge->executeJSCall("BatchedBridge", "callFunctionReturnFlushedQueue", std::move(call));
} catch (...) {
translatePendingCppExceptionToJavaException();
}
}

c++层,直接调用BatchedBridge.jsMessageQueue.js中的callFunctionReturnFlushedQueue,最终会加载js对应的模块并调用到相应方法。

MessageQueue.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
callFunctionReturnFlushedQueue(module, method, args) {
guard(() => this.__callFunction(module, method, args));
return this.flushedQueue();
}
...
__callFunction(module, method, args) {
BridgeProfiling.profile(() => `${module}.${method}(${stringifySafe(args)})`);
if (isFinite(module)) {
method = this._methodTable[module][method];
module = this._moduleTable[module];
}
if (__DEV__ && SPY_MODE) {
console.log('N->JS : ' + module + '.' + method + '(' + JSON.stringify(args) + ')');
}
module = this._require(module);
module[method].apply(module, args);
BridgeProfiling.profileEnd();
}

js调用java

React Native的设计是js不会直接调用java,而是将调用的call插入到一个js层的一个MessageQueue中,java过来调用。

MessageQueue.js
1
2
3
4
5
6
7
8
9
10
11
12
class MessageQueue {
constructor(remoteModules, localModules, customRequire) {
this.RemoteModules = {};
this._require = customRequire || require;
this._queue = [[],[],[]];
this._moduleTable = {};
this._methodTable = {};
this._callbacks = [];
this._callbackID = 0;
...

MessageQueue.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__nativeCall(module, method, params, onFail, onSucc) {
if (onFail || onSucc) {
// eventually delete old debug info
(this._callbackID > (1 << 5)) &&
(this._debugInfo[this._callbackID >> 5] = null);
this._debugInfo[this._callbackID >> 1] = [module, method];
onFail && params.push(this._callbackID);
this._callbacks[this._callbackID++] = onFail;
onSucc && params.push(this._callbackID);
this._callbacks[this._callbackID++] = onSucc;
}
this._queue[MODULE_IDS].push(module);
this._queue[METHOD_IDS].push(method);
this._queue[PARAMS].push(params);
if (__DEV__ && SPY_MODE && isFinite(module)) {
console.log('JS->N : ' + this._remoteModuleTable[module] + '.' +
this._remoteMethodTable[module][method] + '(' + JSON.stringify(params) + ')');
}
}

具体是在java层主动调用或各种事件到来时(触摸,刷新,timer…),都会调用上文说的java层的callFunction,从而最终调用上文说到的callFunctionReturnFlushedQueue,注意该方法会在执行相应js调用后调用flushedQueue并返回MessageQueue.

MessageQueue.js
1
2
3
4
5
6
7
8
flushedQueue() {
BridgeProfiling.profile('JSTimersExecution.callImmediates()');
guard(() => JSTimersExecution.callImmediates());
BridgeProfiling.profileEnd();
let queue = this._queue;
this._queue = [[],[],[]];
return queue[0].length ? queue : null;
}

返回的queue格式化为json经由c++返回给java

Bridge.cpp
1
2
3
4
5
6
7
void executeJSCall(
const std::string& moduleName,
const std::string& methodName,
const std::vector<folly::dynamic>& arguments) {
auto returnedJSON = m_jsExecutor->executeJSCall(moduleName, methodName, arguments);
m_callback(parseMethodCalls(returnedJSON));
}

OnLoad.cpp
1
2
3
4
5
6
7
8
9
10
11
static void create(JNIEnv* env, jobject obj, jobject executor, jobject callback,
jobject callbackQueueThread) {
auto weakCallback = createNew<WeakReference>(callback);
auto weakCallbackQueueThread = createNew<WeakReference>(callbackQueueThread);
auto bridgeCallback = [weakCallback, weakCallbackQueueThread] (std::vector<MethodCall> calls) {
dispatchCallbacksToJava(weakCallback, weakCallbackQueueThread, std::move(calls));
};
auto nativeExecutorFactory = extractRefPtr<JSExecutorFactory>(env, executor);
auto bridge = createNew<Bridge>(nativeExecutorFactory, bridgeCallback);
setCountableForJava(env, obj, std::move(bridge));
}
CatalysInstance.java
1
2
3
4
5
6
7
8
9
10
11
12
13
private class NativeModulesReactCallback implements ReactCallback {
@Override
public void call(int moduleId, int methodId, ReadableNativeArray parameters) {
mCatalystQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();
// Suppress any callbacks if destroyed - will only lead to sadness.
if (mDestroyed) {
return;
}
mJavaRegistry.call(CatalystInstance.this, moduleId, methodId, parameters);
}

最终java层依旧是查表调用对应的方法。

线程

线程方面 主要分为了三种线程

  • UIQueueThread android本身的ui线程,最终绘制肯定还得在这里
  • NativeModulesQueueThread js调过来的native module的方法执行的线程
  • JSQueueThread js解释器线程
    CatalystQueueConfiguration.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**
    * Specifies which {@link MessageQueueThread}s must be used to run the various contexts of
    * execution within catalyst (Main UI thread, native modules, and JS). Some of these queues *may* be
    * the same but should be coded against as if they are different.
    *
    * UI Queue Thread: The standard Android main UI thread and Looper. Not configurable.
    * Native Modules Queue Thread: The thread and Looper that native modules are invoked on.
    * JS Queue Thread: The thread and Looper that JS is executed on.
    */
    public class CatalystQueueConfiguration {
    private final MessageQueueThread mUIQueueThread;
    private final MessageQueueThread mNativeModulesQueueThread;
    private final MessageQueueThread mJSQueueThread;

总结

以上就是React Native Android版java和js间相互调用的主要实现,从demo的体验感觉整体上比较流畅,起码简单的页面感知上和native区分不太开。而且只要reload一遍js bundle,不用重启不用切换,页面立即原地更新,这个应该是一个很大的卖点。

代码上整体分层挺清晰,集合了包括fresco/okhttp/botls/boost/folly等不少开源库,java层用了动态代理,c++层的代码是用c++11封装的,js中也是使用了不少es6的新特性,还有jsx,css layout什么的,模式上也是状态驱动UI,整体实现感觉很是前卫,但是要完全掌握可能要java/c++/js三修,对现有的开发模式是一个新的挑战。而且由于依赖库太多,导致安装包略肿大,光so就有4.5MB,这给集成到现有项目带来了不少问题。

另外,由于监听了Choreographer的事件,使用了些高版本的api,导致4.1以下不能使用React。

而且实际使用中,由于react本身其实主要还是帮助做绑定,导致稍微复杂一点的界面和逻辑,还是少不了native的大量参与,除非导出的组件已经足够用或者界面足够简单。

总的来说,虽然可能刚出来还存在不少问题,包大小与开发方式也需要进一步的探索,但react native的整体设计与实现还是很值得学习的,或许今后会带来app开发新的思路吧。

Feb 8 2015
记macbook扩展到16g内存

我的macbook小白是2010款,macbook最后一代,已经用了4年。中间想过卖掉换成pro,可一看pro的价钱,我又不想再买个13寸的,再加上这个绝版的还算漂亮的小白,就决定先扩展硬件再撑几年,等mac os不支持这款了再说。 期间更新过两次内存,先开始只有2g,升到了4g,后来又买了单根8g的,扩展到了10g。最近又觉得不够用,于是把又升了下级。

ssd

在升级内存前,先是看到说更换为ssd的提升比最大,可我又觉得128g的ssd太小,而且也不想倒腾原来的数据,最后发现可以把光驱拆下来,然后买个光驱托盘,把ssd嵌到里面,放到拆下来的光驱位置上。这样下来,反正现在光驱也没用,多了一个ssd,把系统安到ssd上,从ssd启动,开机速度飞快,再把各种软件和常用的文档也放到ssd下,之前的老爷机仿佛获得新生。

更换ssd这里,没什么好说的,都是物理上的拆机更换零件。有一点需要注意,拆机的工具最好直接从淘宝上购买麦哲伦mac拆机工具套装,免得为找各种型号的螺丝刀折腾。

内存

在更换了ssd后,其实整体感觉已经十分流畅了,但看着系统信息里的2+8,感觉十分的不对称,再加上开个ide chrome等内存杀手,内存使用一下飙到9g+,还是决定再来根8g的。

不换还好,一换才发现有坑。

我的macbook型号太老,只支持1067MHz频率ddr3的内存条,可现在市面上卖的单根8g都是1600的,拿过来插上两根1600的一开机就滴滴响,无法启动。之前的2+8没有问题,是因为8g的1600在2g的1067下会自动降频到1067。

这下就要开始折腾了,网上google了一下,好在之前有强人遇到过这种问题(简单的修改让你1333的内存完美使用在374上),办法是在win下(非虚拟机)利用软件将8g的那根内存条的spd信息改成1067,这样再来一根1600的就会自动降频到1067,也就可以和谐共处了。

好,看上去也不算麻烦,就是安个win7用软件改下内存么,结果蛋疼的事情又来了…

win7

之前把光驱换成了ssd,于是就想那就usb安个win7就行,可usb查到mac上,用bootcamp打开发现我这款老爷机无法用usb安win wtf.. 好在网上找到文章说可以改bootcamp pkg下的信息来绕过去(How to Enable USB Install on Mavericks for unSupported Bootcamps),但实际试了下发现还是不行,开机会找不到u盘。

于是又反复google,找到了refit,这个扩展工具可以让mac在开机后自由选择启动盘,比系统本身的开机option要好用,有点像ubuntu的启动引导。

但杯具的是,这次是找到了U盘,但按下从u盘启动安装又报了个什么错误,还是不行 (但rEFIt本身不错 留着以后可以很方便的在多系统间切换)。

最后,反复google后(我的google能力还是不错的 经得起折腾…) 找到了可以通过虚拟机安装win7后直接copy到bootcamp的方法曲线救国(Install a Windows 7 partition on Mac OSX without Optical Drive or USB), 终于安上了win7…

改内存spd

有了win7后,就可以通过之前提到过的方法,用Thaiphoon这个工具修改8g内存条的spd即可,然后换下2g的,换上扩展的8g的,大功告成!

还有

上面提到的步骤里需要几个工具,找这些工具的破解版又是一顿折腾,这里就不细说了。

parallels / vmware-fusion // 虚拟机 用于安装win7
vmdk mounter // 将虚拟机安装文盘挂载到mac上
winclone // 将挂载后的虚拟盘里的安装文件copy到bootcamp盘上 完成安装
thaiphoon // win下修改内存spd信息

最后

经过一番折腾(不想再来一次了),总算是扩展到了16g内存,顺利开机,顿时有种驾驶ae86的感觉 ^_^

成果图: