Android NDK开发——打通任督二脉

一个始终不被善待的人,最能识得善良,也最能珍视善良 —— 《芳华》

这里不会从头开始讲如何搭建环境之类的,因为Google早有详细文档说明供你起步了向您的项目添加 C 和 C++ 代码;但是可能又有人说了Google无法在国内访问呢,我只想说朋友方法总比困难多,购买VPN修改Host都可以并且网上也有很多环境搭建的教程;有兴趣自己搭建的可以移步这里vultr

为什么使用NDK

对于常年奋斗在应用层的同学来说,可能会很少接触NDK,最多也是使用别人提供的so直接通过JNI调用(说白了其实还是Java的接口);但是Android博大精深远远非应用层能概括的,相信大家都知道Android体系结构;有大量的开源的库都是非Java写的为了使用这些已有的C库我们只能用JNI的方式,或者如果我们想从事驱动开发,又或者我们自己辛苦开发的代码,不被别人轻易的破解窃取等等(当然我本人是很推崇open source,但是商业上难从之);其本质原因都是为了让自己的水平更上一层楼 ^_^;

此处输入图片的描述

Hello Jni

接下来我们来从Jni中获取字符串,当然自带的项目中已经添加了Hello Jni的方法。但是我测试的方式却有些不同

玩转指针

Jni接口定义如下

public class JniTest {

  public static native long getStringFromJni();

  public static native String getStringFromJni(long p);
}

Jni实现如下

#include <jni.h>

extern "C" {

JNIEXPORT jlong JNICALL
Java_com_xiaozhi_jni_JniTest_getStringFromJni__(JNIEnv *env, jobject instance) {

    char *p = "Hello from Jni!";

    return (jlong) p;
}

JNIEXPORT jstring JNICALL
Java_com_xiaozhi_jni_JniTest_getStringFromJni__J(JNIEnv *env, jobject instance, jlong in) {

    char *p = (char *) in;

    return env->NewStringUTF(p);
}

}

MainActivity中调用如下

public class MainActivity extends AppCompatActivity {

  // Used to load the 'native-lib' library on application startup.
  static {
    System.loadLibrary("native-lib");
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // Example of a call to a native method
    TextView tv = (TextView) findViewById(R.id.sample_text);

    long p = JniTest.getStringFromJni();

    tv.setText(JniTest.getStringFromJni(p));
  }
}

看到上面的测试方法我们并没有直接返回字符,而是在c层面将字符的指针地址传出再进行传入获取字符串,是不是很有意思^_^(此种方式多用于c中申请的内存空间的句柄交由java层保留处理)

C反射调用Java

既然题目是说打通任督二脉,那我们就不可能只说Java调用C了,来来来兄弟干了这碗热翔咱们继续聊。

Java方法签名

在用c反射java时我们一定要先了解方法签名这个知识点,方法签名用来干嘛呢?主要用来标识方法中的返回值以及参数的说明,举例如下:
public void test1(){}
签名:()V
public void test2(String str)
签名:(Ljava/lang/String;)V
public byte[] encryB(byte[] src, int srcLen, byte[] dst,int dstLen, long env)
签名:([BI[BIJ)[B

()中表示方法的参数说明,()外表示返回值的类型说明

方法签名JNIjava
Vvoidvoid
Zjbooleanboolean
Ijintint
Jjlonglong
Djdoubledouble
Fjfloatfloat
Bjbytebyte
Cjcharchar
Sjshortshort
[ZjbooleanArrayboolean[]
[IjintArrayint[]
[JjlongArraylong[]
[DjdoubleArraydouble[]
[FjfloatArrayfloat[]
[BjbyteArraybyte[]
[CjcharArraychar[]
[SjshortArrayshort[]
L全类名jobjectclass

[ 数组 以[开头,配合其他的特殊字符,表示对应数据类型的数组,几个[表示几维数组
L全类名; 引用类型 以L开头、;结尾,中间是引用类型的全类名

Jni中获取App包名

调用Java中对象的方法总共分几步?
1.找到方法所在的类
2.找到方法ID
3.通过实例对象调用ID的方法

jni接口定义

public static native String getPackagenameFromJni(Context context);

jni实现

JNIEXPORT jstring JNICALL
Java_com_xiaozhi_jni_JniTest_getPackagenameFromJni(JNIEnv *env, jclass type, jobject context) {

    // 1.Get Context Class descriptor
    jclass contextClass = env->FindClass("android/content/Context");
    // 2.Get methodId from Context class
    jmethodID getPackageNameMethodId = env->GetMethodID(contextClass, "getPackageName",
                                                        "()Ljava/lang/String;");

    // 3.Call the method using context
    jstring stringObject = (jstring) env->CallObjectMethod(context, getPackageNameMethodId);
    // TODO

    const char *packageName = env->GetStringUTFChars(stringObject, 0);

    int isOk = strcmp(packageName, "com.xiaozhi.jni");

    LOGI("包名 isOK:%d", isOk);

    return stringObject;
}

android调用

public class MainActivity extends AppCompatActivity {

  // Used to load the 'native-lib' library on application startup.
  static {
    System.loadLibrary("native-lib");
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // Example of a call to a native method
    TextView tv = (TextView) findViewById(R.id.sample_text);

    long p = JniTest.getStringFromJni();

    tv.setText(JniTest.getStringFromJni(p) + "\n");

    // 获取包名
    tv.append(JniTest.getPackagenameFromJni(this) + "\n");
  }
}

JNI_OnLoad注册本地方法

与传统的方式生成jni头文件的形式不同,本地方法注册有以下优点

  • 效率更高(Android官方建议的方式)
  • 避免了的繁琐的命名方式,更加灵活

具体注册请看以下方式

/**
 * 声明映射本地方法和java层方法的映射关系
 */
static JNINativeMethod methods[]{
        {"getStringFromJni",      "()J",                                           (void *) getStringFromJni__},
        {"getStringFromJni",      "(J)Ljava/lang/String;",                         (void *) getStringFromJni__J},
        {"getPackagenameFromJni", "(Landroid/content/Context;)Ljava/lang/String;", (void *) getPackagenameFromJni}
};

/**
 * 注册本地方法
 *
 * @param env
 * @param class_name
 * @param methods
 * @param num_methods
 * @return
 */
jint registerNativeMethods(JNIEnv *env, const char *class_name, JNINativeMethod *methods,
                           int num_methods) {
    jclass clazz = env->FindClass(class_name);
    int result = env->RegisterNatives(clazz, methods, num_methods);
    return result;
}

/**
 * 与对应java类关联
 *
 * @param env
 * @return
 */
int registerNative(JNIEnv *env) {
    const char *className = "com/xiaozhi/jni1/JniTest";
    return registerNativeMethods(env, className, methods,
                                 ((int) sizeof(methods) / sizeof(methods[0])));
}

//当动态库被加载时这个函数被系统调用(调用System.loadLibrary时回掉)
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    // register native methods
    if (registerNative(env)) {
        return JNI_ERR;
    }
    // return jni version
    return JNI_VERSION_1_6;
}

//当动态库被卸载时这个函数被系统调用
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
}

以上便是最近使用NDK的一些小记,不算太有深度的东西,但是也算在集成别的C库是必须要懂的姿势 :);后续会写一些Android下具体使用c库的例子,如libyuv,ffmpeg等;

项目源码:NDKTest

Google官方NDK示例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值