安卓Android Studio JNI开发问题澄清与汇总
Posted KingsMan666
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了安卓Android Studio JNI开发问题澄清与汇总相关的知识,希望对你有一定的参考价值。
安卓android Studio JNI开发问题澄清与汇总
Bitmap的操作
如何在JNI中读取和输出Bitmap
AndroidBitmap_lockPixels和AndroidBitmap_unlockPixels的底层逻辑就是在处理bitmap中的数据的时候,把内存锁定,防止像素缓存被改变导致数据变化。
下面是Bitmap操作的示例代码:
#include <jni.h>
#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
using namespace cv;
extern "C"
JNIEXPORT jobject JNICALL Java_com_example_NativeUtils_processImage(JNIEnv *env, jobject obj, jobject bitmap)
AndroidBitmapInfo info;
void *pixels = 0;
//获取bitmap的信息
if (AndroidBitmap_getInfo(env, bitmap, &info) < 0)
return NULL;
//获取bitmap的像素数据
if (AndroidBitmap_lockPixels(env, bitmap, &pixels) < 0)
return NULL;
//将像素数据转为Mat矩阵
Mat src(info.height, info.width, CV_8UC4, pixels);
//图像处理,这里以灰度化为例
cvtColor(src, src, COLOR_RGBA2GRAY);
//将处理后的图像数据转为Bitmap
jobject newBitmap = createBitmap(env, src);
//释放像素数据
AndroidBitmap_unlockPixels(env, bitmap);
return newBitmap;
jobject createBitmap(JNIEnv *env, Mat &src)
jclass bitmapCls = env->FindClass("android/graphics/Bitmap");
jmethodID createBitmapMethodID = env->GetStaticMethodID(bitmapCls, "createBitmap", "(IIZ)Landroid/graphics/Bitmap;");
jobject newBitmap = env->CallStaticObjectMethod(bitmapCls, createBitmapMethodID, src.cols, src.rows, Bitmap_Config::ARGB_8888);
AndroidBitmapInfo info;
void *pixels = 0;
//获取Bitmap的像素数据
if (AndroidBitmap_getInfo(env, newBitmap, &info) < 0)
return NULL;
if (AndroidBitmap_lockPixels(env, newBitmap, &pixels) < 0)
return NULL;
//将Mat矩阵中的数据复制到Bitmap中
uint8_t *srcPixels = src.data;
uint8_t *dstPixels = reinterpret_cast<uint8_t *>(pixels);
int len = info.width * info.height * 4;
for (int i = 0; i < len; ++i)
*dstPixels++ = *srcPixels++;
//释放像素数据
AndroidBitmap_unlockPixels(env, newBitmap);
return newBitmap;
Java端代码:
public class NativeUtils
static
System.loadLibrary("native-lib");
public static native Bitmap processImage(Bitmap bitmap);
使用时,可以直接调用NativeUtils中的processImage方法即可。当然,这只是一个简单的示例,实际开发中需要针对不同的图像处理需求进行修改和优化。
在上述代码中,我们使用了createBitmap函数将Mat矩阵中的数据复制到Bitmap中,其中Bitmap_Config::ARGB_8888表示Bitmap的像素格式为ARGB8888,即每个像素点占4个字节。而在Mat矩阵中,我们使用了CV格式来指定矩阵的数据类型。以下是常用的CV格式:
- CV_8UC1 表示8位无符号单通道
- CV_8UC2 表示8位无符号双通道
- CV_8UC3 表示8位无符号三通道
- CV_8UC4 表示8位无符号四通道
- CV_32FC1 表示32位单精度浮点单通道
- CV_32FC2 表示32位单精度浮点双通道
- CV_32FC3 表示32位单精度浮点三通道
- CV_32FC4 表示32位单精度浮点四通道
其中,UC表示unsigned char,即无符号字符类型;FC表示float,即浮点数类型。通道数可以根据需要自由组合,例如CV_8UC3表示8位无符号三通道数据。在Mat矩阵中,每个像素点所占的字节数由数据类型和通道数共同决定。例如,CV_8UC3类型的Mat矩阵中,每个像素点占用3个字节。在处理图像时,需要根据图像的实际情况进行不同类型的Mat矩阵的创建和处理。
setPixels()方法:
给bitmap赋像素值的方法有两种:
-
bitmap.setPixel(int x,int y,color)此方法功能为给bitmap中的某个像素赋RGB值。
参数
x,y
表示该像素的坐标。
color
为整型的RGB值。 -
bitmap.setPixels(int [] pixels,int index,int stride, int x,int y,int width, int length)
参数
pixels
数组表示像素RGB值
index
表示从数组的那里开始
stride
表示bitmap的跨宽,其中除了一行像素点的个数外还有其他信息,所以通常stride要大于width的值。
x,y
表示从bitmap的哪个坐标开始。
width, length
表示多宽多行
其中记住width*length
要小于或等于pixels的数组长度,否则会抛出异常。
二维数组转化为Bitmap
//输入的数据类型可以根据需要更改
public static Bitmap ConvertToBinaryBitmap(float[][] data,float threshold)
int width = data[0].length;
int height = data.length;
//创建初始图像
Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
//bitmap赋值
int[] pixs = new int[width * height];
for(int i=0;i<height;i++)
for(int j=0;j<width;j++)
int x = i*width+j;
//大于阈值,设置为白色
if(data[i][j]>threshold)
int r = ((pixs[x] >> 16) & 0xff)|0xff;
int g = ((pixs[x] >> 8) & 0xff)|0xff;
int b =( pixs[x] & 0xff)|0xff;
pixs[x] = 0xff000000 | (r << 16) | (g << 8) | b;
//小于阈值,设置为黑色
else
pixs[x] = 0xff000000;
//写入图像
result.setPixels(pixs,0,width,0,0,width,height);
return result;
Bitmap和CV::Mat的相互转化
Bitmap转化Matrix:
bool BitmapToMatrix(JNIEnv * env, jobject obj_bitmap, cv::Mat & matrix)
void * bitmapPixels; // Save picture pixel data
AndroidBitmapInfo bitmapInfo; // Save picture parameters
ASSERT_FALSE( AndroidBitmap_getInfo(env, obj_bitmap, &bitmapInfo) >= 0); // Get picture parameters
ASSERT_FALSE( bitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888
|| bitmapInfo.format == ANDROID_BITMAP_FORMAT_RGB_565 ); // Only ARGB? 8888 and RGB? 565 are supported
ASSERT_FALSE( AndroidBitmap_lockPixels(env, obj_bitmap, &bitmapPixels) >= 0 ); // Get picture pixels (lock memory block)
ASSERT_FALSE( bitmapPixels );
if (bitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888)
cv::Mat tmp(bitmapInfo.height, bitmapInfo.width, CV_8UC4, bitmapPixels); // Establish temporary mat
tmp.copyTo(matrix); // Copy to target matrix
else
cv::Mat tmp(bitmapInfo.height, bitmapInfo.width, CV_8UC2, bitmapPixels);
cv::cvtColor(tmp, matrix, cv::COLOR_BGR5652RGB);
//convert RGB to BGR
cv::cvtColor(matrix,matrix,cv::COLOR_RGB2BGR);
AndroidBitmap_unlockPixels(env, obj_bitmap); // Unlock
return true;
Matrix转化Bitmap:
bool MatrixToBitmap(JNIEnv * env, cv::Mat & matrix, jobject obj_bitmap)
void * bitmapPixels; // Save picture pixel data
AndroidBitmapInfo bitmapInfo; // Save picture parameters
ASSERT_FALSE( AndroidBitmap_getInfo(env, obj_bitmap, &bitmapInfo) >= 0); // Get picture parameters
ASSERT_FALSE( bitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888
|| bitmapInfo.format == ANDROID_BITMAP_FORMAT_RGB_565 ); // Only ARGB? 8888 and RGB? 565 are supported
ASSERT_FALSE( matrix.dims == 2
&& bitmapInfo.height == (uint32_t)matrix.rows
&& bitmapInfo.width == (uint32_t)matrix.cols ); // It must be a 2-dimensional matrix with the same length and width
ASSERT_FALSE( matrix.type() == CV_8UC1 || matrix.type() == CV_8UC3 || matrix.type() == CV_8UC4 );
ASSERT_FALSE( AndroidBitmap_lockPixels(env, obj_bitmap, &bitmapPixels) >= 0 ); // Get picture pixels (lock memory block)
ASSERT_FALSE( bitmapPixels );
if (bitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888)
cv::Mat tmp(bitmapInfo.height, bitmapInfo.width, CV_8UC4, bitmapPixels);
switch (matrix.type())
case CV_8UC1: cv::cvtColor(matrix, tmp, cv::COLOR_GRAY2RGBA); break;
case CV_8UC3: cv::cvtColor(matrix, tmp, cv::COLOR_RGB2RGBA); break;
case CV_8UC4: matrix.copyTo(tmp); break;
default: AndroidBitmap_unlockPixels(env, obj_bitmap); return false;
else
cv::Mat tmp(bitmapInfo.height, bitmapInfo.width, CV_8UC2, bitmapPixels);
switch (matrix.type())
case CV_8UC1: cv::cvtColor(matrix, tmp, cv::COLOR_GRAY2BGR565); break;
case CV_8UC3: cv::cvtColor(matrix, tmp, cv::COLOR_RGB2BGR565); break;
case CV_8UC4: cv::cvtColor(matrix, tmp, cv::COLOR_RGBA2BGR565); break;
default: AndroidBitmap_unlockPixels(env, obj_bitmap); return false;
AndroidBitmap_unlockPixels(env, obj_bitmap); // Unlock
return true;
CV操作
ConvertTo
convertTo函数的用法
void Mat::convertTo( Mat& m, int rtype, double alpha=1, double beta=0 )
const;
输入参数:
m
目标矩阵。如果m的大小与原矩阵不一样,或者数据类型与参数不匹配,那么在函数convertTo内部会先给m重新分配空间。
rtype
指定从原矩阵进行转换后的数据类型,即目标矩阵m的数据类型。当然,矩阵m的通道数应该与原矩阵一样的。如果rtype是负数,那么m矩阵的数据类型应该与原矩阵一样。
alpha
缩放因子。默认值是1。即把原矩阵中的每一个元素都乘以alpha。
beta
增量。默认值是0。即把原矩阵中的每一个元素都乘以alpha,再加上beta。
功能:
把一个矩阵从一种数据类型转换到另一种数据类型,同时可以带上缩放因子和增量,公式如下:
m(x,y)=saturate_cast<rType>(alpha*(*this)(x,y)+beta);
由于有数据类型的转换,所以需要用saturate_cast来处理数据的溢出。
安卓实战开发之JNI入门及高效的配置(android studio一键生成.h,so及方法签名)
前言
以前也讲过NDK开发,但是开始是抱着好玩的感觉去开始的,然后呢会helloWord就觉得大大的满足,现在静下来想这NDK开发到底是干什么呢?
NDK开发,其实是为了项目需要调用底层的一些C/C++的一些东西;另外就是为了效率更加高效些但是在java与C相互调用时平白又增大了开销(其实效率不见得有所提高),然后呢,基于安全性的考虑也是为了防止代码被反编译我们为了安全起见,使用C语言来编写这些重要的部分来增大系统的安全性,最后呢生成so库便于给人提供方便。
好了,我们来看一下qq的结构,我们就能理解任何有效的代码混淆对于会smail语法反编译你apk是分分钟的事,即使你加壳也不能幸免高手的攻击,当然你的apk没有什么机密和交易信息就没有人去做这事了。
分析qq的apk架构:
1.使用ClassyShark.jar来打开qq.apk
2.点开Archive我们来查看架构
从上图我们可以看出qq里面是一堆的so库是吗,所以呢so库可见比代码混淆安全系数高的多。
JNI与NDK的关系
- NDK:
NDK是一系列工具的集合。它提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的。它集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。它可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作。
- JNI:
JavaNative Interface (JNI)标准是java平台的一部分,JNI是Java语言提供的Java和C/C++相互沟通的机制,Java可以通过JNI调用本地的C/C++代码,本地的C/C++的代码也可以调用java代码。JNI 是本地编程接口,Java和C/C++互相通过的接口。Java通过C/C++使用本地的代码的一个关键性原因在于C/C++代码的高效性。
现在明白了吧,NDK就是为我们生成了c/c++的动态链接库而已,jni呢只不过是java和c沟通而已,两者与android没有半毛钱关系,只因为安卓是java程序开发然后jni又能与c沟通,所以使“Java+C”的开发方式终于转正。
Android是JVM架设在Linux之上的架构。所以无论如何,在Linux OS层面,都应该可以跑C/C++程序。
Android Native C就是使用C/C++程序直接跑到Linux OS层面上的程序。与其它平台类似,只需要交叉编译后。并得到Linux OS root权限,就可以直接跑起来了。
android studio 中简单的jni开发
Let’s Go!!!
准备工作不再需要什么cgwin来编译ndk(太特么操蛋了),现在只需要你下载一下NDK的库就ok了,然后你也可以去离线下载http://www.androiddevtools.cn最新版,这里吐槽一下android studio对NDK的支持还有待提高。
效果
看下今天的效果:(安卓jni获取 apk的包名及签名信息)
必须的步骤
1.配置你的ndk路径(local.properties)
ndk.dir=E:\\Android\\sdk\\android-ndk-r11b-windows-x86_64\\android-ndk-r11b
2.grale配置使用ndk(gradle.properties)
android.useDeprecatedNdk=true
3.在module下的build.gradle添加ndk以及jni生成目录
ndk
moduleName “JNI_ANDROID”
abiFilters “armeabi”, “armeabi-v7a”, “x86” //输出指定三种abi体系结构下的so库,目前可有可无。
sourceSets.main
jniLibs.srcDirs = [‘libs’]
准备工作做好了开始写代码:(jni实现获取应用的包名和签名信息)
步骤1:先写要实现本地方法的类,及加载库(JNI_ANDROID也就是ndk 里面配的moduleName)
package com.losileeya.getapkinfo;
/**
* User: Losileeya (847457332@qq.com)
* Date: 2016-07-16
* Time: 11:09
* 类描述:
*
* @version :
*/
public class JNIUtils
/**
* 获取应用的签名
* @param o
* @return
*/
public static native String getSignature(Object o);
/**
* 获取应用的包名
* @param o
* @return
*/
public static native String getPackname(Object o);
/**
* 加载so库或jni库
*/
static
System.loadLibrary("JNI_ANDROID");
注意我们 的加载c方法都加了native关键字,然后要使用jni下的c/c++文件就必须使用System.loadLibrary()。
步骤2:使用javah命令生成.h(头文件)
javah -jni com.losileeya.getapkinfo.JNIUtils
执行完之后你可以在module下文件夹app\\build\\intermediates\\classes\\debug下看见生成的 .h头文件为:
com_losileeya_getapkinfo_JNIUtils.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_losileeya_getapkinfo_JNIUtils */
#ifndef _Included_com_losileeya_getapkinfo_JNIUtils
#define _Included_com_losileeya_getapkinfo_JNIUtils
#ifdef __cplusplus
extern "C"
#endif
JNIEXPORT jstring JNICALL Java_com_losileeya_getapkinfo_JNIUtils_getPackname(JNIEnv *, jobject, jobject);
JNIEXPORT jstring JNICALL Java_com_losileeya_getapkinfo_JNIUtils_getSignature(JNIEnv *, jobject, jobject);
#ifdef __cplusplus
#endif
#endif
在工程的main目录下新建一个名字为jni的目录,然后将刚才的.h文件剪切过来,当然文件名字是可以修改的
步骤3:根据.h文件生成相应的c/cpp文件
//
// Created by Administrator on 2016/7/16.
//
#include <stdio.h>
#include <jni.h>
#include <stdlib.h>
#include "appinfo.h"
JNIEXPORT jstring JNICALL Java_com_losileeya_getapkinfo_JNIUtils_getPackname(JNIEnv *env, jobject clazz, jobject obj)
jclass native_class = env->GetObjectClass(obj);
jmethodID mId = env->GetMethodID(native_class, "getPackageName", "()Ljava/lang/String;");
jstring packName = static_cast<jstring>(env->CallObjectMethod(obj, mId));
return packName;
JNIEXPORT jstring JNICALL Java_com_losileeya_getapkinfo_JNIUtils_getSignature(JNIEnv *env, jobject clazz, jobject obj)
jclass native_class = env->GetObjectClass(obj);
jmethodID pm_id = env->GetMethodID(native_class, "getPackageManager", "()Landroid/content/pm/PackageManager;");
jobject pm_obj = env->CallObjectMethod(obj, pm_id);
jclass pm_clazz = env->GetObjectClass(pm_obj);
// 得到 getPackageInfo 方法的 ID
jmethodID package_info_id = env->GetMethodID(pm_clazz, "getPackageInfo","(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
jstring pkg_str = Java_com_losileeya_getapkinfo_JNIUtils_getPackname(env, clazz, obj);
// 获得应用包的信息
jobject pi_obj = env->CallObjectMethod(pm_obj, package_info_id, pkg_str, 64);
// 获得 PackageInfo 类
jclass pi_clazz = env->GetObjectClass(pi_obj);
// 获得签名数组属性的 ID
jfieldID signatures_fieldId = env->GetFieldID(pi_clazz, "signatures", "[Landroid/content/pm/Signature;");
jobject signatures_obj = env->GetObjectField(pi_obj, signatures_fieldId);
jobjectArray signaturesArray = (jobjectArray)signatures_obj;
jsize size = env->GetArrayLength(signaturesArray);
jobject signature_obj = env->GetObjectArrayElement(signaturesArray, 0);
jclass signature_clazz = env->GetObjectClass(signature_obj);
jmethodID string_id = env->GetMethodID(signature_clazz, "toCharsString", "()Ljava/lang/String;");
jstring str = static_cast<jstring>(env->CallObjectMethod(signature_obj, string_id));
char *c_msg = (char*)env->GetStringUTFChars(str,0);
return str;
注意:要使用前得先声明,方法名直接从h文件考过来就好了,studio目前还是很操蛋的,对于jni的支持还是不很好。
步骤4:给项目添加Android.mk和Application.mk
此步骤显然也是不必要的,如果你需要生成so库添加一下也好,为什么不呢考过去改一下就好了,如果你不写这2文件也是没有问题的,因为debug下也是有这些so库的。
好吧,勉强看一下这2货:
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := JNI_ANDROID
LOCAL_SRC_FILES =: appinfo.cpp
include $(BUILD_SHARED_LIBRARY)
Application.mk
APP_MODULES := JNI_ANDROID
APP_ABI := all
android studio下External Tools的高级配置NDK一键javah,ndk生成so库
eclipse开发ndk的时候你可能就配置过javah,所以android studio也可以配置,是不是很兴奋:
Settings—>Tools—->External Tools就可以配置我们的终端命令了,别急一个一个来:
- javah -jni 命令的配置(一键生成h文件)
我们先来看参数的配置:
1.Program: JDKPath \\bin\\javah.exe 这里配置的是javah.exe的路径(基本一致)
2.Parametes: -classpath . -jni -d ModuleFileDir /src/main/jni FileClass 这里指的是定位在Module的jni文件你指定的文件执行jni指令
3.Working: ModuleFileDir \\src\\main\\java
- ndk-build(一键生成so库)
我们同样来看参数的配置:
1.Program:E:\\Android\\sdk\\android-ndk-r11b-windows-x86_64\\android-ndk-r11b\\ndk-build.cmd 这里配置的是ndk下的ndk-build.cmd的路径(自己去找下)
2.Working: ModuleFileDir \\src\\main\\
- javap-s(此命令用于c掉java方法时方法的签名)
我们同样来看参数的配置:
1.Program: JDKPath \\bin\\javap.exe 这里配置的是javap.exe的路径(基本一致)
2.Parametes: -classpath ModuleFileDir /build/intermediates/classes/debug -s FileClass 这里指的是定位到build的debug目录下执行 javap -s class文件
3.Working: ModuleFileDir
这里介绍最常用的3个命令,对你的帮助应该还是很大的来看一下怎么使用:
- javah -jni的使用:选中native文件—>右键—>External Tools—>javah -jni
效果如下:
是不是自动生成了包名.类名的.h文件。
- ndk-build的使用:选中jni文件—>右键—>External Tools—>ndk-build
效果如下:
是不是一键生成了7种so库,你还想去debug目录下面去找吗
- javap-s的使用:选中native文件—>右键—>External Tools—>javap-s
效果如下:
看见了每个方法下的descriptor属性的值就是你所要的方法签名。
3种一键生成的命令讲完了,以后你用到了什么命令都可以这样设置,是不是很给力。
新实验版Gradle插件与AS下NDK开发
近期的 AS 与 Gradle 版本的快速更新对 NDK 开发又有了更加牛叉的体验,因为它完全支持使用 GDB 和 LLDB (不清楚这两是啥的请自行脑部Unix编程基础)来 GUI 化 debug 我们得 native 代码了(以前真的好蛋疼,命令行巴拉巴拉的,泪奔啊!)。
总之现在的 AS 和 Gradle 已经趋于实验完善 NDK 开发了,主要表现在如下方面:
- AS 完全支持 GUI 模式的 GDB/LLDB 调试 native 代码(LLDB调试引擎需要gradle-experimental plugin的支持)。
- AS 可以很好的直接编写 native 代码。
Java 层代码声明好以后 AS 可以自动帮我们生成 JNI 接口规范代码。
*推出了几乎针对 NDK 的实验版 Gradle 插件。
可以看见,现在 NDK 开发已经渐渐的变得越来越方便了,牛叉的一逼!
因为是实验版本,所以我就试了下,不作为今后开发的主要方向,但是还是需要了解下。区别如下:1.情况1
//Project的build.gradle文件
buildscript
repositories
jcenter()
dependencies
classpath 'com.android.tools.build:gradle-experimental:0.7.0-alpha1'
allprojects
repositories
jcenter()
2.情况2
//Module的build.gradle文件
apply plugin: 'com.android.model.application'
model
android
compileSdkVersion = 23
buildToolsVersion = "23.0.2"
defaultConfig.with
applicationId = "com.losileeya.getapkinfo"
minSdkVersion.apiLevel = 8
targetSdkVersion.apiLevel = 23
/*
* native build settings
*/
android.ndk
moduleName = "JNI_ANDROID"
/*
* Other ndk flags configurable here are
* cppFlags.add("-fno-rtti")
* cppFlags.add("-fno-exceptions")
* ldLibs.addAll(["android", "log"])
* stl = "system"
*/
android.productFlavors
// for detailed abiFilter descriptions, refer to "Supported ABIs" @
// https://developer.android.com/ndk/guides/abis.html#sa
create("arm")
ndk.abiFilters.add("armeabi")
create("arm7")
ndk.abiFilters.add("armeabi-v7a")
create("arm8")
ndk.abiFilters.add("arm64-v8a")
create("x86")
ndk.abiFilters.add("x86")
create("x86-64")
ndk.abiFilters.add("x86_64")
create("mips")
ndk.abiFilters.add("mips")
create("mips-64")
ndk.abiFilters.add("mips64")
// To include all cpu architectures, leaves abiFilters empty
create("all")
可以明显感觉到 Project 和 Module 的 build.gradle 文件编写闭包都有了变化。
入门就讲完了,你也可以删掉jni目录,把so库放入jniLibs下,效果还是一模一样的,很晚了,睡觉。
总结
初步使用ndk的技巧已经说完了,后续还会介绍jni中c调用java以及java调用c和相关一系列ndk开发中所要注意的。
注意事项
1.jni调用前记得申明,比如:#include stdio.h,#include jni.h,#include stdlib.h,方法被调用者写前面或者头文件里面
2.c中env调方法时(*env)->但是cpp中就得这样env->,原因是cpp中是一级指针,所以指针特别注意
本文已同步到 github
demo 传送门:GetApkInfo.rar
以上是关于安卓Android Studio JNI开发问题澄清与汇总的主要内容,如果未能解决你的问题,请参考以下文章