开发流程

以下介绍如何在Android Studio上搭建开发环境,并使用视图控件进行开发。

新建工程

在Android Studio中新建工程,操作如下:

图:创建工程
图:添加工程信息

配置Gradle

以下与其它Android开发环境类似,若您熟悉该流程,可跳过该步骤。

7.0以下版本

此处使用gradle6.5,若要使用7.0版本及其以上的gradle,注意环境配置差异。

现打开Project Structure,在Project栏中指定对应版本。

图:设置工程结构
图:设置Gradle版本

项目build.gradle文件参考:

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
  repositories {
    google()
    mavenCentral()
    maven { url 'https://jitpack.io' }
  }
  dependencies {
    classpath "com.android.tools.build:gradle:4.1.2"
  }
}
allprojects {
  repositories {
  maven { url 'https://jitpack.io' }
    google()
    jcenter()
 }
}

项目settings.gradle文件参考:

rootProject.name = "项目工程名称"
include ':模块名称'

7.0版本

第一步,打开Android Studio项目级“build.gradle”文件,添加Maven代码库。 在“buildscript > repositories”中配置Maven仓地址。

buildscript {
  repositories {
    google()
    jcenter()
    maven {url "https://..." }
  }
}

第二步,打开项目级“settings.gradle”文件,配置Maven仓地址。

dependencyResolutionManagement {
  repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
  repositories {
    repositories {
      google()
      jcenter()
      maven {url "https://..." }
    }
  }
}

7.1及以上版本

第一步,打开Android Studio项目级“build.gradle”文件,添加Maven代码库。 在“buildscript > repositories”中配置Maven仓地址。

buildscript {
  repositories {
    google()
    jcenter()
    maven {url "https://..." }
  }
}

第二步,打开项目级“settings.gradle”文件,配置Maven仓地址。

pluginManagement {
  repositories {
    repositories {
      google()
      jcenter()
      maven {url "https://..." }
    }
  }
}
dependencyResolutionManagement {
  repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
  repositories {
    repositories {
      google()
      jcenter()
      maven {url "https://..." }
    }
  }
}

配置模块Gradle

以下与其它安卓开发环境类似。若您熟悉该流程,可跳过该步骤,关注jar与so导入即可。

配置模块的build.gradle,引入产品包中的so库和jar包。

将so库和jar包拷贝至项目如下位置。

图:添加so库和jar包到项目工程

本地视频开发

不引入DJI MSDK,适用于本地视频地图的开发。

build.gradle参考如下:

plugins {
  id 'com.android.application'
}
android {
  compileSdkVersion 28
  defaultConfig {
    applicationId "com.supermap.appuav_scene3d"
    minSdkVersion 24
    targetSdkVersion 28
    versionCode 1
    versionName "1.0"
    ndk{
      //abiFilters 'arm64-v8a'//使用64位的so
      abiFilters 'armeabi-v7a'
    }
  }
  buildTypes {
    release {
      minifyEnabled false
      proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
  }
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
  sourceSets {
    main {
      jniLibs.srcDirs = ['libs']
    }
  }
}
dependencies {
  implementation 'androidx.appcompat:appcompat:1.3.0'
  implementation 'com.google.android.material:material:1.4.0'
  implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
  //注意:当前版本的sceneform-sm
  implementation files('libs\\sceneform-sm-11.0.1.aar')
  implementation files('libs\\com.supermap.data_v1100.jar')
  implementation files(libs\\com.supermap.ar_v1100.jar')
  //按需添加以下内容
  //implementation files(libs\\com.supermap.mapping_v1100.jar')
  //implementation files('libs\\com.supermap.realspace_v1101.jar')
}

实时视频开发

通过引入DJI MSDK接入大疆无人机的实时视频。在依赖配置上需要额外依赖DJI的SDK。

在build.gradle文件的dependencies节点下添加如下内容:

implementation 'androidx.multidex:multidex:2.0.0'
implementation 'com.squareup:otto:1.3.8'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation ('com.dji:dji-uxsdk:4.16', {
  /**
  * Uncomment the following line to exclude amap from the app.
  * Note that Google Play Store does not allow APKs that include this library.
  */
  exclude group: 'com.amap.api'
  exclude group: 'com.mapbox.mapboxsdk'
})
compileOnly ('com.dji:dji-sdk-provided:4.16.1')

此外,为了防止DJI MSDK内部可能引起的依赖冲突。需要在build.gradle文件的android节点下,添加如下内容:

//use DJI SDK,添加 packagingOptions 以防冲突。
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'
  //exclude 'META-INF/dji-sdk-lib_aar.kotlin_module'
}

完整的build.gradle文件参考如下:

plugins {
  id 'com.android.application'
}
android {
  compileSdk 28

  defaultConfig {
    applicationId "com.supermap.uavfly"
    minSdk 24
    targetSdk 28
    versionCode 1
    versionName "1.0"
    ndk{
      abiFilters 'armeabi-v7a'
    }
  }
  buildTypes {
    release {
      minifyEnabled false
      proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
  }
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
  sourceSets {
    main {
      jniLibs.srcDirs = ['libs']
    }
  }
  //use DJI SDK,添加 packagingOptions 以防程序出现意外崩溃。
  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'
    //exclude 'META-INF/dji-sdk-lib_aar.kotlin_module'
  }
}
dependencies {
  implementation 'androidx.appcompat:appcompat:1.1.0'
  implementation 'com.google.android.material:material:1.1.0'
  implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
  //use DJI SDK
  implementation 'androidx.multidex:multidex:2.0.0'
  implementation 'com.squareup:otto:1.3.8'
  implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
  implementation ('com.dji:dji-uxsdk:4.16', {
    /**
    * Uncomment the following line to exclude amap from the app.
    * Note that Google Play Store does not allow APKs that include this library.
    */
    exclude group: 'com.amap.api'
    exclude group: 'com.mapbox.mapboxsdk'
  })
  compileOnly ('com.dji:dji-sdk-provided:4.16.1')
  implementation 'pub.devrel:easypermissions:2.0.1'//权限申请
  implementation files('libs\\sceneform-sm-11.0.1.aar')
  implementation files('libs\\com.supermap.data_v1100.jar')
  implementation files(libs\\com.supermap.ar_v1100.jar')
  //按需添加以下内容
  //implementation files(libs\\com.supermap.mapping_v1100.jar')
  //implementation files('libs\\com.supermap.realspace_v1101.jar')
}

配置Android清单

每个Android应用都需要一个名为AndroidManifest.xml的程序清单文件,这个清单文件名是固定的并且放在每个Android应用的根目录下。

正常情况下,采用本地视频的开发方式,添加上手机的读写权限、网络相关权限即可。若是采用实时视频的开发方式,则需要连接无人机。

这可参考大疆的应用激活示例,链接如下: https://github.com/DJI-Mobile-SDK-Tutorials。

以下是采用USB连接方式的安卓清单(AndroidMainifest.xml)示例。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  package="com.supermap.uavfly2">
  <!-- 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.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.WRITE_EXTERNAL_STORAGE" />
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  <uses-permission android:name="android.permission.READ_PHONE_STATE" />
  <uses-permission android:name="android.permission.RECORD_AUDIO" />
  <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" />
  <!-- SDK requirement permission ↑ ↑ ↑-->
  <application
    android:name="com.supermap.ar.areffect.uavfly.DJIFlyApplication"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name_uav"
    android:supportsRtl="true"
    android:theme="@style/Theme.UavFly"
    tools:replace="android:label">
  <!-- ↓ ↓ ↓ ↓ ↓ ↓ DJI SDK 配置 ↓ ↓ ↓ ↓ ↓ ↓ -->
  <uses-library android:name="com.android.future.usb.accessory" />
  <uses-library
    android:name="org.apache.http.legacy"
    android:required="false" />
  <meta-data
    android:name="com.dji.sdk.API_KEY"
    android:value="这里是DJI APP KEY" />
  <activity
    android:name="dji.sdk.sdkmanager.DJIAoaControllerActivity"
    android:theme="@android:style/Theme.Translucent"
    tools:ignore="IntentFilterExportedReceiver">
  <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"
    tools:ignore="Instantiatable"></service>
  <!-- ↑ ↑ ↑ ↑ ↑ ↑ DJI SDK 配置 ↑ ↑ ↑ ↑ ↑ ↑-->
  <activity
    android:name="com.supermap.MainActivity"
    android:screenOrientation="landscape"
    android:theme="@style/Theme.UavFly2"
    tools:ignore="IntentFilterExportedReceiver">
  <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
  </application>
</manifest>

至此,开发环境已搭建完成。

编写代码

布局文件

(1). 在layout中添加视图控件(也可通过代码创建视图控件,这与使用RealativeLayout的方式一致)。

图:在layout中添加视图控件

(2). 在layout中添加一个按钮,用于控制视频的播放与暂停。

图:在layout中添加按钮

(3). 在layout中添加一个时间轴,便于视频播放位置的调节。

图:在layout中添加时间轴

应用代码

在MainActivity中编写应用程序代码如下:

/**
 * 无人机视频简单开发模板
 */
public class MainActivity extends AppCompatActivity {
  private UAVVideoEffectView effectView;
  private UAVVideoTimeLine timeLine;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
    Manifest.permission.INTERNET,
    Manifest.permission.ACCESS_NETWORK_STATE,
    Manifest.permission.CHANGE_WIFI_STATE,
    Manifest.permission.READ_EXTERNAL_STORAGE,
    Manifest.permission.READ_PHONE_STATE,
  }, PackageManager.PERMISSION_GRANTED);
  Environment.setLicensePath(StartActivity.LICENSE);
  Environment.initialization(this);
  setContentView(R.layout.activity_main_scene3d);
  //布局控件
  effectView = findViewById(R.id.ef_view);
  timeLine = findViewById(R.id.uav_video_timeline);
  //...读取数据(载入视频数据集)
  //绑定时间轴,必须在读取数据后绑定
  timeLine.bindView(effectView);
  //指定视频到初始位置
  effectView.getMediaPlayer().seekTo(0);
  //UAV视图实时更新监听事件
  effectView.addOnUpdateListener(new EffectView.OnUpdateListener() {
    @Override
    public void onUpdate() {
      //画面更新时回调
    }
  });
}
@Override
protected void onResume() {
  super.onResume();
  effectView.onResume();
}
@Override
protected void onPause() {
  super.onPause();
  effectView.onPause();
}
@Override
protected void onDestroy() {
  effectView.onDestroy();
  super.onDestroy();
}
//基础事件
public void exchangeAction(View view) {
  if (effectView.isPlaying()){
    effectView.pause();
  }else {
    effectView.start();
  }
}
}

注:参考“视频接入”部分介绍加载视频。

运行工程

选择真机或者模拟器,点击“运行”,将程序安装到设备中。

运行效果如下图所示。

图:工程运行效果