Android中JNI(二)------java调用C和C回调java

一、交叉编译
* 在一个平台上去编译另一个平台上可以执行的本地代码
* cpu平台 arm x86 mips
* 操作系统平台  windows linux mac os
* 原理 模拟不同平台的特性去编译代码 
##jni开发工具
* ndk native develop kit 
* ndk目录
   * docs 帮助文档
* platforms  好多平台版本文件夹 选择时选择项目支持的最小版本号对应的文件夹
* 每一个版本号的文件夹中放了 不同cpu架构的资源文件
* include文件夹 jni开发中常用的 .h头文件
* lib 文件夹 google打包好的 提供给开发者使用的 .so文件
* samples google官方提供的样例工程 可以参考进行开发
* android-ndk-r9d\build\tools linux系统下的批处理文件 在交叉编译时会自动调用
* ndk-build 交叉编译的命令
* cdt eclipse的插件 高亮C代码 C的代码提示

二、Java调用C:jnihelloworld
* jni开发的步骤
 * ①写java代码 声明本地方法 用到native关键字 本地方法不用去实现
 * ②项目根目录下创建jni文件夹
 * ③在jni文件夹下创建.c文件
  * 本地函数命名规则: Java_包名_类名_本地方法名
  * JNIENV* env JNIEnv 是JniNativeInterface这个结构体的一级指针 
  * JniNativeInterface这个结构体定义了大量的函数指针
  * env 就是结构体JniNativeInterface这个结构体的二级指针
  * (*env)->调用结构体中的函数指针
  * 第二个参数jobject 调用本地函数的java对象就是这个jobject
 * ④ 导入<jni.h>
 * ⑤ 创建Android.mk makefile 告诉编译器.c的源文件在什么地方,要生成的编译对象的名字是什么
LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE    := hello   #指定了生成的动态链接库的名字
    LOCAL_SRC_FILES := hello.c #指定了C的源文件叫什么名字
    include $(BUILD_SHARED_LIBRARY)
 * ⑥ 调用ndk-build编译c代码生成动态链接库.so文件 文件的位置 lib->armeabi->.so
 * ⑦ 在java代码中加载动态链接库 System.loadlibrary("动态链接库的名字"); Android.mkLOCAL_MODULE所指定的名字
##jni开发中的常见错误
* java.lang.UnsatisfiedLinkError: Native method not found: 本地方法没有找到
* 本地函数名写错
* 忘记加载.so文件 没有调用System.loadlibrary 
* findLibrary returned null
* System.loadLibrary("libhello"); 加载动态链接库时 动态链接库名字写错
* 平台类型错误 把只支持arm平台的.so文件部署到了 x86cpu的设备上 
* 在jni目录下创建 Application.mk 在里面指定 
* APP_ABI := armeabi
 APP_PLATFORM := android-14
* javah 
* jdk 1.7 项目 src目录下运行javah
* jdk 1.6 项目 bin目录下 classes文件夹
* javah native方法声明的java类的全类名 

## jni简便开发流程
* ① 写java代码 native 声明本地方法
* ② 添加本地支持 右键单击项目->andorid tools->add native surport
* 如果发现 finish不能点击需要给工作空间配置ndk目录的位置
* window->preferences->左侧选择android->ndk 把ndk解压的目录指定进来
* ③ 如果写的是.c的文件 先修改一下生成的.cpp文件的扩展名 不要忘了 相应修改Android.mk文件中LOCAL_SRC_FILES的值
* ④ javah生成头文件 在生成的头文件中拷贝c的函数名到.c的文件
* ⑤ 解决CDT插件报错的问题
* 右键单击项目选择 properties 选测 c/c++ general->paths and symbols->include选项卡下->点击add..->file system 选择ndk目录下 platforms文件夹 对应平台下(项目支持的最小版本)
 usr 目录下 arch-arm -> include  确定后 会解决代码提示和报错的问题
* ⑥编写C函数 如果需要单独编译一下c代码就在c/c++视图中找到小锤子 
*  如果想直接运行到模拟器上 就不用锤子了
* ⑦ java代码中不要忘了 system.loadlibrary();  

## C代码中向logcat输出内容
Android.mk文件增加以下内容
LOCAL_LDLIBS += -llog
C代码中增加以下内容
#include <android/log.h>
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
* define C的宏定义 起别名  #define LOG_TAG "System.out" 给"System.out"起别名LOG_TAG 
* #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
* 给 __android_log_print函数起别名  写死了前两个参数 第一个参数 优先级 第二个参数TAG
* __VA_ARGS__ 可变参数的固定写法
* LOGI(...)在调用的时候 用法跟printf()一样

三、C代码回调java方法
* ① 找到字节码对象 
*  //jclass      (*FindClass)(JNIEnv*, const char*); 
*  //第二个参数 要回调的java方法所在的类的路径 "com/itheima/callbackjava/JNI"
* ② 通过字节码对象找到方法对象
* //jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
* 第二个参数 字节码对象 第三个参数 要反射调用的java方法名 第四个参数 要反射调用的java方法签名
* javap -s 要获取方法签名的类的全类名 项目/bin/classes 运行javap -s
* ③ 通过字节码创建 java对象(可选) 如果本地方法和要回调的java方法在同一个类里可以直接用 jni传过来的java对象 调用创建的Method
* jobject obj =(*env)->AllocObject(env,claz);
* 当回调的方法跟本地方法不在一个类里 需要通过刚创建的字节码对象手动创建一个java对象
* 再通过这个对象来回调java的方法
* 需要注意的是 如果创建的是一个activity对象 回调的方法还包含上下文 这个方法行不通!!!回报空指针异常 
* ④ 反射调用java方法
* //void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);

* 第二个参数 调用java方法的对象 第三个参数 要调用的jmethodID对象 可选的参数 调用方法时接收的参数 






## c++ 开发JNI
* C的预处理命令
* #开头的就是c/c++的预处理命令
* 在编译之前 先会走预编译阶段 预编译阶段的作用就是 把 include进来的头文件 copy到源文件中
* define这些宏定义 用真实的值替换一下
* #if #else #endif 该删除的删除掉

* c++开发jni代码时 env不再是结构体Jninativeinterface的二级指针
* _JNIEnv JNIEnv  _JNIEnv 是C++的结构体 C++结构体跟C区别 C++的结构体可以定义函数
* env 是JNIEnv的一级指针 也就是结构体_JNIEnv的一级指针 env-> 来调用 结构体里的函数
* _JNIEnv的函数 实际上调用的就是结构体JNINativeInterface的同名函数指针
* 在调用时第一个参数 env已经传进去了


* C++的函数要先声明再使用 可以把javah生成的头文件include进来作为函数的声明
* include的方法 <> "" ""
* 如果用"" 来导入头文件 系统会先到 源代码所在的文件夹去找头文件 如果找不到再到系统指定的incude文件夹下找
* //用<> 直接到系统指定的include目录下去找


## am 命令
* am命令 :在adb shell里可以通过am命令进行一些操作 如启动activity Service 启动浏览器等等
* am命令的源码在Am.java中, 在adb shell里执行am命令实际上就是启动一个线程执Am.java的main方法,am命令后面带的参数都会当作运行时的参数传递到main函数中
* am命令可以用start子命令,并且带指定的参数
* 常见参数: -a: action  -d data   -t 表示传入的类型 -n 指定的组件名字
* 举例: 在adb shell中通过am命令打开网页
* am start --user 0 -a android.intent.action.VIEW -d http://www.baidu.com
* 通过am命令打开activity
* am start --user 0 -n   com.itheima.fork/com.itheima.fork.MainActivity
* (系统sdk版本>16 需要加上--user 0 , <16不需要加)


* execlp c语言中执行系统命令的函数 
* execlp() 会从PATH环境变量所指的目录中查找符合参数file的文件找到后就执行该文件, 第二个参数开始就是执行这个文件的 args[0],args[1] 最后一个参数用(char*)NULL结束
* android开发中 execlp函数对应android的path路径为system/bin/目录
* 调用格式

execlp("am", "am", "start", "--user","0","-a", "android.intent.action.VIEW", "-d", "http://www.baidu.com", (char *) NULL);
execlp("am", "am", "start", "--user","0", "-n" , "com.itheima.cforktest/com.itheima.cforktest.MainActivity",(char *) NULL);
C中回调Java中方法的案例:

package com.itheima.callbackjava;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends Activity {
	JNI jni;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		jni = new JNI(getApplicationContext());
	}


	public void callbackvoid(View v){
		jni.callbackvoidmethod();
	}
	
	public void callbackint(View v){
		jni.callbackintmethod();
	}
	
	public void callbackString(View v){
		jni.callbackStringmethod();
	}
	
	public void callbackshowtoast(View v){
		jni.callbackShowToast();
	}
	//public native void callbackShowToast();
	
}

package com.itheima.callbackjava;
import android.content.Context;
import android.widget.Toast;
public class JNI {
	static{
		System.loadLibrary("callback");
	}
	private Context mContext;
	public JNI(Context context){
		mContext = context;
	}
	public native void callbackvoidmethod();
	
	public native void callbackintmethod();
	
	public native void callbackStringmethod();
	
	public native void callbackShowToast();
	//C调用java空方法
	public void helloFromJava(){
		System.out.println("hello from java");
	}
	//C调用java中的带两个int参数的方法
	public int add(int x,int y) {
		return x+y;
	}
	//C调用java中参数为string的方法
	public void printString(String s){
		System.out.println(s);
	}
	public void showToast(String s){
		Toast.makeText(mContext, s, 0).show();
	}
}

#include <jni.h>
#include <stdlib.h>
#include <android/log.h>
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
/**
 * 把一个jstring转换成一个c语言的char* 类型.
 */
char* _JString2CStr(JNIEnv* env, jstring jstr) {
	 char* rtn = NULL;
	 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;
}
JNIEXPORT void JNICALL Java_com_itheima_callbackjava_JNI_callbackvoidmethod
  (JNIEnv * env, jobject clazz){
	//jclass      (*FindClass)(JNIEnv*, const char*);
	//① 获取字节码对象
	jclass claz = (*env)->FindClass(env,"com/itheima/callbackjava/JNI");
	//②获取Method对象
	//jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
	jmethodID methodID =(*env)->GetMethodID(env,claz,"helloFromJava","()V");
	//③通过字节码对象创建一个Object
	//④通过对象调用方法
	//void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
	(*env)->CallVoidMethod(env,clazz,methodID);


}


JNIEXPORT void JNICALL Java_com_itheima_callbackjava_JNI_callbackintmethod
  (JNIEnv * env, jobject clazz){
	//① 获取字节码对象
	jclass claz =(*env)->FindClass(env,"com/itheima/callbackjava/JNI");
	//②获取Method对象
	jmethodID methodID = (*env)->GetMethodID(env,claz,"add","(II)I");
	//jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
	int result =(*env)->CallIntMethod(env,clazz,methodID,3,4);
	LOGD("result = %d",result);
}


JNIEXPORT void JNICALL Java_com_itheima_callbackjava_JNI_callbackStringmethod
  (JNIEnv * env, jobject clazz){
	//① 获取字节码对象
		jclass claz =(*env)->FindClass(env,"com/itheima/callbackjava/JNI");
	//② 获取Method对象
		jmethodID methodid =(*env)->GetMethodID(env,claz,"printString","(Ljava/lang/String;)V");
	//
		jstring result =(*env)->NewStringUTF(env,"hello from c");
		(*env)->CallVoidMethod(env,clazz,methodid,result);
}
JNIEXPORT void JNICALL Java_com_itheima_callbackjava_JNI_callbackShowToast
  (JNIEnv * env, jobject clazz){
	jclass claz =(*env)->FindClass(env,"com/itheima/callbackjava/JNI");
	jmethodID methodid =(*env)->GetMethodID(env,claz,"showToast","(Ljava/lang/String;)V");
	//jobject     (*AllocObject)(JNIEnv*, jclass);
	//通过字节码对象创建 java对象 在这儿就是创建了mainactivity的对象
	//jobject obj =(*env)->AllocObject(env,claz);
	jstring result =(*env)->NewStringUTF(env,"hello from c");
	//void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
	(*env)->CallVoidMethod(env,clazz,methodid,result);
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值