通过ndk导出aar和so库文件供其他android项目使用
2016-08-01 00:00
591 查看
摘要: 一个通过ndk导出aar和so库文件供其他android项目使用的例子
下面用实例一步一步介绍这个过程。目标如下:
这里建立的一个MainActivity是用来做项目内调试的
其实jni头文件可以自己编写,当时java和c的对应规则比较变态,建议用javah命令生成
在包 com.dqz.www.mysdklibrary中建立类文件MySdk,代码如下,
以上代码并不是最终代码,是用来生成jni头文件用的
转到命令行窗口,执行javah
这时候,你会看见包根目录下多了一个com_dqz_www_mysdklibrary_MySdk.h头文件,
方法名字很长吧,不要怕,反正还有更长的,你也不用修改它。它的代码如下:
其中第11步,只需要建立main.c即可
第12步,是把刚才的com_dqz_www_mysdklibrary_MySdk.h转移到jni目录下面
把我们最开始的c源代码,user.h和user.c 也复制到jni目录下面
编写main.c,做好桥接工作
看吧,其实就是对接口方法做了一次改造重写
编写Android.mk文件
jni部分的工作基本完成了,
注意上面的LOCAL_MODULE := mysdklibrary,这里的定义,最终生成的so文件是 libmysdklibrary.so,在第二部分会提及
注意,System.loadLibrary("mysdklibrary");就是加载ndk的so库的
在import com.dqz.www.mysdklibrary.*时 ,编译器会提示你导入库依赖,修改gradle的配置文件
其中
a、MySdkDemo/gradle.properties自动加入了一句
b、MySdkDemo/app/build.gradle的dependencies节点自动加入一句话 compile project(path: ':mysdklibrary') 如:
c、MySdkDemo/mysdklibrary/build.gradle 加入了ndk的配置,如下图
其中红框部分是手动添加的
第一阶段的测试结果如下:
编译过程有很多warning或者notice,这些问题就问问大家的c和java水平了
编译后,变换android视图到project视图,生在的aar文件和so文件如下图位置
其中和jni平衡的libs,文件里so文件有很多不同的子目录,对应着不同的android设备架构
要为某个架构单独生成so文件,可参考 官网的standalone_toolchain
aar文件和so文件可以提取出来在别的项目中用了
用系统浏览器转到上面的目录MySdkTest/app/libs
把其中一个aar文件(比如mysdklibrary-debug.aar)复制进来,改名字为 mysdklibrary.aar
把全部的so文件(如果你知道设备架构,复制其中一个就可以)
最终目录如下:
修改MySdkTest/app/build.gradle,增加一个repositories节点和修改dependencies,如下
刷新一下项目,运行,结果如下
测试成功!
2、《Java基础知识——JNI入门介绍》
3、https://github.com/googlesamples/android-ndk
4、https://github.com/mcxiaoke/android-ndk-notes
5、Using-android-ndk-android-studio-part-1/2
附1:你可以在main.c中通过下面的代码来查看android的架构
附2: 如果原来c代码中使用了其它系统的so库,可能需要重新编译,或者在ndk的源码中找解决方案
目标
根据项目的需求,我们用纯c实现的模块,需要移植到android设备中,制作java版本的sdk,我们需要用到jni和ndk。下面用实例一步一步介绍这个过程。目标如下:
c源接口 | java目标接口 |
#include <stdlib.h> #include <stdio.h> #include <string.h> #ifndef USER_H #define USER_H /** * 数字加法 */ int my_fun1(int a,int b); /** * 连接字符串 */ char* my_fun2(char *s1, char *s2); #endif // USER_H | /** * 整数加法 * @param a * @param b * @return int */ public static int myFun1(int a, int b) {} /** * 字符串拼接 * @param a * @param b * @return String */ public static String myFun2(String a, String b) {} |
第一阶段:ndk项目内联合调试
1.1、建立一个项目MySdkDemo
这里建立的一个MainActivity是用来做项目内调试的
1.2、在MySdkDemo内建立一个模块MySdkLibrary
这个module就是用来制作aar包和so库的,其中MySdk就是对外的接口的类名1.3、在MySdkLibrary建立一个类MySdk,编写native方法,并生成jni头文件
为什么要写native,为什么要写jni头文件,这里不展开,请参考附后的参考文章。其实jni头文件可以自己编写,当时java和c的对应规则比较变态,建议用javah命令生成
在包 com.dqz.www.mysdklibrary中建立类文件MySdk,代码如下,
package com.dqz.www.mysdklibrary; /** * Created by 等钱中 on 16/7/29. */ public class MySdk { /** * 整数加法 * * @param a * @param b * @return int */ public native int fun1(int a, int b); /** * 字符串拼接 * * @param a * @param b * @return String */ public native String fun2(String a, String b); }
以上代码并不是最终代码,是用来生成jni头文件用的
转到命令行窗口,执行javah
King-4:java dengqianzhong$ cd mysdklibrary/src/main/java King-4:java dengqianzhong$ javah com.dqz.www.mysdklibrary.MySdk
这时候,你会看见包根目录下多了一个com_dqz_www_mysdklibrary_MySdk.h头文件,
方法名字很长吧,不要怕,反正还有更长的,你也不用修改它。它的代码如下:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_dqz_www_mysdklibrary_MySdk */ #ifndef _Included_com_dqz_www_mysdklibrary_MySdk #define _Included_com_dqz_www_mysdklibrary_MySdk #ifdef __cplusplus extern "C" { #endif /* * Class: com_dqz_www_mysdklibrary_MySdk * Method: fun1 * Signature: (II)I */ JNIEXPORT jint JNICALL Java_com_dqz_www_mysdklibrary_MySdk_fun1 (JNIEnv *, jclass, jint, jint); /* * Class: com_dqz_www_mysdklibrary_MySdk * Method: fun2 * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_dqz_www_mysdklibrary_MySdk_fun2 (JNIEnv *, jclass, jstring, jstring); #ifdef __cplusplus } #endif #endif
1.4、建立jni文件夹,建立main.c,实现上述头函数,并编写编译文件Android.mk
其中第11步,只需要建立main.c即可
第12步,是把刚才的com_dqz_www_mysdklibrary_MySdk.h转移到jni目录下面
King-4:java dengqianzhong$ mv com_dqz_www_mysdklibrary_MySdk.h ../jni
把我们最开始的c源代码,user.h和user.c 也复制到jni目录下面
user.h | user.c |
#include <stdlib.h> #include <stdio.h> #include <string.h> #ifndef USER_H #define USER_H /** * 数字加法 */ int my_fun1(int a,int b); /** * 链接字符串 */ char* my_fun2(char *s1, char *s2); #endif // USER_H | #include <user.h> /** * 数字加法 */ int my_fun1(int a, int b) { return a + b; } /** * 链接字符串 */ char *my_fun2(char *s1, char *s2) { char *result = malloc(strlen(s1) + strlen(s2) + 1); //+1 for the zero-terminator //in real code you would check for errors in malloc here strcpy(result, s1); strcat(result, s2); return result; } |
// // Created by 等钱中 on 16/7/29. // #include <stdlib.h> #include "user.h" #include "com_dqz_www_mysdklibrary_MySdk.h" /* * Class: com_dqz_www_mysdklibrary_MySdk * Method: fun1 * Signature: (II)I */ JNIEXPORT jint JNICALL Java_com_dqz_www_mysdklibrary_MySdk_fun1(JNIEnv *env, jobject thiz, jint m, jint n) { int im = (unsigned short) m; int in = (unsigned short) m; return my_fun1(im, in); } /* * Class: com_dqz_www_mysdklibrary_MySdk * Method: fun2 * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_dqz_www_mysdklibrary_MySdk_fun2(JNIEnv *env, jobject thiz, jstring a, jstring b) { const char *ca = (*env)->GetStringUTFChars(env, a, 0); const char *cb = (*env)->GetStringUTFChars(env, b, 0); char *result = my_fun2(ca, cb); jstring ret = (*env)->NewStringUTF(env, result); (*env)->ReleaseStringUTFChars(env, a, ca); (*env)->ReleaseStringUTFChars(env, b, cb); return ret; }
看吧,其实就是对接口方法做了一次改造重写
编写Android.mk文件
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := mysdklibrary LOCAL_SRC_FILES := user.c LOCAL_SRC_FILES += main.c include $(BUILD_SHARED_LIBRARY)
jni部分的工作基本完成了,
注意上面的LOCAL_MODULE := mysdklibrary,这里的定义,最终生成的so文件是 libmysdklibrary.so,在第二部分会提及
1.5 完善类文件MySdk.java
上面的代码只是实现了native方法到c代码的嫁接,现在MySdk.java需要完善,超到最开始的目标前进package com.dqz.www.mysdklibrary; /** * Created by 等钱中 on 16/7/29. */ public class MySdk { public static native int fun1(int a, int b); public static native String fun2(String a, String b); static { System.loadLibrary("mysdklibrary"); } /** * 整数加法 * * @param a * @param b * @return int */ public static int myFun1(int a, int b) { return fun1(a, b); } /** * 字符串拼接 * * @param a * @param b * @return String */ public static String myFun2(String a, String b) { return fun2(a, b); } }
注意,System.loadLibrary("mysdklibrary");就是加载ndk的so库的
1.6、通过1.1建立的MainActivity来测试一下这个mysdklibrary
MainActivity.java | activity_main.xml |
package com.dqz.www.mysdkdemo; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; import com.dqz.www.mysdklibrary.*; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView tx1 = (TextView) findViewById(R.id.tx_debug1); TextView tx2 = (TextView) findViewById(R.id.tx_debug2); int ret1 = MySdk.fun1(77, 88); tx1.setText(ret1 + ""); String ret2 = MySdk.fun2("hello", "sophiaL"); tx1.setText(ret2); } } | <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.dqz.www.mysdkdemo.MainActivity"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="测试加法:" /> <TextView android:id="@+id/tx_debug1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="r1" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="测试字符拼接:" /> <TextView android:id="@+id/tx_debug2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="r2" /> </LinearLayout> |
其中
a、MySdkDemo/gradle.properties自动加入了一句
android.useDeprecatedNdk=true
b、MySdkDemo/app/build.gradle的dependencies节点自动加入一句话 compile project(path: ':mysdklibrary') 如:
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:24.1.1' compile project(path: ':mysdklibrary') }
c、MySdkDemo/mysdklibrary/build.gradle 加入了ndk的配置,如下图
其中红框部分是手动添加的
第一阶段的测试结果如下:
第二阶段:导出aar和so后,在其它项目使用
2.1、在jni目录下执行ndk-build
King-4:java dengqianzhong$ cd ../jni/ King-4:jni dengqianzhong$ ls Android.mk main.c user.h com_dqz_www_mysdklibrary_MySdk.h user.c King-4:jni dengqianzhong$ ndk-build [arm64-v8a] Compile : mysdklibrary <= user.c [arm64-v8a] Compile : mysdklibrary <= main.c ......
编译过程有很多warning或者notice,这些问题就问问大家的c和java水平了
编译后,变换android视图到project视图,生在的aar文件和so文件如下图位置
其中和jni平衡的libs,文件里so文件有很多不同的子目录,对应着不同的android设备架构
要为某个架构单独生成so文件,可参考 官网的standalone_toolchain
aar文件和so文件可以提取出来在别的项目中用了
2.2、在项目中导入aar和so库(手动)
按照2.1章节的办法,建立项目MySdkTest,其中MainActivity.java文件和activity_main.xml 文件的代码参考上述1.6章节用系统浏览器转到上面的目录MySdkTest/app/libs
把其中一个aar文件(比如mysdklibrary-debug.aar)复制进来,改名字为 mysdklibrary.aar
把全部的so文件(如果你知道设备架构,复制其中一个就可以)
最终目录如下:
修改MySdkTest/app/build.gradle,增加一个repositories节点和修改dependencies,如下
repositories { flatDir { dirs 'libs' } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:24.1.1' compile(name: 'mysdklibrary', ext: 'aar') }
刷新一下项目,运行,结果如下
测试成功!
参考资料和文章:
1、《Oracle官方Jni 6.0 文档》2、《Java基础知识——JNI入门介绍》
3、https://github.com/googlesamples/android-ndk
4、https://github.com/mcxiaoke/android-ndk-notes
5、Using-android-ndk-android-studio-part-1/2
附1:你可以在main.c中通过下面的代码来查看android的架构
#if defined(__arm__) #if defined(__ARM_ARCH_7A__) #if defined(__ARM_NEON__) #if defined(__ARM_PCS_VFP) #define ABI "armeabi-v7a/NEON (hard-float)" #else #define ABI "armeabi-v7a/NEON" #endif #else #if defined(__ARM_PCS_VFP) #define ABI "armeabi-v7a (hard-float)" #else #define ABI "armeabi-v7a" #endif #endif #else #define ABI "armeabi" #endif #elif defined(__i386__) #define ABI "x86" #elif defined(__x86_64__) #define ABI "x86_64" #elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */ #define ABI "mips64" #elif defined(__mips__) #define ABI "mips" #elif defined(__aarch64__) #define ABI "arm64-v8a" #else #define ABI "unknown" #endif
附2: 如果原来c代码中使用了其它系统的so库,可能需要重新编译,或者在ndk的源码中找解决方案
相关文章推荐
- Android项目生成aar包或者共享库文件供其他项目使用以及问题解决
- Android项目中使用Eclipse导出jar文件
- android把activity和资源文件打包成jar包给其他项目使用
- Eclipse android项目中配置NDK自动编译生成so文件
- Android Studio 导入eclipse带NDK的项目,使用Android.mk文件
- eclipse中的项目导出成Androidstudio的识别的项目,so文件打包不进去
- Android studio JNI 制作SO文件,在其他项目中调用
- [置顶] android项目生成aar包和在其他项目中的使用方法详解
- Android导出aar插件供Unity使用以及通过android scheme启动unityApp
- android项目中配置NDK自动编译生成so文件
- Android项目中使用Eclipse导出jar文件
- 使用ec编译cocos项目是出现Android.mk文件报错Are you sure your NDK_MODULE_PATH variable is properly defined 的解决办法
- (Android)如何将一个高复用性项目供其他项目使用(jar导出,导入,Is Library)(转)
- android NDK JNI so文件的制作和使用
- android NDK JNI so文件的制作和使用
- Android项目中使用Eclipse导出jar文件
- Android中NDK的so文件产生和使用
- Android开发中如何将自己编译的.so文件用到其他的项目中
- Android .so .aar..jar文件的使用方式
- Android项目中使用Eclipse导出jar文件