This guide starts with a brand-new Android Studio project and adds the DJI Mobile SDK V5 (MSDK V5) step by step, until the app starts and registers with DJI’s servers.
Most of the work is matching versions. The SDK is picky: a Gradle, plugin or Kotlin version that is too new can break the build. So the first thing to do is use the versions DJI recommends.
Before you start
You need:
- Android Studio, version Narwhal 3 Feature Drop (2025.1.3) or the one you use for DJI work.
- Java 17, set as the Gradle JDK.
- A DJI developer account and an app key from the developer portal.
- A DJI supported remote controller, and a physical arm64 Android device. The SDK does not run on an emulator.
When you create the project, choose Groovy DSL as the build configuration language. The code in this guide uses Groovy build files (build.gradle). If you use Kotlin DSL (build.gradle.kts), the ideas are the same but the syntax is different.
Versions used in this guide
| Tool or setting | Version |
|---|---|
| Android Studio | Narwhal 3 Feature Drop (2025.1.3) |
| Java runtime | 17 |
| Kotlin | 2.1.0 |
| Gradle | 8.12 |
| Android Gradle Plugin | 8.7.0 |
| minSdk | 24 |
| targetSdk, compileSdk | 35 |
| DJI MSDK V5 | 5.17.0 |
These are the versions DJI recommends for MSDK V5. A newer Android Gradle Plugin or Gradle version can cause build failures.
1. Gradle and toolchain setup
1.1 Gradle wrapper: 8.12
In gradle/wrapper/gradle-wrapper.properties:
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
You can also change this in File → Project Structure → Project → Gradle version.
1.2 Android Gradle Plugin: 8.7.0
In gradle/libs.versions.toml, under [versions]:
agp = "8.7.0"
1.3 Kotlin plugin: 2.1.0
In the same file, under [versions]:
kotlin = "2.1.0"
1.4 Java runtime: 17
DJI recommends Java 17. Go to Settings → Build, Execution, Deployment → Build Tools → Gradle and set:
Gradle JDK = 17 (or "Embedded JDK" if it is already version 17)
2. Build configuration
2.1 compileSdk, targetSdk and minSdk
In app/build.gradle:
android {
compileSdk 35
defaultConfig {
applicationId "com.example.myapplication"
minSdk 24
targetSdk 35
// ...
}
}
DJI recommends compileSdk 35. Version 36 may work, but it is not tested with this SDK version.
2.2 Use older versions of two core libraries
Newer versions of these libraries can conflict with the SDK. In gradle/libs.versions.toml:
coreKtx = "1.15.0"
activity = "1.9.3"
2.3 Turn on kotlin-kapt
The SDK uses annotation processors, so kapt must be enabled. In the plugins block of app/build.gradle:
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
id("org.jetbrains.kotlin.kapt") // add this line
}
Sync the project now, then continue.
2.4 Use only the arm64-v8a ABI
DJI ships native libraries for arm64 only. Limiting the build to one ABI avoids packaging problems and makes builds faster:
android {
defaultConfig {
ndk {
abiFilters += "arm64-v8a"
}
}
}
3. Packaging and native libraries
The SDK contains many .so files (native libraries). Without the rules below you get duplicate entry errors at build time and missing symbols at run time.
In app/build.gradle, inside the android block:
android {
packaging {
jniLibs {
// Several DJI libraries bring their own libc++_shared.so. Keep the first one.
pickFirsts.add("lib/arm64-v8a/libc++_shared.so")
// Keep the debug symbols of the DJI native libraries
keepDebugSymbols.addAll([
"**/libconstants.so",
"**/libdji_innertools.so",
"**/libdjibase.so",
"**/libDJICSDKCommon.so",
"**/libDJIFlySafeCore-CSDK.so",
"**/libdjifs_jni-CSDK.so",
"**/libDJIRegister.so",
"**/libdjisdk_jni.so",
"**/libDJIUpgradeCore.so",
"**/libDJIUpgradeJNI.so",
"**/libDJIWaypointV2Core-CSDK.so",
"**/libdjiwpv2-CSDK.so",
"**/libFlightRecordEngine.so",
"**/libvideo-framing.so",
"**/libwaes.so",
"**/libagora-rtsa-sdk.so",
"**/libc++.so",
"**/libc++_shared.so",
"**/libmrtc_28181.so",
"**/libmrtc_agora.so",
"**/libmrtc_core.so",
"**/libmrtc_core_jni.so",
"**/libmrtc_data.so",
"**/libmrtc_log.so",
"**/libmrtc_onvif.so",
"**/libmrtc_rtmp.so",
"**/libmrtc_rtsp.so"
])
}
}
}
4. DJI SDK dependencies
4.1 Add the SDK version
In gradle/libs.versions.toml, under [versions]:
djiSdk = "5.17.0"
4.2 Declare the library aliases
In the same file, under [libraries]:
dji-sdk-v5-aircraft = { group = "com.dji", name = "dji-sdk-v5-aircraft", version.ref = "djiSdk" }
dji-sdk-v5-aircraft-provided = { group = "com.dji", name = "dji-sdk-v5-aircraft-provided", version.ref = "djiSdk" }
What the two libraries are:
dji-sdk-v5-aircraftis the real SDK. It ships inside your app.dji-sdk-v5-aircraft-providedhas compile-time stubs only. It works likecompileOnly.
4.3 Use them in app/build.gradle
dependencies {
implementation(libs.dji.sdk.v5.aircraft) // the SDK at run time
compileOnly(libs.dji.sdk.v5.aircraft.provided) // compile-time stubs only
// your other app dependencies...
}
Sync the project now, then continue.
5. The UX SDK module
The line implementation project(":android-sdk-v5-uxsdk") is not a Maven dependency. It points to a local Gradle module inside your project, so you have to add that module by hand.
Step 1: Get the UX SDK source
Clone the official DJI sample project and copy its android-sdk-v5-uxsdk folder into the root folder of your project:
https://github.com/dji-sdk/Mobile-SDK-Android-V5
Step 2: Register the module in settings.gradle
include(":android-sdk-v5-uxsdk")
Step 3: Add the module to your app
In app/build.gradle:
dependencies {
implementation(project(":android-sdk-v5-uxsdk"))
// ...
}
Step 4: Set the properties in gradle.properties
These let the UX SDK module use the right versions:
android.enableJetifier=true
ANDROID_COMPILE_SDK_VERSION=35
ANDROID_MIN_SDK_VERSION=24
ANDROID_TARGET_SDK_VERSION=35
KOTLIN_VERSION=2.1.0
SDK_VERSION=5.17.0
Step 5: Create dependencies.gradle
Create a file named dependencies.gradle next to your root build.gradle. Paste in the content of the same file from the DJI sample project:
https://github.com/dji-sdk/Mobile-SDK-Android-V5/blob/dev-sdk-main/SampleCode-V5/android-sdk-v5-as/dependencies.gradle
Step 6: Apply it from the root build.gradle
At the very bottom of the root build.gradle (the project-level one):
apply from: rootProject.file("dependencies.gradle")
Sync the project after all the UX SDK steps.
6. AndroidManifest configuration
6.1 Permissions
Add these inside <manifest>:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
6.2 USB hardware features
These are needed to connect to the DJI remote controller over USB. Add them inside <manifest>:
<uses-feature
android:name="android.hardware.usb.host"
android:required="false"/>
<uses-feature
android:name="android.hardware.usb.accessory"
android:required="true"/>
6.3 The application tag
Add these attributes to the <application> tag:
<application
android:name=".MyApplication"
android:extractNativeLibs="true"
... >
android:extractNativeLibs="true" tells Android to unpack the native libraries when the app is installed. Recent Android Gradle Plugin versions leave them packed inside the APK by default, and the DJI SDK needs them unpacked.
6.4 Your DJI app key
Get an app key on the DJI developer portal. Then add it inside the <application> tag:
<meta-data
android:name="com.dji.sdk.API_KEY"
android:value="YOUR_APP_KEY_HERE"/>
The package name of your app must match the one you gave the portal when you created the key.
6.5 USB accessory intent filter
Add this inside your main <activity> tag. It starts your app when a DJI remote controller is plugged in over USB:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
android:resource="@xml/accessory_filter" />
Make sure res/xml/accessory_filter.xml exists. Copy it from the DJI sample project.
7. The Application class
Create MyApplication.kt in your main source set:
import android.app.Application
import android.content.Context
import android.util.Log
import dji.v5.common.error.IDJIError
import dji.v5.common.register.DJISDKInitEvent
import dji.v5.manager.SDKManager
import dji.v5.manager.interfaces.SDKManagerCallback
import dji.ux.beta.core.util.Helper
class MyApplication : Application() {
companion object {
private const val TAG = "MyApplication"
}
// Runs before onCreate. The DJI Helper must be installed here.
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
Helper.install(this)
}
override fun onCreate() {
super.onCreate()
SDKManager.getInstance().init(this, object : SDKManagerCallback {
override fun onInitProcess(event: DJISDKInitEvent?, totalProcess: Int) {
Log.i(TAG, "onInitProcess: $event")
// When initialization is complete, register with the DJI servers
if (event == DJISDKInitEvent.INITIALIZE_COMPLETE) {
SDKManager.getInstance().registerApp()
}
}
override fun onRegisterSuccess() {
Log.i(TAG, "onRegisterSuccess: SDK registered successfully")
}
override fun onRegisterFailure(error: IDJIError?) {
Log.i(TAG, "onRegisterFailure: $error")
}
override fun onProductConnect(productId: Int) {
Log.i(TAG, "onProductConnect: productId=$productId")
}
override fun onProductDisconnect(productId: Int) {
Log.i(TAG, "onProductDisconnect: productId=$productId")
}
override fun onProductChanged(productId: Int) {
Log.i(TAG, "onProductChanged: productId=$productId")
}
override fun onDatabaseDownloadProgress(current: Long, total: Long) {
Log.i(TAG, "onDatabaseDownloadProgress: ${current * 100 / total}%")
}
})
}
}
Why two lifecycle methods?
attachBaseContext:Helper.install()must run before any DJI class is loaded, so it belongs here.onCreate:SDKManager.init()starts the SDK. When initialization is complete,registerApp()registers your app key with DJI.
8. First-run checklist
Check these before you build and run on a device:
- Gradle wrapper is
8.12, the Android Gradle Plugin is8.7.0, Kotlin is2.1.0. - The Gradle JDK is Java 17.
compileSdkandtargetSdkare35, andminSdkis24.- The
kaptplugin is applied inapp/build.gradle. - The ABI filter is
arm64-v8a. dji-sdk-v5-aircraftis animplementationdependency, anddji-sdk-v5-aircraft-providediscompileOnly.- The
android-sdk-v5-uxsdkmodule is copied into the project, included insettings.gradle, and listed as a dependency. dependencies.gradleexists and is applied at the bottom of the rootbuild.gradle.gradle.propertieshas all five UX SDK properties.- All permissions and USB features are in
AndroidManifest.xml. android:extractNativeLibs="true"is on the<application>tag.- Your real DJI app key is in the
com.dji.sdk.API_KEYmeta-data. res/xml/accessory_filter.xmlexists.MyApplication.ktexists and is named in the manifest asandroid:name=".MyApplication".- You test on a physical arm64 device, not an emulator.
9. Troubleshooting
Duplicate class or libc++_shared.so conflict. Make sure the pickFirsts.add(...) rule from section 3 is in packaging.jniLibs.
A crash at startup in Helper.install(). Make sure Helper.install(this) is called in attachBaseContext, not in onCreate.
Registration fails (onRegisterFailure). Check your app key. Check that the INTERNET permission is declared. Check that your app’s package name is the one registered on the DJI developer portal.
Build error: “Unsupported class file major version”. Your Gradle JDK is not Java 17. Change it in Settings → Build Tools → Gradle → Gradle JDK.
Execution failed for task ':app:mergeNativeLibs'. The ABI filter is missing, or pickFirsts is not set. Review sections 2.4 and 3.