Android-NDK-开发-从-Assets-文件夹加载图片并上传纹理

原文链接:Android NDK 开发 —— 从 Assets 文件夹加载图片并上传纹理在 OpenGL 开发中,我们要渲染一张图片,通常先是得到一张图片对应的 Bitmap ,然后将该 Bitmap 作为纹理上传到 OpenGL 中。在 Android 中有封装好的 GLUtils 类的 texImage2D 方法供我们调用。 public static void texImage2D(int target, int level, int internalformat, Bitmap bitmap, int type, int border)该方法的底层原理实际上也是解析了该 Bitmap ,得到了 Bitmap 所有的像素数据,类似于 Android NDK 关于 Bitmap 操作的 AndroidBitmap_lockPixels 方法,如果你不太了解该方法,可以参考这篇文章:Android JNI 之 Bitmap 操作。 得到了所有像素数据之后,实际最终还是调用了 OpenGL 的 glTexImage2D 来实现纹理上传。当然,如果可以直接得到所有数据,也不需要走解析 Bitmap 这一步了,这种场景最常见的就是把相机作为输入了。 <!--more--> 接下来我们会通过 Android NDK 开发中去渲染一张图片,步骤还是如上,从图像解析到纹理上传,不同的是我们将会解析 Assets 文件夹中的图片,而不是一张已经保存在手机 SDCard 上的图片。 ...

May 15, 2019 · 1 min · jiezi

Android NDK开发之JNI基础

前言之前写了一篇文章简单的介绍了Android NDK的组件和结构,以及在Android studio中开发NDK,NDK是Android底层的c/c++库,然而要在java中调用c/c++的原生功能,则需要使用JNI来实现。什么是JNIJNI(Java Native Interface)是java本地接口,它主要是为了实现Java调用c、c++等本地代码所封装的一层接口。大家都知道java是跨平台开发语言,它的狂平台特性导致与本地交互的能力不够强大,一些和操作系统相关的特性Java无法完成,所以Java提供了JNI用于和Native代码进行交互。通过JNI,Java可以调用c、c++,相反,c、c++也可以调用Java的相关代码。创建NDK工程开发环境MacAndroid studio:3.3.2新建工程本地的Android studio版本为3.3.2,当你创建项目的时候有一个选项是选择Native C++的模板点击next,配置项目的信息点击next,选择使用哪种C++标准,选择Toolchain Default会使用默认的CMake设置即可。点击finish即可完成工程的创建。工程结构这时候主工程目录下会有cpp文件夹和.externalNativeBuild文件夹。.externalNativeBuild文件夹:用于存放cmake编译好的文件,包括支持的各种硬件等信息,有点类似于build.gradle文件明确Gradle如何编译APP;cpp文件夹:存放C/C++代码文件,native-lib.cpp文件默认生成的;cpp文件夹下有两个文件,一个是native-lib.cpp文件,一个是CMakeLists.txt文件。CMakeLists.txt文件是cmake脚本配置文件,cmake会根据该脚本文件中的指令去编译相关的C/C++源文件,并将编译后产物生成共享库或静态块,然后Gradle将其打包到APK中。CMakeLists.txt的相关配置如下:# 设置构建本地库所需的最小版本的cbuild。cmake_minimum_required(VERSION 3.4.1)# 创建并命名一个库,将其设置为静态# 或者共享,并提供其源代码的相对路径。# 您可以定义多个库,而cbuild为您构建它们。# Gradle自动将共享库与你的APK打包。add_library( native-lib #设置库的名称。即SO文件的名称,生产的so文件为“libnative-lib.so”, 在加载的时候“System.loadLibrary(“native-lib”);” SHARED # 将库设置为共享库。 native-lib.cpp # 提供一个源文件的相对路径 helloJni.cpp # 提供同一个SO文件中的另一个源文件的相对路径 )# 搜索指定的预构建库,并将该路径存储为一个变量。因为cbuild默认包含了搜索路径中的系统库,所以您只需要指定您想要添加的公共NDK库的名称。cbuild在完成构建之前验证这个库是否存在。find_library(log-lib # 设置path变量的名称。 log # 指定NDK库的名称 你想让CMake来定位。 )#指定库的库应该链接到你的目标库。您可以链接多个库,比如在这个构建脚本中定义的库、预构建的第三方库或系统库。target_link_libraries( native-lib # 指定目标库中。与 add_library的库名称一定要相同 ${log-lib} # 将目标库链接到日志库包含在NDK。 )#如果需要生产多个SO文件的话,写法如下add_library( natave-lib # 设置库的名称。另一个so文件的名称 SHARED # 将库设置为共享库。 nataveJni.cpp # 提供一个源文件的相对路径 )target_link_libraries( natave-lib #指定目标库中。与 add_library的库名称一定要相同 ${log-lib} # 将目标库链接到日志库包含在NDK。 ) build.gradle中有CMake的相关配置代码结构java调用c、c++代码分为三个步骤:加载so库编写java函数编写c函数在MainActivity.java,static{}语句中使用了加载so库,此语句在类加载中只执行一次。static { System.loadLibrary(“native-lib”);}然后,编写了原生的函数,函数名中要带有native。public native String stringFromJNI();最后,编写相对应的c函数,注意函数名的构成Java_com_example_myapplication_MainActivity_stringFromJNI为Java_加上包名、类型、方法名的下划线连成一起。native-lib.cpp文件#include <jni.h>#include <string>extern “C” JNIEXPORT jstring JNICALLJava_com_example_myapplication_MainActivity_stringFromJNI( JNIEnv env, jobject / this */) { std::string hello = “Hello from C++”; return env->NewStringUTF(hello.c_str());}这就是一个JNI方法调用示例。虽然Java函数不带参数,但是原生方法却带了两个参数,第一个参数JNIEnv是指向可用JNI函数表的接口指针,第二个参数jobject是Java函数所在类的实例的Java对象引用。JNIEnv接口指针原生代码(c)通过JNIEnv接口指针提供的各种函数来使用虚拟机的功能,JNIEnv是一个指向线程-局部数据的指针,线程-局部数据中包含指向函数表的指针。原生代码是c与原生代码是c++的调用JNI函数的语法不同,在c代码中,JNIEnv是指向JNINativeInterface结构的指针,而在c++代码中,JNIEnv是c++类实例,这两种方式调用函数的方式是不一样的。例如:c代码中:(*env)->NewStringUTF(env,“Hello from JNI”);c++代码中:env->NewStringUTF(“Hello from JNI”);实例方法与静态方法Java程序设计有两类方法,实例方法和静态方法。实例方法与类实例相关,只能在类实例中调用。静态方法不与类死里相关,它们可以在静态上下文中直接调用。在原生代码中可以获取Java类的实例引用和类引用。例如:类实例引用extern “C” JNIEXPORT void JNICALLJava_com_example_myapplication_MainActivity_stringFromJNI( JNIEnv *env, jobject thiz) {}类引用extern “C” JNIEXPORT void JNICALLJava_com_example_myapplication_MainActivity_stringFromJNI( JNIEnv *env, jclass clazz) {}从函数中看出来,JNI提供了自己的数据类型从而让原生代码了解Java数据类型。JNI数据类型JNI的数据类型包含两种:基本类型和引用类型。与Java数据类型的对应关系如下:基本数据类型:| JNI类型 | Java类型 || —— | —— | | jboolean | boolean || jbyte | byte | | jchar | char || jshort | short || jint | int || jlong | long || jfloat | float || jdouble | double || void | void |引用类型:| JNI类型 | Java类型 || —— | —— | | jobject | Object || jclass | Class | | jstring | String || jobjectArray | Object[] || jbooleanArray | boolean[] || jbyteArray | char[] || jshortArray | short[] || jintArray | int[] || jlongArray | long[] | | jfloatArray | float[] | | jdoubleArray | double[] | | jthrowable | Throwable |引用数据类型的操作JNI提供了与引用类型密切相关的一组API,这些API通过JNIEnv接口指针提供给原生函数。例如:字符串数组NIO缓冲区字段方法字符串操作JNI把Java字符串当成引用类型处理,提供了Java与c字符串之间相互转换的必要函数,由于Java字符串对象是不可变得,所以JNI不提供修改现有Java字符串内容的函数。创建字符串可以在原生代码中使用NewString函数构建Unicode编码格式的字符串实例,也可以中NewStringUTF函数构建UTF-8编码格式的字符串实例,这些函数以C字符串为参数,并返回一个Java字符串引用类型jstring值。例如:jstring javaStr = (env)->NewStringUTF(env,“Hello”);把Java字符串转换成C字符串为了在原生代码中使用Java字符串,需要将Java字符串转换成C字符串。用GetStringChars函数可以将Unicode格式的Java字符串转换成C字符串,用GetStringUTFChars函数可以将UTF-8格式的Java字符串转换成C字符串。例如:const jbyte strjboolean isCopy;str = (*env)->GetStringUTFChars(env,javaString,&isCopy);释放字符串通过JNI GetStringChars函数和GetStringUTFChars函数获得的C字符串在原生代码中使用完后要释放,否则会引起内存泄漏。JNI提供了ReleaseStringChars函数和ReleaseStringUTFChars函数来释放Unicode编码和UTF-8编码格式的字符串。例如:(*env)->ReleaseStringUTFChars(env,javaString,str);数组操作创建数组用New"Type"Array函数在原生代码中创建数组实例,其中"Type"可以是Int、Char等类型,例如: jintArray javaArray = (env)->NewIntArray(env,10);访问数组元素将数组的代码复制成C数组或者让JNI提供直接指向数组元素的指针方式来访问Java数组元素。对副本的操作Get"Type"ArrayRegion函数将给定的基本Java数组复制到给定的C数组中,例如: jint nativeArray[10]; (env)->GetIntArrayRegion(env,javaArray,0,10,nativeArray);原生代码可以使用和修改数组元素,使用Set"Type"ArrayRegion函数将C数组复制回Java数组中。例如:(env)->SetIntArrayRegion(env,javaArray,0,10,nativeArray);NIO操作JNI提供了在原生代码中使用NIO的函数,与数组操作相比,NIO性能较好,更适合在原生代码和Java应用程序之间传送大量数据。创建直接字节缓冲区unsigned char buffer = (unsigned char) malloc(1024);jobject directBuffer = (env)->NewDirectByteBuffer(env,buffer,1024);注意:原生函数应用通过释放未使用的内存分配以避免内存泄漏。获取直接字节缓冲区unsigned char buffer;buffer = (unsigned char)(*env)->GetDirectBufferAddress(env,directBuffer);访问域Java有两类域:实例域和静态域,这两个的区别就是有没有static声明静态。获取域IDJNI提供了用域ID访问两类域的方法,可以通过给定实例的class对象获取域ID,用GetObjectClass函数来获取class对象。例如:jclass clazz = (*env)->GetObjectClass(env,instance);用GetFieldId函数来获取实例域。jfieldId instanceFieldId = (*env)->GetFieldId(env,clazz,“instanceField”,“Ljava/lang/String”);用GetStaticFieldId获取静态域ID。jfieldID staticFieldId = (*env)->GetStaticFieldID(env,clazz,“staticField”,“Ljava/lang/String”);其中最后一个参数是Java中表示域类型的域描述符,“Ljava/lang/String"表明域类型是String。获取域获得域ID之后可以用Get"Type"Field函数获取实际的实例域。例如:jstring instanceField = (*env)->GetObjectField(env,instance,instanceFieldId);用GetStatic"Type"Field函数获得静态域。例如:jstring staticField = (*env)->GetStaticObjectField(env,clazz,staticFieldId);调用方法与域类似,Java中有两类方法:实例方法和静态方法。获取方法IDJNI提供了用方法ID访问两类方法的途径,可以用给定实例的class对象获取方法ID,用GetMethodID函数获得实例方法的方法ID。例如:jmethodID instanceMethodId = (*env)->GetMethodID(env,clazz,“instanceMethod”,”()Ljava/lang/String;");用GetStaticMethodID函数获得静态域的方法ID,例如:jmethodID staticMethodId=(*env)->GetStaticMethodID(env,clazz,“staticMethod”,"()Ljava/lang/String;");调用方法以方法ID为参数通过Call"Type"Method类函数调用实际的实例方法。例如:jstring instanceMethodResult = (*env)->CallStringMetthod(env,instance,instanceMethodId);用CallStatic"Type"Field类函数调用静态方法,例如:jstring staticMethodResult = (*env)->CallStaticStringMethod(env,clazz,staticMethodId);域和方法描述符在上面获取域ID和方法ID均分别需要域描述符和方法描述符,域描述符和方法描述符可以通过下表Java类型签名映射获取:| Java类型 | 签名|| —— | —— | | Boolean | Z || Byte | B | | Char | C || Short | S || Long | J || Int | I || Float | F || Double | D || void | V || fully-qualified-class | Lfully-qualified-class | | type[] | [type | | method type | (arg-type)ret-type | 类的签名采用"L+包名+类名+;“的形式,将其中的.替换为/即可,比如java.lang.String,它的签名为Ljava/lang/String;数组的签名就是[+类型签名,比如int数组,签名就是[I,多维数组就是[[I。方法的签名为(参数类型签名)+返回值类型签名,例如:boolean fun1(int a,double b,int[] c),其中参数类型的签名为ID[I,返回值类型的签名为Z,所以这个方法的签名就是(ID[I)Z。 ...

March 17, 2019 · 2 min · jiezi

Android进阶: 10分钟实现NDK-JNI 开发教程

项目简介JNI:Java Native Interface(Java 本地编程接口),一套编程规范,它提供了若干的 API 实现了 Java 和其他语言的通信(主要是 C/C++)。Java 可以通过 JNI 调用本地的 C/C++ 代码,本地的 C/C++ 代码也可以调用 java 代码。Java 通过 C/C++ 使用本地的代码的一个关键性原因在于 C/C++ 代码的高效性。NDK:Native Development Kit(本地开发工具),一系列工具的集合,提供了一系列的工具,帮助开发者快速开发 C/C++,极大地减轻了开发人员的打包工作。项目环境Android studio 3.1.2gradle 4.4 plugin 3.1.2targetSdkVersion 28Jni三部曲1.新建Java文件编写相关代码2.通过命令工具Terminal生成.h文件3.新建.c 或者.cpp文件编写相关代码环境配置1.安装NDK+CMakeNDK:这套工具集允许为 Android 使用 C 和 C++ 代码。CMake:一款外部构建工具,可与 Gradle 搭配使用来构建原生库。如果只计划使用 ndk-build,则不需要此组件。Ps:CMake 是 AS 2.2 之后加入的一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程),简单来说就是简化 JNI 开发的编译步骤2.NDK环境配置1.local.propertiesndk.dir=D:\workTime\android-studio-sdk-2.3\android-studio-sdk-2.3\ndk-bundlesdk.dir=D:\workTime\android-studio-sdk-2.3\android-studio-sdk-2.32.gradle.properties#gradle:3.0.1 studio3.0 之前用android.useDeprecatedNdk=true#gradle:3.0.1 studio3.0 之后用android.deprecatedNdkCompileLease=15118326988133.build.gradle中添加CMakeandroid { ……… externalNativeBuild { cmake { path “CMakeLists.txt” } }}4.在app目录下新建CMakeLists.txtCMakeLists.txt所在目录和上面path “CMakeLists.txt"相关连CMakeLists.txt中内容如下:# CMake的编译脚本配置文件# 1. 标注需要支持的CMake最小版本cmake_minimum_required(VERSION 3.4.1)# 2. add_library 定义需要编译的代码库 名称, 类型, 包含的源码add_library( # Sets the name of the library. JNIControl # Sets the library as a shared library. SHARED src/main/jni/JNIControl.cpp)# 3. find_library 定义当前代码库需要依赖的系统或者第三方库文件(可以写多个)find_library( log_lib # 指定要查找的系统库, 给一个名字 log # 真正要查找的liblog.so或者liblog.a)# 4. target_link_libraries设置最终编译的目标代码库target_link_libraries( JNIControl # add_library 生成的 ${log_lib} # find_library 找到的系统库)}到这里环境就搭建完成了,那么下面我们开始装逼了。。。执行装逼三部曲1.新建要编译成.h文件的java文件/** * <pre> * author : Wp * e-mail : 18141924293@163.com * time : 2018/11/15 * desc : * version: 1.0 * </pre> /public class JNIUtils { static { //JNIControl 后面新建的.c 或者.cpp 文件名 在这里可以先注释掉 System.loadLibrary(“JNIControl”); } public static native String printStringByJni();}2.打开Android studio 最下面的命令工具Terminal1.进入java目录下,默认为项目根目录cd app/src/main/java2.如上图,确保在java目录下,执行以下命令,会在java目录下生成.h文件javah king.bird.ndkjnidemo.JNIUtils3.main下面新建jni文件夹,将.h文件拷贝过来4. .h文件如下/ DO NOT EDIT THIS FILE - it is machine generated /#include <jni.h>/ Header for class king_bird_ndkjnidemo_JNIUtils /#ifndef _Included_king_bird_ndkjnidemo_JNIUtils#define _Included_king_bird_ndkjnidemo_JNIUtils#ifdef __cplusplusextern “C” {#endif/ * Class: king_bird_ndkjnidemo_JNIUtils * Method: printStringByJni * Signature: ()Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_king_bird_ndkjnidemo_JNIUtils_printStringByJni (JNIEnv *, jclass);#ifdef __cplusplus}#endif#endif3.编写.c或者.cpp文件JNIControl.cpp文件内容:#include “king_bird_ndkjnidemo_JNIUtils.h”//king_bird_ndkjnidemo_JNIUtils_printStringByJni 包名+文件名+文件内方法名JNIEXPORT jstring JNICALL Java_king_bird_ndkjnidemo_JNIUtils_printStringByJni (JNIEnv *env, jclass jclass){ //字符串返回return env->NewStringUTF(“没想到吧!我竟然会JNI了!!!”);}到这里已经大功告成了1.MainActivity文件package king.bird.ndkjnidemoimport android.support.v7.app.AppCompatActivityimport android.os.Bundleimport kotlinx.android.synthetic.main.activity_main.*class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) mBtnLoadNative.setOnClickListener { val jniUtils = JNIUtils.printStringByJni() mTvText.text = jniUtils } }}2.activity_main.xml文件<?xml version=“1.0” encoding=“utf-8”?><android.support.constraint.ConstraintLayout xmlns:android=“http://schemas.android.com/apk/res/android" xmlns:app=“http://schemas.android.com/apk/res-auto" xmlns:tools=“http://schemas.android.com/tools" android:layout_width=“match_parent” android:layout_height=“match_parent” tools:context=".MainActivity”> <TextView android:id=”@+id/mTvText” android:layout_width=“wrap_content” android:layout_height=“wrap_content” android:text=“Hello World!” app:layout_constraintBottom_toBottomOf=“parent” app:layout_constraintLeft_toLeftOf=“parent” app:layout_constraintRight_toRightOf=“parent” app:layout_constraintTop_toTopOf=“parent” /> <Button android:id=”@+id/mBtnLoadNative" android:layout_width=“match_parent” android:layout_height=“wrap_content” android:text=“LoadNativeText” app:layout_constraintTop_toBottomOf="@+id/mTvText" app:layout_constraintBottom_toBottomOf=“parent” app:layout_constraintStart_toStartOf=“parent” app:layout_constraintEnd_toEndOf=“parent” tools:ignore=“HardcodedText” /></android.support.constraint.ConstraintLayout>参与贡献Fork 本项目新建 Feat_xxx 分支提交代码新建 Pull Request个人说明编译报错或有什么问题call meQQ群:830556582QQ:1101313414github地址你的star和fork是我永生的追求 ...

November 15, 2018 · 2 min · jiezi