I have an app that uses a native code library -- a custom build of ffmpeg as it happens, though that isn't strictly relevant.
Up until now I have added the executable to my res/raw directory and then at runtime extracted it to a data folder before calling it with exec(). However, as of compileSdkVersion=29 this has stopped working as a result of new security for a W^X vulnerability. Calling exec() now causes an exception:
java.io.IOException: Cannot run program "..." (in directory "..."): error=13, Permission denied
I have read these pages:
permission is denied using Android Q ffmpeg": error=13, Permission denied
https://issuetracker.google.com/issues/152645643
So, as I understand it the correct way to do this is:
- Put my library in the /libs directory of the project
- Add
android:extractNativeLibs = "true"
to the AndroidManifest.xml - Ensure the filename matches the pattern "lib*.so"
then the installation should unpack the library into an executable location and I should be able to read it at runtime.
It's just not working. I must be missing a vital step.
My code
app/build.gradle:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 30
buildToolsVersion "30.0.1"
defaultConfig {
applicationId "com.example.nativelibrarytest"
minSdkVersion 29
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.1'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.nativelibrarytest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:extractNativeLibs="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Activity code (based on the Basic Activity template):
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onResume() {
super.onResume()
// Test code: see if we can find the /libs library
val libName = "libffmpeg.so"
val nativeLibsDir = applicationInfo.nativeLibraryDir
val libPath = "$nativeLibsDir/$libName"
val libFile = File(libPath)
val result = if (libFile.exists()) "exists" else "does not exist"
val label = findViewById<TextView>(R.id.label)
label.text = "$libPath $result"
Log.d("MainActivity", label.text.toString())
}
}
Result (on emulator)
/data/app/~~Q0jHO-XwEvieuwr-92_Fyg==/com.example.nativelibrarytest-byqCoqnbYsgtmCOVg361NA==/lib/x86/libffmpeg.so does not exist
Why can't my runtime code find the library?
I have tried:
- Adding
implementation files('libs/libffmpeg.so')
inside the build.gradle > dependencies { ... } - Adding
implementation fileTree(dir: "libs", include: ["lib*.so"])
- Creating libs/x86 and libs/arm64 subdirectories and putting libffmpeg.so inside those
I have expanded the .apk file and unzipped it and there is no lib directory in it. It's as though the build process is not aware that the native library exists and is not copying it in.