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

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 
/* 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 
#include 
#include 
#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编译路线)