下面四个小程序用到的java类
public class Jni { static { System.loadLibrary("javacallc"); } /** * 求两个数字的和 * * @param x * @param y * @return */ public native int sum(int x, int y); /** * 将两个字符串拼接后返回 * * @param s * @return */ public native String sayHello(String s); /** * 将数组中的每个元素增加10 * * @param intArray * @return */ public native int[] increaseArrayEles(int[] intArray); /** * 应用: 检查密码是否正确, 如果正确返回200, 否则返回400 "123456" * @param pwd * @return */ public native int checkPwd(String pwd); }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
测试1:将传入的两个int值相加并返回
/** * 求两个数字的和 * * @param x * @param y * @return */ JNIEXPORT jint JNICALL Java_bruce_chang_javacallc_Jni_sum (JNIEnv *env, jobject jobj, jint int1, jint int2) { //jint可以直接进行算术运算 int sum = int1 + int2; //可直接将int类型数据作为jint返回 return sum; }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
测试2:将两个字符串拼接后返回
/** * 将两个字符串拼接后返回 * * @param s I am BruceChang * c I am SWJ * @return I am BruceChang add I am SWJ */ JNIEXPORT jstring JNICALL Java_bruce_chang_javacallc_Jni_sayHello (JNIEnv * env, jobject jobj, jstring js){ //将jstring类型的js转换为char*类型数据 char * fromJava = _JString2CStr(env,js); //c char * fromC = "add I am SWJ"; //将拼接两个char*类型字符串拼接在第一个上 strcat(fromJava, fromC); //将结果转换为jstring类型返回 return (*env)->NewStringUTF(env, fromJava); }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
这里面用到一个函数_JString2CStr 将字符串转换成字符指针
/** * 工具函数 * 把一个jstring转换成一个c语言的char* 类型. */ char* _JString2CStr(JNIEnv* env, jstring jstr) { char* rtn; jclass clsstring = (*env)->FindClass(env, "java/lang/String"); jstring strencode = (*env)->NewStringUTF(env,"GB2312"); jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B"); jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid, strencode); // String .getByte("GB2312"); jsize alen = (*env)->GetArrayLength(env, barr); jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE); if(alen > 0) { rtn = (char*)malloc(alen+1); //"\0" memcpy(rtn, ba, alen); rtn[alen]=0; } (*env)->ReleaseByteArrayElements(env, barr, ba,0); return rtn; }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
测试3:将数组的每个元素增加10
/** * 将数组中的每个元素增加10 * * @param intArray * @return */ JNIEXPORT jintArray JNICALL Java_bruce_chang_javacallc_Jni_increaseArrayEles (JNIEnv * env, jobject jobj, jintArray arr){ //1. 得到数组的长度 //jsize (*GetArrayLength)(JNIEnv*, jarray); jsize length = (*env)->GetArrayLength(env, arr); //2. 得到数组 //jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*); jint* array = (*env)->GetIntArrayElements(env, arr, JNI_FALSE); //3. 遍历数组, 并将每个元素+10 int i; for(i=0;i<length;i++) { *(array+i) += 10; } //4. 返回数组 return arr; }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
测试4:检查密码是否正确
/** * 应用: 检查密码是否正确, 如果正确返回200, 否则返回400 "123456" * @param pwd * @return */ JNIEXPORT jint JNICALL Java_bruce_chang_javacallc_Jni_checkPwd (JNIEnv * env, jobject jobj, jstring string){ //1. 将jString转换为char* char* cs = _JString2CStr(env, string); char* pwd = "123456"; //2. 比较两个字符串是否相等 int result = strcmp(cs, pwd); //3. 根据比较的结果返回不同的值 if(result==0) { return 200; } return 400; }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
结果:
测试2,字符串的拼接,这个结果在不同的手机上显示效果还不太一样,在oppo r7上运行结果就是1、2、3、4、5,这个问题可能是程序不兼容导致的。
C调用java方法和java调用C的方法步骤一样,但C回调java方法的核心思想是利用反射的原理,其次还需要用到java中的javap -s命令来显示所有方法的签名信息,这个非常的重要,否则无法得到java类中的方法,从而也就无法实现C函数调用java方法。
显示方法签名的命令
E:\>cd E:\GithubDownload\TestNDK\ccalljava\src\main E:\GithubDownload\TestNDK\ccalljava\src\main>javah -d jni -classpath C:\AndroidDevelop\sdk\platforms\android-23\android.jar;E:\GithubDownload\TestNDK\ccalljava\build\intermediates\classes\debug com.bruce.chang.ccalljava.Jni E:\GithubDownload\TestNDK\ccalljava\src\main>javap -s -classpath C:\AndroidDevelop\sdk\platforms\android-23\android.jar;E:\GithubDownload\TestNDK\ccalljava\build\intermediates\classes\debug com.bruce.chang.ccalljava.Jni Compiled from "Jni.java" public class com.bruce.chang.ccalljava.Jni { public com.bruce.chang.ccalljava.Jni(); descriptor: ()V public native void callbackHelloFromJava(); descriptor: ()V public native void callbackAdd(); descriptor: ()V public native void callbackPrintString(); descriptor: ()V public native void callbackSayHello(); descriptor: ()V public void helloFromJava(); descriptor: ()V public int add(int, int); descriptor: (II)I public void printString(java.lang.String); descriptor: (Ljava/lang/String;)V public static void sayHello(java.lang.String); descriptor: (Ljava/lang/String;)V static {}; descriptor: ()V } E:\GithubDownload\TestNDK\ccalljava\src\main>
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
一般调用步骤
-
加载类得到class对象
-
得到对应方法的Method对象
-
创建类对象
-
调用方法
测试1:回调一般方法(无参无返回)
java端方法
public native void callbackHelloFromJava();
- 1
java端被回调方法
public void helloFromJava() { Log.e("TAG", "helloFromJava()"); }
-
1
-
2
-
3
C端函数
void Java_com_bruce_chang_ccalljava_Jni_callbackHelloFromJava (JNIEnv * env, jobject obj) { //1. 加载类得到jclass对象: //jclass (*FindClass)(JNIEnv*, const char*); jclass jc = (*env)->FindClass(env, "com/bruce/chang/ccalljava/Jni"); //2. 得到对应方法的Method对象 : GetMethodId() //jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*) jmethodID method = (*env)->GetMethodID(env, jc, "helloFromJava", "()V"); //3. 创建类对象 //jobject (*AllocObject)(JNIEnv*, jclass); jobject obj2 = (*env)->AllocObject(env, jc); //4. 调用方法 (*env)->CallVoidMethod(env, obj2, method); }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
测试2:回调带int参数方法
java端方法
public native void callbackAdd();
- 1
java端被回调方法
public int add(int x, int y) { Log.e("TAG", "add() x=" + x + " y=" + y); return x + y; }
-
1
-
2
-
3
-
4
C端函数
void Java_com_bruce_chang_ccalljava_Jni_callbackAdd(JNIEnv * env, jobject obj){ //1. 加载类得到class对象 jclass jc = (*env)->FindClass(env, "com/bruce/chang/ccalljava/Jni"); //2. 得到对应方法的Method对象 jmethodID method = (*env)->GetMethodID(env, jc, "add", "(II)I"); //3. 创建类对象 jobject obj2 = (*env)->AllocObject(env, jc); //4. 调用方法 (*env)->CallIntMethod(env, obj2, method, 3, 4); }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
测试3:回调带String参数方法
java端方法
public native void callbackPrintString();
- 1
java端被回调方法
public void printString(String s) { Log.e("TAG", "C中输入的:" + s); }
-
1
-
2
-
3
C端函数
void Java_com_bruce_chang_ccalljava_Jni_callbackPrintString(JNIEnv * env, jobject obj){ //1. 加载类得到class对象 jclass jc = (*env)->FindClass(env, "com/bruce/chang/ccalljava/Jni"); //2. 得到对应方法的Method对象 jmethodID method = (*env)->GetMethodID(env, jc, "printString", "(Ljava/lang/String;)V"); //3. 创建类对象 jobject obj2 = (*env)->AllocObject(env, jc); //4. 调用方法 jstring js = (*env)->NewStringUTF(env, "I from C"); (*env)->CallVoidMethod(env, obj2, method, js); }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
测试4:回调静态方法
java端方法
public native void callbackSayHello();
- 1
java端被回调方法
public static void sayHello(String s) { Log.e("TAG", "我是java代码中的JNI." + "java中的sayHello(String s)静态方法,我被C调用了:" + s); }
-
1
-
2
-
3
-
4
C端函数
void Java_com_bruce_chang_ccalljava_Jni_callbackSayHello(JNIEnv * env, jobject obj){ //1. 加载类得到class对象 jclass jc = (*env)->FindClass(env, "com/bruce/chang/ccalljava/Jni"); //2. 得到对应方法的Method对象 jmethodID method = (*env)->GetStaticMethodID(env, jc, "sayHello", "(Ljava/lang/String;)V"); //3. 调用方法 jstring js = (*env)->NewStringUTF(env, "I from C"); (*env)->CallStaticVoidMethod(env, jc, method, js); }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
结果:
小应用:回调更新UI的方法
java端方法,在MainActivity中写
/** * 让C代码调用MainActivity中的showToast方法 */ public native void callbackShowToast();
-
1
-
2
-
3
-
4
java端被回调方法
public void showToast() { Toast.makeText(MainActivity.this, "this is a toast!!!", Toast.LENGTH_SHORT).show(); }
-
1
-
2
-
3
C端函数
/** * 让C代码调用MainActivity中的showToast方法 * obj 谁调用了当前Java_com_bruce_chang_ccalljava_Jni_callbackShowToast对应的java方法就是就是谁的实例, * 该方法如果放在Jni这个类中就是是Jni.this,如果放在MainActivity中就是MainActivity.this */ void Java_com_bruce_chang_ccalljava_MainActivity_callbackShowToast(JNIEnv * env, jobject obj){ //1. 加载类得到class对象 jclass jc = (*env)->FindClass(env, "com/bruce/chang/ccalljava/MainActivity"); //2. 通过方法名和方法签名得到对应方法的Method对象 jmethodID method = (*env)->GetMethodID(env, jc, "showToast", "()V"); //3. 创建类对象 // jobject obj2 = (*env)->AllocObject(env, jc); //4. 调用方法 (*env)->CallVoidMethod(env, obj, method); }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
点击调用callbackShowToast方法
// jni.callbackShowToast();不能调用jni中的callbackShowToast方法,这样会报空指针错误,因为发射得到的是MainActivity的类, // 并不是Activity,在执行showToast方法的时候就出错了 MainActivity.this.callbackShowToast();
-
1
-
2
-
3
结果:
切记切记
通过本实例可以发现,通过C代码调用活动中的方法的时候,一定要在活动中调用native方法,否则在C回调的时候就会出现空指针的错误。
//功能: 加载类得到类对象 //返回: jni的class类型 jclass (*FindClass)(JNIEnv*, const char*)
-
1
-
2
-
3
//功能: 得到对应方法的Method对象 jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*)
-
1
-
2
//功能: 创建对象 jobject (*AllocObject)(JNIEnv*, jclass)
-
1
-
2
//功能: 调用没有返回值的方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...)
-
1
-
2
-
3
//功能: 调用返回int值的方法 jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...)
-
1
-
2
//功能: 得到静态方法的method jmethodID (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*)
-
1
-
2
-
3
//功能: 调用静态方法 void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...)
-
1
-
2
-
3
//功能: 将C中的字符串转换为JNI中的jstring类型 //第二个: 被转换的字符串 jstring (*NewStringUTF)(JNIEnv*, const char*)
-
1
-
2
-
3
-
4
//功能: 将第二个字符串连接到第一个字符串上 string.h---char* strcat(char *, const char *);
-
1
-
2
-
3
//功能: 得到jni类型数组的长度(元素个数) jsize (*GetArrayLength)(JNIEnv*, jarray);
-
1
-
2
-
3
//功能: 得到jni数组中所有元素的指针 //第二个: jni中的int数组 第三个: 是否返回一个复制的数组 jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);
-
1
-
2
-
3
//参数为两个字符串 //第一个与第二个相等, 返回0 第一个大于第二个, 返回大于0 第一个小于第二个, 返回小于0 string.h---int strcmp(const char *, const char *)
-
1
-
2
-
3
-
4
=======================================================================
步骤
-
创建工程MTXX
-
把对应的.so文件拷贝到java/main/jniLibs目录。。armeabi/libmtimage-jin.so
-
创建一个类叫做JNI.java并且要在com.mt.mtxx.image包下创建
-
加载动态链接库,System.loadLibrary(“mtimage-jni”)
-
写布局文件和实现各个效果的点击事件
-
处理图片相关的工作
-
6.1把图片转换成矩阵(数组)
-
6.2把数组传入给C代码处理
-
6.3把处理好的数组重新生成图片
-
6.4把图片显示
按照上面的步骤一步一步的来,布局文件非常简单,就不写出来了。
布局效果
java程序中调用处理
- 加载Jni程序
JNI jni = new JNI();
- 1
- 高亮效果
public void lomoHDR(View view) { //6.1,把图片转换成数组 Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.fbb); //装图片的像数 int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()]; /** * 参数 pixels 接收位图颜色值的数组 offset 写入到pixels[]中的第一个像素索引值 stride pixels[]中的行间距个数值(必须大于等于位图宽度)。可以为负数 x 从位图中读取的第一个像素的x坐标值。 y 从位图中读取的第一个像素的y坐标值 width 从每一行中读取的像素宽度 height 读取的行数 异常 */ bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight()); //6.2,把数组传入给C代码处理 jni.StyleLomoHDR(pixels, bitmap.getWidth(), bitmap.getHeight()); // 6.3,把处理好的数组重新生成图片 bitmap = Bitmap.createBitmap(pixels, bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); // 6.4,把图片像数 iv_icon.setImageBitmap(bitmap); }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
- 黑白效果
public void lomoC(View view) { //6.1,把图片转换成数组 Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.fbb); //装图片的像数 int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()]; bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight()); //6.2,把数组传入给C代码处理 jni.StyleLomoC(pixels, bitmap.getWidth(), bitmap.getHeight()); // 6.3,把处理好的数组重新生成图片 bitmap = Bitmap.createBitmap(pixels, bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); // 6.4,把图片像数 iv_icon.setImageBitmap(bitmap); }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
- 怀旧效果
public void lomoB(View view) { //6.1,把图片转换成数组 Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.fbb); //装图片的像数 int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()]; bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight()); //6.2,把数组传入给C代码处理 jni.StyleLomoB(pixels, bitmap.getWidth(), bitmap.getHeight()); // 6.3,把处理好的数组重新生成图片 bitmap = Bitmap.createBitmap(pixels, bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); // 6.4,把图片像数 iv_icon.setImageBitmap(bitmap); }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
- 还原
iv_icon.setImageResource(R.mipmap.fbb);
- 1
剩下的四种效果就不说明了,稍后看一下结果。
结果(只能在arm架构的处理器上运行,x86的处理器的手机是不能运行该程序的):
步骤
- 创建一个module名字叫做GuoLu,
1.1 在MainActivity中写得到锅炉压力值的native方法—getPressure();
1.2 在build.gradle文件中配置生成.so文件的名称
defaultConfig { applicationId "com.bruce.chang.guolu" minSdkVersion 15 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" ndk{ moduleName "GuoLu" //so文件: lib+moduleName+.so abiFilters "armeabi", "armeabi-v7a", "x86" //cpu的类型 } }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
分析实现的原理
-
在C代码中写锅炉的压力值,返回给java
/** 得到锅炉的压力值 */ int pressure = 20; int getPressure(){ int incease = rand()%20; pressure += incease; return pressure; }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
- 在视图中动态绘制
GuoLu.c代码
//
// Created by Administrator on 2016/4/19.
//
#include <stdio.h> #include <stdlib.h>
include <jni.h>
===============
/**
得到锅炉的压力值
*/
int pressure = 20;
int getPressure(){
int incease = rand()%20;
pressure += incease;
return pressure;
}
/**
* 从锅炉感应器中得到锅炉压力值
*/
jint Java_com_bruce_chang_guolu_MainActivity_getPressure(JNIEnv *env, jobject instance) {
int pressur = getPressure();
return pressur;
}
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
MainActivity.java中调用
public class MainActivity extends AppCompatActivity { { System.loadLibrary("GuoLu"); } PressureView pressureView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); pressureView = new PressureView(MainActivity.this); setContentView(pressureView); new Thread(new Runnable() { @Override public void run() { while (true) { SystemClock.sleep(500); int pressure = Math.abs(getPressure()); pressureView.setPressure(pressure); //如果压力大于220 if (pressure > 220) { break; } } } }).start(); } /** * native代码 * 调用C代码中的对应方法 * * @return */ public native int getPressure(); }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
自定义view—PressureView来控制显示的过程
public class PressureView extends View { private int pressure; private Paint mPaint; public PressureView(Context context) { super(context); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setTextSize(50); } public void setPressure(int pressure) { this.pressure = pressure; // invalidate();//在主线程中运行 postInvalidate();//ondraw()执行 } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //1:如果压力值大于220,就绘制文本,显示锅炉爆炸了,快跑 if (pressure > 220) { mPaint.setColor(Color.RED); canvas.drawText("要爆炸了,快跑炮", 10, getHeight() / 2, mPaint); } else { //2:正常和提示的情况 //设置背景颜色为灰色 mPaint.setColor(Color.GRAY); canvas.drawRect(10, 10, 60, 260, mPaint); canvas.drawText("pressure=="+pressure, 10, getHeight() / 2, mPaint); //2.1 如果是小于200正常显示并且设置画笔颜色绿色 if (pressure < 200) { mPaint.setColor(Color.GREEN); canvas.drawRect(10, 260-pressure, 60, 260, mPaint); } else if (pressure>200){ //2.2 如果是大于200警示显示给看护者,并且设置画笔颜色红色 mPaint.setColor(Color.YELLOW); canvas.drawRect(10, 260-pressure, 60, 260, mPaint); } } } }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37