3.1 在Android应用中使用Mobile SDK
本节将介绍如何从零开始通过Android Studio创建和配置一个Mobile SDK应用程序,以及如何使用Android Mobile SDK注册应用程序、完成实名制认证、绑定和连接无人机等操作。
3.1.1 整合Mobile SDK到Android项目中
为了方便移动开发初学者的学习,本节将从创建一个Android项目开始,详细介绍整合Mobile SDK到Android项目的方法。但是,如果读者手中已经有一个Android项目需要使用Mobile SDK,那么在设置该项目的最低SDK版本选项为5.0或以上版本的基础上,跳过下面的“1. 创建Android应用程序”部分进行配置即可。
1. 创建Android应用程序
打开Android Studio(本书使用4.0.1版本进行演示),并在Android Studio欢迎页面中单击“+ Start a new Android Studio project”(见图3-2)选项创建一个新的项目,并弹出“Create New Project”对话框。
图3-2 Android Studio欢迎页面
在“Select a Project Template”标题页中选择所创建的项目模板“Phone and Tablet”选项卡下的“Empty Activity”选项,并单击【Next】按钮进入下一步,如图3-3所示。
图3-3 “Select a Project Template”标题页
在“Configure Your Project”标题页中输入项目的基本信息:在“Name”选项中输入项目名称“DroneFly”;在“Package name”选项中输入包名“cas.igsnrr.dronefly”;在“Save location”选项中输入项目的保存位置;在“Language”选项中选择使用的编程语言“Java”。由于Mobile SDK所支持的最低Android SDK版本为5.0,因此在“MinimumSDK”选项中选择项目的最低SDK版本“API 21: Android 5.0 (Lollipop)”,如图3-4所示。设置完成后,单击【Finish】按钮确认。
图3-4 “Configure Your Project”标题页
❀ 这里设置的项目包名用于申请应用程序密钥(详见“2.3.3申请应用程序密钥”节内容)。
此时,Android Studio已经根据所选项目模板创建了一个名为“DroneFly”的项目,并自动创建了一个空的Activity,即MainActivity,如图3-5所示。
图3-5 DroneFly项目中的MainActivity
2. 整合Mobile SDK到Android项目
Android Mobile SDK 4.2之前的版本需要导入本地的SDK类库文件到应用程序项目中,但是在最新版本的Android Mobile SDK中只需要使用Maven或Gradle等构建工具,通过代码仓库的方式下载即可。具体的操作方法如下。
(1)在项目资源管理器中,打开“Gradle Scripts”下的“build.gradle (Module: app)”文件,如图3-6所示。
图3-6 DroneFly项目中的build.gradle (Module: app)文件
(2)在该Gradle配置文件的dependencies部分加入Mobile SDK的相关依赖,具体实现如代码3-1所示。注意,本书中所有的代码框均统一使用加粗文字标注新增代码。
代码3-1
由此可见,Android Mobile SDK分为“dji-sdk”和“dji-sdk-provided”两个部分:
① 通过implementation指令依赖的“dji-sdk”包括dji-sdk-4.13.1.aar文件(包含运行时所需要的各种资源与类库)和dji-sdk-4.13.1.pom文件(项目对象模型)。Android Studio会将其编译并打包到最终的应用程序中。
② 通过compileOnly指令依赖的“dji-sdk-provided”包含有dji-sdk-provided-4.13.1sources.jar、dji-sdk-provided-4.13.1.jar、dji-sdk-provided-4.13.1-javadoc.jar、dji-sdk-provided 4.13.1.pom四个文件,仅在代码编辑和编译时支持使用。
❀ 在使用Android Mobile SDK时,必须要加入“lifecycle-extensions”依赖,否则在注册应用程序时会出现“闪退”,并报出“ java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/lifecycle/ProcessLifecycleOwner;”错误。
另外,对于“dji-sdk”依赖部分,可通过“exclude module”代码去除部分Module以降低移动应用程序安装包的大小。例如,通过“exclude module: 'library-anti-distortion'”代码可去除抗畸变(Anti Distortion)模块,通过“exclude module: 'fly-safe-database'”代码可去除限飞数据库模块,这两个模块在编译后的应用程序中会占用很大的存储空间,具体实现如代码3-2所示。
代码3-2
抗畸变(Anti Distortion)模块是对御2 Pro、御2 Zoom和御2 Enterprise Zoom三款无人机特别设计的用于原始视频流的解码与失真校正类库,去除该模块后将不支持上述无人机的图传视频解码。当从“dji-sdk”依赖中去除限飞数据库模块后,在应用程序注册(调用SDK管理器的registerApp方法)时会自动下载最新的限飞数据库。
(3)在Gradle配置文件的android部分添加动态链接库的打包选项,以避免编译错误,具体实现如代码3-3所示。
代码3-3
此时,选择Android Studio菜单栏中的【File】-【Sync Project with Gradle Files】菜单更新Gradle配置,并通过仓库下载依赖项。稍等片刻后,当Android Studio状态栏出现“Gradle sync finished in …”提示时说明Gradle配置完成。
❀ 由于Android Mobile SDK的类库体积很大,因此可能会在编译时出现“Error: Cannot fit requested classes in a single dex file (# methods: 72725 > 65536)”错误。此时,可以在上述Gradle配置文件中,在android部分的defaultConfig部分内添加“multiDexEnabled true”代码解决这个问题。
通过以下方法可以检查是否通过Gradle成功下载Mobile SDK的相关依赖:单击Android Studio菜单栏中的【File】-【Project Structure…】菜单(快捷键:Ctrl+Alt+Shift+S)打开“ Project Structure ”对话框,如图3-7所示。在该对话框中,选择左侧的“Dependencies”选项,并在Modules列表中选择“app”。此时,如果在右侧的依赖声明列表中找到“dji-sdk:4.13.1”与“dji-sdk-provided:4.13.1”依赖,则说明Gradle配置成功。
图3-7 “Project Structure”对话框
3. AndroidManifest.xml设置与初始化类库
打开DroneFly项目的AndroidManifest.xml文件,在manifest根标签内添加以下权限、特征等XML声明语句,具体实现如代码3-4所示。
代码3-4
在代码3-4中,需要通过应用程序包名(已在代码3-4中用下画线标注)在大疆开发者网站申请的应用程序密钥(详见“2.3.3申请应用程序密钥”节内容),并将密钥填入“<输入应用程序密钥>”位置。
另外,uses-permission标签表示应用程序所需要使用的用户权限;uses-feature标签表示应用程序的软硬件需求特征(一般用于应用上线商店);uses-library标签表示应用程序所需要连接的共享库;名为“DJIAoaControllerActivity”的activity标签用于通过USB设备连接无人机遥控器;名为“DJIGlobalService”的service标签为应用程序提供Mobile SDK的全局服务。
4. 初始化类库
初始化类库,即在应用程序的Application对象中,通过com.secneo.sdk.Helper类的install(Application app)方法注册Mobile SDK,具体的方法如下。
(1)在“cas.igsnrr.dronefly”类库中单击鼠标右键,选择【New】-【Java Class】创建Java类,弹出如图3-8所示的“New Java Class”对话框。
图3-8 “New Java Class”对话框
(2)在“New Java Class”对话框中,在“Name”选项中输入类名“DroneApplication”;在“Superclass”选项中输入其父类“android.app.Application”;确认“Package”选项的内容为“cas.igsnrr.dronefly”;其他选项保持默认值,单击【OK】按钮创建DroneApplication类。
(3)在DroneApplication类中复写attachBaseContent(Context paramContext)方法,并调用Helper的install(Application app)方法,具体实现如代码3-5所示。
代码3-5
(4)在AndroidManifest.xml中注册DroneApplication对象,具体实现如代码3-6所示。
代码3-6
此时,初始化类库的代码已经完成。在每次启动DroneFly应用程序时,都会实例化一个DroneApplication对象,并调用attachBaseContent方法的代码。
值得注意的是,Helper的install(Application app)方法不能在MainActivity中执行。如果错误地将该代码添加到了MainActivity的OnCreate(Bundle savedInstanceState)方法中或者直接缺失上述代码,则当使用Mobile SDK的类库时会报“java.lang.NoClassDefFoundError:Failed resolution of: Ldji/sdk/sdkmanager/DJISDKManager;”错误。
5. 申请用户权限
在MainActivity.java中申请用户权限,如代码3-7所示。
代码3-7
通过上述代码,在MainActivity被创建时会检查PERMISSION_LIST列表中的权限是否被正确授权。如果存在没有被授权的权限,则会将其放置在missingPermission列表对象中。最后,如果missingPermission列表不为空,则调用requestPermissions()方法申请缺失的权限。
此时调试运行程序,会在应用程序启动时弹出权限确认提示框(界面会依Android发行版类型的不同而有所不同),如图3-9所示。
图3-9 权限确认提示框
此时,用户需要全部单击【允许】按钮后才可正常使用Mobile SDK。
3.1.2 注册应用程序与连接无人机
本节将介绍通过Android Mobile SDK注册应用程序和连接无人机的方法。
1. 注册应用程序
首先介绍通过SDK管理器的registerApp(…)方法注册应用程序,以获得使用Mobile SDK的授权的方法。在使用该方法时,还需要实现registerApp(…)方法的SDK管理器回调(SDKManagerCallback),以判断应用程序是否注册成功。
SDK管理器回调共包括以下几个回调函数。
• onRegister(DJIError djiError):注册应用程序回调方法。通过djiError变量回调注册信息。当djiError对象为空时,则说明注册成功;当djiError对象不为空时,该对象包含了注册错误的相关说明。
• onProductConnect(BaseProduct baseProduct):无人机连接回调方法。当移动设备连接到无人机时回调该函数,其中baseProduct即为连接的大疆产品对象。
• onProductDisconnect():无人机失去连接回调方法。当移动设备与无人机断开连接时回调该函数。注意,此处的无人机失去连接不是指无人机与遥控器之间的信号丢失,而是指移动设备与遥控器断开连接。
• onProductChanged(BaseProduct baseProduct):无人机连接变化回调方法。当移动设备所连接的无人机发生变化时回调,其中baseProduct即为变化后的无人机对象。
• onComponentChange(BaseProduct.ComponentKey componentKey, BaseComponent oldCom ponent, BaseComponent newComponent):无人机组件变化回调方法。该回调函数包括3个参数,即组件键(componentKey)、变化前组件对象(oldComponent)和变化后组件对象(newComponent)。组件键用于声明组件类型,为枚举类型变量,包括相机(CAMERA)、云台(GIMBAL)、飞行控制器(FLIGHTCONTROLLER)等类型。
• onInitProcess(DJISDKInitEvent djisdkInitEvent, int process):初始化进程回调方法。在注册应用程序时实际上也在进行Mobile SDK的初始化,包括资源初始化、限飞数据库初始化等。通过djisdkInitEvent对象的getInitializationState()方法即可获取初始化状态InitializationState。InitializationState为一枚举变量,包括在开始初始化时(STARTTOINITIALIZE)、资源加载完成时(ASSETSLOADED)和限飞数据库加___载完成(DATABASELOADED_)三个状态定义。另外,通过process变量可获得初始化进度,其值域为[0,100]。
• onDatabaseDownloadProgress(long process, long sum):限飞数据库下载进度回调方法。当在Gradle依赖中使用了“exclude module: 'fly-safe-database'”语句时,应用程序内部不包括限飞数据库,则需要在第一次初始化应用程序时下载完整的数据库。当现有的限飞数据库过时时,也需要下载最新的数据库。该方法的process参数为已经下载的字节数,而sum参数为限飞数据库总共的字节数。在限飞数据库全部下载完成后,才会在上述的onInitProcess(…)方法中回调ASSETSLOADED_类型的djisdkInitEvent对象。
另外,registerApp(…)方法必须要使用异步调用,不能在主线程中直接使用,例如,可以通过在界面中增加一个按钮,然后在其单击事件监听器中使用registerApp(…)方法。为了完成在应用启动时自动注册,也可直接通过Runnable类创建一个新的线程,并将注册应用程序的代码放入到该线程中。下面我们在MainActivity.java文件中onCreate(…)生命周期方法中注册应用程序,具体实现如代码3-8所示。
代码3-8
此时,编译并运行应用程序。当弹出MainActivity的界面后会依次弹出“STARTTO__INITIALIZE”“ASSETSLOADED_”“应用程序注册成功!API Key successfully registered”等提示,如图3-10所示。另外,读者可以在Android Studio的Logcat面板中查看到下载限飞数据库的提示,且这些下载提示出现在“ASSETSLOADED”弹_出之前。
图3-10 注册应用程序成功界面
通过onRegister(DJIError djiError)方法回调参数djiError的getDescription()方法可获取注册失败的详细描述信息,常见的错误如下所示。
• For first time registration, app should be connected to Internet:首次注册时,需要连接互联网对应用程序密钥进行验核。
• The app key submitted is invalid. Please check the app key you provided:密钥输入错误,需要检查密钥的正确性。
• The metadata received from server is invalid, please reconnect to the server and try:从服务器传递回来的元数据校验失败,此时可尝试重新连接服务器再次注册。
• The app key reached maximum number of activations, please contact <dev@dji.com> for help:免费版账号申请的密钥注册应用程序的次数超出了20次的限制。
• The app key is prohibited, please contact <dev@dji.com> for help:密钥被吊销,此时需要联系大疆官方寻求帮助。
另外,应用程序是否注册成功可通过SDK管理器的hasSDKRegistered()方法确认。
2. 连接无人机
连接与断开无人机分别通过SDK管理器的startConnectionToProduct()和stopConnection ToProduct()方法实现。为了方便起见,可以将startConnectionToProduct()方法调用放置在注册应用程序成功的代码中。另外,在SDKManagerCallback回调中的onProductConnect(…)、onProductDisconnect(…)和onComponentChange(…)方法中添加一些提示代码,具体实现如代码3-9所示。
代码3-9
❀ 上述连接方法仅限于大疆无人机产品和通过Wi-Fi连接的手持云台相机产品。对于采用蓝牙连接的手持云台相机,则需要通过SDK管理器的getBluetoothProductConnector()方法搜索蓝牙设备连接器,并分别通过蓝牙设备连接器对象的searchBluetoothProducts(…)、connect(…)和disconnect(…)方法搜索、连接和断开蓝牙设备。
编译运行程序并连接大疆无人机,稍等片刻应用程序会先后回调到onComponentChange(…)方法和onProductConnect(…)方法,并弹出类似如图3-11所示的连接提示。当无人机断开连接时,会回调onProductDisconnect(…)方法,并弹出如图3-12所示的断开连接提示。
图3-11 无人机连接提示
图3-12 无人机断开连接提示
3.1.3 实名制认证与绑定无人机
在中国大陆地区使用Mobile SDK,除了通过密钥注册应用程序,还需要登录DJI账号进行实名制认证。如果无人机没有与某个DJI账号绑定,那么还需要提示用户跳转到DJI GO、DJI GO 4等应用程序中绑定无人机。如果应用程序没有经过实名制认证,或者无人机没有绑定,那么Mobile SDK无法接收图传信息,并且其飞行高度限制在30m,飞行半径显示在100m之内。
本节仅适用于针对在中国大陆地区开发Mobile SDK应用程序,包括如下几个方面的内容。
(1)DJI账号的登录与退出。
(2)获取与监听应用程序激活状态和无人机绑定状态。
(3)跳转到DJI GO或DJI GO 4的方法。
上述内容涉及两个主要的管理器对象,分别为用户账号管理器UserAccountManager和应用程序激活管理器AppActivationManager。由于实名制认证的结果和状态通过应用程序激活管理器的getAppActivationState()方法获取,因此实名制认证状态也就是应用程序的激活状态。
为了完成上述功能,我们首先修改MainActivity的用户界面,在其中增加一些文本框和按钮,即打开activity_main.xml文件添加如代码3-10所示的内容。
代码3-10
1. 登录DJI账号
实名制认证(激活应用程序)是通过登录DJI账号实现的。具体来说,是通过用户账号管理器的logIntoDJIUserAccount(…)方法弹窗登录账号的。现在,我们在MainActivity.java中创建一个initUI()方法,用于初始化UI界面。然后,在MainActivity的onCreate(…)方法中调用initUI()方法。最后,在initUI()方法中获取【登录DJI账号】按钮的实例,并监听其单击事件。在单击【登录DJI账号】按钮后调用用户账号管理器的logIntoDJIUserAccount(…)方法。具体的代码如代码3-11所示。
代码3-11
在上述代码中,logIntoDJIUserAccount(…)方法的回调中包含了onSuccess(…)和onFailure(…)两个回调函数。前者在登录成功时回调,后者在登录失败时回调。
2. 退出DJI账号
与登录DJI账号类似,使用用户账号管理器的logoutOfDJIUserAccount(…)方法即可退出DJI账号。在MainActivity类的initUI()方法的末尾添加如代码3-12所示的内容。
代码3-12
但是,与登录DJI账号不同的是,退出DJI账号方法的回调对象中只包括onResult(DJIError djiError)一个回调函数。当djiError参数为空时,说明退出成功;反之,则可通过djiError对象的getDescription()方法获取其错误的具体信息。
3. 获取应用程序激活状态(实名制认证状态)信息
应用程序激活状态由应用程序激活管理器的应用程序激活状态AppActivationState枚举类型决定,包括不支持(NOT_SUPPORTED)、需要登录激活(LOGIN_REQUIRED)、已激活(ACTIVATED)和未知(UNKNOWN)四种状态。当未连接无人机时,激活状态为UNKNOWN;当网络无法访问且未被激活时,激活状态为NOT_SUPPORTED;当连接无人机且网络状态正常,激活状态为LOGIN_REQUIRED时,则需要登录DJI账号进行在线实名制认证。通过应用程序激活管理器的getAppActivationState()方法即可获得应用程序激活状态对象。在MainActivity类的initUI()方法的末尾添加如代码3-13所示的内容。
代码3-13
4. 获取无人机绑定状态信息
无人机绑定状态由应用程序激活管理器的无人机绑定状态AircraftBindingState枚举类型确定,包括不支持(NOT_SUPPORTED)、初始化(INITIAL)、已绑定(BOUND)、无须绑定(NOT_REQUIRED)、未绑定(UNBOUND)、未绑定且无法同步(UNBOUND_BUT_CANNOT_SYNC)和未知(UNKNOWN)等类型。这里的同步是指当前移动应用程序中无人机绑定状态信息在无人机之间的同步。
通过应用程序激活管理器的getAircraftBindingState()方法即可获得无人机的绑定状态信息。在MainActivity类的initUI()方法的末尾添加如代码3-14所示的内容。
代码3-14
编译并运行程序,单击【登录DJI账号】按钮即可弹出用于DJI用户登录的窗口,如图3-13所示。连接无人机后,单击【获取应用激活状态】按钮和【获取无人机绑定状态】按钮可分别弹出应用程序激活状态(实名制认证)和无人机绑定状态的提示框。
图3-13 登录DJI账号(Android)
只有当应用程序激活状态为ACTIVATED,并且无人机绑定状态为BOUND时,无人机的各项功能才可正常使用。
当无人机绑定状态为UNBOUND或者UNBOUND_BUT_CANNOT_SYNC时,开发者需要引导用户跳转到DJI GO或DJI GO 4软件进行无人机绑定。
跳转到DJI GO的代码如下:
跳转到DJI GO 4的代码如下:
执行上述代码后,如果已安装DJI GO(或DJI GO 4)软件,则会通知用户自动跳转到DJI GO(Android)(见图3-14),否则会弹出相应的“未安装DJI Go 4?”提示。应用跳转失败如图3-15所示。
图3-14 跳转到DJI GO(Android)
图3-15 应用跳转失败
❀ 由于DJI GO和DJI GO 4应用程序所针对的无人机类型不同,所以开发者应当根据无人机的具体类型决定跳转到DJI GO还是DJI GO 4。无人机的类型可通过无人机对象的getModel()方法获取。
5. 应用程序激活状态监听器与无人机绑定监听器
除了通过应用程序激活管理器对象的getAppActivationState()方法获取应用程序激活状态信息,还可通过应用程序激活监听器AppActivationListenter实时监听并获取应用程序的激活状态信息。通过应用程序激活管理器的addAppActivationStateListener(…)方法或removeAppActivationStateListener(…)方法可分别添加或移除此类监听器。
类似地,通过无人机绑定状态监听器AircraftBindingStateListener可实时监听并获取无人机绑定状态信息。通过应用程序激活管理器对象的addAircraftBindingStateListener()方法或removeAircraftBindingStateListener()方法可分别添加或移除上述监听器。
具体的实现方法如下所示。
(1)在MainActivity.java文件中添加如下成员变量:
(2)在initUI()方法中初始化文本视图成员变量。在新创建的initListener()方法中初始化应用程序激活状态监听器和无人机绑定状态监听器,并且initListener()方法由onCreate(…)方法负责调用,如代码3-15所示。
代码3-15
应用程序激活监听器和无人机绑定监听器都采用了onUpdate(…)方法,并且分别传递回来了AppActivationState对象和AircraftBindingState对象。为了将状态信息显示在相应的文本框中,上述代码通过runOnUiThread(…)方法跳转到UI线程对文本框进行更新。由于涉及线程跳转,在AppActivationState对象和AircraftBindingState对象前需要通过final关键词进行修饰。
(3)复写MainActivity的OnDestory()生命周期方法,并移除应用程序激活监听器和无人机绑定监听器,以防止出现当MainActivity对象被回收后,应用程序激活管理器单例对象还保留着MainActivity中的监听器方法从而出现错误,如代码3-16所示。
代码3-16
❀ 在许多情况下,很难定位和查找Mobile SDK的内部异常。因此,为了避免出现这些问题,开发者一定要养成良好的编程习惯。其中很重要的一个方面就是要管理好Mobile SDK中的各类监听器,有添加(add)就要有移除(remove),有设置(set)就要有取消设置(unset)。
编译并运行程序,DJI账号登录界面如图3-13所示,连接无人机后,应用程序激活状态和无人机绑定状态如图3-16所示。
图3-16 应用程序激活状态与无人机绑定状态(Android)