本节全篇为大疆 Mobile SDK 安卓教程 部分,ios教程参见 IOS教程 .
开始使用UX SDK
在本教程中,您将学习如何使用DJI Android UX SDK和DJI Android SDK轻松创建功能齐全的mini-DJI Go app,具有标准的DJI Go UI和功能。在本教程结束时,您将拥有一个可用于显示相机FPV视图,检查飞机状态,拍摄照片,录制视频等功能的app。
您可以从 Github Page 下载教程的最终示例项目。
我们以Mavic Pro和Nexus 5为例进行演示。有关自定义iPhone设备布局的更多详细信息,请查看教程的Github示例项目。让我们开始吧!
介绍
DJI UX SDK是由UI元素组成的可视化SDK。它可以帮助您简化基于Android的DJI Mobile SDK应用程序的创建。与DJI Go类似的设计,UI元素允许您在应用程序和DJI应用程序之间创建一致的UX。
此外,由于易于使用,UX SDK让您更专注于业务和应用程序逻辑。
由于DJI UX SDK是构建在DJI Mobile SDK和VideoPreviewer之上的,所以在应用程序开发中需要将它们一起使用。
关于DJI UX SDK的深入学习,请访问 UX SDK Introduction 。
在中国进行应用激活和飞机绑定
对于在中国使用的DJI SDK移动应用程序,需要激活应用程序并将飞机绑定到用户的DJI帐户。
如果未激活应用程序,未绑定飞机(如果需要)或使用旧版SDK(<4.1),则将禁用所有** 摄像机实时流 **,并且飞行将限制为直径100米的区域和30米高度,以确保飞机保持在视线范围内。
要了解如何实现此功能,请查看本教程 Application Activation and Aircraft Binding .
导入DJI UX SDK
- 创建新项目,名称为 "UXSDKDemo"
- 输入域名和包名(这里我们使用 "com.dji.uxsdkdemo" )
- 选择手机版,设置最低SDK版本为
API 19: Android 4.4 (KitKat)
- 选择 "Empty Activity"
- 其他默认
配置 Gradle 脚本
打开 "build.gradle(Module: app)" 并添加以下代码:
apply plugin: 'com.android.application'
android {
...
defaultConfig {
...
}
...
packagingOptions{
doNotStrip "*/*/libdjivideo.so"
doNotStrip "*/*/libSDKRelativeJNI.so"
doNotStrip "*/*/libFlyForbid.so"
doNotStrip "*/*/libduml_vision_bokeh.so"
doNotStrip "*/*/libyuv2.so"
doNotStrip "*/*/libGroudStation.so"
doNotStrip "*/*/libFRCorkscrew.so"
doNotStrip "*/*/libUpgradeVerify.so"
doNotStrip "*/*/libFR.so"
pickFirst 'lib/*/libstlport_shared.so'
pickFirst 'lib/*/libRoadLineRebuildAPI.so'
pickFirst 'lib/*/libGNaviUtils.so'
pickFirst 'lib/*/libGNaviMapex.so'
pickFirst 'lib/*/libGNaviData.so'
pickFirst 'lib/*/libGNaviMap.so'
pickFirst 'lib/*/libGNaviSearch.so'
exclude '/lib/armeabi-v7a/libChineseFontPkg.so'
exclude 'META-INF/rxjava.properties'
}
}
dependencies {
...
//compile ('com.dji:dji-uxsdk:4.9')
//provided ('com.dji:dji-sdk-provided:4.9')
implementation 'com.dji:dji-uxsdk:4.9'
compileOnly 'com.dji:dji-sdk-provided:4.9'
}
在上面的代码中,我们实现了以下功能:
- 添加
packagingOptions
以防程序出现意外崩溃。 - 添加
compile
和provided
(新版AS为implementation
和compileOnly
)依赖用来导入最新的DJI Android UX SDK和SDK Maven依赖项。
完成以上步骤后,点击 Sync Now 并等待Gradle Project 同步完成。
双重检查Maven依赖
在Android Studio菜单中选择 File->Project Structure ,打开 "Project Structure" 窗口。然后选择“app”模块并单击 Dependencies 选项卡。你应该看到最新的DJI Android UX SDK compile和sdk provided的依赖项已经导入。
使用UX SDK构建默认布局
现在,让我们继续打开 "activity_main.xml" 文件,并用替换为以下代码:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_blue"
android:orientation="horizontal"
tools:context=".MainActivity">
<!-- Widget to see first person view (FPV) -->
<dji.ux.widget.FPVWidget
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<dji.ux.widget.FPVOverlayWidget
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<dji.ux.workflow.CompassCalibratingWorkFlow
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<!-- Widgets in top status bar -->
<LinearLayout
android:id="@+id/signal"
android:layout_width="match_parent"
android:layout_height="25dp"
android:background="@color/dark_gray"
android:orientation="horizontal">
<dji.ux.widget.PreFlightStatusWidget
android:layout_width="238dp"
android:layout_height="25dp"/>
<dji.ux.widget.FlightModeWidget
android:layout_width="103dp"
android:layout_height="22dp"/>
<dji.ux.widget.GPSSignalWidget
android:layout_width="44dp"
android:layout_height="22dp"/>
<dji.ux.widget.VisionWidget
android:layout_width="22dp"
android:layout_height="22dp"/>
<dji.ux.widget.RemoteControlSignalWidget
android:layout_width="38dp"
android:layout_height="22dp"/>
<dji.ux.widget.VideoSignalWidget
android:layout_width="38dp"
android:layout_height="22dp"/>
<dji.ux.widget.WiFiSignalWidget
android:layout_width="22dp"
android:layout_height="20dp"/>
<dji.ux.widget.BatteryWidget
android:layout_width="96dp"
android:layout_height="22dp"
custom:excludeView="singleVoltage"/>
<dji.ux.widget.ConnectionWidget
android:layout_marginTop="3dp"
android:layout_width="18dp"
android:layout_height="18dp"/>
</LinearLayout>
<LinearLayout
android:id="@+id/camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/signal"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:background="@color/dark_gray"
android:orientation="horizontal">
<dji.ux.widget.AutoExposureLockWidget
android:layout_width="30dp"
android:layout_height="30dp"/>
<dji.ux.widget.FocusExposureSwitchWidget
android:layout_width="30dp"
android:layout_height="30dp"/>
<dji.ux.widget.FocusModeWidget
android:layout_width="30dp"
android:layout_height="30dp"/>
<dji.ux.widget.config.CameraConfigISOAndEIWidget
android:layout_width="60dp"
android:layout_height="30dp"/>
<dji.ux.widget.config.CameraConfigShutterWidget
android:layout_width="60dp"
android:layout_height="30dp"/>
<dji.ux.widget.config.CameraConfigApertureWidget
android:layout_width="60dp"
android:layout_height="30dp"/>
<dji.ux.widget.config.CameraConfigEVWidget
android:layout_width="60dp"
android:layout_height="30dp"/>
<dji.ux.widget.config.CameraConfigWBWidget
android:layout_width="70dp"
android:layout_height="30dp"/>
<dji.ux.widget.config.CameraConfigStorageWidget
android:layout_width="130dp"
android:layout_height="30dp"/>
</LinearLayout>
<dji.ux.widget.ManualFocusWidget
android:layout_below="@id/camera"
android:layout_alignLeft="@id/camera"
android:layout_marginLeft="25dp"
android:layout_marginTop="5dp"
android:layout_width="42dp"
android:layout_height="218dp"
tools:ignore="RtlHardcoded"/>
<dji.ux.widget.RemainingFlightTimeWidget
android:layout_alignParentTop="true"
android:layout_marginTop="18dp"
android:layout_width="match_parent"
android:background="@color/transparent"
android:layout_height="20dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
android:padding="12dp">
<dji.ux.widget.dashboard.DashboardWidget
android:id="@+id/Compass"
android:layout_width="405dp"
android:layout_height="91dp"
android:layout_marginRight="12dp"
tools:ignore="RtlHardcoded"/>
</LinearLayout>
<!--Take off and return home buttons on left -->
<LinearLayout
android:layout_width="40dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="12dp"
android:orientation="vertical">
<dji.ux.widget.TakeOffWidget
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginBottom="12dp"/>
<dji.ux.widget.ReturnHomeWidget
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="12dp"/>
</LinearLayout>
<dji.ux.widget.controls.CameraControlsWidget
android:id="@+id/CameraCapturePanel"
android:layout_alignParentRight="true"
android:layout_below="@id/camera"
android:layout_width="50dp"
android:layout_height="213dp"
tools:ignore="RtlHardcoded"/>
<dji.ux.panel.CameraSettingExposurePanel
android:layout_width="180dp"
android:layout_below="@id/camera"
android:layout_toLeftOf="@+id/CameraCapturePanel"
android:background="@color/transparent"
android:gravity="center"
android:layout_height="263dp"
android:visibility="invisible"
tools:ignore="RtlHardcoded"/>
<dji.ux.panel.CameraSettingAdvancedPanel
android:layout_width="180dp"
android:layout_height="263dp"
android:layout_below="@id/camera"
android:layout_toLeftOf="@+id/CameraCapturePanel"
android:background="@color/transparent"
android:gravity="center"
android:visibility="invisible"
tools:ignore="RtlHardcoded"/>
<!-- Pre-flight checklist panel -->
<dji.ux.panel.PreFlightCheckListPanel
android:layout_width="400dp"
android:layout_height="wrap_content"
android:layout_below="@id/signal"
custom:excludeItem="ESCStatus"
android:visibility="gone"/>
</RelativeLayout>
在上面的xml文件中,我们实现了以下UI:
-
首先,我们添加
dji.ux.widget.FPVWidget
anddji.ux.widget.FPVOverlayWidget
元素来显示第一人称视图(FPV)。 -
接下来,在屏幕顶部,我们创建一个 LinearLayout 去组成顶部状态栏的小组件,如
PreFlightStatusWidget
,FlightModeWidget
,GPSSignalWidget
,RemoteControlSignalWidget
等 -
此外,我们在状态栏下面创建另一个 LinearLayout 去组成相机配置和配置小组件的,如
AutoExposureLockWidget
,FocusExposureSwitchWidget
,CameraConfigISOWidget
,CameraConfigStorageWidget
等。此外,我们添加dji.ui.widget.RemainingFlightTimeWidget
元素,用来显示顶部状态栏小组件集下方的剩余飞行时间组件。 在顶部状态栏下方,我们添加一个
RemainingFlightTimeWidget
用来显示剩余飞行时间。-
在屏幕的底部,我们添加另一个 LinearLayout 来分组
DashboardWidget
。它包括圆形CompassWidget
(罗盘),DistanceHomeWidget
(home距离),HorizontalVelocityWidget
(水平速度),DistanceRCWidget
(遥控器距离),VerticalVelocityWidget
(垂直速度) andAltitudeWidget
(高度) 如下所示: -
在屏幕的左侧,我们添加了一个 LinearLayout 对
TakeOffWidget
和ReturnHomeWidget
进行分组,将其显示为两个按钮。 -
在屏幕右侧,我们添加
dji.ui.widget.controls.CameraControlsWidget
元素以创建一个CameraSettingAdvancedPanel
以显示相机控件组件。点击顶部的菜单按钮将触发CameraSettingAdvancedPanel
。点击中间的切换按钮将在 拍照 和 录像 之间切换相机模式。点击底部按钮将触发CameraSettingExposurePanel
组件显示和隐藏。 -
为了添加
CameraSettingExposurePanel
,我们添加dji.ui.panel.CameraSettingExposurePanel
元素并配置其属性。 -
为了添加
CameraSettingAdvancedPanel
,我们添加dji.ui.panel.CameraSettingAdvancedPanel
元素并配置其属性。 -
最后,我们添加
dji.ui.panel.PreFlightCheckListPanel
元素来创建PreFlightCheckListPanel
。当用户按下时PreFlightStatusWidget
,它将显示在顶部状态栏下方。
完成上述步骤后,打开 "colors.xml" 文件并替换以下内容:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<color name="background_blue">#242d34</color>
<color name="transparent">#00000000</color>
<color name="dark_gray">#80000000</color>
</resources>
此外,让我们打开 "styles.xml" 文件并替换以下内容:
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="@style/Base.Theme.AppCompat.Light.NoActionBar">
</style>
<!-- by Android Stduio 3.3.2 -->
<!--<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">-->
<!--</style>-->
</resources>
在DJI UX SDK的帮助下,在您自己的应用程序中简单直接的实现了标准的DJI Go UI和功能。
应用程序注册
现在让我们使用您从DJI开发者网站申请的 App Key 注册我们的应用程序。如果您不熟悉App Key,请查看 Get Started 。
实现MApplication和DemoApplication
新建一个 "MApplication" 类,然后用以下代码替换:
package com.dji.uxsdkdemo;
import android.app.Application;
import android.content.Context;
import com.secneo.sdk.Helper;
public class MApplication extends Application {
private DemoApplication demoApplication;
@Override
protected void attachBaseContext(Context paramContext) {
super.attachBaseContext(paramContext);
Helper.install(MApplication.this);
if (demoApplication == null) {
demoApplication = new DemoApplication();
demoApplication.setContext(this);
}
}
@Override
public void onCreate() {
super.onCreate();
demoApplication.onCreate();
}
}
在这里,我们重写 attachBaseContext()
方法,然后添加 Helper.install(MApplication.this);
,此外, 重写 onCreate()
方法来调用 DemoApplication
类中的 onCreate()
方法。
Note: 由于现在需要在使用之前加载一些SDK类,所以加载过程由
Helper.install()
完成。开发人员需要在使用任何SDK功能之前调用此方法。如果不这样做,将导致意外崩溃。
完成上述步骤后,我们继续创建 DemoApplication
类,并在本教程的 sample project 中用同名文件替换代码,这里我们解释一下重要部分:
@Override
public void onCreate() {
super.onCreate();
mHandler = new Handler(Looper.getMainLooper());
//Check the permissions before registering the application for android system 6.0 above.
int permissionCheck = ContextCompat.checkSelfPermission(getApplicationContext(), android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
int permissionCheck2 = ContextCompat.checkSelfPermission(getApplicationContext(), android.Manifest.permission.READ_PHONE_STATE);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || (permissionCheck == 0 && permissionCheck2 == 0)) {
//This is used to start SDK services and initiate SDK.
DJISDKManager.getInstance().registerApp(getApplicationContext(), mDJISDKManagerCallback);
} else {
Toast.makeText(getApplicationContext(), "Please check if the permission is granted.", Toast.LENGTH_LONG).show();
}
/**
* When starting SDK services, an instance of interface DJISDKManager.DJISDKManagerCallback will be used to listen to
* the SDK Registration result and the product changing.
*/
mDJISDKManagerCallback = new DJISDKManager.SDKManagerCallback() {
//Listens to the SDK registration result
@Override
public void onRegister(DJIError error) {
if(error == DJISDKError.REGISTRATION_SUCCESS) {
DJISDKManager.getInstance().startConnectionToProduct();
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "Register Success", Toast.LENGTH_LONG).show();
}
});
loginAccount();
} else {
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "Register Failed, check network is available", Toast.LENGTH_LONG).show();
}
});
}
Log.e("TAG", error.toString());
}
@Override
public void onProductDisconnect() {
Log.d("TAG", "onProductDisconnect");
notifyStatusChange();
}
@Override
public void onProductConnect(BaseProduct baseProduct) {
Log.d("TAG", String.format("onProductConnect newProduct:%s", baseProduct));
notifyStatusChange();
}
@Override
public void onComponentChange(BaseProduct.ComponentKey componentKey, BaseComponent oldComponent,
BaseComponent newComponent) {
if (newComponent != null) {
newComponent.setComponentListener(new BaseComponent.ComponentListener() {
@Override
public void onConnectivityChange(boolean isConnected) {
Log.d("TAG", "onComponentConnectivityChanged: " + isConnected);
notifyStatusChange();
}
});
}
Log.d("TAG",
String.format("onComponentChange key:%s, oldComponent:%s, newComponent:%s",
componentKey,
oldComponent,
newComponent));
}
};
}
我们在这里实现了几个功能:
- 我们重写了
onCreate()
方法,用来检查权限,并且调用了DJISDKManager
的registerApp()
方法用来首先注册应用程序。 - 初始化
SDKManagerCallback
变量,并实现了他的两个接口方法。你可以使用onRegister()
方法去检查App注册状态和显示文本信息通知用户。当产品连接或者断开连接的时候,onProductConnect()
andonProductDisconnect()
方法将被调用。此外,我们使用onComponentChange()
方法去检查组件改变,并调用notifyStatusChange()
方法去通知这次改变。
创建 MApplication
类后,请在 DemoApplication
类的 onCreate()
方法中初始化 DJI Android SDK 类变量,通过调用 Helper.install()
来完成SDK类的加载。
更多详情,请查看本教程的github示例。
修改AndroidManifest文件
完成上述步骤后,我们打开 "AndroidManifest.xml" 文件,并在 application 元素上添加一下元素:
<!-- DJI SDK need permission -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature
android:name="android.hardware.usb.host"
android:required="false" />
<uses-feature
android:name="android.hardware.usb.accessory"
android:required="true" />
在这里,我们请求的应用程序必须被授权,以便它正确注册DJI SDK。此外,我们还声明了应用程序所使用的相机和USB硬件。
此外,在 application
元素开头添加 android:name=".MApplication"
。
<application
android:name=".MApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
再然后,我们在 "MainActivity" activity 上面添加以下元素:
<!-- DJI SDK -->
<uses-library android:name="com.android.future.usb.accessory" />
<meta-data
android:name="com.dji.sdk.API_KEY"
android:value="Please enter your App Key here." />
<activity
android:name="dji.sdk.sdkmanager.DJIAoaControllerActivity"
android:theme="@android:style/Theme.Translucent">
<intent-filter>
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
android:resource="@xml/accessory_filter" />
</activity>
<service android:name="dji.sdk.sdkmanager.DJIGlobalService"></service>
<!-- DJI SDK -->
在上面的代码中,你可以用你申请的 App Key 代替 value 属性中的 "Please enter your App Key here." 。 至于 "accessory_filter.xml" 文件,你可以在教程示例程序中找到它(sdk4.9中已经有了,无需再次添加)。
最后,更新 "MainActivity" activity 元素,如下所示:
<activity android:name=".MainActivity"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
在以上代码中,多了一行强制横屏的代码。
处理MainActivity
最后,让我们打开 "MainActivity.java" 文件,替换如下代码:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// When the compile and target version is higher than 22, please request the
// following permissions at runtime to ensure the
// SDK work well.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.VIBRATE,
Manifest.permission.INTERNET, Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.WAKE_LOCK, Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_NETWORK_STATE, Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.CHANGE_WIFI_STATE, Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS,
Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.SYSTEM_ALERT_WINDOW,
Manifest.permission.READ_PHONE_STATE,
}
, 1);
}
setContentView(R.layout.activity_main);
}
}
在 onCreate()
方法中,我们在运行时请求几种权限来确保编译和目标SDK版本高于22时可以正常工作。(从Android 6.0 = API 23开始需要动态权限)
现在,我们来构建和运行项目,并安装到你的Android设备上。如果一些ok,当你注册成功时,将会看到 “Register Success” 文本框。
连接到飞机并运行该项目
现在,请检查这个连接移动设备并运行应用程序指南来运行应用程序,并根据我们目前完成的应用程序来试试UX SDK的mini-DJI Go特性!
Connect Mobile Device and Run Application
如果您能够看到实时视频提要并像这样测试这些特性,那么恭喜您!使用DJI UX SDK就是这么简单。
摘要
在本教程中,您已经学习了如何使用DJI Android UX SDK和DJI Android SDK轻松创建一个功能齐全的mini-DJI Go app,具有典型的DJI Go ui和功能。希望你喜欢!