文档章节

通过ndk导出aar和so库文件供其他android项目使用

乾中
 乾中
发布于 2016/08/01 18:24
字数 1902
阅读 793
收藏 0
点赞 0
评论 0

目标

根据项目的需求,我们用纯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;
}

 

编写main.c,做好桥接工作

//
// 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>

 

在import com.dqz.www.mysdklibrary.*时  ,编译器会提示你导入库依赖,修改gradle的配置文件

其中

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的源码中找解决方案

© 著作权归作者所有

共有 人打赏支持
乾中
粉丝 2
博文 4
码字总数 3532
作品 0
佛山
技术主管
从Android到React Native开发(四、打包流程解析和发布为Maven库)

1、从Android到React Native开发(一、入门) 2、从Android到React Native开发(二、通信与模块实现) 3、从Android到React Native开发(三、自定义原生控件支持)  作为失踪人口,本篇是对...

恋猫月亮 ⋅ 06/13 ⋅ 0

Android 模块化编程之引用本地的aar

转: http://www.stormzhang.com/android/2015/03/01/android-reference-local-aar/ 随着项目越来越多,代码的复用就变得异常重要,这时候就要进行模块化编程,就是把一些通用的组件或者类库...

wei-spring ⋅ 2015/03/23 ⋅ 0

Android JNI学习(二)——实战JNI之“hello world”

本系列文章如下: Android JNI(一)——NDK与JNI基础 Android JNI学习(二)——实战JNI之“hello world” Android JNI学习(三)——Java与Native相互调用 Android JNI学习(四)——JNI的常用方法...

隔壁老李头 ⋅ 05/09 ⋅ 0

Android模块化编程之引用本地的aar

Android模块化编程之引用本地的aar Just Normal2015-10-29271 阅读 IntelliJ代码IDEA开发工具StudioAndro 随着项目越来越多,代码的复用就变得异常重要,这时候就要进行模块化编程,就是把一...

Just Normal ⋅ 2015/10/29 ⋅ 0

Android JNI开发之Friso分词功能

分词 NDK是Google开发的一套开发和编译工具集,用于Android上JNI编程。通过NDK我们可以在Android上执行C/C++代码,进行一些CPU密集型的运算,比如游戏引擎,信号处理,物理仿真等。本文将通过...

JackMeGo ⋅ 2017/08/16 ⋅ 0

用Android Studio进行NDK编程入门实例

参考了网上各种教程,跌跌撞撞最终才把流程走通,特此记录一下: 有必要先交代下开发环境: 操作系统:Win7 Android Studio 3.0.1 gradle 3.0.1 首先,新建一个Android项目,然后在MainActiv...

analogous_love ⋅ 04/17 ⋅ 0

AndroidStudio3.0NDK输出多个so库

个人博客地址 http://dandanlove.com/ 前言 去年的 Android之NDK开发初体验 这篇文章讲述NDK开发环境的搭建,以及在AndroidStudio3.0版本之前的NKDK简单开发。这次升级到Android Studio3.1,...

静默加载 ⋅ 04/12 ⋅ 0

避免踩坑:易盾安全老司机起底Android九大漏洞,附解决建议

Android应用会遇到各种各样的漏洞,如何从细节上了解各种安全隐患,积极采取适当的防御措施便变得尤为重要。为了让大家对Android漏洞有一个非常全面的认识,网易云易盾资深安全工程师徐从祥为...

网易云易盾 ⋅ 05/16 ⋅ 0

【我的Android进阶之旅】Android自定义Lint实践

一、Lint介绍 android lint是一个静态代码分析工具,通过lint工具,你可以不用边运行边调试,或者通过单元测试进行代码检查,可以检测代码中不规范、不和要求的问题,解决一些潜在的bug。lin...

qq446282412 ⋅ 05/19 ⋅ 0

Android Studio 3.2新功能特性

android studio3.2预览版本已经发布了,下面这些功能在最新的版本已经提供,但可能尚未在测试版本中发布渠道中提供。 什么是新的助理 Android Studio 3.2有一个新的Assistant面板,可以通知您...

我就是马云飞 ⋅ 06/14 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

R计算IV

参考文章 #读取文件 rawdata = read.csv("/path/to/csv/file",header=T) colnames(rawdata)[18] <- "y" //重命名因变量y #数据分区 训练集测试集 trainIdx <- sample(nrow(rawdata), round(......

火力全開 ⋅ 5分钟前 ⋅ 0

SQL老司机,在SQL中计算 array & map & json数据

摘要: 场景 通常,我们处理数据,一列数据类型要么是字符串,要么是数字,这些都是primitive类型的数据。 场景 通常,我们处理数据,一列数据类型要么是字符串,要么是数字,这些都是primi...

阿里云云栖社区 ⋅ 5分钟前 ⋅ 0

SQL老司机,在SQL中计算 array & map & json数据

摘要: 场景 通常,我们处理数据,一列数据类型要么是字符串,要么是数字,这些都是primitive类型的数据。 场景 通常,我们处理数据,一列数据类型要么是字符串,要么是数字,这些都是primi...

猫耳m ⋅ 16分钟前 ⋅ 0

关于ireport自定义变量类型为list的时候

自己摸石头过河,我真的应该去趟市中心图书馆,借本真正靠谱的教材 网上的东西,只有0.01%是有用的,还有0.99%是垃圾,剩下的99%是垃圾的复制品。。 哎!~ 问题是这样的,报表带sql,从db中获...

炑炑milina ⋅ 17分钟前 ⋅ 0

Spring mvc ContextLoaderListener 原理解析

对于熟悉Spring MVC功能,首先应从web.xml 开始,在web.xml 文件中我们需要配置一个监听器 ContextLoaderListener,如下。 <!-- 加载spring上下文信息,最主要的功能是解析applicationContex...

轨迹_ ⋅ 17分钟前 ⋅ 0

阿里云发布企业数字化及上云外包平台服务:阿里云众包平台

摘要: 阿里云正式发布旗下众包平台业务(网址:https://zhongbao.aliyun.com/),支持包括:网站定制开发,APP、电商系统等软件开发,商标、商品LOGO、VI、产品包装设计、营销推广、大数据人...

阿里云官方博客 ⋅ 19分钟前 ⋅ 0

Redis安装异常解决办法

官网地址:http://redis.io/ 官网下载地址:http://redis.io/download 1. 下载Redis源码(tar.gz),并上传到Linux 2. 解压缩包:tar zxvf redis-2.8.17.tar.gz 3. 进入解压缩后的文件夹:c...

slagga ⋅ 24分钟前 ⋅ 0

006. 深入JVM学习—年轻代

1. 年轻代图片 年轻代(Young)属于JVM堆内存空间的一个组成部分 所有使用关键字new新实例化的对象一定会在伊甸园区进行保存,而对于存活区保存的一定是已经在伊甸园区存在一段时间并且经过了...

影狼 ⋅ 24分钟前 ⋅ 0

如何成为一个合格的程序员

偶尔的,我会被人问道:如何成为一名优秀的程序员,更或者,如何成为一名程序员。每次人们问起,我都力图给出不同的答案。因此,我的答案是各种各样的。下面就是我认为的成为一名优秀的程序员...

柳猫 ⋅ 25分钟前 ⋅ 0

cups error_log日志暴增

日志内容 File \"/usr/lib/cups/notifier/dbus\" has insecure permissions 解决(未验证适用范围) sudo service cups stopsudo rm /etc/cups/subscriptions.conf*sudo rm -r /var/cac......

一介码夫_Hum ⋅ 29分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部