[Android]记关于编译适用于安卓平台的OpenSSL

参考的资料:

  1. OpenSSL实践-Android下的编译和使用
  2. Android – OpenSSLWiki

编译平台:Ubuntu 16.04.3 LTS 64 位

编译

首先是下载相关的源码和工具,在SDK里安装上NDK和cmake(话说以前不是用Android.mk么什么时候换成cmake的)。

下载下来的setenv-android.sh往往还不能直接运行,需要根据自己的环境进行配置。

配置完后执行,可能会出现以下这样的输出

ANDROID_NDK_ROOT: /mnt/d/ndk/android-ndk-r14b
ANDROID_ARCH: arch-arm
ANDROID_EABI: arm-linux-androideabi-4.9
ANDROID_API: android-18
ANDROID_SYSROOT: /mnt/d/ndk/android-ndk-r14b/platforms/android-18/arch-arm
ANDROID_TOOLCHAIN: /mnt/d/ndk/android-ndk-r14b/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin
FIPS_SIG: /mnt/d/openssl-1.1.0f/util/incore
CROSS_COMPILE: arm-linux-androideabi-
ANDROID_DEV: /mnt/d/ndk/android-ndk-r14b/platforms/android-18/arch-arm/usr

以上的NDK目录、工具链等需要根据自己的情况来配置。可能会有一个报错:Error: FIPS_SIG does not specify incore module, please edit this script,这个错误可以忽略,如果你不需要FIPS的话。

设置完然后切入openssl源码目录下,输入

./config shared no-ssl2 no-ssl3 no-comp no-hw no-engine --openssldir=/usr/local/ssl/android-14/

配置完毕后就可以编译了:

make depend
make all

如果编译失败,那么请根据错误信息进行操作。例如我在又一次编译时,#include<linux/mman.h>提示找不到头文件,找到这个源文件修改为sys/mman.h可以解决。

编译完成后我们可以得到一系列的文件:

  1. libssl.a
  2. libcrypto.a
  3. libcrypto.so.1.1
  4. libssl.so.1.1
  5. 两个以上动态库的链接(没有版本后缀)

这样就算编译成功了。

编译的坑

从以上过程确实可以编译出arm平台的openssl库,网上有不少教程交代到这个地方,但是这样就可以用了吗?

我发现这样主要有两个坑的地方:

1. 在导入到安卓项目后,编译时总是出现undefined reference的错误。说明库根本连接不上。

我明明就已经在CMakeList.txt中添加了这俩库了,进一步查看错误信息,有一句:

xxx/ld:
skipping incompatible xxxx

印象中这个问题在32位程序链接64位库,x86程序链接arm库的时候会出现,联想到安卓也是支持很多平台的,在编译库时就可以看到有很多的目录:armeabi、armeabi-v7a、x86、x86_64等等,而我们在之前编译的时候只编译了一个平台的,例如上文的配置貌似就是armeabi-v7a。如果我们编译的安卓程序和这个库的程序不匹配的话,就会链接失败。

至少有两个解决办法,一种是改变不同的参数,编译很多次openssl,生成不同架构的库并放在不同名字的文件夹里,这样无论是编译哪一个都行。另一种我之前编译的是什么架构的,安卓里就只编译和这个架构一样的呗。可以在app目录下的build.gradle里加上一句

android {
        ...
        ndk {
            moduleName"ssltest"
            ldLibs "log", "z", "m"
            abiFilters "armeabi-v7a"
        }
    }

这样,在安卓里应该就可以正确的编译并链接了。但是这样有一个问题就是,发布出来的apk在一些人的手机上好使,在一些人的手机上可能就不好使。

2. 这个.so库烦人的版本号

说实话上面那个问题应该是要怪我自己对安卓开发不熟悉。但是这个问题这么明显,居然在我查阅的教程里没有一个提到这个问题(怕不是他们也只是编译了一下,根本没用放到项目里用哦)。我在编译生成了apk之后,点运行刚一进去突然就闪退了。从logcat可以发现它说它要libcrypto.so.1.1但是找不到,我心想这有的啊怎么找不到。于是build->Analyze apk发现生成的apk中的lib只有项目自己生成的.so,没有我们的openssl。

后来查到Android不支持linux这种骚气的区分不同版本的动态链接库的方式,即便是放到了库目录里,也只拷贝.so文件,而.so.1.1这样的不拷贝到apk包里的。但是我们编译时用的.so文件指明了在程序运行时是去找名为.so.1.1的动态链接库(那个.so只是一个link)。这就造成了矛盾。我看github上也有人反馈这个问题,下面有个人说,他临时的解决办法是改Makefile,打开Makefile把这几个库的名字(xxx.so.xx.xx)全部去掉后面的后缀(xxx.so),然后再编译一次。事实证明,虽然很糙,但这是好使的(不过这样就不好区分不同的版本了)。

在我们的安卓项目里使用它

我们新建一个安卓项目,在配置里勾选上c++ support。这样,我们就会得到一个Native的Hello World程序。(对于已有项目如何添加NDK支持可以百度)

在src/main/cpp里有一个native-lib.cpp,里面定义了一个函数,返回一个字符串”Hello from c++”。同时,在MainActivity.java中是这样的:

public class MainActivity extends AppCompatActivity {

    // 这个地方载入了这个项目生成的库(包含上面那个返回一个字符串的函数)
    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);
        tv.setText(stringFromJNI());
    }

    // 此处是对那个C函数的声明,注意它们的命名
    public native String stringFromJNI();
}

运行程序可以显示一个TextView,text内容是是来自于C++函数的返回值。

OpenSSL是一个C++库,所以我们可以在上面这些C++代码中调用openssl,然后通过JNI传递到Java中去,就像例程中那样。

至于如何在上面的.cpp文件中调用openssl,就和我们平时写c++程序一样要容易一些了:

我们把openssl源码中的include目录拷贝到安卓项目里的一个位置,然后在CMakeLists.txt中加上这个包含目录:

# Add openssl library
include_directories( src/main/cpp/include/ )

然后我们还要把编译好的.a,.so(这时已经没有.so.1.1这样的库咯)复制到项目的某个地方,再在CMakeList.txt中加上库目录

link_directories( src/main/cpp/libs/${ANDROID_ABI}/ )

再其次,我们需要添加上我们需要的库:

add_library( ssl
             SHARED
             IMPORTED )
set_target_properties( # Specifies the target library.
                       ssl

                       # Specifies the parameter you want to define.
                       PROPERTIES IMPORTED_LOCATION

                       # Provides the path to the library you want to import.
                       /home/bob/Projects/AndroidTestOpenSSL/app/src/main/jniLibs/${ANDROID_ABI}/libssl.so )
add_library( crypto
             SHARED
             IMPORTED )
set_target_properties( # Specifies the target library.
                       crypto

                       # Specifies the parameter you want to define.
                       PROPERTIES IMPORTED_LOCATION

                       # Provides the path to the library you want to import.
                       /home/bob/Projects/AndroidTestOpenSSL/app/src/main/jniLibs/${ANDROID_ABI}/libcrypto.so )

额当然,上面的路径是根据你的环境来配置的。所以直接复制的话肯定会出事的。

最后一步,把两个.so文件复制到src/main/jniLibs下(加个文件夹:src/main/jniLibs/armeabi-v7a)。这样Android Studio在打包apk时,会把这个文件夹下的.so文件打包进lib里,以供我们的程序运行时链接。

鉴于我对Linux和Android也没有系统的学习过,以上可能有多处描述是错误的,欢迎指出。

这样我们就可以成功构建安卓项目了。

发表评论

电子邮件地址不会被公开。