编译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 <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则是编译产出。

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


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容