Error trying to loadLibrary for PDFNet Android

Question:
I am getting a strange error when trying to integrate with PDFNet Android SDK, such as:

UnsatisfiedLinkError: Couldn't load PDFNetC: findLibrary returned null. Expected armv7a, found cpu_info: abi: armeabi-v7a

Or

UnsatisfiedLinkError: Couldn't load PDFNetC: findLibrary returned null. Expected arm or x86 or arm64, found cpu_info: abi: x86

What is wrong?

Answer:

Most likely the native library provided by PDFNet Android SDK is not included in your project correctly.
Inside the lib folder in the download package, there are native libraries and java library that both need to be included to your project.

1. PDFNet.jar

If PDFViewCtrlTools project is used in your application, PDFNet.jar file needs to be included in PDFViewCtrlTools project’s libs folder.
Otherwise, PDFNet.jar file needs to be included in the libs folder of your main project that uses PDFNet API.

2. libPDFNetC.so (and libPDFNetC-v7a.so)
Note, for information on what the differences are between full and standard version, please click here.

There are two ways to include the native libraries (.so). Both methods lead to the same goal which is to allow Android Studio to recognize where the native libraries are.

Method 1:
Create a folder called jniLibs in main folder and place all .so (with corresponding folder structure) inside the jniLibs folder, i.e.:

This will allow Android Studio to automatically recognize the native library path.

You could also specify product flavor to filter which .so to use, such as:

productFlavors {
    // The order of the flavors should not change
    arm {
        ndk {
            abiFilters "armeabi"
        }
    }
    armv7a {
        ndk {
            abiFilters "armeabi-v7a"
        }
    }
    armv8 {
        ndk {
            abiFilters "arm64-v8a"
        }
    }
    x86 {
        ndk {
            abiFilters "x86"
        }
    }
    x86_64 {
        ndk {
            abiFilters "x86_64"
        }
    }
    dev {
        ndk {
            abiFilters "armeabi-v7a", "arm64-v8a", "x86"
        }
        minSdkVersion 16
    }
}

Then select any flavor in the build variants window to get started.

Method 2:
Copy all .so (with corresponding folder structure) to your project’s libs folder, i.e.:

Then specify the jniLibs directory inside build.gradle file of your project, i.e.:

jniLibs.srcDirs = ['libs']

The benefit of this approach is that you will be able to directly import this project to Eclipse without a problem.

You can also specify product flavor same way as mentioned above in method 1.

Could you expand more on how to setup the product flavour, where in the code should this be placed?

You could also specify product flavor to filter which .so to use, such as:

productFlavors {
    // The order of the flavors should not change
    arm {
        versionCode computeVersionCode(0)
        ndk {
            abiFilters "armeabi"
        }
    }
    armv7a {
        versionCode computeVersionCode(1)
        ndk {
            abiFilters "armeabi-v7a"
        }
    }
    armv8 {
        versionCode computeVersionCode(2)
        ndk {
            abiFilters "arm64-v8a"
        }
    }
    x86 {
        versionCode computeVersionCode(3)
        ndk {
            abiFilters "x86"
        }
    }
    x86_64 {
        versionCode computeVersionCode(4)
        ndk {
            abiFilters "x86_64"
        }
    }
    dev {
        versionCode 1
        ndk {
            abiFilters "armeabi-v7a", "arm64-v8a", "x86"
        }
        minSdkVersion 16
    }
}

Then select any flavor in the build variants window to get started.

There is a new getting started guide.
https://blog.pdftron.com/2017/03/21/getting-started-with-android-2/

Otherwise, the included gradle files are a great place to learn about setup.

If you are having a particular issue, please post more details.

Thanks Ryan, ive placed the code in the build.gradle file however Im encountering this error from android studio

Could not find method computeVersionCode() for arguments [0] on ProductFlavor_Decorated{name=arm, dimension=null, minSdkVersion=null, targetSdkVersion=null, renderscriptTargetApi=null, renderscriptSupportModeEnabled=null, renderscriptSupportModeBlasEnabled=null, renderscriptNdkModeEnabled=null, versionCode=null, versionName=null, applicationId=null, testApplicationId=null, testInstrumentationRunner=null, testInstrumentationRunnerArguments={}, testHandleProfiling=null, testFunctionalTest=null, signingConfig=null, resConfig=null, mBuildConfigFields={}, mResValues={}, mProguardFiles=[], mConsumerProguardFiles=[], mManifestPlaceholders={}, mWearAppUnbundled=null} of type com.android.build.gradle.internal.dsl.ProductFlavor.

Are all these product flavours needed if I only have a problem with a single device with a single chipset eg: ‘armeabi-v7a’
The example on the link you provided only contains a single abiFilter

productFlavors {` armv7a { ```ndk {
abiFilters ``"armeabi-v7a"` } ```}
```}`

Can you post, or email us, your complete build.gradle file?

buildscript {

repositories {
maven { url https://maven.fabric.io/public }
maven { url https://jitpack.io } // For sdk manager plugin until https://github.com/JakeWharton/sdk-manager-plugin/issues/99 is fixed
mavenCentral()
jcenter()
}

dependencies {
classpath 'com.android.tools.build:gradle:2.2.3’
classpath 'io.fabric.tools:gradle:1.21.5’
classpath ‘com.github.JakeWharton:sdk-manager-plugin:master’ // on master until https://github.com/JakeWharton/sdk-manager-plugin/issues/99 is fixed
classpath 'com.github.ben-manes:gradle-versions-plugin:0.12.0’
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8’
}
}

apply plugin: 'android-sdk-manager’
apply plugin: 'com.android.application’
apply plugin: 'io.fabric’
apply plugin: 'maven’
apply plugin: 'com.github.ben-manes.versions’
apply plugin: 'com.neenbedankt.android-apt’
apply plugin: 'jacoco’

task configure {
def versionMajor = 4;
def versionMinor = 02;
def versionPatch = 00;

String buildNumber = buildJobNumber.substring(0, 7)
project.ext[“appVersionName”] = versionMajor + “.” + versionMinor + “.” + versionPatch + “.” + buildNumber
project.ext[“appVersionCode”] = (versionMajor * 10000) + (versionMinor * 100) + versionPatch

if (environment == ‘uklive’) {
project.ext[“appId”] = 'com.huddle.huddle’
} else {
project.ext[“appId”] = ‘com.huddle.huddle.’ + environment
}

}

preBuild.dependsOn configure

task printConfiguration << {
def configMap = project.ext.properties.sort { it.key }
configMap.each { k, v →
println "${k}: ${v}"
}
}

printConfiguration.dependsOn configure

android {
compileSdkVersion 23
buildToolsVersion '23.0.3’
useLibrary 'org.apache.http.legacy’

defaultConfig {

minSdkVersion 16
targetSdkVersion 23
versionCode appVersionCode
versionName appVersionName
applicationId appId

resValue “string”, “app_name”, appName
resValue “string”, “host”, “*.huddle.” + tld

buildConfigField “String”, “ENVIRONMENT”, “****”$environment**“"**
buildConfigField “boolean”, “IGNORE_SSL_CERT_ERRORS”, "$ignoreSSLCertErrors**"**
buildConfigField “String”, “SEGMENT_KEY”, **"
$segmentKey""**
buildConfigField “String”, “CLIENT_ID”, **"
"$clientId""**
buildConfigField “String”, “REDIRECT_URL”, **"
"$redirectUrl""**
buildConfigField “String”, “LOGIN_END_POINT”, **"
"$loginEndPoint""**
buildConfigField “String”, “ENTRY_POINT”, **"
"$entryPoint""**
buildConfigField “String”, “TLD”, **"
"$tld""**
buildConfigField “String”, “UPDATE_MANIFEST_URL”, **"
"$updateManifestUrl"****"**
}

sourceSets {
test {
java.srcDirs += 'src/testDataBuilders/java’
}
}

lintOptions {
lintConfig file(“src/lint.xml”)
abortOnError false
ignoreWarnings false
warningsAsErrors true
checkAllWarnings true
}

signingConfigs {
release {
storeFile file(keyStoreLocation)
storePassword keyStorePassword
keyAlias signingKeyAlias
keyPassword signingKeyPassword
}
}

buildTypes {

debug {
minifyEnabled false
// set to false to enable method parameter debugging - http://stackoverflow.com/a/31981612
testCoverageEnabled false

def pathToFile = "\\src\\test\\res\\"
def pathToApp = projectDir.toString().replace(\, \\)
buildConfigField “String”, “TEST_JSON_LOCATION”, " + pathToApp + pathToFile + """
}

release {
signingConfig signingConfigs.release
minifyEnabled true
proguardFiles getDefaultProguardFile(‘proguard-android-optimize.txt’)
proguardFiles fileTree(‘proguard’).asList().toArray() }
}

productFlavors {
// The order of the flavors should not change
arm {
versionCode computeVersionCode(0)
ndk {
abiFilters "armeabi"
}
}
armv7a {
versionCode computeVersionCode(1)
ndk {
abiFilters "armeabi-v7a"
}
}
armv8 {
versionCode computeVersionCode(2)
ndk {
abiFilters "arm64-v8a"
}
}
x86 {
versionCode computeVersionCode(3)
ndk {
abiFilters "x86"
}
}
x86_64 {
versionCode computeVersionCode(4)
ndk {
abiFilters "x86_64"
}
}
dev {
versionCode 1
ndk {
abiFilters “armeabi-v7a”, “arm64-v8a”, "x86"
}
minSdkVersion 16
}

}

packagingOptions {
exclude 'LICENSE.txt’
exclude 'META-INF/LICENSE’
exclude 'META-INF/LICENSE.txt’
exclude 'META-INF/NOTICE’
}
}

task addCustomLintRules(type: Copy) {

def lintLocation = System.getenv(‘ANDROID_SDK_HOME’)

if (lintLocation == null) {
lintLocation = System.getenv(‘HOME’)
}

lintLocation += '/.android/lint/'

from(‘lint/’) into lintLocation
}

preBuild.dependsOn addCustomLintRules

task jacocoTestReport(type: JacocoReport, dependsOn: “testUkDebugUnitTest”) {
group = "Reporting"

description = "Generate Jacoco coverage reports"

classDirectories = fileTree(
dir: ‘…/app/build/intermediates/classes/uk/debug’,
excludes: [’**/R.class’,
’**/R$*.class’,
’**/BuildConfig.*’,
’**/Manifest*.*’]
)

def coverageSourceDirs = [
’…/app/src/main/java’
]

additionalSourceDirs = files(coverageSourceDirs)
sourceDirectories = files(coverageSourceDirs)
executionData = files(’…/app/build/jacoco/testUkDebugUnitTest.exec’)

reports {
xml.enabled = true
html.enabled = true
}
}

// robolectric tests
android.testOptions.unitTests.all {

ignoreFailures false

afterTest { descriptor, result →
println "Executing test for ${descriptor.name} with result: ${result.resultType}"
}
}

repositories {
maven { url http://dl.bintray.com/populov/maven }
maven { url https://maven.fabric.io/public }
mavenCentral()
jcenter()
}

dependencies {
compile project(’:pdftron’)
compile fileTree(include: [’*.jar’], dir: ‘libs’)
compile 'com.android.support:design:23.3.0’
compile 'com.android.support:support-v4:23.3.0’
compile 'com.android.support:support-v13:23.3.0’
compile 'com.android.support:appcompat-v7:23.3.0’
compile 'com.android.support:cardview-v7:23.3.0’
compile 'com.android.support:recyclerview-v7:23.3.0’
compile 'com.squareup.okhttp:okhttp:2.7.5’
compile 'com.squareup.okhttp:okhttp-urlconnection:2.7.5’
compile 'de.greenrobot:eventbus:2.4.0’
compile(‘com.segment.analytics.android:analytics-core:3.4.0’) {
transitive = true
}
compile(‘com.segment.analytics.android:analytics-integration-flurry:3.4.0’) {
transitive = true
}
compile(‘com.crashlytics.sdk.android:crashlytics:2.5.5@aar’) {
transitive = true
}
compile 'com.github.chrisbanes.photoview:library:1.2.4’
compile 'de.hdodenhof:circleimageview:2.0.0’
compile 'com.google.dagger:dagger:2.2’
apt "com.google.dagger:dagger-compiler:2.2"
provided 'javax.annotation:jsr250-api:1.0’
compile 'com.jakewharton.timber:timber:4.1.2’
compile 'org.apmem.tools:layouts:1.10@aar’
compile 'com.viewpagerindicator:library:2.4.1’
compile 'com.jonathanfinerty.once:once:1.0.3’
compile 'jp.wasabeef:recyclerview-animators:2.2.2’
compile 'com.daimajia.swipelayout:library:1.2.0@aar’
compile 'com.facebook.conceal:conceal:1.1.3@aar’
testCompile 'org.apache.maven:maven-ant-tasks:2.1.3’
testCompile 'com.squareup.okhttp:mockwebserver:2.7.5’
testCompile 'org.skyscreamer:jsonassert:1.3.0’
testCompile 'org.robolectric:robolectric:3.0’
testCompile(‘junit:junit:4.12’) {
exclude module: 'hamcrest-core’
}
testCompile 'org.mockito:mockito-core:2.0.44-beta’
}

Please remove the computeVersionCode as it is not required