Android 4.0使用Kotlin调用C语言以及汇编语言
如今,Google早已将Kotlin编程语言作为了头等语言(first-class programming language)用于Android开发中,并且在Android Studio中获得了非常全面的支持。与此同时,我们看到Google从Android Studio 3.0开始就已经支持了Java 8,过了这么多年仍然不对Java语言进行升级就能看到Google当前对Java已经持有相当冷淡的态度了,预计Java 8将是Android Studio最高能支持的Java版本了(*^_^*)。或许这跟Oracle之前的官司有关,不过Kotlin这种类Rust语言确实已经成为现代化编程语言的代名词了,小巧、轻便、简洁、安全。而且Kotlin 1.3还增加了Java所没有的无符号整数类型,尽管是实验阶段(由于动用了实验性的内联类),不过在1.4版本起就能正式开始使用了。
好了,废话不多说了,下面进入正题。上一节已经提供了Android开发相关链接还有Android Studio下载地址,各位下载完之后,Android Studio的包管理器会再自动下载一些必要的组件,然后我们开始创建一个新项目,笔者这里给项目起的名称为 KotlinTest。然后项目模板选择“Native C++”。这个项目模板会自动创建本地C/C++语言所需的各种属性以及CMakeList.txt文件,并且会使用空的activity作为起始Android启动框架。下面我们先来看一下笔者编辑好的 MainActivity.kt 这一Kotlin源文件。
package com.example.kotlintest
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
private external fun packageMethod(i: Int)
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Example of a call to a native method
sample_text.text = stringFromJNI()
packageMethod(100)
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
private external fun stringFromJNI(): String
companion object {
// Used to load the 'native-lib' library on application startup.
init {
System.loadLibrary("native-lib")
}
}
}
上述代码很简单,基本就是项目向导自动生成的代码。这里声明了两个需要在native端实现的方法。一个是向导自动生成的 stringFromJNI() 成员方法,还有一个是笔者自己添加的 packageMethod(i: Int) 函数。Java中使用 native 关键字来声明一个本地端实现的方法;而Kotlin中则使用 external 关键字。Kotlin的成员方法桥接到本地代码的签名方式与Java完全一致,详细可见(Java JNI接口的详细描述)这篇博文;而Kotlin尽管没有Java那种纯粹的静态方法(或称为类方法),但它有文件作用域的函数,其签名方式比起静态方法大致相同,并且更为简单。两者不同的是静态方法的签名末尾是 <包名> . <类名> . <静态方法名> 的方式;而函数签名则是 <包名> . <kotlin源文件名> . <函数名> 的方式。我们知道,Kotlin的源文件名都是以 kt 作为文件后缀名的,因此这里的 <kotlin源文件名> 其实就是去除后缀的源文件名加上 _kt 。像上述 packageMethod(i: Int) 的完整函数签名为:Java_com_example_kotlintest_MainActivityKt_packageMethod。
接着我们就可以来看一下项目向导自动创建的 native-lib.c 源文件名的代码了:
#include <jni.h>
#include <syslog.h>
#include <cpu-features.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#ifndef let
#define let __auto_type
#endif
extern int MyASMTest(int a, int b);
JNIEXPORT jstring JNICALL
Java_com_example_kotlintest_MainActivity_stringFromJNI(JNIEnv* env, jobject object)
{
let const value = 100;
const char *s = "Hello from C";
syslog(LOG_INFO, "The value is: %d\n", value);
return (*env)->NewStringUTF(env, s);
}
JNIEXPORT void JNICALL
Java_com_example_kotlintest_MainActivityKt_packageMethod(JNIEnv *env, jclass clazz, jint i)
{
// TODO: implement packageMethod()
let a = MyASMTest(5, 3) + i;
syslog(LOG_INFO, "a = %d\n", a);
}
大家可以注意到,上述代码与向导自动生成的不太一样。这是由于向导自动生成的是庞杂且臃肿的C艹代码,而笔者这里已经改为轻便且优美的C语言代码了。而这里的 MyASMTest 函数则是用汇编来实现的,后面会详细介绍。这里大家能看到 stringFromJNI() 和 packageMethod(i: Int) 桥接到本地代码后的完整签名方式,包括两者参数类型也是所有不同的。由于定义在文件作用域的函数后续无论是在成员方法中调用,还是在companion object块中的静态方法中被调用,它的调用者都是这些方法所属的类,而不是对象实例!因此 packageMethod 的第二个参数类型铁定是 jclass ,而不是 jobject,尽管从JNI类型架构上,jclass是jobject的子类型。
这里再提一点,有些朋友会问,那我们能不能在companian object中声明一个本地方法呢?这是完全可以的,但不推荐。因为companion object其实是一个匿名模块,因此当你使用这里面的“静态方法”桥接到本地端时,其方法签名会带有此匿名模块的编号,比如对于上述例子,可能会是:Java_com_example_kotlintest_MainActivity_0012_packageMethod 这种形式,一方面很别扭,另一方面可能会有二进制兼容、移植上等问题。
上面两个主要源文件看完之后,我们接下去就要准备设置项目,对整个工程进行编译了。在此之前,我们先关闭当前的Android Studio。然后,到文件管理器找到创建的这一项目的目录,在 app/src/main/cpp 文件夹中我们可以找到向导自动生成的 native-lib.cpp 和 CMakeList.txt 这两个文件。我们先把 native-lib.cpp 改为:native-lib.c。然后创建文件夹,命名为 asm 。然后在此文件夹中新增三个文件,分别为:x86_asmtest.asm、arm32_asmtest.s、arm64_armtest.s。
上面已经给出了 native-lib.c 源文件的内容,下面给出先给出三个汇编文件的内容。
x86_asmtest.asm
; 这是一个汇编文件
; YASM的注释风格使用分号形式
global MyASMTest
section .text
MyASMTest:
sub edi, esi
mov eax, edi
ret
arm32_asmtest.s
.text
.align 4
.globl MyASMTest
MyASMTest:
sub r0, r0, r1
bx lr
arm64_asmtest.s
.text
.align 4
.globl MyASMTest
MyASMTest:
sub w0, w0, w1
ret
各位也可以选择在编辑完CMakeList.txt文件之后在Android Studio中进行编辑。Android Studio 4.0已经能支持汇编语言的语法高亮了。
下面是比较关键的,对CMakeList.txt的编辑,请看以下代码:
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
if(${ANDROID_ABI} STREQUAL "x86_64")
enable_language(ASM_NASM)
set(asm_SRCS ${PROJECT_SOURCE_DIR}/asm/x86_asmtest.asm)
elseif(${ANDROID_ABI} STREQUAL "arm64-v8a")
enable_language(ASM)
set(asm_SRCS ${PROJECT_SOURCE_DIR}/asm/arm64_asmtest.s)
elseif(${ANDROID_ABI} STREQUAL "armeabi-v7a")
enable_language(ASM)
set(asm_SRCS ${PROJECT_SOURCE_DIR}/asm/arm32_asmtest.s)
endif()
include(AndroidNdkModules)
android_ndk_import_module_cpufeatures()
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library(
# Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
${asm_SRCS}
native-lib.c)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library(
# Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log
android)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries(
# Specifies the target library.
native-lib
cpufeatures
# Links the target library to the log library
# included in the NDK.
${log-lib})
这里对x86_64汇编源文件使用NASM汇编器进行编译,而ARM的代码均由GAS汇编器进行编译。此外,这里还引入了 cpufeature 和 android 这两个额外的库,有需要的话后面可以直接使用。
上述文件都编辑完之后,我们重新打开Android Studio,进入刚才创建的KotlinTest这一i项目,然后在左侧导航栏中鼠标右键点击 app 这个文件夹,然后选择“Reload from disk”,进行文件同步。这么一来,我们就能在 cpp -> native-lib 下看到 native-lib.c、还有新建的 asm 文件夹,包括里面所有文件了。
我们随后可以编辑 build.gradle (Module: app) 这一文件,添加相关的编译选项。我们找到 externalNativeBuild 这一符号,将这一语句块替换为如下所示的代码:
externalNativeBuild {
cmake {
// 对于ARMv7架构使用NEON技术,并且对于32位的ARM执行模式使用ARM指令集而不是默认的Thumb指令集
arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_ARM_MODE=arm"
// C语言使用GNU17标准
cFlags "-std=gnu17", "-Os"
}
ndk {
// Specifies the ABI configurations of your native
// libraries Gradle should build and package with your APK.
abiFilters 'x86_64', 'armeabi-v7a', 'arm64-v8a'
}
}
这里对C语言启用了GNU17这一最新的C18标准外加GNU语法扩展。然后对ARMv7架构启用了NEON指令集扩展。同时,指定了当前本地二进制代码只生成x86_64、ARMv7以及ARM64-v8三种架构,以减少不必要的架构二进制。而且Google也已经声明了ARMv7以下的架构即将作废,后面将最低支持ARMv7的处理器。而32位的x86架构当然也是不需要的。
设置完之后我们就可以启动模拟器或是真机进行运行了。运行模拟器的童鞋请注意,如果是第一次启用,Google会默认安装带有Google Play的模拟器,这比较麻烦,需要apk签名,因此笔者的做法是新建一个模拟器,不带Google Play的,然后将处理器架构设置为x86_64,这样就能直接跑app了。
此外,笔者也建议各位使用Windows系统的童鞋可以将文件编码都改为无BOM的UTF-8编码格式。这样项目源文件具有更好的跨平台性,尤其是带有中文等其他语言文字,因为国内Windows系统上默认会使用古老且非现代化标准的GBK编码。方法是在上方菜单栏中的左侧点击“File”,然后点击“Settings...”,进入对话框后展开“Editor”,然后点击“File Encodings”,将这里面的所有编码方式设置为UTF-8。
本文地址:https://blog.csdn.net/zenny_chen/article/details/107732197
下一篇: 软文营销怎么写?何做好软文推广?