NDK开发之java调用C++

首先创建一个空的android项目,默认生成的MainActivity,修改如下

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
public class MainActivity extends Activity {

static {
System.loadLibrary("learnNDK");
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

TextView t = (TextView)this.findViewById(R.id.textview);
t.setText("" + hello(1));
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

public static native int hello(int num);

}

这里通过System.loadLibrary(“learnNDK”);来加载咱们等下要生成的动态库leanNDK

通过public static native int hello(int num);来声明一个C++层可以供我们调用的方法,返回值为int,函数名hello,接受一个形参int num

在项目根目录创建一个jni文件夹,执行命令

1
javah -d jni -classpath bin/classes/ com.example.learnndk.MainActivity

关于javah的详细参数介绍请参考javah

这里简单介绍一下,-d 表示输出的头文件目录,-classpath表示class文件的路径

然后就可以在jni目录下看到生成的文件com_example_learnndk_MainActivity.h

我们新建一个cpp文件来实现这个hello方法,新建一个learnNDK.cpp内容如下

1
2
3
4
5
6
#include <com_example_learnndk_MainActivity.h>

JNIEXPORT jint JNICALL Java_com_example_learnndk_MainActivity_hello
(JNIEnv *, jclass, jint) {
return 99;
}

然后在jni目录下,我们新建个Android.mk文件,内容如下

1
2
3
4
5
6
7
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := learnNDK
LOCAL_SRC_FILES := learnNDK.cpp

include $(BUILD_SHARED_LIBRARY)

然后使用ndk编译

1
2
ndk-build clean
ndk-build

成功之后,运行这个android程序,就可以看到结果了。

实际中Android.mk可以编写很复杂,也可能需要在jni目录下通过Application.mk来设定一些参数,如果不正确设定可能导致编译出错。

使用closureCompiler深度混淆的一个坑

混淆命令为:

1
java -jar compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js a.js --js_output_file b.js

混淆前代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function test(result){
console.log(result.fuck.text);
console.log(result.text.fuck);
console.log(result.color.colour);
var tmp = {
test1:Math.random(),
"test2":Math.random()
}
for(var i = 0; i < result.length; i++) {
console.log(result[i].text);
console.log(result[i].text.text1.text2.text3);
tmp.test1 += 1;
tmp.test2 += 2;
console.log(tmp.test1);
console.log(tmp.test1.test11.test111);
console.log(tmp.test2);
}
result.tmp = tmp;
}

混淆后代码

1
2
3
4
5
6
7
8
9
10
function d(b) {
console.log(b.b.text);
console.log(b.text.b);
console.log(b.color.d);
for (var a = {
a: Math.random(),
test2: Math.random()
}, c = 0; c < b.length; c++) console.log(b[c].text), console.log(b[c].text.c.j.k), a.a += 1, a.b += 2, console.log(a.a), console.log(a.a.h.i), console.log(a.b);
b.l = a
}

目前可以看到 text和color,在深度混淆中不会被混淆。是否还有别的一些关键字不被混淆,还不确定。比如colour就会被混淆= =

git在错误分支上提交后的修正

应该在A分支上做的改动,不小心在B上改动并提交了,过了好几天才发现,此时需要做的操作包括,把这个提交移到A分支上,同时在B分支上把这个提交删除

移到A分支上很简单,在A分支上执行

1
git cherry-pick commitId

即可,有冲突的话,解决冲突

在B分支上的删除也简单,直接在B分支上执行

1
git reset commitId

这里的commitId是需要删除的提交之前的那个提交,然后把不想要的提交去掉,重新commit即可。这里写个简单的示例流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
touch a.txt
git add a.txt
git commit -m "add a"

touch b.txt
git add b.txt
git commit -m "add b"

git reset d9a3249 #这是add a之前的那个提交
git status #此时可以看到a.txt和b.txt都是Untracked files,也就是从add a之后的所有提交都变成了没有add的状态

rm -rf a.txt
git add b.txt
git commit -m "reset" #完成,add a这个提交已经没有了,而且在sourceTree上也看不到这次提交了

git reset d9a3249这步,也可以添加–soft参数,这个参数可以让所有的改动是已经add的状态,可以省掉之后再add一次,但要移除修改,则需要执行git rm –cached

tableview的一个优化方案

方案说明

使用scrollView + bakeLayer(android) + batchNode(ios)来代替tableview。适用于tableCell数量不是很多且结构简单,但占用了大尺寸图片的情况。例如

example

这里中间的滑动界面,第一反应就使用了tableview来做。后来想在性能上做优化的时候,使用了scrollView + bakeLayer来代替。实现上很简单,只要将一个layer作为scrollview的container,并让其bake即可,构造时设定好每个cell的坐标。

官方关于bake的介绍点击这里

为什么要用scrollview

为什么tableView不能直接使用bake呢? 注意事项里对于子节点经常会变的层, 启用bake功能,会给游戏性能带来额外的开销,建议对于不常修改子节点的层才开启该功能

tableview的实现,会不停地将tableCell添加和移除,所以肯定不能直接取tableView的container出来直接bake

细节

这里的实现需要稍微注意的两点有:

  1. container的尺寸要大于等于scrollView的尺寸,否则滑动时容易显示异常。如果container尺寸大于scrollView时,注意是否需要设置初始位移。
  2. 添加触摸事件时,把触摸点坐标转换成container内的坐标,再根据各个cell在container内的位置即可判断点击了哪个cell

代码

直接上代码

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
var cellheight = 206; //单元格高度
var cellcount = 6; //单元格数量
var row = Math.ceil(cellcount * 0.5);
var containerHeight = Math.max(cellheight * row, h);
var scrollHeight = h; //scrollview高度
var scrollsize = cc.size(w, scrollHeight);

var container = new cc.Layer();
container.setContentSize(cc.size(w, containerHeight));

var scrollView = this._scrollview = cc.ScrollView.create(scrollsize, container);
scrollView.setDirection(cc.SCROLLVIEW_DIRECTION_VERTICAL);
scrollView.setPosition(cc.p(0, 119));
this.addChild(scrollView);

//因为方向是从上往下,如果container高度超出,则需要设置初始位移
var delta = scrollHeight - containerHeight;
if (delta < 0) {
scrollView.setContentOffset(cc.p(0, delta), false)
}


for (var i = 0; i < cellcount; i++) {
var s1 = new cc.Sprite("#hall_enter_" + i + ".png");
var posx = i % 2 == 0 ? w * 0.25 : w * 0.75;
var posy = containerHeight - (Math.floor(i / 2) + 0.5) * cellheight;
s1.setPosition(posx, posy);
container.addChild(s1);
}
container.bake();


var bTouchCanceled = false;
var touchBeganPos = cc.p(0, 0);
var that = this;
cc.eventManager.addListener({
event: cc.EventListener.TOUCH_ONE_BY_ONE,
swallowTouches: false,
onTouchBegan: function(touch, event) {
var bounding = scrollView.getBoundingBox();
var pos = touch.getLocation();
if (cc.rectContainsPoint(bounding, pos)) {
bTouchCanceled = false;
touchBeganPos = pos;
return true;
}
return false;
},
onTouchEnded: function(touch, event) {
if (bTouchCanceled) {
return;
}
var loc = container.convertToNodeSpace(touch.getLocation());
var col = loc.x < w * 0.5 ? 0 : 1;
var row = Math.floor((containerHeight - loc.y) / cellheight);
var index = 2 * row + col;
that.onCellNodeClick(index);
},
onTouchCancelled: function() {
bTouchCanceled = true;
},

onTouchMoved: function(touch, event) {
var loc = touch.getLocation();
if (Math.abs(loc.x - touchBeganPos.x) > 10 || Math.abs(loc.y - touchBeganPos.y) > 10) {
bTouchCanceled = true;
}
}
}, container);

batchNode也是同理,我们抛弃tableCell,直接把组件添加到scrollView的container内,就可以使用batch功能了。

tableViewCell上的触摸事件

1.tableCellTouched

这是最简单的处理tableViewCell上触摸事件的方法了,接口为

1
tableCellTouched:function (table, cell){}

它是基于对整个tableView的触摸,所以优先级会低于tableViewCell上的事件。

2.对tableViewCell添加事件

这个就是比较坑的了,我们知道tableView在滚动中,会把屏幕外的tableViewCell从container中移除

1
this.getContainer().removeChild(cell, true);

这里cleanup参数为true,也就意味着对这个cell绑定的所有事件都会被移除,所以如果直接对tableViewCell进行绑定事件,当这个cell被重用时,它的绑定事件已经无影无踪,不会再有响应了。如果只是触摸事件,那么直接用tableCellTouched接口即可,如果确实需要绑定特殊事件例如CustomEventListener,那需要将listener存起来,在tableCellAtIndex接口中每次都进行绑定,当然也可以重写tableCellView的onEnter方法,在里面添加。

3.tableCellView内子控件添加事件

例如往tableCellView内添加controlButton,这时候即使cell被重用,触摸事件仍然会响应,因为cleanup并不会递归对子节点调用,所以事件会被保留。(这里理解错误,cleanup会对所有子孙结点递归调用,移除其触摸事件。之所以controlbutton事件被保留了,是因为它在onEnter方法里重新添加了)不过在添加触摸事件时要小心,例如这段代码

1
2
3
4
5
6
7
8
9
10
11
12
tableCellAtIndex: function(table, index) {
var cell = table.dequeueCell();
if (!cell) {
var cell = new cc.TableViewCell();
var button = new cc.ControlButton(new cc.LabelTTF("test", "Arial", 16), back)
cell.addChild(button);
button.addTargetWithActionForControlEvents(this, function(sender, event) {
console.log(index);
}, cc.CONTROL_EVENT_TOUCH_UP_INSIDE)
}
return cell;
}

这里点击按钮后显示的index肯定与期望的不一致,原因就是addTargetWithActionForControlEvents时传递的闭包内使用的index是tableViewCell构造时的index,当这个Cell被重用时,当前的index和构造时候的index很可能就不一致了。尤其是如果tableView方向是TABLEVIEW_FILL_TOPDOWN的话,tableView在构造时就发生了一次cell重用。如果希望显示正确的结果,必须使用cell当前的index来获取数据,也就是

1
2
3
4
button.addTargetWithActionForControlEvents(this, function(sender, e){
var idx = cell.getIdx();
console.log(idx);
}, cc.CONTROL_EVENT_TOUCH_UP_INSIDE)

不使用controlButton,而是自己给子控件添加事件处理,也是一样的方法。