前言
Android JNI开发信任多数Android开发者都有所了解,可是网上许多教程分为两种,一种是告知你怎么装备NDK环境变量,建个helloWorld的Demo,另一种便是太过于高端,C言语一大片,云里雾里,尽管许多公司开发都会有单独的人员来写C,可是从Android开发人员角度来说,学习C仍是很有必要的,一切源码终归C.
能够学到什么
-
Java言语怎么调用C代码,以C言语验证用户名和暗码为例
-
C言语怎么调用Java代码,以C言语调用Java办法为例
-
怎么运用C言语,实现简略有用的功用,以APP卸载反应为例
好了,假如你对以上内容感兴趣,那就接着往下来,我要阐明的是这篇文章不会告知你怎么装备NDK环境,假如你解决不了,怎么办?重视我,重视我~
实例演示
首要咱们要明白的是,为什么有些项目中要运用C,原因很简略,哪怕是一个核算,C的功率也要高于Java,Java做的C能够做,Java不能够做的C也能够做,所以有些杂乱的处理操作或者是底层相关的逻辑都能够交给C去做,像美图秀秀,播放器等软件都用了大量的C代码处理业务。
Java调用C代码
以验证用户名暗码为例,验证用户名暗码咱们肯定要将用户名和暗码传给C,咱们新建一个JNI类,在类中新建一个回来整形的办法,如下所示。
publicnativeintcheckUser(String name,String pass);
记住运用关键字native,这个时分咱们就要在C中编写相应的办法,像什么,javah生成头文件什么的那种我在前言中说了,就不讲解了,在studio东西中生成,鼠标点击到办法,Alt + Enter快捷方式主动生成如下办法:
#include<jni.h>
JNIEXPORT jint JNICALL
Java_jnidemo_hlq_com_jnidemo_JNI_checkUser(JNIEnv*env,jobject instance,jstring name_,
jstring pass_){
constchar*name=(env)->GetStringUTFChars(name_,0);
constchar*pass=(env)->GetStringUTFChars(pass_,0);
//TODO
(env)->ReleaseStringUTFChars(name_,name);
(env)->ReleaseStringUTFChars(pass_,pass);
}
在这儿要注意一下,这儿咱们建的是.cpp文件,至于.c 和 .cpp 便是一个是c一个是c++,c++中代码是:
const char*name=(env)->GetStringUTFChars(name_,0);
c中对应的便是:
const char*name=(*env)->GetStringUTFChars(env,name_,0);
接下来咱们要在cmake中进行装备
add_library(#Sets the name of the library.
checkuser
SHARED
src/main/cpp/cheruser.cpp
)
checkuser 便是装备生成的so名称为libcheckuser.so,SHARED装备库文件是同享, src/main/cpp/cheruser.cpp便是对应的途径了
target_link_libraries(#Specifies the target library.
checkuser
#Links the target library to the log library
#included in the NDK.
${log-lib})
checkuser保持和上面名字对应就能够了。这样咱们就能够在JNI类中,加载这个库
static{
System.loadLibrary("checkuser");
}
在C代码中咱们已经得到了name和pass
const char*name=(env)->GetStringUTFChars(name_,0);
const char*pass=(env)->GetStringUTFChars(pass_,0);
直接和用户名暗码比较即可,这儿在代码中将变量名界说为name 暗码为123
constchar*tureName="name";
constchar*turePass="123";
运用strcmp函数来比较,两个字符串相等则回来0,记住引证string.h头文件
#include<string.h>
if(strcmp(name,tureName)==0&&strcasecmp(pass,turePass)==0){
return1;
}else{
return0;
}
咱们在Activity中输入用户名暗码,调用C办法,若回来1则阐明登陆成功,若回来0则阐明用户名暗码不正确,登陆失败
if(new JNI().checkUser("name","123")==1){
Toast.makeText(MainActivity.this,"登陆成功",Toast.LENGTH_LONG).show();
}else{
Toast.makeText(MainActivity.this,"登陆shibai",Toast.LENGTH_LONG).show();
}
c言语调用Java办法
首要咱们在JNI类中新建一个sum办法,回来两数之和
publicintsum(inti,intj){
Log.d("---","我是java我被c调用了"+(i+j));
returni+j;
}
C调用Java肯定要Java调用C的某个办法,在这个办法中调用java办法,所以咱们再来新建一个testHello办法
publicnativeStringtestHello();
默认生成的C办法为:
JNIEXPORT jstring JNICALL
Java_jnidemo_hlq_com_jnidemo_JNI_testHello(JNIEnv*env,jobject){
return(env)->NewStringUTF("huanglinqing");
}
咱们要调用的java办法在JNI类中,想想java能够经过反射来调用另一个类的办法,那么C其实也是经过反射的,首要咱们界说要调用办法的途径,JNI类全途径为jnidemo.hlq.com.jnidemo.JNI,在C中将.替换为:
constchar*className="jnidemo/hlq/com/jnidemo/JNI";
办法名为sum:
constchar*sum="sum";
经过findClass获取class对象,然后经过AllocObject获取类的实例:
jclassjclass1=env->FindClass(className);
jobjectjobject1=env->AllocObject(jclass1);
然后咱们获取到要调用办法的methodId:
jmethodIDjmethodID1=env->GetMethodID(jclass1,sum,"(II)I");
第一个参数是class对象,第二个参数是函数名,第三个参数是办法签名
复制项目app\build\intermediates\classes\debug文件途径,翻开cmd,进入途径,(假如之前没有编译过项目记住先编译一下,这样才能获取class文件),运用指令 javap -s jnidemo.hlq.com.jnidemo.JNI jnidemo.hlq.com.jnidemo.JNI是调用办法的全途径。
运转能够看到sum办法的签名是(II)I
获取到办法的jmethodID1之后调用CallIntMethod即可调用办法
jintvalue=env->CallIntMethod(jobject1,jmethodID1,1,2);
第一个参数是类的实例,第二个参数是获取的jmethodID1,后面便是sum函数顺次对应的参数。代码全体为:
constchar*className="jnidemo/hlq/com/jnidemo/JNI";
constchar*sum="sum";
jclass jclass1=env->FindClass(className);
jmethodID jmethodID1=env->GetMethodID(jclass1,sum,"(II)I");
jobject jobject1=env->AllocObject(jclass1);
jintvalue=env->CallIntMethod(jobject1,jmethodID1,1,2);
printf("c运转成果为%d",value);
咱们在activity中调用:
newJNI().testHello();
成果如下图所示:
上述即为C言语调用了java的办法
检测APP的卸载
信任许多同伴在面试的时分,总会被问到APP保活的问题,假如你回答不上来,面试官还会一脸轻视的看着你,APP怎么保活?
websocket心跳?三方推送?JNI fork进程?其实我觉得都是扯淡,体系版别越高Google约束的越严厉,咱们自己做的APP除非是大厂,有白名单,否则不或许做到保活,而这个问题其实问的也没有多大的含义。我曾经试过fork保活,杀死也是秒死。
检测APP卸载便是,当APP被用户卸载之后,主动翻开浏览器网页跳转到一个调查问卷让用户去填写为什么会卸载,这个功用PC端软件常常能够看到,APP用的不多,可是也是挺有意思的,可是和保活相同这个功用很鸡肋,版别略微高一点,就完全死了,可是咱们了解一下仍是很有必要的。
首要,咱们界说一个办法,传递当时运用包名和当时体系版别:
publicnativevoiduninstall(String packageName,intversionCode);
在c中运用:
#include"unistd.h"
intcode=fork();
当fork的值>=0的时分 阐明fork子进程和父进程成功,能够去做判别,当然一般都是子进程成功才去判别,app安装之后默认目录都是/data/data/包名。所以咱们做一个1秒守时循环去fopen这个文件夹,当文件夹不存在的时分阐明APP被卸载了,
if(code>=0){
intflag=1;
while(flag){
sleep(1);
FILE*file;
try{
try{
file=fopen("/data/data/jnidemo.hlq.com.jnidemo","rt");
}catch(_JNIEnv env){
LOGD("---%s","i一场了");
}
if(file==NULL){
flag=0;
if(versionCode<17){
execlp("am","am","start","-a","android.intent.action.VIEW","-d",
"http://baidu.com",NULL);
}else{
execlp("am","am","start","--user","0","-a",
"android.intent.action.VIEW",
"-d","http://baidu.com",(char*)NULL);
}
}else{
fclose(file);
LOGD("---%s","我还在");
}
这儿咱们看到LOGD便是咱们界说的log 这样能够将c代码中的日志输出到控制台,界说如下:
#defineLOGD(...)__android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
当file为null的时分咱们运用execlp指令 去操作android的一个目的android.intent.action.VIEW,翻开百度的网址。
当然,我自己在测试的时分,能够完美运转的只有一个4.0的3G手机,其他高版别手机也是无济于事。
写在最终
好了,JNI便是这样了,别的偷偷告知你,假如你想做个美图秀秀的软件,直接下载一个美图秀秀,解压,获取里面的so文件,和JNI办法类就能够了,你或许会说都混杂了去哪里找,你或许忘了,JNI办法是不能混杂的。