用Go语言写Android应用 (2) - 从Android的Java调用Go代码

简介: 激动人心的时刻到了,从这一刻开始,我们可以通过Android的Java来调用go写的代码了!

用Go语言写Android应用 (2) - 从Android的Java调用Go代码

上一篇我们讲到,Go在Android中的作用,就相当于NDK中的C/C++。上节我们学习了参照NDK的方式用纯Go语言来写应用。
但是,也正如在Android中,C/C++主要是通过JNI的方式被Java代码调用,本节我们就学习如何使用Java代码来调用Go代码。

Java调Go的JNI例子

Java部分

我们首先来看这个简单得不能再简单的Java部分的代码,只有一个TextView,然后调用Go写的Hello.Greetings函数返回一个字符串,显示在这个TextView上。

package org.golang.example.bind;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

import go.hello.Hello;


public class MainActivity extends Activity {

    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView) findViewById(R.id.mytextview);

        // Call Go function.
        String greetings = Hello.Greetings("Android and Gopher");
        mTextView.setText(greetings);
    }
}

布局也没什么好说的,就只有一个TextView:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/mytextview" />

</RelativeLayout>

Go代码

我们再看看Go代码,也是简单到不能再简单了,把收到的字符串前面加上"Hello, ",然后再返回给Java就完事了。

package hello

import "fmt"

func Greetings(name string) string {
    return fmt.Sprintf("Hello, %s!", name)
}

编译和运行

于是,见证奇迹的时刻来了,我们把这个bind的例子编译运行一下吧。
按照Android的惯例,编译是通过gradle脚本来完成的,我们首先要在build.gradle中配一下Go的环境。

修改GOROOT下面的src/golang.org/x/mobile/example/bind/android/hello/build.gradle,配置GOPATH,GO和GOMOBILE的路径:

plugins {
    id "org.golang.mobile.bind" version "0.2.6"
}

gobind {
    /* The Go package path; must be under one of the GOPATH elements or
     a relative to the current directory (e.g. ../../hello) */
    pkg = "golang.org/x/mobile/example/bind/hello"

    /* GOPATH where the Go package is; check `go env` */
    GOPATH = "D:/go/"

    /* Absolute path to the go binary */
    GO = "d:/go/bin/"

    /* Optionally, set the absolute path to the gomobile binary if the
    /* gomobile binary is not located in the GOPATH's bin directory. */
    GOMOBILE = "d:/go/bin/gomobile"
}

下面我们就可能通过gradle来编译这两家伙,以Windows下为例:
执行下面的命令:

gradlew assembleDebug

这时需要科学上网去下载对应版本的gradle,还需要在环境变量里配置好javac的路径。

然后我们就可以看到欢快的build的日志了:

:app:preBuild UP-TO-DATE
:app:preDebugBuild UP-TO-DATE
:app:checkDebugManifest
:app:preReleaseBuild UP-TO-DATE
:hello:gobind
:app:prepareAndroidHelloUnspecifiedLibrary
:app:prepareComAndroidSupportAppcompatV72211Library UP-TO-DATE
:app:prepareComAndroidSupportSupportV42211Library UP-TO-DATE
:app:prepareDebugDependencies
:app:compileDebugAidl UP-TO-DATE
:app:compileDebugRenderscript UP-TO-DATE
:app:generateDebugBuildConfig UP-TO-DATE
:app:mergeDebugShaders UP-TO-DATE
:app:compileDebugShaders UP-TO-DATE
:app:generateDebugAssets UP-TO-DATE
:app:mergeDebugAssets UP-TO-DATE
:app:generateDebugResValues UP-TO-DATE
:app:generateDebugResources UP-TO-DATE
:app:mergeDebugResources UP-TO-DATE
:app:processDebugManifest UP-TO-DATE
:app:processDebugResources UP-TO-DATE
:app:generateDebugSources UP-TO-DATE
:app:incrementalDebugJavaCompilationSafeguard UP-TO-DATE
:app:compileDebugJavaWithJavac UP-TO-DATE
:app:compileDebugNdk UP-TO-DATE
:app:compileDebugSources UP-TO-DATE
:app:prePackageMarkerForDebug
:app:transformClassesWithDexForDebug UP-TO-DATE
:app:mergeDebugJniLibFolders UP-TO-DATE
:app:transformNative_libsWithMergeJniLibsForDebug
:app:processDebugJavaRes UP-TO-DATE
:app:transformResourcesWithMergeJavaResForDebug UP-TO-DATE
:app:validateDebugSigning
:app:packageDebug
:app:zipalignDebug
:app:assembleDebug

BUILD SUCCESSFUL

Total time: 56.107 secs

Build成功之后,apk就生成在GOROOTsrcgolang.orgxmobileexamplebindandroidappbuildoutputsapk下面。
adb install安装下就可以看到我们的成果啦~

当然,如果喜欢用IDE的,可以将工程导入到Android Studio里面,hello下面的build.gradle按上面说的改好,通过Android Studio来生成apk也是一样的。

原理

上面我们很欢快地完成了一个例子,但是背后的事情都是gradle插件和脚本帮我们做了。比JNI还要舒服,因为JNI总还有个Javah工具要生成个头文件,还要写Android.mk的。

我们打开生成的apk或者aar文件就可以看到,GoBind帮我们将我们的代码和相关的包装代码编译进了一个叫libgojni.so的库里面。

下面我们揭开这个工程的神秘面纱,看看我们如何也能够实现从Go到Java的映射,我们有一个神奇的工具gomobile bind.

比如说我们写一个最简单的计数器类吧:

package testCounter

type Counter struct {
    Value int
}

func (c *Counter) Inc() { c.Value++ }

func New() *Counter { return &Counter{100} }

然后我们执行gomobile bind命令:

gomobile bind -target=android testCounter

结果,gomobile命令给我们生成了一个testCounter.aar。
这是一个jar包一样的压缩格式,我们把它解开看看。发现,有classes.jar,还有jni,下面是各种指令集下的so库。

我们反编译一下classes.jar,看看我们上面几行的Go代码变成了什么样子。

先看干货吧,我们的Inc()方法,Value的set和get方法都被声明成了JNI的代码。

    public final native long getValue();

    public final native void setValue(long paramLong);

    public native void Inc();

下面是完整版的:

import go.Seq;
import go.Seq.Proxy;
import go.Seq.Ref;
import java.util.Arrays;

public abstract class TestCounter
{
  public static void touch()
  {
  }

  private static native void init();

  public static native Counter New();

  static
  {
    Seq.touch();
    init();
  }

  public static final class Counter extends Seq.Proxy
  {
    private Counter(Seq.Ref paramRef)
    {
      super(); } 
    public final native long getValue();

    public final native void setValue(long paramLong);

    public native void Inc();

    public boolean equals(Object paramObject) { if ((paramObject == null) || (!(paramObject instanceof Counter))) {
        return false;
      }
      Counter localCounter = (Counter)paramObject;
      long l1 = getValue();
      long l2 = localCounter.getValue();
      if (l1 != l2) {
        return false;
      }
      return true; }

    public int hashCode()
    {
      return Arrays.hashCode(new Object[] { Long.valueOf(getValue()) });
    }

    public String toString() {
      StringBuilder localStringBuilder = new StringBuilder();
      localStringBuilder.append("Counter").append("{");
      localStringBuilder.append("Value:").append(getValue()).append(",");
      return "}";
    }
  }
}

所以,gomobile bind的秘密很简单,利用Java与C语言的JNI接口,将Go也封装成C的接口,然后完成两者的对接。

为什么不只是接口转化下这么简单,还要写这么复杂的类呢?答案在于GC。不管是Java还是Go都是有GC的,两种带GC的语言碰到一起,是需要小心行事的。具体的细节可以看下这篇文章:https://docs.google.com/document/d/1y9hStonl9wpj-5VM-xWrSTuEJFUAxGOXOhxvAs7GZHE/edit?pref=2&pli=1

当然,这其中还有很多细节值得讲一讲,我们下节就从Go与C语言的接口:cgo开始讲起吧。

目录
相关文章
|
1天前
|
Java Devops 持续交付
探索Java中的Lambda表达式:简化代码,提升效率DevOps实践:持续集成与部署的自动化之路
【8月更文挑战第30天】本文深入探讨了Java 8中引入的Lambda表达式如何改变了我们编写和管理代码的方式。通过简化代码结构,提高开发效率,Lambda表达式已成为现代Java开发不可或缺的一部分。文章将通过实际例子展示Lambda表达式的强大功能和优雅用法。
|
1天前
|
Java
用JAVA架建List集合为树形结构的代码方法
这段代码定义了一个表示树形结构的 `Node` 类和一个用于构建树形结构的 `TreeController`。`Node` 类包含基本属性如 `id`、`pid`、`name` 和 `type`,以及子节点列表 `children`。`TreeController` 包含初始化节点列表并将其转换为树形结构的方法。通过过滤和分组操作实现树形结构的构建。详情可见:[代码示例链接1](http://www.zidongmutanji.com/zsjx/43551.html),[代码效果参考链接2](https://www.257342.com/sitemap/post.html)。
20 5
|
1天前
|
Java
java代码和详细的代码应用
代码块分为局部、构造、静态和同步代码块。局部代码块控制变量生命周期,例如 `int a` 只在特定代码块内有效。构造代码块用于创建对象时执行附加功能,避免构造方法中代码重复。静态代码块随类加载执行一次,常用于初始化操作。同步代码块确保多线程环境下方法执行的原子性,通过 `synchronized` 关键字实现。
16 3
|
1天前
|
设计模式 算法 Java
【揭秘】如何巧妙运用Java模板方法模式,让你的代码优雅升级?
【8月更文挑战第30天】模板方法模式是一种行为型设计模式,它定义了算法的骨架并将某些步骤延迟到子类中,使子类能在不改变算法结构的情况下重定义特定步骤。此模式适用于具有共同结构但细节不同的场景,如角色升级系统。通过定义一个抽象类 `Character` 包含模板方法 `levelUp` 和抽象步骤方法,子类如 `Warrior` 和 `Mage` 可以实现具体逻辑。这种方式提供了良好的扩展性,确保算法结构不变,同时保持系统的稳定性和一致性,在数据处理和业务流程管理中广泛应用。
12 2
|
1天前
|
Java C# 容器
Java代码的第一行实战
这段代码展示了Java的基本结构,包括`package`(包)、`public`(访问修饰符)、`class`(类)、`static`(静态)、`void`(空)及`System.out.println()`(系统输出)。同时介绍了Java中的注释、数据类型(如`byte`、`short`、`int`、`long`、`float`、`double`、`char`、`boolean`)、变量、常量、运算符、类型转换、赋值运算符、关系运算符与逻辑运算符等内容。通过生动的例子帮助理解各种概念。
11 2
|
1天前
|
Java 开发者 UED
“Java开发者必看:异步编程实战解析,掌握这些技巧,让你的代码跑得更快!
【8月更文挑战第30天】随着互联网技术的发展,系统性能和用户体验成为关注焦点。异步编程作为提高应用响应速度和吞吐量的技术,在Java中广泛采用。本文详细介绍了Java异步编程的概念与优势,并通过实战示例展示了如何利用Future、Callable及CompletableFuture在实际项目中实施异步编程,帮助开发者更好地理解和应用这一技术。
10 2
|
1天前
|
Java
编写规范JAVA代码
本文档制定了Java编程规范,旨在确保系统源程序的可读性和可维护性,适用于所有Java开发、测试及维护过程。规范包括命名规则(如Package、Class及其成员等)与样式规定,强调统一风格以提高协作效率,并列举了具体示例与注意事项,如避免单字符变量名及使用有意义的反义词组命名等。
12 1
|
1天前
|
Java UED 开发者
揭开Java性能提升之谜:异步编程如何让你的应用响应速度飞升?
【8月更文挑战第30天】随着互联网技术的发展,系统性能和用户体验成为关注焦点,异步编程因其能提高应用响应速度和吞吐量而在Java领域广泛应用。本文将详细阐述Java异步编程的概念与优势,并通过实战示例展示其在实际项目中的应用,如使用`Future`、`Callable`及`CompletableFuture`等接口和类实现异步操作,帮助开发者更好地理解和运用这一技术,以提升程序性能和用户体验。
|
1天前
|
Java 数据安全/隐私保护
Java代码的执行顺序和构造方法
构造方法是类的一种特殊方法,用于初始化新对象。在 Java 中,每个类默认都有一个与类名同名的构造方法,无需返回类型。构造方法不能用 static、final、synchronized、abstract 或 native 修饰。它可以重载,通过不同的参数列表实现多种初始化方式。构造方法在对象实例化时自动调用,若未显式声明,默认提供一个无参构造方法。构造代码块和静态代码块分别用于对象和类的初始化,按特定顺序执行。
14 0
|
1天前
|
Java 程序员 调度
Java中的多线程基础与实战应用
【8月更文挑战第30天】在Java的世界里,多线程是提升程序性能的利器,但也是新手开发者常碰壁的难点。本文旨在通过浅显易懂的语言和生动的比喻,带领读者走进Java多线程的大门。我们将从线程的基本概念出发,逐步深入到线程的创建、启动、管理以及同步机制,最后通过一个简易版的图书管理系统实例,展示如何在实际开发中灵活运用多线程技术。
下一篇
云函数