欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Android 4.0使用Kotlin调用C语言以及汇编语言

程序员文章站 2022-07-05 19:41:57
如今,Google早已将Kotlin编程语言作为了头等语言(first-class programming language)用于Android开发中,并且在Android Studio中获得了非常全面的支持。与此同时,我们看到Google从Android Studio 3.0开始就已经支持了Java 8,过了这么多年仍然不对Java语言进行升级就能看到Google当前对Java已经持有相当冷淡的态度了,预计Java 8将是Android Studio最高能支持的Java版本了(*^_^*)。或许这跟Ora...

如今,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.asmarm32_asmtest.sarm64_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汇编器进行编译。此外,这里还引入了 cpufeatureandroid 这两个额外的库,有需要的话后面可以直接使用。

上述文件都编辑完之后,我们重新打开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

相关标签: Android开发相关