首先安装1.5以上版本的Go语言环境,这个大家都应该有了。
然后需要通过科学上网方法去下载gomobile命令:
go get golang.org/x/mobile/cmd/gomobile
接着需要通过gomobile init去下载Android NDK的部分toolchain,执行下面的命令:
gomobile init
可以去android网站上下载一份最新的NDK以备用,将来遇上缺什么文件的话,就从NDK里面复制就是了。
下载地址在:https://developer.android.com/ndk/downloads/index.html
一切就绪了之后,先运行个例子试一试吧:
gomobile build -target=android golang.org/x/mobile/example/basic
成功之后,就生成了basic.apk。
可以通过gomobile install命令安装这个apk:
gomobile install golang.org/x/mobile/example/basic
当然啦,apk都生成了,直接用adb install就是了。
basic例子的功能很简单,红底上画一个绿色渐变的三角形,随着手指的点击事件,三角形的直角顶点的位置跟着一起走。
纯用Go写的Android代码,与Android NDK用C++写的代码异曲同工,都是用OpenGL/ES的命令直接作画的方式。
我们来看下这个不长的例子,涉及到Go语言相关或者是OpenGL相关的不理解的不要紧,后面我们都会介绍,我们先过几个例子找找感觉:
第一步是引用了一堆要用的包,跟app相关,event相关,openGL相关。
package main
import (
"encoding/binary"
"log"
"golang.org/x/mobile/app"
"golang.org/x/mobile/event/lifecycle"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/event/touch"
"golang.org/x/mobile/exp/app/debug"
"golang.org/x/mobile/exp/f32"
"golang.org/x/mobile/exp/gl/glutil"
"golang.org/x/mobile/gl"
)
基本是OpenGL相关的几个值,还有颜色,X和Y的位置。
var (
images *glutil.Images
fps *debug.FPS
program gl.Program
position gl.Attrib
offset gl.Uniform
color gl.Uniform
buf gl.Buffer
green float32
touchX float32
touchY float32
)
先向app.Main中注册我们的主函数。
func main() {
app.Main(func(a app.App) {
var glctx gl.Context
var sz size.Event
下面就是一个典型的消息驱动的循环,app将事件传给我们的主函数,我们根据事件来处理。GUI系统的代码的框架都差不多哈。
for e := range a.Events() {
switch e := a.Filter(e).(type) {
case lifecycle.Event:
switch e.Crosses(lifecycle.StageVisible) {
case lifecycle.CrossOn:
glctx, _ = e.DrawContext.(gl.Context)
onStart(glctx)
a.Send(paint.Event{})
case lifecycle.CrossOff:
onStop(glctx)
glctx = nil
}
case size.Event:
sz = e
touchX = float32(sz.WidthPx / 2)
touchY = float32(sz.HeightPx / 2)
case paint.Event:
if glctx == nil || e.External {
// As we are actively painting as fast as
// we can (usually 60 FPS), skip any paint
// events sent by the system.
continue
}
onPaint(glctx, sz)
a.Publish()
// Drive the animation by preparing to paint the next frame
// after this one is shown.
a.Send(paint.Event{})
case touch.Event:
touchX = e.X
touchY = e.Y
}
}
})
}
后面基本上就需要一些OpenGL的知识了
func onStart(glctx gl.Context) {
var err error
通过glutil.CreateProgram创建一个程序,OpenGL里程序是将顶点着色器和片段着色器绑定在一起的实体。
program, err = glutil.CreateProgram(glctx, vertexShader, fragmentShader)
if err != nil {
log.Printf("error creating GL program: %v", err)
return
}
buf = glctx.CreateBuffer()
glctx.BindBuffer(gl.ARRAY_BUFFER, buf)
glctx.BufferData(gl.ARRAY_BUFFER, triangleData, gl.STATIC_DRAW)
position = glctx.GetAttribLocation(program, "position")
color = glctx.GetUniformLocation(program, "color")
offset = glctx.GetUniformLocation(program, "offset")
images = glutil.NewImages(glctx)
fps = debug.NewFPS(images)
}
func onStop(glctx gl.Context) {
glctx.DeleteProgram(program)
glctx.DeleteBuffer(buf)
fps.Release()
images.Release()
}
onPaint的过程,是个标准的OpenGL绘制三角形的流程:
func onPaint(glctx gl.Context, sz size.Event) {
glctx.ClearColor(1, 0, 0, 1)
glctx.Clear(gl.COLOR_BUFFER_BIT)
glctx.UseProgram(program)
green += 0.01
if green > 1 {
green = 0
}
glctx.Uniform4f(color, 0, green, 0, 1)
glctx.Uniform2f(offset, touchX/float32(sz.WidthPx), touchY/float32(sz.HeightPx))
glctx.BindBuffer(gl.ARRAY_BUFFER, buf)
glctx.EnableVertexAttribArray(position)
glctx.VertexAttribPointer(position, coordsPerVertex, gl.FLOAT, false, 0, 0)
glctx.DrawArrays(gl.TRIANGLES, 0, vertexCount)
glctx.DisableVertexAttribArray(position)
fps.Draw(sz)
}
下面是三角形的三个顶点的坐标:
var triangleData = f32.Bytes(binary.LittleEndian,
0.0, 0.4, 0.0, // top left
0.0, 0.0, 0.0, // bottom left
0.4, 0.0, 0.0, // bottom right
)
const (
coordsPerVertex = 3
vertexCount = 3
)
最后是我们创建顶点着色器时所使用的GLSL:
const vertexShader = `#version 100
uniform vec2 offset;
attribute vec4 position;
void main() {
// offset comes in with x/y values between 0 and 1.
// position bounds are -1 to 1.
vec4 offset4 = vec4(2.0*offset.x-1.0, 1.0-2.0*offset.y, 0, 0);
gl_Position = position + offset4;
}`
const fragmentShader = `#version 100
precision mediump float;
uniform vec4 color;
void main() {
gl_FragColor = color;
}`
我们来对照下,Android用NDK来写这种native的activity都需要做什么吧。总的代码比Go版本的还是长不少,我们就挑个Android的主函数看一下:
void android_main(struct android_app* state) {
struct engine engine;
// Make sure glue isn't stripped.
app_dummy();
memset(&engine, 0, sizeof(engine));
state->userData = &engine;
state->onAppCmd = engine_handle_cmd;
state->onInputEvent = engine_handle_input;
engine.app = state;
...
if (state->savedState != NULL) {
// We are starting with a previous saved state; restore from it.
engine.state = *(struct saved_state*)state->savedState;
}
// loop waiting for stuff to do.
while (1) {
// Read all pending events.
int ident;
int events;
struct android_poll_source* source;
// If not animating, we will block forever waiting for events.
// If animating, we loop until all events are read, then continue
// to draw the next frame of animation.
while ((ident=ALooper_pollAll(engine.animating ? 0 : -1, NULL, &events,
(void**)&source)) >= 0) {
// Process this event.
if (source != NULL) {
source->process(state, source);
}
...
// Check if we are exiting.
if (state->destroyRequested != 0) {
engine_term_display(&engine);
return;
}
}
if (engine.animating) {
// Done with events; draw next animation frame.
engine.state.angle += .01f;
if (engine.state.angle > 1) {
engine.state.angle = 0;
}
// Drawing is throttled to the screen update rate, so there
// is no need to do timing here.
engine_draw_frame(&engine);
}
}
}
//END_INCLUDE(all)
Android例子中用的OpenGL比上面Go的例子要简单一些:
static void engine_draw_frame(struct engine* engine) {
if (engine->display == NULL) {
// No display.
return;
}
// Just fill the screen with a color.
glClearColor(((float)engine->state.x)/engine->width, engine->state.angle,
((float)engine->state.y)/engine->height, 1);
glClear(GL_COLOR_BUFFER_BIT);
eglSwapBuffers(engine->display, engine->surface);
}
上面的例子,除去了OpenGL的知识以外,我们不需要知道任何跟Android系统相关的知识。
别小看了这段小小的代码,在gomobile的支持下,它可是跨平台的哟。
下一期,我们讨论更加激动人心的话题,如何从Android的Java代码调用Go的代码。