Självstudie: Stegvisa instruktioner för att skapa en ny Android-app med Azure Spatial Anchors

Den här självstudien visar hur du skapar en ny Android-app som integrerar ARCore-funktioner med Azure Spatial Anchors.

Förutsättningar

För att kunna följa den här självstudien måste du ha:

Komma igång

Starta Android Studio. I fönstret Välkommen till Android Studio klickar du på Starta ett nytt Android Studio projekt.

  1. Välj Arkiv -> Ny Project.
  2. I fönstret Skapa Project, under avsnittet Telefon surfplatta, väljer du Tom aktivitet och klickar på Nästa.
  3. I fönstret Ny Project – Tom aktivitet ändrar du följande värden:
    • Ändra Namn, Paketnamn och Spara plats till önskade värden
    • Ange Språk är till Java
    • Ange lägsta API-nivå tillAPI 26: Android 8.0 (Oreo)
    • Lämna de andra alternativen som de är
    • Klicka på Finish.
  4. Installationsprogrammet för komponenter körs. Efter viss bearbetning Android Studio IDE:en.

Android Studio – Ny Project

Prova

Om du vill testa den nya appen ansluter du den utvecklaraktiverade enheten till utvecklingsdatorn med en USB-kabel. Längst upp till höger Android Studio du den anslutna enheten och klickar på ikonen Kör "app". Android Studio installerar appen på den anslutna enheten och startar den. Nu bör du se "Hello World!" visas i appen som körs på enheten. Klicka på Kör Stoppa -> "app". Android Studio – Kör

Integrera ARCore

ARCore är Googles plattform för att skapa upplevelser med förhöjd verklighet, vilket gör det möjligt för din enhet att spåra sin position när den rör sig och skapar sin egen förståelse för den verkliga världen.

Ändra app\manifests\AndroidManifest.xml så att följande poster inkluderas i rotnoden. <manifest> Det här kodfragmentet gör några saker:

  • Det gör att din app kan komma åt enhetens kamera.
  • Det säkerställer också att din app endast visas i Google Play Butik enheter som stöder ARCore.
  • Den konfigurerar Google Play Butik att ladda ned och installera ARCore, om det inte redan är installerat, när din app installeras.
<manifest ...>

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera.ar" />

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

</manifest>

Ändra Gradle Scripts\build.gradle (Module: app) så att följande post inkluderas. Den här koden ser till att din app har ARCore version 1.25 som mål. Efter den här ändringen kan du få ett meddelande från Gradle som ber dig att synkronisera: klicka på Synkronisera nu.

dependencies {
    ...
    implementation 'com.google.ar:core:1.25.0'
    ...
}

Integrera Sceneform

Sceneform gör det enkelt att återge realistiska 3D-scener i Augmented Reality-appar utan att behöva lära dig OpenGL.

Ändra Gradle Scripts\build.gradle (Module: app) så att följande poster inkluderas. Med den här koden kan din app använda språkkonstruktioner från Java 8, vilket Sceneform kräver det. Det säkerställer också att din app har Sceneform version 1.15 som mål. Efter den här ändringen kan du få ett meddelande från Gradle som ber dig att synkronisera: klicka på Synkronisera nu.

android {
    ...

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    ...
    implementation 'com.google.ar.sceneform.ux:sceneform-ux:1.15.0'
    ...
}

Öppna och app\res\layout\activity_main.xml ersätt det befintliga HelloRd-elementet <TextView ... /> med följande ArFragment. Den här koden gör att kameraflödet visas på skärmen så att ARCore kan spåra enhetens position när den flyttas.

<fragment android:name="com.google.ar.sceneform.ux.ArFragment"
    android:id="@+id/ux_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Anteckning

Om du vill se xml-rådata för din huvudaktivitet klickar du på knappen "Code" (Kod) eller "Split" (Dela) längst upp till höger Android Studio.

Distribuera om appen till enheten för att verifiera den igen. Den här gången bör du bli ombedd att ange kamerabehörigheter. När det har godkänts bör du se din återgivning av kameraflödet på skärmen.

Placera ett objekt i den verkliga världen

Nu ska vi skapa & placera ett objekt med hjälp av din app. Lägg först till följande importer i app\java\<PackageName>\MainActivity din :

package com.example.myfirstapp;

import android.support.v7.app.AppCompatActivity;
import com.google.ar.core.Plane;
import com.google.ar.sceneform.AnchorNode;
import com.google.ar.sceneform.math.Vector3;
import com.google.ar.sceneform.rendering.Color;
import com.google.ar.sceneform.rendering.MaterialFactory;
import com.google.ar.sceneform.rendering.Renderable;
import com.google.ar.sceneform.rendering.ShapeFactory;

Så här bör den fullständiga `MainActivity` klass filen se ut när alla de olika elementen har samlats ihop. Du kan använda den som en referens för att jämföra med din egen fil och ange om du kan ha skillnader kvar.

Lägg sedan till följande medlemsvariabler i MainActivity klassen:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.microsoft.azure.spatialanchors.AnchorLocateCriteria;
import com.microsoft.azure.spatialanchors.LocateAnchorStatus;

Lägg sedan till följande kod i metoden app\java\<PackageName>\MainActivity onCreate() . Den här koden ansluter en lyssnare med namnet handleTap() , som identifierar när användaren trycker på skärmen på enheten. Om tryckningen råkar finnas på en verklig yta som redan har identifierats av ARCores spårning körs lyssnaren.

private CloudSpatialAnchorSession cloudSession;

private String anchorId = null;
private boolean scanningForUpload = false;
private final Object syncSessionProgress = new Object();
private ExecutorService executorService = Executors.newSingleThreadExecutor();

    Scene scene = sceneView.getScene();

Slutligen lägger du till följande handleTap() metod som binder samman allt. Det skapar en sfär och placerar den på den tryckna platsen. Sfären kommer initialt att vara svart, this.recommendedSessionProgress eftersom är inställd på noll just nu. Det här värdet justeras senare.


    this.tapExecuted = true;
}

if (this.anchorId != null) {
    this.anchorNode.getAnchor().detach();
    this.anchorNode.setParent(null);
    this.anchorNode = null;
    initializeSession();
MaterialFactory.makeOpaqueWithColor(this, new Color(
        this.recommendedSessionProgress,
    .thenAccept(material -> {
        this.nodeRenderable = ShapeFactory.makeSphere(0.1f, new Vector3(0.0f, 0.15f, 0.0f), material);
        this.anchorNode.setRenderable(nodeRenderable);
        this.anchorNode.setParent(arFragment.getArSceneView().getScene());

        uploadCloudAnchorAsync(cloudAnchor)
            .thenAccept(id -> {
                this.anchorId = id;
                Log.i("ASAInfo", String.format("Cloud Anchor created: %s", this.anchorId));
                runOnUiThread(() -> {
    this.scanningForUpload = true;

Distribuera om appen till enheten för att verifiera den igen. Den här gången kan du flytta runt på enheten för att få ARCore att börja känna igen din miljö. Tryck sedan på skärmen för att & placera din svarta sfär över valfri yta.

Koppla en lokal Azure Spatial Anchor

Ändra Gradle Scripts\build.gradle (Module: app) så att följande post inkluderas. Det här exempelkodfragmentet riktar sig mot Azure Spatial Anchors SDK version 2.10.2. Observera att SDK-version 2.7.0 för närvarande är den lägsta version som stöds och att det även bör fungera att referera till en senare version av Azure Spatial Anchors. Vi rekommenderar att du använder den senaste versionen av Azure Spatial Anchors SDK. Du hittar sdk:ns versionsanteckningar här.

dependencies {
    ...
    implementation 'com.microsoft.azure.spatialanchors:spatialanchors_jni:[2.10.2]'
    implementation 'com.microsoft.azure.spatialanchors:spatialanchors_java:[2.10.2]'
    ...
}

Om du riktar in dig på Azure Spatial Anchors SDK 2.10.0 eller senare inkluderar du följande post i avsnittet lagringsplatsen i projektets settings.gradle -fil. Detta inkluderar URL:en till Maven-paketflödet som är värd för Azure Spatial Anchors Android-paket för SDK 2.10.0 eller senare:

dependencyResolutionManagement {
    ...
    repositories {
        ...
        maven {
            url 'https://pkgs.dev.azure.com/aipmr/MixedReality-Unity-Packages/_packaging/Maven-packages/maven/v1'
        }
        ...
    }
}

Högerklicka på Ny app\java\<PackageName> -> -> Java-klass. Ange Namn till MyFirstApp och välj Klass. En fil MyFirstApp.java med namnet skapas. Lägg till följande import i den:

import com.microsoft.CloudServices;

Definiera android.app.Application som dess superklass.

public class MyFirstApp extends android.app.Application {...

Lägg sedan till följande kod i den nya klassen, vilket säkerställer MyFirstApp att Azure Spatial Anchors initieras med programmets kontext.

    @Override
    public void onCreate() {
        super.onCreate();
        CloudServices.initialize(this);
    }

Ändra nu så app\manifests\AndroidManifest.xml att följande post inkluderas i rotnoden. <application> Den här koden ansluter programklassen som du skapade till din app.

    <application
        android:name=".MyFirstApp"
        ...
    </application>

När du app\java\<PackageName>\MainActivity är tillbaka i lägger du till följande importer i den:


## <a name="putting-everything-together"></a>Placera allt tillsammans
Så här bör den fullständiga `MainActivity` klass filen se ut när alla de olika elementen har samlats ihop. Du kan använda den som en referens för att jämföra med din egen fil och ange om du kan ha skillnader kvar.
import android.os.Bundle;
import com.google.ar.core.HitResult;

import android.view.MotionEvent;

Lägg sedan till följande medlemsvariabler i MainActivity klassen:


public class MainActivity extends AppCompatActivity {

    private boolean tapExecuted = false;
    private final Object syncTaps = new Object();

Nu ska vi lägga till följande initializeSession() metod i klassen mainActivity . När den anropas ser den till att en Azure Spatial Anchors-session skapas och initieras korrekt under appstarten. Den här koden ser till att sceneview-sessionen som skickas till ASA-sessionen via cloudSession.setSession anropet inte är null genom att ha tidig retur.

private void initializeSession() {
    if (this.cloudSession != null){
        this.cloudSession.close();
    }
    this.cloudSession = new CloudSpatialAnchorSession();
    this.cloudSession.setSession(sceneView.getSession());
    this.cloudSession.setLogLevel(SessionLogLevel.Information);
    this.cloudSession.addOnLogDebugListener(args -> Log.d("ASAInfo", args.getMessage()));
    this.cloudSession.addErrorListener(args -> Log.e("ASAError", String.format("%s: %s", args.getErrorCode().name(), args.getErrorMessage())));

    this.cloudSession.addSessionUpdatedListener(args -> {
        synchronized (this.syncSessionProgress) {
            this.recommendedSessionProgress = args.getStatus().getRecommendedForCreateProgress();
            Log.i("ASAInfo", String.format("Session progress: %f", this.recommendedSessionProgress));
            if (!this.scanningForUpload)
            {
    synchronized (this.syncTaps) {

Eftersom kan göra en tidig retur om sceneView-sessionen ännu inte har ställts in (dvs. om är null), lägger vi till ett onUpdate-anrop för att se till att initializeSession() ASA-sessionen initieras när sceneView.getSession() sceneView-sessionen har skapats.

Nu ska vi koppla metoden initializeSession() och scene_OnUpdate(...) till metoden onCreate() . Dessutom ser vi till att bildrutor från din kamerafeed skickas till Azure Spatial Anchors SDK för bearbetning.

private CloudSpatialAnchorSession cloudSession;

private String anchorId = null;
private boolean scanningForUpload = false;
private final Object syncSessionProgress = new Object();
private ExecutorService executorService = Executors.newSingleThreadExecutor();

// <onCreate>
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    this.arFragment = (ArFragment) getSupportFragmentManager().findFragmentById(R.id.ux_fragment);
    this.arFragment.setOnTapArPlaneListener(this::handleTap);

    this.sceneView = arFragment.getArSceneView();
    Scene scene = sceneView.getScene();

Slutligen lägger du till följande kod i metoden handleTap() . Den kopplar en lokal Azure Spatial Anchor till den svarta sfär som vi placerar i den verkliga världen.


    this.tapExecuted = true;
}

if (this.anchorId != null) {
    this.anchorNode.getAnchor().detach();
    this.anchorNode.setParent(null);
    this.anchorNode = null;
    initializeSession();
MaterialFactory.makeOpaqueWithColor(this, new Color(
        this.recommendedSessionProgress,
        this.recommendedSessionProgress,
        this.recommendedSessionProgress))
    .thenAccept(material -> {
        this.nodeRenderable = ShapeFactory.makeSphere(0.1f, new Vector3(0.0f, 0.15f, 0.0f), material);
        this.anchorNode.setRenderable(nodeRenderable);
        this.anchorNode.setParent(arFragment.getArSceneView().getScene());

        uploadCloudAnchorAsync(cloudAnchor)
            .thenAccept(id -> {
                this.anchorId = id;
                Log.i("ASAInfo", String.format("Cloud Anchor created: %s", this.anchorId));
                runOnUiThread(() -> {
    this.scanningForUpload = true;

Distribuera om appen en gång till. Flytta runt enheten, tryck på skärmen och placera en svart sfär. Men den här gången kommer din kod att skapa och koppla en lokal Azure Spatial Anchor till din sfär.

Innan du fortsätter måste du skapa ett Azure Spatial Anchors-konto för att hämta kontoidentifieraren, nyckeln och domänen, om du inte redan har dem. Följ följande avsnitt för att hämta dem.

Skapa en Spatial Anchors-resurs

Gå till Azure-portalen.

Välj Skapa en resurs i den vänstra rutan.

Använd sökrutan för att söka efter Spatial Anchors.

Skärmbild som visar resultatet av en sökning efter Spatial Anchors.

Välj Spatial Anchors och välj sedan Skapa.

I fönstret Spatial Anchors konto gör du följande:

  • Ange ett unikt resursnamn med hjälp av vanliga alfanumeriska tecken.

  • Välj den prenumeration som du vill koppla resursen till.

  • Skapa en resursgrupp genom att välja Skapa ny. Ge den namnet myResourceGroup och välj sedan OK.

    En resurs grupp är en logisk behållare där Azure-resurser, till exempel webbappar, databaser och lagrings konton, distribueras och hanteras. Du kan exempelvis välja att ta bort hela resursgruppen i ett enkelt steg längre fram.

  • Välj en plats (region) där du vill placera resursen.

  • Välj Skapa för att börja skapa resursen.

Skärmbild av fönstret Spatial Anchors för att skapa en resurs.

När resursen har skapats visar Azure Portal att distributionen är klar.

Skärmbild som visar att resursdistributionen är klar.

Välj Gå till resurs. Nu kan du visa resursegenskaperna.

Kopiera resursens konto-ID-värde till en textredigerare för senare användning.

Skärmbild av fönstret resursegenskaper.

Kopiera också resursens kontodomänvärde till en textredigerare för senare användning.

Skärmbild som visar resursens kontodomänvärde.

Under Inställningar väljer du Åtkomstnyckel. Kopiera värdet för Primärnyckel, Kontonyckel, till en textredigerare för senare användning.

Skärmbild av fönstret Nycklar för kontot.

Upload din lokala fästpunkt i molnet

När du har din Azure Spatial Anchors-kontoidentifierare, nyckel och domän kan vi gå tillbaka till app\java\<PackageName>\MainActivity och lägga till följande importer i den:


import com.google.ar.sceneform.ArSceneView;
import com.google.ar.sceneform.Scene;
import com.microsoft.azure.spatialanchors.CloudSpatialAnchor;
import com.microsoft.azure.spatialanchors.CloudSpatialAnchorSession;
import com.microsoft.azure.spatialanchors.SessionLogLevel;

Lägg sedan till följande medlemsvariabler i MainActivity klassen:

private final Object syncTaps = new Object();
private ArFragment arFragment;
private AnchorNode anchorNode;
private Renderable nodeRenderable = null;
private float recommendedSessionProgress = 0f;

Lägg nu till följande kod i metoden initializeSession() . Den här koden gör att din app kan övervaka förloppet som Azure Spatial Anchors SDK gör när den samlar in bildrutor från kameraflödet. Som den gör börjar färgen på din sfär att ändras från dess ursprungliga svarta, till grått. Sedan blir den vit när tillräckligt många bildrutor samlas in för att skicka din fästpunkt till molnet. För det andra anger den här koden de autentiseringsuppgifter som krävs för att kommunicera med molnets backend-del. Här konfigurerar du appen så att den använder din kontoidentifierare, nyckel och domän. Du kopierade dem till en textredigerare när du konfigurerade Spatial Anchors resurs.

private void initializeSession() {
    if (this.cloudSession != null){
        this.cloudSession.close();
    }
    this.cloudSession = new CloudSpatialAnchorSession();
    this.cloudSession.setSession(sceneView.getSession());
    this.cloudSession.setLogLevel(SessionLogLevel.Information);
    this.cloudSession.addOnLogDebugListener(args -> Log.d("ASAInfo", args.getMessage()));
    this.cloudSession.addErrorListener(args -> Log.e("ASAError", String.format("%s: %s", args.getErrorCode().name(), args.getErrorMessage())));

    this.cloudSession.addSessionUpdatedListener(args -> {
        synchronized (this.syncSessionProgress) {
            this.recommendedSessionProgress = args.getStatus().getRecommendedForCreateProgress();
            Log.i("ASAInfo", String.format("Session progress: %f", this.recommendedSessionProgress));
            if (!this.scanningForUpload)
            {
                return;
            }
        }

        runOnUiThread(() -> {
            synchronized (this.syncSessionProgress) {
                MaterialFactory.makeOpaqueWithColor(this, new Color(
                        this.recommendedSessionProgress,
                        this.recommendedSessionProgress,
                        this.recommendedSessionProgress))
                    .thenAccept(material -> {
                        this.nodeRenderable.setMaterial(material);
                    });
            }
        });
    });

    this.cloudSession.addAnchorLocatedListener(args -> {
        if (args.getStatus() == LocateAnchorStatus.Located)
        {
            runOnUiThread(()->{
                this.anchorNode = new AnchorNode();
                this.anchorNode.setAnchor(args.getAnchor().getLocalAnchor());
// </initializeSession>

// <handleTap>
protected void handleTap(HitResult hitResult, Plane plane, MotionEvent motionEvent) {
    synchronized (this.syncTaps) {

Lägg sedan till följande uploadCloudAnchorAsync() metod i klassen mainActivity . När den här metoden har anropats väntar den asynkront tills tillräckligt många bildrutor har samlats in från enheten. När det händer växlar den färgen på din sfär till gul, och sedan börjar den ladda upp din lokala Azure Spatial Anchor till molnet. När uppladdningen är klar returnerar koden ett ankar-ID.

private CompletableFuture<String> uploadCloudAnchorAsync(CloudSpatialAnchor anchor) {
    synchronized (this.syncSessionProgress) {
        this.scanningForUpload = true;
    }

    return CompletableFuture.runAsync(() -> {
        try {
            float currentSessionProgress;
            do {
                synchronized (this.syncSessionProgress) {
                    currentSessionProgress = this.recommendedSessionProgress;
                }
                if (currentSessionProgress < 1.0) {
                    Thread.sleep(500);
                }
            }
            while (currentSessionProgress < 1.0);

            synchronized (this.syncSessionProgress) {
                this.scanningForUpload = false;
            }
            runOnUiThread(() -> {
                MaterialFactory.makeOpaqueWithColor(this, new Color(android.graphics.Color.YELLOW))
                    .thenAccept(yellowMaterial -> {
                        this.nodeRenderable.setMaterial(yellowMaterial);
                    });
            });

            this.cloudSession.createAnchorAsync(anchor).get();
        } catch (InterruptedException | ExecutionException e) {
            Log.e("ASAError", e.toString());
            throw new RuntimeException(e);
        }
    }, executorService).thenApply(ignore -> anchor.getIdentifier());
}

Slutligen ska vi koppla ihop allt. Lägg till handleTap() följande kod i metoden . Den anropar uploadCloudAnchorAsync() metoden så fort sfären har skapats. När metoden returneras utför koden nedan en sista uppdatering av sfären och ändrar dess färg till blått.


        this.tapExecuted = true;
    }

    if (this.anchorId != null) {
        this.anchorNode.getAnchor().detach();
        this.anchorNode.setParent(null);
        this.anchorNode = null;
        initializeSession();
    MaterialFactory.makeOpaqueWithColor(this, new Color(
            this.recommendedSessionProgress,
            this.recommendedSessionProgress,
            this.recommendedSessionProgress))
        .thenAccept(material -> {
            this.nodeRenderable = ShapeFactory.makeSphere(0.1f, new Vector3(0.0f, 0.15f, 0.0f), material);
            this.anchorNode.setRenderable(nodeRenderable);
            this.anchorNode.setParent(arFragment.getArSceneView().getScene());

            uploadCloudAnchorAsync(cloudAnchor)
                .thenAccept(id -> {
                    this.anchorId = id;
                    Log.i("ASAInfo", String.format("Cloud Anchor created: %s", this.anchorId));
                    runOnUiThread(() -> {
                        MaterialFactory.makeOpaqueWithColor(this, new Color(android.graphics.Color.BLUE))
                            .thenAccept(blueMaterial -> {
                                this.nodeRenderable.setMaterial(blueMaterial);
                                synchronized (this.syncTaps) {
                                    this.tapExecuted = false;
                                }
                            });
                    });
                });
        });
}
// </handleTap>

// <uploadCloudAnchorAsync>
private CompletableFuture<String> uploadCloudAnchorAsync(CloudSpatialAnchor anchor) {
    synchronized (this.syncSessionProgress) {
        this.scanningForUpload = true;

Distribuera om appen en gång till. Flytta runt enheten, tryck på skärmen och placera sfären. Men den här gången kommer sfären att ändra färg från svart till vit när kameraramar samlas in. När vi har tillräckligt med bildrutor blir sfären gul och molnuppladdningen startar. Kontrollera att din telefon är ansluten till Internet. När uppladdningen är klar blir sfären blå. Du kan också övervaka fönstret i Logcat Android Studio att visa loggmeddelandena som appen skickar. Exempel på meddelanden som loggas är sessionsförloppet under bildrutorna och ankaridentifieraren som molnet returnerar när uppladdningen är klar.

Anteckning

Om du inte ser värdet för (i felsökningsloggarna som kallas ) ska du se till att du både flyttar och roterar telefonen runt den sfär som du recommendedSessionProgress Session progress har placerat.

Leta upp din rumsliga molnankare

När fästpunkten har laddats upp till molnet är vi redo att försöka hitta den igen. Först lägger vi till följande importer i koden.

import com.microsoft.azure.spatialanchors.SessionLogLevel;

import com.google.ar.sceneform.ux.ArFragment;
import android.util.Log;

Sedan lägger vi till följande kod i metoden handleTap() . Den här koden kommer att:

  • Ta bort vår befintliga blå sfär från skärmen.
  • Initiera azure-Spatial Anchors sessionen igen. Den här åtgärden ser till att fästpunkten som vi ska hitta kommer från molnet i stället för den lokala fästpunkt som vi skapade.
  • Utfärda en fråga för fästpunkten som vi laddade upp till molnet.
protected void handleTap(HitResult hitResult, Plane plane, MotionEvent motionEvent) {
    synchronized (this.syncTaps) {
        if (this.tapExecuted) {
            return;
        }

        this.tapExecuted = true;
    }

    if (this.anchorId != null) {
        this.anchorNode.getAnchor().detach();
        this.anchorNode.setParent(null);
        this.anchorNode = null;
        initializeSession();
        AnchorLocateCriteria criteria = new AnchorLocateCriteria();
        criteria.setIdentifiers(new String[]{this.anchorId});
        cloudSession.createWatcher(criteria);
        return;
    }

    this.anchorNode = new AnchorNode();
    this.anchorNode.setAnchor(hitResult.createAnchor());
    CloudSpatialAnchor cloudAnchor = new CloudSpatialAnchor();
    cloudAnchor.setLocalAnchor(this.anchorNode.getAnchor());

    MaterialFactory.makeOpaqueWithColor(this, new Color(
            this.recommendedSessionProgress,
            this.recommendedSessionProgress,
            this.recommendedSessionProgress))
        .thenAccept(material -> {
            this.nodeRenderable = ShapeFactory.makeSphere(0.1f, new Vector3(0.0f, 0.15f, 0.0f), material);
            this.anchorNode.setRenderable(nodeRenderable);
            this.anchorNode.setParent(arFragment.getArSceneView().getScene());

            uploadCloudAnchorAsync(cloudAnchor)
                .thenAccept(id -> {
                    this.anchorId = id;
                    Log.i("ASAInfo", String.format("Cloud Anchor created: %s", this.anchorId));
                    runOnUiThread(() -> {
                        MaterialFactory.makeOpaqueWithColor(this, new Color(android.graphics.Color.BLUE))
                            .thenAccept(blueMaterial -> {
                                this.nodeRenderable.setMaterial(blueMaterial);
                                synchronized (this.syncTaps) {
                                    this.tapExecuted = false;
                                }
                            });
                    });
                });
        });
}

Nu ska vi koppla koden som ska anropas när fästpunkten som vi frågar efter finns. Lägg till initializeSession() följande kod i metoden . Det här kodfragmentet skapar & en grön sfär när den rumsliga molnankaren finns. Det aktiverar även skärmavtryck igen, så att du kan upprepa hela scenariot en gång till: skapa en annan lokal fästpunkt, ladda upp den och leta upp den igen.

private void initializeSession() {
    if (this.cloudSession != null){
        this.cloudSession.close();
    }
    this.cloudSession = new CloudSpatialAnchorSession();
    this.cloudSession.setSession(sceneView.getSession());
    this.cloudSession.setLogLevel(SessionLogLevel.Information);
    this.cloudSession.addOnLogDebugListener(args -> Log.d("ASAInfo", args.getMessage()));
    this.cloudSession.addErrorListener(args -> Log.e("ASAError", String.format("%s: %s", args.getErrorCode().name(), args.getErrorMessage())));

    this.cloudSession.addSessionUpdatedListener(args -> {
        synchronized (this.syncSessionProgress) {
            this.recommendedSessionProgress = args.getStatus().getRecommendedForCreateProgress();
            Log.i("ASAInfo", String.format("Session progress: %f", this.recommendedSessionProgress));
            if (!this.scanningForUpload)
            {
                return;
            }
        }

        runOnUiThread(() -> {
            synchronized (this.syncSessionProgress) {
                MaterialFactory.makeOpaqueWithColor(this, new Color(
                        this.recommendedSessionProgress,
                        this.recommendedSessionProgress,
                        this.recommendedSessionProgress))
                    .thenAccept(material -> {
                        this.nodeRenderable.setMaterial(material);
                    });
            }
        });
    });

    this.cloudSession.addAnchorLocatedListener(args -> {
        if (args.getStatus() == LocateAnchorStatus.Located)
        {
            runOnUiThread(()->{
                this.anchorNode = new AnchorNode();
                this.anchorNode.setAnchor(args.getAnchor().getLocalAnchor());
                MaterialFactory.makeOpaqueWithColor(this, new Color(android.graphics.Color.GREEN))
                    .thenAccept(greenMaterial -> {
                        this.nodeRenderable = ShapeFactory.makeSphere(0.1f, new Vector3(0.0f, 0.15f, 0.0f), greenMaterial);
                        this.anchorNode.setRenderable(nodeRenderable);
                        this.anchorNode.setParent(arFragment.getArSceneView().getScene());

                        this.anchorId = null;
                        synchronized (this.syncTaps) {
                            this.tapExecuted = false;
                        }
                    });
            });
        }
    });

    this.cloudSession.getConfiguration().setAccountId(/* Copy your account Identifier in here */);
    this.cloudSession.getConfiguration().setAccountKey(/* Copy your account Key in here */);
    this.cloudSession.getConfiguration().setAccountDomain(/* Copy your account Domain in here */);
    this.cloudSession.start();
}

Klart! Distribuera om appen en sista gång för att testa hela scenariot från slut till slut. Flytta runt enheten och placera den svarta sfären. Fortsätt sedan att flytta enheten för att fånga kameraramar tills sfären blir gul. Din lokala fästpunkt laddas upp och sfären blir blå. Tryck slutligen på skärmen igen, så att din lokala fästpunkt tas bort, och sedan frågar vi efter dess molnmotsvarighet. Fortsätt att flytta runt enheten tills din rumsliga molnankare finns. En grön sfär bör visas på rätt plats och du kan & upprepa hela scenariot igen.

Sätta ihop allt

Så här bör den fullständiga MainActivity klassfilen se ut när alla olika element har satts ihop. Du kan använda den som referens för att jämföra med din egen fil och upptäcka om det finns några skillnader kvar.

Placera allt tillsammans

Så här bör den fullständiga MainActivity klass filen se ut när alla de olika elementen har samlats ihop. Du kan använda den som en referens för att jämföra med din egen fil och ange om du kan ha skillnader kvar.

package com.example.myfirstapp;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.google.ar.core.HitResult;
import com.google.ar.core.Plane;
import com.google.ar.sceneform.AnchorNode;
import com.google.ar.sceneform.math.Vector3;
import com.google.ar.sceneform.rendering.Color;
import com.google.ar.sceneform.rendering.MaterialFactory;
import com.google.ar.sceneform.rendering.Renderable;
import com.google.ar.sceneform.rendering.ShapeFactory;
import com.google.ar.sceneform.ux.ArFragment;

import android.view.MotionEvent;
import android.util.Log;

import com.google.ar.sceneform.ArSceneView;
import com.google.ar.sceneform.Scene;
import com.microsoft.azure.spatialanchors.CloudSpatialAnchor;
import com.microsoft.azure.spatialanchors.CloudSpatialAnchorSession;
import com.microsoft.azure.spatialanchors.SessionLogLevel;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.microsoft.azure.spatialanchors.AnchorLocateCriteria;
import com.microsoft.azure.spatialanchors.LocateAnchorStatus;

public class MainActivity extends AppCompatActivity {

    private boolean tapExecuted = false;
    private final Object syncTaps = new Object();
    private ArFragment arFragment;
    private AnchorNode anchorNode;
    private Renderable nodeRenderable = null;
    private float recommendedSessionProgress = 0f;

    private ArSceneView sceneView;
    private CloudSpatialAnchorSession cloudSession;

    private String anchorId = null;
    private boolean scanningForUpload = false;
    private final Object syncSessionProgress = new Object();
    private ExecutorService executorService = Executors.newSingleThreadExecutor();

    // <onCreate>
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        this.arFragment = (ArFragment) getSupportFragmentManager().findFragmentById(R.id.ux_fragment);
        this.arFragment.setOnTapArPlaneListener(this::handleTap);

        this.sceneView = arFragment.getArSceneView();
        Scene scene = sceneView.getScene();
        scene.addOnUpdateListener(frameTime -> {
            if (this.cloudSession != null) {
                this.cloudSession.processFrame(sceneView.getArFrame());
            }
        });

        initializeSession();
    }
    // </onCreate>

    // <initializeSession>
    private void initializeSession() {
        if (this.cloudSession != null){
            this.cloudSession.close();
        }
        this.cloudSession = new CloudSpatialAnchorSession();
        this.cloudSession.setSession(sceneView.getSession());
        this.cloudSession.setLogLevel(SessionLogLevel.Information);
        this.cloudSession.addOnLogDebugListener(args -> Log.d("ASAInfo", args.getMessage()));
        this.cloudSession.addErrorListener(args -> Log.e("ASAError", String.format("%s: %s", args.getErrorCode().name(), args.getErrorMessage())));

        this.cloudSession.addSessionUpdatedListener(args -> {
            synchronized (this.syncSessionProgress) {
                this.recommendedSessionProgress = args.getStatus().getRecommendedForCreateProgress();
                Log.i("ASAInfo", String.format("Session progress: %f", this.recommendedSessionProgress));
                if (!this.scanningForUpload)
                {
                    return;
                }
            }

            runOnUiThread(() -> {
                synchronized (this.syncSessionProgress) {
                    MaterialFactory.makeOpaqueWithColor(this, new Color(
                            this.recommendedSessionProgress,
                            this.recommendedSessionProgress,
                            this.recommendedSessionProgress))
                        .thenAccept(material -> {
                            this.nodeRenderable.setMaterial(material);
                        });
                }
            });
        });

        this.cloudSession.addAnchorLocatedListener(args -> {
            if (args.getStatus() == LocateAnchorStatus.Located)
            {
                runOnUiThread(()->{
                    this.anchorNode = new AnchorNode();
                    this.anchorNode.setAnchor(args.getAnchor().getLocalAnchor());
                    MaterialFactory.makeOpaqueWithColor(this, new Color(android.graphics.Color.GREEN))
                        .thenAccept(greenMaterial -> {
                            this.nodeRenderable = ShapeFactory.makeSphere(0.1f, new Vector3(0.0f, 0.15f, 0.0f), greenMaterial);
                            this.anchorNode.setRenderable(nodeRenderable);
                            this.anchorNode.setParent(arFragment.getArSceneView().getScene());

                            this.anchorId = null;
                            synchronized (this.syncTaps) {
                                this.tapExecuted = false;
                            }
                        });
                });
            }
        });

        this.cloudSession.getConfiguration().setAccountId(/* Copy your account Identifier in here */);
        this.cloudSession.getConfiguration().setAccountKey(/* Copy your account Key in here */);
        this.cloudSession.getConfiguration().setAccountDomain(/* Copy your account Domain in here */);
        this.cloudSession.start();
    }
    // </initializeSession>

    // <handleTap>
    protected void handleTap(HitResult hitResult, Plane plane, MotionEvent motionEvent) {
        synchronized (this.syncTaps) {
            if (this.tapExecuted) {
                return;
            }

            this.tapExecuted = true;
        }

        if (this.anchorId != null) {
            this.anchorNode.getAnchor().detach();
            this.anchorNode.setParent(null);
            this.anchorNode = null;
            initializeSession();
            AnchorLocateCriteria criteria = new AnchorLocateCriteria();
            criteria.setIdentifiers(new String[]{this.anchorId});
            cloudSession.createWatcher(criteria);
            return;
        }

        this.anchorNode = new AnchorNode();
        this.anchorNode.setAnchor(hitResult.createAnchor());
        CloudSpatialAnchor cloudAnchor = new CloudSpatialAnchor();
        cloudAnchor.setLocalAnchor(this.anchorNode.getAnchor());

        MaterialFactory.makeOpaqueWithColor(this, new Color(
                this.recommendedSessionProgress,
                this.recommendedSessionProgress,
                this.recommendedSessionProgress))
            .thenAccept(material -> {
                this.nodeRenderable = ShapeFactory.makeSphere(0.1f, new Vector3(0.0f, 0.15f, 0.0f), material);
                this.anchorNode.setRenderable(nodeRenderable);
                this.anchorNode.setParent(arFragment.getArSceneView().getScene());

                uploadCloudAnchorAsync(cloudAnchor)
                    .thenAccept(id -> {
                        this.anchorId = id;
                        Log.i("ASAInfo", String.format("Cloud Anchor created: %s", this.anchorId));
                        runOnUiThread(() -> {
                            MaterialFactory.makeOpaqueWithColor(this, new Color(android.graphics.Color.BLUE))
                                .thenAccept(blueMaterial -> {
                                    this.nodeRenderable.setMaterial(blueMaterial);
                                    synchronized (this.syncTaps) {
                                        this.tapExecuted = false;
                                    }
                                });
                        });
                    });
            });
    }
    // </handleTap>

    // <uploadCloudAnchorAsync>
    private CompletableFuture<String> uploadCloudAnchorAsync(CloudSpatialAnchor anchor) {
        synchronized (this.syncSessionProgress) {
            this.scanningForUpload = true;
        }

        return CompletableFuture.runAsync(() -> {
            try {
                float currentSessionProgress;
                do {
                    synchronized (this.syncSessionProgress) {
                        currentSessionProgress = this.recommendedSessionProgress;
                    }
                    if (currentSessionProgress < 1.0) {
                        Thread.sleep(500);
                    }
                }
                while (currentSessionProgress < 1.0);

                synchronized (this.syncSessionProgress) {
                    this.scanningForUpload = false;
                }
                runOnUiThread(() -> {
                    MaterialFactory.makeOpaqueWithColor(this, new Color(android.graphics.Color.YELLOW))
                        .thenAccept(yellowMaterial -> {
                            this.nodeRenderable.setMaterial(yellowMaterial);
                        });
                });

                this.cloudSession.createAnchorAsync(anchor).get();
            } catch (InterruptedException | ExecutionException e) {
                Log.e("ASAError", e.toString());
                throw new RuntimeException(e);
            }
        }, executorService).thenApply(ignore -> anchor.getIdentifier());
    }
    // </uploadCloudAnchorAsync>
}

Nästa steg

I den här självstudien har du lärt dig hur du skapar en ny Android-app som integrerar ARCore-funktionen med avstånds ankare för Azure. Om du vill veta mer om Azure Spatial Anchors-biblioteket fortsätter du till vår guide om hur du skapar och hittar fästpunkter.

Nästa steg

I den här självstudien har du sett hur du skapar en ny Android-app som integrerar ARCore-funktioner med Azure Spatial Anchors. Om du vill veta mer om Azure Spatial Anchors-biblioteket fortsätter du till vår guide om hur du skapar och hittar fästpunkter.