Skip to content

Android SDK 接入指南

1. 导入前提

SDK 仓库:Android Release Note

系统要求

  • Android 6.0及以上系统,API Level >= 23
  • armeabi-v7a or arm64-v8a
  • Android Studio 3.6.3及以上版本
  • Android Build Tools Ver. > 23

2. 导入SDK

2.1. 申请你的 AppKey 及 AppSecret

先注册登录 ARWorld 创作者平台,在【我的应用】模块【创建应用】。

目前仅开放移动端 SDK,如有接入 SIM、Nreal、EZXR AR Glasses 的需求请联系易现工作人员或产品kelexin@ezxr.com

关键填写项目:

  • 适配设备,指应用适配何种设备,我们会生成对应平台的授权信息;

  • 存储方式,指应用的内容存储在何处,我们在平台提供存储服务,你也可使用阿里云的 OSS 服务,填写 阿里云 OSS 的相关配置即可。

    完成应用创建后,再进入应用详情页,填写你要接入的 SDK 版本。

    2.2. SDK 库导入方式

    易现 AR-World SDK Android 版现在暂时只提供 离线aar 方式接入。请通过 Gitlab 获取离线的SDK aar 包。

    将 insightar_world_unity.aar 文件复制到工程 app 模块的 libs 目录下,打开 app/build.gradle,在 dependencies 中添加以下引用:

java
repositories {
    ...
    flatDir {
        dirs 'libs'
    }
}
dependencies {
    //...
    //use insight aar
    implementation (name:'insightar_world_unity',ext:'aar')
    //...
}

如果你使用的是 Android Studio 3.0 以下版本,请将 gradle中implementation 关键字替换为compile

3. 配置SDK

3.1. 权限配置

在 AndroidManifest.xml 添加以下 permission 及 feature

xml
<uses-feature android:name="android.hardware.camera" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!--存储拍照或视频等媒体文件使用-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!--录屏功能使用-->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!--通过网络以及GPS获取设备当前位置信息,定位需求使用-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

当然在Android 6.0以上系统需要代码动态开启相应 Dangerous 权限

由于 SDK 内部的 min-sdk-version 是 23。如果外部的 min-sdk-version 小于 23,会带来构建问题,需要外部 APP 在 AndroidManifest.xml 中配置:

问题描述:

Manifest merger failed : uses-sdk:minSdkVersion 17 cannot be smaller than version 21 declared in library [:arinsight] /Users/zfxu/work/androidstudio_workspace/InsightSDK/arinsight/build/intermediates/manifests/full/debug/AndroidManifest.xml as the library might be using APIs not available in 17

xml
<uses-sdk tools:overrideLibrary="com.netease.insightar"></uses-sdk>

3.2. 混淆配置

在 app 目录 proguard-rules.pro 中添加以下防混淆代码:

java
-keep class com.netease.insightar.** { *; }
-dontwarn com.netease.insightar.**

3.3. AndroidManifest 配置

3.3.1. 配置 theme

由于洞见 ARWorld SDK-Android 现只支持 AR 视图全屏的样式展示,所以业务方需要在展示 AR 的 activity 中配置全屏的 theme。可由业务方自己定义,也可以使用 SDK 内部的 theme(推荐)。

xml
<style name="InsightActivityTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:windowFullscreen">true</item>
    <item name="android:windowNoTitle">true</item>
</style>

在 app 清单文件 AndroidManifest.xml 中配置,例:

xml
<activity
    android:name=".activity.DemoActivity"
    android:theme="@style/InsightActivityTheme" />

3.3.2. 使用第三方 AR

本版本支持 ARCore 如需使用,在 build.gradle 中添加

dependencies { ...... //google arcore implementation 'com.google.ar:core:1.23.0' }

并在 AndroidManifest.xml 中添加

xml
<application>
    <meta-data
            android:name="com.google.ar.core"
            android:value="optional" />
 </application>

本版本支持 HUAWEI AR Engine ,如需使用请在工程中添加 HUAWEI AR Engine 的库

dependencies {
        ......
        //华为ar
                implementation 'com.huawei.hms:arenginesdk:2.15.0.4'
    }

建议 SDK 接入方支持同时支持上述两种 AR,我们的 SDK 会根据机型采取适当的 AR 运行策略。

4. 接入SDK

4.1. 启用SDK

在 app 的 Application.java 类中初始化 SDK 并传入 AppKey 及 AppSecret。

开发者也可以在 Activity 中调用此方法,但是必须要保证在调用洞见 SDK 其他方法前调用此方法,否则会报错。

java
public class DemoApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        //...
        EzArInsight.init(this, APP_KEY, APP_SECRET, false);
        //...
    }
}

参数:

  1. 应用上下文 context
  2. 开发者申请的 AppKey
  3. 开发者申请的 AppSecret
  4. 当前版本默认传入 false

4.2. 验证 SDK 可用性

SDK 内部对 AR 能力的可用性验证包含两部分:

  • 验证当前手机是否支持 AR(含离线和在线两种方式)
  • 判断 so 动态库是否加载成功
java
 boolean isArSupport = EzArInsight.isArSupport(Context context);

isArSupport(Context context)会验证系统版本,传感器,CPU 架构是否支持 AR。

参数:

设备上下文 context

在线验证设备是否支持 AR(推荐使用该方法)

java
EzArInsight.isArSupportOnline(Context context, OnArInsightDeviceSupportCallback callback);

该方法除了验证系统版本,传感器,CPU 架构是否支持 AR 外,还会联网验证黑名单系统,将极个别不能友好支持 AR 体验的设备也排除掉。

  • 检查 so 库是否成功
java
EzArInsight.checkArLibrary() throws ArInsightLibraryNotFoundException;

checkArLibrary 会检查当前 SDK 内部的 So 文件是否加载成功。如果失败会以异常的方式throw。

开发者可以根据异常判断出当前的错误日志。

4.3. SDK快速接入

目前洞见 ARWorld SDK 提供以 Landmark 为基础 AR 场景的 AR 体验:

主场景 AR 体验:通过传入主场景的 CID,调用 SDK 的 API 下载对应的主场景资源包,并通过 SDK 的自定义 AR View 展示 AR 效果。

在集成 SDK 后,开发者需要关注 SDK 中的两个主要功能类 EzArInsight.class 和EzArWorldView.class。两者主要的功能如下:

EzArInsight:主要负责业务数据(如资源包)的获取以及初始化、判断是否支持AR等功能接口的支持。

EzArWorldView:主要负责 AR 场景的加载与渲染。

注意:承载 EzArWorldView 所在 Activity 需要单独开辟一个进程进行加载,否则应用退出时,会将整个 App 杀掉。

4.3.1 创建 EzArWorldView

在Activity/Fragment的XML 布局文件中创建 EzArWorldView 控件:

xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ArWorldActivity">

    <com.netease.insightar.EzArWorldView
        android:id="@+id/ezArWorldView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

也可通过代码手动创建 EzArWorldView 实例。

在 Activity/Fragment 的生命周期方法中调用 EzArWorldView 对应的生命周期方法。下面以Activity 为例:

java
public class ArWorldActivity extends AppCompatActivity {

    private EzArWorldView mArWorldView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ar_world);

        mArWorldView = findViewById(R.id.ezArWorldView);
        
        // 注意:这里需要向请求相机、存储、录音的Dangerous权限,否则EzArWorldView会显示异常或功能异常
        // TODO:request dangerous permissions of CAMERA WRITE_EXTERNAL_STORAGE and RECORD_AUDIO 
        
        // create EzArWorldView
        mArWorldView.create();
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (null != mArWorldView) {
            // EzArWorldView start
            mArWorldView.start();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (null != mArWorldView) {
            // EzArWorldView resume
            mArWorldView.resume();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (null != mArWorldView) {
            // EzArWorldView pause
            mArWorldView.pause();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (null != mArWorldView) {
            // EzArWorldView stop
            mArWorldView.stop();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (null != mArWorldView) {
            // EzArWorldView destroy
            mArWorldView.destroy();
        }
    }
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        // To support deep linking, we need to make sure that the client can get access to
        // the last sent intent. The clients access this through a JNI api that allows them
        // to get the intent set on launch. To update that after launch we have to manually
        // replace the intent with the one caught here.
        setIntent(intent);
        mArWorldView.onNewIntent(intent);
    }
    // Low Memory Unity
    @Override public void onLowMemory() {
        super.onLowMemory();
        mArWorldView.lowMemory();
    }

    // Trim Memory Unity
    @Override public void onTrimMemory(int level) {
        super.onTrimMemory(level);
        mArWorldView.onTrimMemory(level);
    }

    // This ensures the layout will be correct.
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mArWorldView.onConfigurationChanged(newConfig);
    }

    // Notify Unity of the focus change.
    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        mArWorldView.onWindowFocusChanged(hasFocus);
    }

    // For some reason the multiple keyevent type is not supported by the ndk.
    // Force event injection by overriding dispatchKeyEvent().
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (event.getAction() == KeyEvent.ACTION_MULTIPLE) return mArWorldView.injectEvent(event);
        return super.dispatchKeyEvent(event);
    }

    // Pass any events not handled by (unfocused) views straight to UnityPlayer
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        return mArWorldView.injectEvent(event);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
            finish();
            return false;
        }
        return mArWorldView.injectEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return mArWorldView.injectEvent(event);
    }

    /*API12*/
    public boolean onGenericMotionEvent(MotionEvent event) {
        return mArWorldView.injectEvent(event);
    }
}

4.3.2 注册 AR 消息监听

通过注册 OnArInsightResultListener 来获取算法以及渲染引擎的状态和消息:

java
 public class ArWorldActivity extends AppCompatActivity implements OnArInsightResultListener
                                  ,OnArInsightCaptureFileSavedCallback
                                  OnArInsightRecordCallback, OnArInsightUnityLoadingCallback
                                  ,RequestPermissionCallback{

    private EzArWorldView mArWorldView;

    ...

    @Override
    protected void onStart() {
        super.onStart();
        if (null != mArWorldView) {
            // EzArWorldView start
            mArWorldView.start();
            // register AR states and messages listener
            mArWorldView.registerArInsightResultListener(this);
            //注册拍照监听
            mArWorldView.registerArInsightCaptureListener(this);
            //注册录屏监听
            mArWorldView.registerArRecordListener(this);
            //注册ar场景加载进度回调监听
            mArWorldView.registerArInsightUnityLoadingListener(this);
            //注册权限申请回调
            mArSceneView.setRequestPermissionCallback(this);
        }
        
    }

    ...
       
    @Override
    protected void onPause() {
        super.onPause();
        if (null != mArWorldView) {
            // EzArWorldView pause
            mArWorldView.pause();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (null != mArWorldView) {
            mArWorldView.stop();
            // unreigster AR listener
            mArWorldView.unregisterArInsightResultListener(this);
            mArWorldView.unRegisterArInsightCaptureListener(this);
            mArWorldView.unRegisterArRecordListener(this);
            mArWorldView.unRegisterArInsightUnityLoadingListener(this);
        }
        
    }
    ...
    
    @Override
    public void on3dEventMessage(IAr3dEventMessage iAr3dEventMessage) {
        // 具体实现见 4.1节「SDK与AR内容交互」
    }

    @Override
    public void onArEngineResult(EzArAlgResult ezArAlgResult) {
        // 除非需求方面需要特别关注算法状态,否则不需要实现
    }

    @Override
    public void onArRenderResult(EzArRenderResult ezArRenderResult) {
        // 除非需求方面需要特别引擎加载算法的状态,否则不需要实现
    }
    
     @Override public void onCaptureFinish(String s) {
        //showErrorView("截屏成功:"+s);
    }

    @Override public void onCaptureError(String s) {
        //showErrorView("截屏失败:"+s);
    }
    
    @Override public void onRecordStart() {
        showErrorView("开始录屏");
    }

    @Override public void onRecordFinish(String s, @Nullable Bitmap bitmap) {
        showErrorView("录屏成功:" + s);
        //保存到相册.可以调用下面的代码,也可以改成自己的实现
        MediaSaveUtil.saveMediaFile(this, s, MediaSaveUtil.MEDIA_TYPE_VIDEO);
    }

    @Override public void onRecordError(String s) {
        showErrorView("录屏失败:" + s);
    }
   @Override
    public void requestPermissionAccess(int type) {
        //unity申请权限请求
    }
    @Override public void onUnityLoading(int loadType, int downloadProgress) {
        if (loadType == 0) {//主场景资源加载,要覆盖全屏
            showLoadingView("资源加载中" + downloadProgress + "%", loadType);
        } else {//蒙层布局,一般用于子场景资源加载,如果没有该场景,可以忽略
            showLoadingView("内容加载中" + downloadProgress + "%", loadType);
        }
    }

    @Override public void onUnityLoadingComplete(int loadType) {
        hideLoadingView(loadType);
    }
    
}

4.3.3 获取AR场景数据

EzArWorldView 加载 AR 场景资源时需要本地有资源文件,而资源文件是通过 EzArInsight 类从洞见的平台上进行获取:

java
  public class ArWorldActivity extends AppCompatActivity {

    private EzArWorldView mArWorldView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ar_world);

        mArWorldView = findViewById(R.id.ezArWorldView);
        
        ...
            
        loadData();
    }
    
    ...
    
    private void loadData() {
        // 下载AR资源,CID需要通过后台数据获取,来源有两个:
        // 1.通过调用EzArInsight.getLandmarkListAll()方法获取当前APP_KEY能获取到的资源列表
        // 2.写死固定的CID
        EzArInsight.loadLandmarkDataByCid(CID, new SceneResourceFetchCallback(ArWorldActivity.this));
    }

    private static class SceneResourceFetchCallback implements OnArInsightDataCallback<EzArLandmark> {
        // 使用弱引用包裹Callback,避免异步回调引起内存泄漏
        private WeakReference<ArWorldActivity> mActivityRef;

        SceneResourceFetchCallback(ArWorldActivity activity) {
            mActivityRef = new WeakReference<>(activity);
        }

        @Override
        public void onDownloadStart(String id) {
            ArWorldActivity ins = mActivityRef.get();
            if (null == ins) {
                return;
            }
            // 显示开始下载交互UI,由外部自定义
            ins.showLoadingView("资源加载中",0);
        }

        @Override
        public void onDownloadProgress(String id, int progress) {
            ArWorldActivity ins = mActivityRef.get();
            if (null == ins) {
                return;
            }
            // 显示下载进度交互UI,由外部自定义
            ins.showLoadingProgress(progress);
        }

        @Override
        public void onDownloadError(String id, int code, String msg) {
            ArWorldActivity ins = mActivityRef.get();
            if (null == ins) {
                return;
            }
            // 显示下载失败交互UI,由外部自定义
            ins.showErrorView();
        }

        @Override
        public void onDownloadPause(String id) {
            // 可以不用实现
        }

        @Override
        public void onDownloadResume(String id) {
            // 可以不用实现
        }

        @Override
        public void onDownloadSucc(String id) {
            // 可以不用实现
        }

        @Override
        public void onNetworkDataSucc(@Nullable EzArLandmark ezArLandmark) {
            // 获取后台取到的相关数据模型信息
        }

        @Override
        public void onNetworkDataError(int code, String msg) {
            ArWorldActivity ins = mActivityRef.get();
            if (null == ins) {
                return;
            }
            // 显示数据获取错误交互UI,由外部自定义
            ins.showErrorView();
        }

        @Override
        public void onUnzipStart(String id) {
            ArWorldActivity ins = mActivityRef.get();
            if (null == ins) {
                return;
            }
            // 显示正在解压交互UI,由外部自定义
            ins.showUnzipStartView();
        }

        @Override
        public void onUnzipEnd(String id, String destFilePath) {
            ArWorldActivity ins = mActivityRef.get();
            if (null == ins) {
                return;
            }
            // 解压完成加载AR场景资源
            ins.showAr(id, destFilePath);
        }
    }
    
}

4.3.4 加载AR场景资源

在 unZipEnd(String id,String destFilePath) 回调方法的参数当中,destFilePath

表示的是本地资源下载后的路径,需要调用 showAr(id, destFilePath) 方法进行 AR 场景资源的加载。

java
public class ArWorldActivity extends AppCompatActivity implements OnArInsightResultListener{

    private EzArWorldView mArWorldView;

    ...

    private void showAr(String id, String destFilePath) {
        if (TextUtils.isEmpty(destFilePath) || !new File(destFilePath).exists()) {
            showErrorView("本地资源包异常...");
            return;
        }
        try {
            mArWorldView.doArShowView(id, destFilePath);
            showLoadingView("场景资源加载中...", 0);
        } catch (ArResourceNotFoundException e) {
            showErrorView("资源包异常...");
            e.printStackTrace();
        }
    }
    
    ...
}

到这一步,顺利的话,基本可以正常加载一个 AR 场景了。

4.3.5 加载AR场景资源进度回调

跟 Unity 游戏一样,加载 AR 场景需要有段时间,这个时候 SDK 提供了相关进度的回调接口供上层接入方使用。

EzArWorldView 类对外提供两个方法供外部注册和反注册监听

java
//注册加载进度监听器
public void registerArInsightUnityLoadingListener(OnArInsightUnityLoadingCallback listener);
//反注册加载进度监听器
public void unRegisterArInsightUnityLoadingListener(OnArInsightUnityLoadingCallback listener);

OnArInsightUnityLoadingCallback.java 是回调接口,当外部通过 EzArWorldView 的方式集成时,需要自己手动实现。而且为了防止内存泄露,请外部在 Activity 或者 Fragment 的对称生命周期内依次调用 register 和 unregister 方法(onStart/onStop 或者 onResume/onPause)。

java
OnArInsightUnityLoadingCallback.java

/**
 * 对外提供的拍照结果回调接口,如果接入方需要关心,可以设置监听器
 * 所有的回调都是在UI线程中
 */
public interface OnArInsightUnityLoadingCallback {
     /**
     * 加载进度回调
     *
     * @param loadType 加载类型 0:全屏,1:透明非全屏蒙层
     * @param downloadProgress 下载进度
     *
     */
    void onUnityLoading(int loadType,int downloadProgress);

    /**
     * 加载完成
     * @param loadType 加载类型 0:全屏,1:非全屏
     */
    void onUnityLoadingComplete(int loadType);
}

下面介绍如何在SDK中使用拍照和录制视频的功能。

4.4. 使用拍照与录制视频

4.4.1关于 SDK 接入方调用拍照功能

EzArWorldView 类对外提供两个方法供外部注册和反注册监听

java
//注册拍照监听器
public void registerArInsightCaptureListener(OnArInsightCaptureFileSavedCallback listener);
//反注册拍照监听器
public void unRegisterArInsightCaptureListener(OnArInsightCaptureFileSavedCallback listener);

OnArInsightCaptureFileSavedCallback.java 是回调接口,当外部通过 EzArWorldView 的方式集成时,需要自己手动实现。而且为了防止内存泄露,请外部在 Activity 或者 Fragment 的对称生命周期内依次调用 register 和 unregister 方法(onStart/onStop 或者 onResume/onPause)。

java
OnArInsightCaptureFileSavedCallback.java

/**
 * 对外提供的拍照结果回调接口,如果接入方需要关心,可以设置监听器
 * 所有的回调都是在UI线程中
 */
public interface OnArInsightCaptureFileSavedCallback {
     /**
     * 拍照结束
     * @param savePath 图片保存路径
     */
    void onCaptureFinish(String savePath);

    /**
     * 拍照失败
     * @param msg
     */
    void onCaptureError(String msg);
}

4.4.2关于 SDK 接入方调用录屏功能

SDK 内部提供 AR 场景的录屏功能,具体接入步骤如下:

由于录屏的行为是异步的,外部开发者需要在界面初始化完成(onViewCreated)和界面退出(onDestroyView)中完成录屏监听器的注册和反注册。当然如果外部是以Activity的形式接入。

也可以在视图的对称生命周期内完成监听器的注册和反注册,例如 onResume - onPause 以及 onStart - onStop。

java
protected void registerArRecordListener(OnArInsightRecordCallback listener);
protected void unRegisterArRecordListener(OnArInsightRecordCallback listener);

注意:如果不注册监听器,前台不会收到录屏的回调。只会保存到文件夹中。由于 Unity 录屏的特殊性,在 App 生命周期里只要执行了 onPause 方法后,SDK 会自动停止录屏。

触发开始录屏

java
protected void doArStartRecord();

触发结束录屏

java
protected void doArStopRecord();

OnArInsightRecordCallback.java

java
/**
 * ar 界面录屏的回调
 * 由于录屏的行为是异步,所以相应的结果都是通过回调给出,
 * 所有的回调都是在主线程中回调
 */
public interface OnArInsightRecordCallback {
    /**
     * 录屏开始
     */
    void onRecordStart();
    /**
     * 录屏结束
     * @param savePath 录屏文件存储的路径
     * @param thumbnailBitmap 缩略图
     */
    void onRecordFinish(String savePath, @Nullable Bitmap thumbnailBitmap);
    /**
     * 录屏产生错误
     * @param msg
     */
    void onRecordError(String msg);
}

5. 使用SDK

5.1 SDK 与 AR 内容交互

在制作完 AR 内容后,内容里会存在与上层业务的交互。SDK 提供相关的接口供外部实现或者重载。

强烈建议开发者实现和【AR内容交互】的所有消息协议,便于后续业务需求扩展需要

EzArWorldView 类对外提供两个方法供外部注册和反注册监听。

java
//注册内容及算法交互监听器
public void registerArInsightResultListener(OnArInsightResultListener listener);
//反注册内容及算法交互监听器
public void unregisterArInsightResultListener(OnArInsightResultListener listener);

OnArInsightResultListener.java 是回调接口,当外部通过 EzArWorldView 的方式集成时,需要自己手动实现。而且为了防止内存泄露,请外部在 Activity 或者 Fragment 的对称生命周期内依次调用 register 和 unregister 方法(onStart/onStop 或者 onResume/onPause)。

java
OnArInsightResultListener.java

/**
 * 对外提供的ar结果回调接口,如果接入方需要关心,可以设置监听器
 * 所有的回调都是在UI线程中
 */
public interface OnArInsightResultListener {
        /**
     * 回调绘制引擎运行的结果
     * @param message 回调的数据结构
     */
    void on3dEventMessage(IAr3dEventMessage message);
    /**
     * 回调当前ar算法运行状态
     * @param result
     */
    void onArEngineResult(EzArAlgResult result);}

    /**
     * 回调当前ar渲染引擎运行状态
     *
     * @param result 渲染引擎回调状态结果
     */
    void onArRenderResult(EzArRenderResult result);

IAr3dEventMessage.java 是一个接口,不同的 type,SDK 内部会回调不同的数据 model。

java
IAr3dEventMessage.java

public interface IAr3dEventMessage {
    
    int TYPE_EXECUTE_SCRIPTS = 100;
    int TYPE_CLOSE_AR        = 101;
    int TYPE_RELOAD_AR       = 102;
    int TYPE_OPEN_URL        = 108;
    int TYPE_SCREENSHOT      = 110;
    int TYPE_SHARE           = 111;
    int TYPE_SCREEN_RECORD_START = 112;
    int TYPE_SCREEN_RECORD_STOP  = 113;
    int TYPE_CALLBACK_SCRIPT     = 500;
 

    /**
     * 事件类型
     * @return
     */
    int type();
    /**
     * 事件版本
     */
    int version();
}

继承于 I3dEventMessage.java 的各个 model 定义:

类关系图:

从类关系图可以看出,SDK 对外提供的 3dEventMessagemodel 都是 I3dEventMessage.java 的实现。具体每个 model 属性定义如下:

类名属性备注
Base3dEventMessage

mMessageType - 消息类型

mMessageVersion - 消息版本

抽象类,外部无法初始化
Common3dEventMessagemCommonMessage - 通用字段除了下面自定义的消息体,所有剩余的消息type都是已这个model回调给外层
Share3dEventMessage

mShareType - 分享类型(1 text; 2 image; 3 music; 4 video; 5 url)

mTitle - 分享标题

mDesc - 分享描述

mUrl - 分享的url

mLogoBitmapPath - ar内容内部的分享图路径,是一个绝对路径

mScreenshot - 合成后的分享成图,用于分享给三方

mVideoPath - 视频录制后存储在本地的绝对路径

type =TYPE_SHARE

内容发送的分享的消息

ExecuteScript3dEventMessage

mScriptName - 脚本函数名称

mScriptDesc - 脚本对应的具体内容,需要内容开发者和外部具体协议

mIdentity - 消息名称,即内容回调告诉给外部执行的是哪步操作,外部可能需要把这个字段重新通过i3dExecuteScript重新发给内容 (v2.0及之后版本提供)

type =TYPE_EXECUTE_SCRIPTS

内容发送的执行一段内容脚本的消息

OpenUrl3dEventMessage

mOpenUrl - 外部打开的链接

mOpenJson - 打开链接携带的参数,由内容开发者和外部具体协议

type =TYPE_OPEN_URL

内容发送的打开一个外部链接的消息

Reload3dEventMessage

mPid - 携带的事件ID,对于云识别,该参数为null

errorMessage - 内部reload发生错误时的错误信息

isLbsRequired - 该事件是否需要LBS地理位置信息支持的结果

type =TYPE_RELOAD_AR

内容发送的重新加载当前界面内容的指令消息

Screenshot3dEventMessage

image - 截屏传出的Bitmap

type - 截屏消息的业务用途type,可由内容和接入方约定

identity - 消息收到以后给内容的响应回调

type = TYPE_SCREENSHOT

type定义如下:

类型描述对应的数据model
TYPE_RELOAD_AR重新reload ar的消息,在云识别模式下,该指令表示云识别识别到的具体事件的pid,非云识别模式下,代表需要上层完成reload ar的指令Reload3dEventMessage
TYPE_EXECUTE_SCRIPTS执行指定的内容脚本ExecuteScript3dEventMessage
TYPE_SHARE分享的消息Share3dEventMessage
TYPE_SCREENSHOT截屏的消息Common3dEventMessage
TYPE_CLOSE_AR关闭ar界面Common3dEventMessage
TYPE_OPEN_URL打开外部链接OpenUrl3dEventMessage
TYPE_CALLBACK_SCRIPT脚本执行回调消息Common3dEventMessage

5.2 SDK 与AR 算法交互

在 【5.1 SDK和AR内容交互】 章节中介绍了内容与上层的交互,如果外部需要关心 AR 算法的回调结果,只需要注册监听器即可。

监听器和内容一样:

java
//注册内容及算法交互监听器
public void registerArInsightResultListener(OnArInsightResultListener listener);
//反注册内容及算法交互监听器
public void unregisterArInsightResultListener(OnArInsightResultListener listener);

在 OnArInsightResultListener.java 中 OnArEngineResult 里 SDK 会回调算法执行结果。

开发者只需要关心改回调的参数 EzArAlgResult。

该 model 定义如下:

java
/**
 * @desc 洞见算法引擎对外回调结果
 */
public class EzArAlgResult {
    /**
     * 错误码
     * @see com.netease.insightar.ar.InsightARError
     */
    private int mErrorCode;
    /**
     * 当前引擎的状态
     * @see com.netease.insightar.ar.InsightARState
     */
    private int mArState;
    public int getErrorCode() {
        return mErrorCode;
    }
    public void setErrorCode(int errorCode) {
        mErrorCode = errorCode;
    }
    public String getErrorMsg() {
        return InsightARError.getErrorMessage(mErrorCode);
    }
    public int getArState() {
        return mArState;
    }
    public void setArState(int arState) {
        mArState = arState;
    }
}

具体的 errorCode 及 arState:

InsightARError.java

java
  public class InsightARError {
    public final static int SUCCESS = 0;
    public final static int ERROR_CAMERA_DEVICE = 1;
    public final static int ERROR_CAMERA_PERMISSION = 2;
    public final static int ERROR_CAMERA_TIMEOUT = 3;
    public final static int ERROR_CAMERA_DISABLE = 4;
    public final static int ERROR_CAMERA_UNKNOWN = 5;
    public final static int WARNING_IMU_ACCESS = 6;
    public final static int WARNING_ACCE_ACCESS = 7;
    public final static int WARNING_GYRO_ACCESS = 8;
    public final static int WARNING_GRAV_ACCESS = 9;
    public final static int ERROR_NO_IMU_DATA = 10;
    public final static int ERROR_CONFIGFILE_NOT_FOUND = 11;
    public final static int ERROR_CONFIGFILE_ERROR = 12;
    public final static int ERROR_AppKey_SECRET_ERROR = 13;
    public final static int WARNING_AR_RUNGING = 14;
    public final static int ERROR_DEVICE_UNSUPPORTED = 15;
    public final static int WARNING_INSUFFICIENT_FEATURES = 16;
    public final static int WARNING_TRACK_BAD = 17;
    public final static int WARNING_LOW_LIGHT = 18;
    public final static int WARNING_EXCESSIVE_MOTION = 19;
    public final static int ERROR_IMU_ACCESS = 20;
    public final static int ERROR_API_LEVEL = 21;
    public final static int ERROR_ARCORE_INIT_FAIL = 22;
    public final static int ERROR_ARCORE_RESUME_FAIL = 23;
    public static String getErrorMessage(int error){
        String msg = "SUCCESS";
        switch (error){
            case ERROR_CAMERA_DEVICE:
                msg = "ERROR_CAMERA_DEVICE.Camera may be in use,or maybe permission is not granted";
                break;
            case ERROR_CAMERA_PERMISSION:
                msg = "ERROR_CAMERA_PERMISSION.Camera permission is not granted";
                break;
            case ERROR_CAMERA_TIMEOUT:
                msg = "ERROR_CAMERA_TIMEOUT.Opening camera is out of wait Time";
                break;
            case ERROR_CAMERA_DISABLE:
                msg = "ERROR_CAMERA_DISABLE.Camera is not available,or permission may be not granted";
                break;
            case ERROR_CAMERA_UNKNOWN:
                msg = "ERROR_CAMERA_UNKNOWN.Camera is not available with unknown reason";
                break;
            case WARNING_IMU_ACCESS:
                msg = "WARNING_IMU_ACCESS.This device's IMU sensor service is not available";
                break;
            case WARNING_ACCE_ACCESS:
                msg = "WARNING_ACCE_ACCESS.This device's accelerator sensor is not available";
                break;
            case WARNING_GYRO_ACCESS:
                msg = "WARNING_GYRO_ACCESS.This device's gyroscrope sensor is not available";
                break;
            case WARNING_GRAV_ACCESS:
                msg = "WARNING_GRAV_ACCESS.This device's gravity sensor is not available";
                break;
            case ERROR_NO_IMU_DATA:
                msg = "ERROR_NO_IMU_DATA.This type ARScene is running with IMU sensors which is not available";
                break;
            case ERROR_CONFIGFILE_NOT_FOUND:
                msg = "ERROR_CONFIGFILE_NOT_FOUND.InsightAR SDK config files is missing,please check the files configured in MR";
                break;
            case ERROR_CONFIGFILE_ERROR:
                msg = "ERROR_CONFIGFILE_ERROR.InsightAR SDK config files is Wrong,please check the files configured in MR";
                break;
            case ERROR_AppKey_SECRET_ERROR:
                msg = "ERROR_AppKey_SECRET_ERROR.Checking authentication work went wrong,plaese check the network, AppKey or AppSecret ";
                break;
            case WARNING_AR_RUNGING:
                msg = "WARNING_AR_RUNGING.Current ARScene is running";
                break;
            case ERROR_DEVICE_UNSUPPORTED:
                msg = "ERROR_DEVICE_UNSUPPORTED.Sorry this device is not support by InsightAR,maybe because OS version is too low,or performace is not satisfied";
                break;
            case WARNING_INSUFFICIENT_FEATURES:
                msg = "WARNING_INSUFFICIENT_FEATURES.This environment may be too simple and lack of features for AR-Tracking";
                break;
            case WARNING_TRACK_BAD:
                msg = "WARNING_TRACK_BAD.Sorry ,AR is working badly right now";
                break;
            case WARNING_LOW_LIGHT:
                msg = "WARNING_LOW_LIGHT.This environment may be too dark.Please move to somewhere brighter";
                break;
            case WARNING_EXCESSIVE_MOTION:
                msg = "WARNING_EXCESSIVEMOTION.Your movement was too fast";
                break;
            case ERROR_IMU_ACCESS:
                msg = "ERROR_IMU_ACCESS.Some problem occur when accessing sensors";
                break;
            case ERROR_API_LEVEL:
                msg = "ERROR_API_LEVEL.This device's API level may be not in supported list";
                break;
            case ERROR_ARCORE_INIT_FAIL:
                msg = "ERROR_ARCORE_INIT_FAIL.Failed to init ARCore";
                break;
            case ERROR_ARCORE_RESUME_FAIL:
                msg = "ERROR_ARCORE_RESUME_FAIL.Failed to resume ARCore";
                break;
            default:
                    break;
        }
        return msg;
    }
}

同样的,如果关心 Unity 引擎加载结果,请参看如下状态值:

InsightARState.java

java
public class InsightARState {
    public final static int UNINITIALIZED = 0;
    public final static int INITING = 1;
    public final static int INIT_OK = 2;//unity加载资源成功
    public final static int INIT_FAIL = 3;//unity加载资源失败
    public final static int DETECTING = 4;
    public final static int DETECT_OK = 5;
    public final static int DETECT_FAIL = 6;
    public final static int TRACKING = 7;
    public final static int TRACK_LIMITED = 8;
    public final static int TRACK_LOST = 9;
    public final static int TRACK_FAIL = 10;
    public final static int TRACK_STOP = 11;
    public final static int DETECT_TIMEOUT = 12;
}

5.3 SDK 申请应用权限

SDK 需要使用应用权限如麦克风,camera 等时需要注册权限申请回调

java
//设置权限申请回调
public void setRequestPermissionCallback(RequestPermissionCallback permissionCallback);

RequestPermissionCallback 实现见下面示例代码

java
@Override
    public void requestPermissionAccess(int type) {
        switch (type) {
            case PermissionsUtil.IPermissionTypeCamera:
                PermissionsUtil.with(this)
                        .addPermission(Manifest.permission.CAMERA)
                        .initPermission();
                break;
            case PermissionsUtil.IPermissionTypeAudio:
                PermissionsUtil.with(this)
                        .addPermission(Manifest.permission.RECORD_AUDIO)
                        .initPermission();
                break;
            case PermissionsUtil.IPermissionTypeAlbum:
                PermissionsUtil.with(this)
                        .addPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                        .initPermission();
                break;
            case PermissionsUtil.IPermissionTypeGPS:
                PermissionsUtil.with(this)
                        .addPermission(Manifest.permission.ACCESS_FINE_LOCATION)
                        .initPermission();
                break;
            case PermissionsUtil.IPermissionTypeRead:
                PermissionsUtil.with(this)
                        .addPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
                        .initPermission();
                break;
        }

权限申请完毕后,需要将权限申请结果告知 UnitySDK

java
 @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 1 && grantResults.length > 0) {
            for (int i = 0; i < permissions.length; i++) {
                if (permissions[i].equals(Manifest.permission.CAMERA)) {
                    mArSceneView.setPermissionResult(PermissionsUtil.IPermissionTypeCamera,
                            grantResults[i] != PackageManager.PERMISSION_GRANTED ? 1 : 2);
                }
                if (permissions[i].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                    mArSceneView.setPermissionResult(PermissionsUtil.IPermissionTypeAlbum,
                            grantResults[i] != PackageManager.PERMISSION_GRANTED ? 1 : 2);
                }
                if (permissions[i].equals(Manifest.permission.RECORD_AUDIO)) {
                    mArSceneView.setPermissionResult(PermissionsUtil.IPermissionTypeAudio,
                            grantResults[i] != PackageManager.PERMISSION_GRANTED ? 1 : 2);
                }
                if (permissions[i].equals(Manifest.permission.ACCESS_FINE_LOCATION)
                        || permissions[i].equals(Manifest.permission.ACCESS_COARSE_LOCATION)) {
                    mArSceneView.setPermissionResult(PermissionsUtil.IPermissionTypeGPS,
                            grantResults[i] != PackageManager.PERMISSION_GRANTED ? 1 : 2);
                }
            }
        }
    }