简介:面向无人机应用开发者的DJI SDK开发资源包,主要解决Phantom 4等机型的功能接入与二次开发需求,适合需要将飞控、图传、遥控能力整合进自有程序的开发者。压缩包共493个文件、19.76MB,以HTML/JS/CSS文档页面、Markdown笔记、Doxygen生成的dot与png结构图为主,同时包含scss样式、字体图标及必要的配置文件,方便在本地系统查阅SDK接口与调用关系。包内针对PSDK消息订阅、应用功能、命令集等模块提供了较完整的API说明与头文件依赖图,可辅助快速理清模块间引用关系,缩短环境配置和代码调试周期;md与html两种格式互为补充,按模块组织的结构图更易于逐层深入阅读,从而掌握设备接入的整体流程。已有6770人学习下载,适合具备一定编程基础、正在着手接入DJI无人机的开发者使用。
1. 接入 DJI SDK 之前:先想清楚你要控的是整机还是负载
很多开发者拿到 DJI SDK 开发包的第一反应,是把示例工程跑起来、连上飞机、看到实时图传就觉得自己已经“接入”了。实际上,DJI SDK 不是一个单独的库,而是按使用场景分成几条完全不同的技术栈:给手机 App 用的 Mobile SDK(MSDK)、给机载计算机用的 Onboard SDK(OSDK)、给云台/相机/扩展接口用的 Payload SDK(PSDK),以及后起的 Cloud API。选错技术栈,后面的开发工作基本全部白做——比如你想让地面站软件控制无人机飞行,结果去接了 PSDK,摄像头是调通了,起飞指令却发不出去,这种方向性错误在项目群和论坛里反复出现。
这篇文章要解决的,是“把 SDK 开发包接到自己的无人机控制项目里”这条主线。我会按一套可复现的路径走:先交代选型依据和 SDK 的架构关系,再落地到环境配置、代码结构、激活与调试,最后把最容易让人翻车的几个坑直接列出来。适合的读者是两类人:一类是想在现有 App 或上位机里加入无人机控制能力,另一类是准备做无人机巡检、测绘、编队等二次开发,但还不确定从哪条 SDK 入口开始。文章不会替你把所有 API 抄一遍,但能让你拿到资源包后,在半天内判断出该看哪个目录、改哪个文件、跑哪个 Demo。
2. DJI SDK 的架构与选型:哪条链路才属于你的场景
2.1 四条技术栈的边界:从 Mobile SDK 到 Cloud API
DJI 的开发包体系,本质上是按照“谁在算、谁在控、谁在传”来划分的。Mobile SDK 面向手机或平板,核心思路是移动设备作为地面站,通过 USB/蓝牙连接遥控器,再由遥控器转发指令给飞行器。Onboard SDK 面向机载计算机,比如某跨平台系统里挂的嵌入式板卡,它直接通过串口或网口与飞行器通信,适合需要在飞行中实时计算路径、脱离地面站自主飞行的场景。Payload SDK 面向挂在无人机上的第三方负载,比如多光谱相机、喊话器、机械臂,它的核心是供电、通信和挂载控制,不是用来控制飞机飞行的。Cloud API 则是把设备接入云端,适合做机队管理和远程航线下发。
对于绝大多数“接入无人机”的开发需求,第一判断点是:你的控制端是不是智能手机 App。如果是,Mobile SDK 是默认答案,因为它封装了链路管理、图传解码、飞行控制权限等大量底层细节,你只需要关心业务逻辑。如果控制端是电脑上的桌面软件,而且需要长时间连飞、超视距作业,Onboard SDK 或 Cloud API 更合适,因为桌面端跑 Mobile SDK 需要依赖安卓模拟器,这个方案的稳定性和延迟都不太理想。我自己做巡检项目时用的是机载方案,但入门阶段最友好的还是 Mobile SDK。
2.2 为什么大多数项目从 Mobile SDK 起步:配套最全、文档闭环
Mobile SDK 的优势不在于性能,而在于完整度。SDK 提供方能给出从激活、升级、模拟器到真机调试的完整工具链,尤其是那个自带模拟环境的助手工具——不插飞机也能模拟起飞、悬停、降落、拍照。这个特性对新手极其关键,因为你可以在办公桌上把整条业务链路跑通,再考虑去外场实飞。
配套的还有 UX SDK(界面库)和 UX SDK 的定制层,它把飞行状态栏、相机控制面板、地图控件都做成现成组件,直接拖进你的 Android 工程。对于要做地面站 App 的团队,这能省掉将近一半的前端工作量。可以和你的 App 工程结合,也可以当独立模块集成。相比从零开始解析 OSDK 的串口协议,Mobile SDK 的学习曲线要平缓得多,这也是我向大多数开发者推荐它作为入口的原因。
2.3 一个完整的接入链路图:App、遥控器、飞机与负载的关系
用文字描述这套链路是这样的:Android 或 iOS 设备通过 USB(Android 推荐使用 USB 转 Type-C 直连)连接到遥控器,遥控器通过自有射频链路与飞行器通信,飞行器上的负载(云台相机)通过机内数据总线返回视频流和遥测数据。Mobile SDK 在你的 App 里扮演的角色,是这条物理链路的“翻译官”——它把手机端的注册、鉴权、指令封装成对方设备能理解的消息,再把回传的遥测和流媒体解码成业务对象。
从代码角度看,这条链路对应三个对象:SDKManager负责初始化和连接状态管理,Aircraft对象代表飞机本身,而Camera、Gimbal、FlightController这些子对象从Aircraft里取出来使用。这种对象模型是理解整个 SDK 的关键——先拿到飞机实例,再从飞机实例里拿各功能模块,而不是每个模块单独创建一个实例。很多开发者第一次接入时习惯性地 new 了一个 Camera,结果无论如何都收不到画面,就是因为这个对象模型没有被遵守。
3. 跑通第一个 Mobile SDK Demo:环境配置、激活与模拟器
3.1 环境清单与版本匹配:别让 gradle 依赖冲突浪费你一下午
首先明确一件事:Mobile SDK 有两个大版本代际,老一代基于旧架构(需要 registerApp 流程),新一代基于 MOP 通道(Mobile Onboard Protocol),两者的工程配置差异很大。建议直接用最新稳定版 SDK 包,并配合其官方示例工程,而不要拿网上旧教程里的 gradle 配置硬套。我一般这样准备环境:
- Android Studio(版本不低于官方示例工程要求的版本,通常对应 AGP 版本)
- 一台 Android 手机或平板(建议用主流机型,避免系统定制过深的 ROM)
- DJI 遥控器与飞行器(开发阶段可用模拟器代替)
- 官方 SDK 开发包(包含示例工程、API 文档和工具)
// app/build.gradle 关键配置 dependencies { implementation 'com.dji:sdk:4.16.1' implementation 'com.dji:uxsdk:4.16.0' compileOnly 'com.dji:dji-sdk-provided:4.16.1' }这里最容易被忽略的是第三行compileOnly。SDK 提供方把一部分类标记为 provided 依赖,意味着编译时可见、打包时不参与,由真机上的 SDK 核心动态库在运行时注入。如果把它写成implementation,APK 体积暴增且大概率出现重复类冲突。项目里遇到“类重复定义”的报错时,优先检查这一行是不是写错了。
3.2 注册 App 与生成开发的 Key:没有这一步,SDK 根本不工作
DJI 的 SDK 有一道强制激活机制:你的 App 必须先在你的开发账号下完成注册,并把包名与 Key 对应关系配置好后,SDK 才能在设备上完成初始化。这个机制不是摆设——它绑定的是 App 的签名证书与包名,换一台电脑打包、换一个包名,Key 就失效。
常见做法是在 SDK 提供方的开发者网站上创建应用,填入你的包名和签名指纹,然后下载对应的 Key 文件放进工程的 assets 目录。注意 Key 文件不只一个,Android 和 iOS 的 Key 不同,别把 iOS 的配置文件放到了 Android 工程里,这种错误编译不会报错,但激活阶段会一直失败。保存好你的签名证书,后续每一次发版都要用它来打包,否则就是“昨天还能连飞机,今天突然激活失败”的翻车现场。
3.3 模拟器跑起来:不炸机也能验证业务流程
这里说的模拟器,不是 Android Studio 自带的虚拟设备,而是 SDK 开发包附带的 DJI Assistant 系列工具中的模拟功能。我建议的步骤是先启动模拟,再让你的 App 连接。
# 假设你已经将示例工程导入 Android Studio # 1. 先用 USB 连接手机到电脑,并开启调试模式 adb devices # 2. 在手机上启动你的 App(无需连接遥控器) adb shell am start -n com.example.dji.demo/.MainActivity # 3. 打开模拟器的模拟功能,选择飞机型号和起始位置 # 等待模拟器界面显示"Simulator Started"此时 App 的 SDK 初始化会回调成功,你可以在手机地图上看到虚拟飞机,尝试执行起飞指令、调整高度、拍照,所有这些都不会有真实电机转动,非常适合验证业务逻辑和界面交互。我通常会在模拟器里跑三条用例:程序启动自动返航、低电量触发降落、航线任务中断后悬停。每一条用例都能在不开机的情况下把边界条件提前暴露出来。
逻辑说明:模拟器实质上是在 SDK 内部注入了虚拟的硬件事件源,所以你的应用代码不需要任何改动就能工作,这比用真飞机调试能节省大量时间。参数层面,你可以设置模拟的卫星数量、风速、电量,这些参数会影响飞控行为,值得花时间试着调一遍,能帮你理解飞控的决策逻辑。
4. 把控制逻辑写进自己的工程:起飞、拍照与航线任务的代码落地
4.1 初始化与连接监听:找出最稳的接入时机
示例工程的初始化代码通常在 Application 或 MainActivity 里执行,但把 SDK 初始化放在什么位置,直接决定你的 App 是否会出现“首屏黑屏”或“连不上飞机”。我推荐的做法是放在 Application 的onCreate里,并且要用一个注册回调来确认初始化结果。
// MyApplication.java public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); // 注意:这里的 context 必须用 ApplicationContext SDKManager.getInstance().init(this, new SDKManager.SDKManagerCallback() { @Override public void onRegisterSuccess() { // 注册成功后,SDK 才会开始连接设备 ToastUtil.show("SDK 注册成功"); } @Override public void onRegisterError(int errorCode) { // 常见错误码:-1001 表示 Key 验证失败 Log.e("SDK", "注册失败,错误码:" + errorCode); } }); } }这里有一个老生常谈但依然高频的坑:不要在 init 之前调用任何产品对象接口,比如SDKManager.getInstance().getAircraft()。因为此时飞机实例还是空的,调用会返回 null,接着你的代码大概率就是 NullPointerException。更稳妥的做法是在连接回调里再取对象,否则就要做空值判断。初始化回调是异步的,千万别指望它在 Activity 的onCreate里同步完成。
4.2 起飞、拍照、返航:三条最常用的指令链路
起飞、拍照、返航是几乎所有无人机业务都会用到的三个基础动作。它们分别对应 FlightController 的startTakeoff、Camera 的startShootPhoto、FlightController 的startGoHome。注意指令发出的前提条件:起飞前必须满足 GPS 信号良好、电池电量充足、飞控状态为可起飞,否则 SDK 会直接拒绝执行并返回错误码。
// 获取飞控与相机实例 FlightController fc = SDKManager.getInstance().getAircraft().getFlightController(); Camera camera = SDKManager.getInstance().getAircraft().getCamera(); // 起飞:异步指令,结果通过回调返回 fc.startTakeoff(new CommonCallbacks.CompletionCallback() { @Override public void onResult(DJIError error) { if (error == null) { Log.d("Demo", "起飞指令已发送"); } else { Log.e("Demo", "起飞失败:" + error.getDescription()); } } }); // 拍照:建议先确认相机模式为拍照模式 camera.setMode(CameraMode.SHOOT_PHOTO, error -> { if (error == null) { camera.startShootPhoto(new CommonCallbacks.CompletionCallback() { @Override public void onResult(DJIError djierror) { // 这里拿到的是拍摄结果 } }); } }); // 返航:返航点默认在起飞时记录,也可以手动设置 fc.startGoHome(new CommonCallbacks.CompletionCallback() { @Override public void onResult(DJIError error) { // error 为 null 表示指令发送成功 } });如果只传了回调而不检查前置条件,指令就会在飞控层面被拦下来,但回调返回的 error 可能是 null——这里特别容易误判:起飞指令发送成功并不代表飞机真的起飞了。要确认执行结果,需要监听 FlightController 的状态更新回调里的isFlying字段,这是除图传外最可靠的飞行状态来源。
4.3 航线任务:用 Mission 对象替代逐条指令
逐条指令适合控制单个动作,但真实巡检、测绘场景需要的是一串带有航点、高度、速度的任务。Mobile SDK 提供 Waypoint Mission 的封装,你只需要构建航点列表、设置任务参数、然后上传并启动。
// 构建航点任务 WaypointMissionOperator operator = new WaypointMissionOperator(); WaypointMission.Builder builder = new WaypointMission.Builder(); // 设置任务参数 builder.waypointCount(5) .maxFlightSpeed(8.0f) .autoFlightSpeed(4.0f) .finishedAction(WaypointMissionFinishedAction.GO_HOME); // 逐个添加航点 LatLng coord1 = new LatLng(22.5359, 113.9304); builder.addWaypoint(new Waypoint(coord1.getLatitude(), coord1.getLongitude(), 100.0f)); // 加载任务并上传 operator.loadMission(builder.build()); operator.uploadMission(error -> { if (error == null) { operator.startMission(error1 -> { // 任务启动结果 }); } });这里的三个参数值得解释:maxFlightSpeed是任务中允许的最大飞行速度,autoFlightSpeed是航点间匀速巡航的速度,两者不相等是正常的,因为飞控在转弯和加减速时会超过巡航速度,如果设成相等,实际航线会变得迟钝。finishedAction决定任务结束后飞机的行为,一般默认返航,但如果你在船上或移动平台上作业,就要换成悬停,否则返航点坐标早已失效,飞机会往错误的方向飞。
5. 避开 DJI SDK 接入的五个高频坑:激活、链路与日志排查
5.1 激活失败:Key 校验正确,却一直报注册失败
现象:SDK 初始化回调返回错误码 -1001 或类似值,提示 App 未注册或 Key 不匹配。
原因:大概率不是 Key 本身的问题,而是包名或签名证书与开发者网站上登记的不一致。常见的情况是:开发者网站登记的是 debug 签名,而你打包时用了 release 签名。还有一种是测试机和正式机的包名不同,SDK 要求运行时包名必须与注册包名完全一致,哪怕是 debug/release 切换也会导致校验不过。
解决:把登记时的签名文件找出来,确认你本地 gradle 配置的signingConfig用的是同一个文件。如果只是临时测试,把android.injected.build.api设为 debug 类型,让包名和签名完全对齐开发者网站上的配置。另外,Key 文件放置路径必须准确,Android 放在 assets 根目录,iOS 放在主 bundle 里,放错位置不会报编译错误,但激活永远失败。
5.2 连接中断:USB 直连时频繁掉线
现象:手机通过 USB 连接遥控器后,App 能正常显示连接状态,但每隔几分钟就断开一次,需要重新拔插才能恢复。
原因:大多是 USB 供电不稳或数据线质量不过关。Mobile SDK 的通信链路对 USB 信号完整性要求较高,尤其是长线或转接头会引入额外的信号衰减。还有一个原因是手机后台对该 USB 设备做电源管理,进入深度休眠时主动断开了数据传输。
解决:换用带屏蔽层的短线(我一般用 20cm 以内的 Type-C 直连线),同时关闭手机的 USB 电源优化开关。在 Android 的开发者选项里,把“USB 配置”固定为 RNDIS 或 MTP 模式,不要让它自动切换。如果你的 App 在后台被系统回收,重连逻辑要写在 SDK 的onComponentChange回调里,而不是依赖 Activity 的生命周期去重新初始化。
5.3 模拟器与真机行为不一致:模拟能起飞,真机却不执行
现象:模拟环境下起飞、拍照都正常,换成真机后指令发送成功但飞机不动,或者相机无反应。
原因:模拟器绕过了很多硬件层面的前置条件检查,比如 GPS 信号强度和 IMU 校准状态。真机环境下,飞控内部有一套更严格的安全逻辑,这些逻辑不会通过你的接口回调体现出来,只在飞机状态的自检项里给出提示。
解决:真机调试时先查看飞机状态面板——在 UX SDK 里有一个FlightAssistant对象,调用它的getGPSState和getIMUState接口,把所有自检项列出来。特别是 IMU 校准和指南针干扰这两项,很容易被忽略。如果状态里显示传感器数据异常,先按飞控手册完成校准,再跑你的代码,不要反复重启 App 来碰运气。
5.4 日志定位:崩溃堆栈只指向 so 库内部
现象:程序崩溃,堆栈信息只显示 native 层符号,看不到任何 Java 层调用路径。
原因:Mobile SDK 有一部分核心逻辑以 native 库形式提供,比如编解码和链路封装。当堆栈落在libSDK.so内部时,常规的 Logcat 堆栈信息不足以定位是哪个 API 触发了问题。
解决:打开 SDK 提供方的日志工具,把日志级别调到 Verbose,它会额外输出 Java 层与 native 层的映射关系。我一般还会抓两份日志:一份是崩溃前的 Logcat 完整信息,另一份是 SDK 自己的日志文件,两者对照,才能找到触发点。记住,这种崩溃通常不是随机发生的,它能稳定复现,就说明你的调用序列里有一个状态没满足。
5.5 版本升级后接口签名变化:旧代码大面积报错
现象:把 SDK 升级到新版本后,原有的接口全部报错,像是换了一套 API。
原因:DJI 在版本迭代中经常调整接口签名,比如把startTakeoff从返回布尔值改成回调对象,把Camera.setMode改成带回调的形式。这种破坏性变更在 major 版本升级时尤其常见。
解决:升级 SDK 前,先对比官方 Release Notes 里标记为 Breaking Changes 的条目。不要直接改依赖版本号然后让编译器帮你找错——那样会很痛苦,因为错误会分散在业务代码的各个角落。我通常先在示例工程里跑通新版本,再对照示例代码逐一修改自己的调用,这样比直接改老工程效率高得多。
6. 真机调试的几个硬核技巧:把日志变成你的后悔药
真机和模拟器最大的区别,在于真机上的时间窗口极其宝贵——电池在消耗、空域在变化,你不能像在模拟器里那样反复试错。因此,真机调试的核心原则是:把一切可观测的状态都记录下来。我习惯在应用启动时初始化一个全局的日志缓冲队列,把所有 SDK 回调、飞行状态、电池信息全部写入同一个本地文件,同时输出到窗口,这样排查问题时不需要临时加日志再重新跑一遍。
一个很实用的技巧是用BlockCanary或自研的帧率统计工具观察 UI 线程是否被图传解码占满。Mobile SDK 默认在 UI 线程回调很多状态更新,如果业务逻辑里做了耗时的操作(比如把视频帧转成 Bitmap 保存),就会把这些回调堵住,进而影响飞控指令的响应速度。我实测过一个场景:拍照回调里把图片写入外部存储,然后紧接着调用了下载任务接口,结果导致后续指令延迟了接近一秒。解决办法是把所有文件操作丢到子线程,只保留 UI 更新在主线程。
还有一个细节容易被忽视:遥控器与手机连接后,手机会被识别为 USB 网络设备,此时移动网络和数据通道的状态可能影响 SDK 的激活校验。我建议在真机调试时,先把手机的网络切到稳定的 Wi-Fi 环境,确保激活服务可达,待 SDK 完成初始化后再切换回实际使用场景。这一点很多人忽略,导致在户外场地激活失败,白跑一趟。
再分享一个关于飞行数据的小技巧:FlightControllerState回调里的getAircraftLocation()返回的是经纬度坐标,但它依赖 GPS 的精度。如果飞机飞在楼宇或峡谷之间,GPS 漂移可能达到十几米,这时候航线任务里的航点、返航路线都会随之偏移。我会在日志里同时记录getSatelliteCount()和getGPSSignalLevel(),当信号等级低于一定阈值时,在界面上弹窗提醒操作员,而不是把决策交给后台自动执行。这样做的好处是把不确定性显式暴露给用户,避免“明明是 GPS 漂移,程序却按错误坐标执行了返航”的尴尬局面。
从那以后我每个真机调试周期都强制走一遍固定流程:检查签名与 Key、确认网络、查看 IMU 状态、跑模拟器用例、再上真机。这么做看起来慢,实际上是最快的路径。希望帮你把接入 DJI SDK 的弯路提前绕开。
本文还有配套的精品资源,点击获取