美文网首页
编译LibPDFIUM.so:一、安卓ndk编译路线

编译LibPDFIUM.so:一、安卓ndk编译路线

作者: 天下第九九八十一 | 来源:发表于2020-10-22 20:37 被阅读0次

    PDFIUM.so在国内是编译不了的,我试过,付费购买ke学上网都不行,gclient总会在某一处卡住、失败。

    上一篇介绍了一些陈旧的资源:gyp、https://github.com/PDFium/PDFium

    我决定仍然利用 gyp 来生成VS项目,供桌面平台测试用。至于代码,则从谷歌AOSP拉取。

    肯定不能拉去最新的代码,最新版已经舍弃 gyp 了,只能生成ninja的脚本。不过 github 上面也有一个较新的搬运:vs_pdfium

    Google's pdfium codebase set up, by hand, as a vanilla Visual Studio solution that compiles to a static library on Windows under Visual Studio 2017.

    好家伙,直接手动把偌大的、没有工程的项目装进了Visual Studio。下载下来编译看看,新版编译速度慢了两三倍不止,静态库编译出来300+MB(原先5MB),test.exe 17 MB。

    还是编译旧版吧,虽然旧版没有高亮标注的头文件fpdf_annot.h

    安卓 barteksc/PdfiumAndroid
    项目的代码来自 android-7.1.2,这也是最后一个使用 gyp 的版本,就用它了。

    设置proxy,使用git将代码克隆下来:

    git clone https://android.googlesource.com/platform/external/pdfium --branch android-7.1.2_r36

    利用 gyp 可以生成 vs 工程,编译很快,示例也能运行,这就够了。

    接下来就是尝试将这些代码编译到安卓上面运行。PdfiumAndroid 的编译方法是将ASOP项目整个拉取下来,然后更具官方的指示进行编译。国内显然行不通,而且据说编译过程磁盘占用达到了60GB。

    即使这样,这个仓库提供的一些.mk文件还是很有用的。写makefile时,可以直接include这些文件,然后改写这些.mk文件,加入recipe rules,一个个模块地把 LibPDFIUM.so 编译出来。

    安卓ndk编译路线验证

    我们来做一个最基本的路线验证:编译解压缩模块 Pdfium/third_party/zlib_v128 为静态库libpdfiumzlib.a,然后写一个test.c调用 zlib 的方法,将 test.c 编译为“第三方库”libhelloworld.so,最后新建一个安卓jni项目调用libhelloworld.so中的方法。

    一、编译 libpdfiumzlib.a

    编译环境:windows10、ndk21(android sutdio下载的windows版ndk)、在bash(win10下的ubuntu子系统)中编译。

    以前ndk中自带用来交叉编译的gcc,r19以后的版本已经去除,需换用clang来编译代码。

    ./build.sh 设置环境变量然后 make

    export ANDROID_SDK=/mnt/d/Code/NVPACK/android_sdk
    export PATH=$ANDROID_SDK/platform-tools:$PATH
    export PATH=$ANDROID_SDK/tools:$PATH
    
    export NDK=/mnt/d/Code/NVPACK/android-ndk-r21b # 这是我自己下载的linux版ndk,事实证明都可以,不过一些路径要改罢了
    export NDK=/mnt/d/Code/NVPACK/android_sdk/ndk/21.0.6113669
    
    export PATH=$NDK:$PATH
    
    export TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/linux-x86_64# 自己下载的linux版ndk
    export TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/windows-x86_64
    export SYSROOT=$TOOLCHAIN/sysroot
    
    make libpdfiumzlib
    

    总的 third_party/makefile

    BASE_PATH := $(call my-dir)
    
    _ARCH_PX_ := aarch64
    _TARGETRI_ := aarch64-linux-android
    _ARCH_CC_ := $(_TARGETRI_)21-clang
    
    CC := $(TOOLCHAIN)/bin/$(_ARCH_CC_)
    
    AR := $(TOOLCHAIN)/bin/aarch64-linux-android-ar.exe
    
    include pdfiumzlib.mk
    include pdfiumjpeg.mk
    include pdfiumopenjpeg.mk
    include pdfiumbigint.mk
    include pdfiumagg23.mk
    include pdfiumlcms.mk
    
    all: \
        pdfiumzlib.a\
        pdfiumjpeg.a\
        pdfiumopenjpeg.a\
        pdfiumbigint.a\
        pdfiumagg23.a\
        pdfiumlcms.a\
    

    来自 barteksc/PdfiumAndroid,改写(在后面追加了构建规则)后的 pdfiumzlib.mk :

    LOCAL_PATH:= $(call my-dir)
    
    include $(CLEAR_VARS)
    
    LOCAL_MODULE := libpdfiumzlib
    
    LOCAL_ARM_MODE := arm
    LOCAL_NDK_STL_VARIANT := gnustl_static
    
    LOCAL_CFLAGS += -O3 -fstrict-aliasing -fprefetch-loop-arrays -fexceptions
    LOCAL_CFLAGS += -Wno-non-virtual-dtor -Wall
    
    # Mask some warnings. These are benign, but we probably want to fix them
    # upstream at some point.
    LOCAL_CFLAGS += -Wno-shift-negative-value -Wno-unused-parameter
    
    LOCAL_SRC_FILES := \
        zlib_v128/adler32.c \
        zlib_v128/compress.c \
        zlib_v128/crc32.c \
        zlib_v128/deflate.c \
        zlib_v128/gzclose.c \
        zlib_v128/gzlib.c \
        zlib_v128/gzread.c \
        zlib_v128/gzwrite.c \
        zlib_v128/infback.c \
        zlib_v128/inffast.c \
        zlib_v128/inflate.c \
        zlib_v128/inftrees.c \
        zlib_v128/trees.c \
        zlib_v128/uncompr.c \
        zlib_v128/zutil.c
    
    LOCAL_C_INCLUDES := \
        external/pdfium
    
    include $(BUILD_STATIC_LIBRARY)
    
    # 之后的都是是我写的
    OBJS_pdfiumzlib := $(addsuffix .o, $(LOCAL_SRC_FILES))
    OBJS_pdfiumzlib := $(addprefix build/$(_ARCH_PX_)/pdfiumzlib/, $(OBJS_pdfiumzlib))
        
    libpdfiumzlib.a: $(OBJS_pdfiumzlib)
        $(AR) -rv libpdfiumzlib.a $(OBJS_pdfiumzlib)
    
    # .o 文件的构建规则,改了好久呢!
    build/$(_ARCH_PX_)/pdfiumzlib/%.o: %
        @echo $<; set -x;\
        mkdir -p $(dir $@);\
        $(CC) -c -O3 $< -o $(@) -I"../" -I$(LOCAL_C_INCLUDES)
        echo
    

    核心就是 :

    1. 编译.c源代码为.o文件

    $(CC) -c -O3 $< -o $(@) -I"../" -I$(LOCAL_C_INCLUDES)
    翻译:
    aarch64-linux-android21-clang -c -O3 build/aarch64/pdfiumzlib/XXX.c -o build/aarch64/pdfiumzlib/XXX.c.o -I"../" -I$(LOCAL_C_INCLUDES)

    1. 将许多.o打包为.a文件

    $(AR) -rv libpdfiumzlib.a $(OBJS_pdfiumzlib)
    翻译:
    ar.exe -rv libpdfiumzlib.a build/aarch64/pdfiumzlib/XXX.c.o build/aarch64/pdfiumzlib/YYY.c.o build/aarch64/pdfiumzlib/ZZZ.cpp.o

    bash ./build.sh,OK!


    二、编译 libhelloworld.so

    zlib测试代码来自 c语言使用zlib实现文本字符的gzip压缩与gzip解压缩

    test.c :

    #include "test.h"
    
    int helloJ() { // 压缩再解压,打印字符串。
        printf("helloJ!!!n");
        const char *istream = "some foo汉字";
        uLong srcLen = strlen(istream)+1;      // +1 for the trailing `\0`
        uLong destLen = compressBound(srcLen); // this is how you should estimate size
                                             // needed for the buffer
        unsigned char* ostream = (unsigned char*)malloc(destLen);
        int res = compress(ostream, &destLen, (const unsigned char *)istream, srcLen);
        // destLen is now the size of actuall buffer needed for compression
        // you don't want to uncompress whole buffer later, just the used part
        if(res == Z_BUF_ERROR){
        printf("Buffer was too small!\n");
        return 1;
        }
        if(res ==  Z_MEM_ERROR){
        printf("Not enough memory for compression!\n");
        return 2;
        }
    
        unsigned char *i2stream = ostream;
        char* o2stream = (char *)malloc(srcLen);
        uLong destLen2 = destLen; //destLen is the actual size of the compressed buffer
        int des = uncompress((unsigned char *)o2stream, &srcLen, i2stream, destLen2);
        printf("%s\n", o2stream);
        return 0;
    }
    
    int main()
    {
      return helloJ();
    }
    

    makefile

    BASE_PATH := $(call my-dir)
    
    _ARCH_PX_ := aarch64
    _TARGETRI_ := aarch64-linux-android
    _ARCH_CC_ := $(_TARGETRI_)21-clang
    
    CC := $(TOOLCHAIN)/bin/$(_ARCH_CC_)
    
    #AR := $(TOOLCHAIN)/aarch64-linux-android/bin/ar
    AR := $(TOOLCHAIN)/bin/$(_TARGETRI_)-ar.exe
    
    APP_STL:=stlport_static
    
    FLAGSMY=\
        -arch aarch64 \
        -I../pdfium/third_party/zlib_v128 \
        -L../pdfium/third_party
        
    SHARELD = -shared -Wl,-soname,libhelloworld.so
        
    all: 
        $(CC) test.c \
        -lpdfiumzlib \
        -lpdfiumfxcrt \
        -L../pdfium/third_party \
        -L../pdfium/core \
        -o libhelloworld.so $(FLAGSMY) $(SHARELD) 
    

    ( pdfiumzlib 依赖于 pdfiumfxcrt,后者是个内存管理模块。 )

    注意安卓编译动态库需要加上-shared -Wl,-soname,libhelloworld.so 这样的链接选项。如果没有-Wl,-soname,…,则不能正常加载。


    三、JNI测试

    Android Studio 中,新建一个项目。在 main/java 旁边新建一个 main/jni 文件夹。结构如下:

    main
    -----java
    -----jni
    ----------source
    ---------------com_knziha_jni_helloworld.h
    ---------------jniMain.cpp
    ----------lib / arm64-v8a / libhelloworld.so
    ----------Android.mk
    ----------Application.mk

    jni的源文件就两个:

    • com_knziha_jni_helloworld.h
    #include <jni.h>
    /* Header for class com_knziha_jni_helloworld */
    
    #ifndef _Included_com_knziha_jni_helloworld
    #define _Included_com_knziha_jni_helloworld
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     com_knziha_jni_helloworld
     * Method:    testZlib
     * Signature: ()Ljava/lang/String;
     */
    JNIEXPORT jstring JNICALL Java_com_knziha_jni_helloworld_testZlib
      (JNIEnv *, jclass);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    

    这个头文件是用 javah 生成的:

    1. 先写好Java层 helloworld.java
    package com.knziha.jni;
    public class helloworld {
        public static native String testZlib();
        public static boolean iniTestZlib() {
            System.loadLibrary("testZlib");
            return true;
        }
        static {
            iniTestZlib();
        }
    }
    
    1. 然后 make project,在 terminal ,main/java目录中打入:

    javah com.knziha.jni.helloworld

    运行后会在main/java目录生成.h文件,移入main/jni.source即可
    • jniMain.cpp
    #include "com_knziha_jni_helloworld.h"
    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include "zlib.h"
    #include "test.h"
    
    JNIEXPORT jstring JNICALL Java_com_knziha_jni_helloworld_testZlib
            (JNIEnv *env, jclass jobj) {
        FILE* out = freopen("/storage/emulated/0/Android/data/com.knziha.helloj/log.txt","w",stdout);
        helloJ(); //调用"第三方库"中的方法。
        fflush(out);
        return env->NewStringUTF("testZlib!!!");
    }
    

    ( 由于native代码中无法打印,调用 freopen 将 stdout 输出到日志文件。 )

    写完两个 native 文件后,需要准备两个mk文件,以下是参考了PdfiumAndroid写的:

    • android.mk
    LOCAL_PATH := $(call my-dir)
    
    #Prebuilt libraries
    include $(CLEAR_VARS)
    LOCAL_MODULE := helloworld
    
    ARCH_PATH = $(TARGET_ARCH_ABI)
    
    LOCAL_SRC_FILES := $(LOCAL_PATH)/lib/$(ARCH_PATH)/libhelloworld.so
    
    include $(PREBUILT_SHARED_LIBRARY)
    
    #Main JNI library
    include $(CLEAR_VARS)
    LOCAL_MODULE := testZlib
    
    LOCAL_CFLAGS += -DHAVE_PTHREADS
    LOCAL_C_INCLUDES += .
    LOCAL_C_INCLUDES += ../../../../../../pdfium/third_party/zlib_v128
    LOCAL_C_INCLUDES += ../../../../../../hello_zlib_so
    LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
    LOCAL_SHARED_LIBRARIES += helloworld
    LOCAL_LDLIBS += -llog -landroid -ljnigraphics
    
    LOCAL_SRC_FILES :=  $(LOCAL_PATH)/src/jniMain.cpp
    
    include $(BUILD_SHARED_LIBRARY)
    

    其中定义了 jni 主模块 testZlib,依赖于(先前准备好的)预编译模块 helloworld。

    • application.mk
    APP_STL := c++_shared
    APP_CPPFLAGS += -fexceptions
    
    #For ANativeWindow support
    APP_PLATFORM = android-21
    
    APP_ABI :=  armeabi-v7a
    APP_ABI :=  arm64-v8a
    

    最后在 jni 目录调用 ndk-build 即可,ndk会将库文件复制到 main / libs,需要在 app 的 gradle 中写明 jniLibs 目录:

    
    android {
        defaultConfig {
            ndk {
                abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
            }
        }
        sourceSets{
            main {
                jni.srcDirs = []
                jniLibs.srcDir 'src/main/libs'
            }
        }
    }
    
    libc++_shared、libhelloworl 都是 ndk 复制过来的,libtestZlib.so则是编译产出。

    成功运行后就可以在手机上看到日志输出了:


    相关文章

      网友评论

          本文标题:编译LibPDFIUM.so:一、安卓ndk编译路线

          本文链接:https://www.haomeiwen.com/subject/imypmktx.html