Tasks and jobs

[TOC]

介绍

任务绑定到联机或脱机数据或服务,并提供使用这些资源执行异步操作的方法。通过使用任务,您可以:

任务要么直接从任务上的异步方法返回结果,要么使用作业(下文有介绍)来提供状态更新和结果

Tasks

某些操作直接从任务上的异步方法返回其结果,例如LocatorTask.geocodeAsyncRouteTask.solveRouteAsync。对于更复杂或更长时间运行的操作,使用 jobs 代替 tasks。

要使用直接返回结果的任务:

  1. 通过初始化任务来创建任务以使用所需的数据或服务。 某些操作可以在线和离线工作。
  2. 定义任务的输入内容。
    2.1.某些操作仅需要输入简单的值(例如,简单的地理编码操作可能只需要地址字符串作为输入)
    2.2.其他需要定义参数(例如,将地理编码操作限制到特定国家/地区)。
  3. 调用异步操作方法,传入您定义的输入内容。
  4. 根据需要使用请求的结果,例如在地图上显示地理编码结果。

下面的代码使用默认的Esri全局定位器创建LocatorTask,并将地址传递给地理编码。操作完成后,将检索结果位置并显示在GraphicsOverlay中。

// Call geocodeAsync passing in an address
final LocatorTask onlineLocator = new LocatorTask("http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer");
final ListenableFuture<List<GeocodeResult>> geocodeFuture = onlineLocator.geocodeAsync("380 New York Street, Redlands, CA");
geocodeFuture.addDoneListener(new Runnable() {
  @Override
  public void run() {
    try {
      // Get the results of the async operation
      List<GeocodeResult> geocodeResults = geocodeFuture.get();

      if (geocodeResults.size() > 0) {
        //显示结果
        GeocodeResult topResult = geocodeResults.get(0);
        Graphic gecodedLocation = new Graphic(topResult.getDisplayLocation(), topResult.getAttributes(),
            new SimpleMarkerSymbol(SimpleMarkerSymbol.Style.SQUARE, Color.rgb(255, 0, 0), 20.0f));
        mGraphicsOverlay.getGraphics().add(gecodedLocation);
      }
    }
    catch (InterruptedException | ExecutionException e) {
      dealWithException(e); // deal with exception appropriately...
    }
  }
});

某些任务是可加载的,并且在调用需要任务处于加载状态的异步方法(如上所示)时将自行加载。

Define input parameters(定义输入参数)

Tasts 提供了许多选项,使您可以根据需要定制操作。例如,在进行地理编码时,您可以将搜索范围限制在特定区域,国家/地区,地点类别和/或结果数量。当作者发布服务或打包资源时,他们可以为发布的服务从选择适合特定数据的选项或者最常见用例的选项选择默认值。

要查找这些默认参数值,任务会提供创建参数对象的异步方法,并使用这些特定于服务的值进行初始化。然后,您可以在执行操作之前对参数值进行任意更改。以这种方式创建参数对象对于具有许多选项的操作尤其有用,例如计算路径。

下面的代码获取 RouteTask的默认参数,然后确保使用这些参数的结果将返回路径和方向,并且输出空间参考也匹配MapView的结果。

// use async method on RouteTask to get default parameters (task can be loaded or not loaded)
final ListenableFuture<RouteParameters> defaultParametersFuture = routeTask.createDefaultParametersAsync();
defaultParametersFuture.addDoneListener(new Runnable() {
  @Override
  public void run() {
    try {
      // get the parameters from the future
      RouteParameters routeParameters = defaultParametersFuture.get();

      // update properties of route parameters
      routeParameters.setReturnDirections(true);
      routeParameters.setReturnRoutes(true);
      if (routeParameters.getOutputSpatialReference() != mapView.getSpatialReference()) {
        routeParameters.setOutputSpatialReference(mapView.getSpatialReference());
      }
      // Use the updated parameters to solve a route...
    }
    catch (InterruptedException | ExecutionException e) {
      dealWithException(e); // deal with exception appropriately...
    }
  }
});

如果您知道所有输入参数的值,则可以使用你想使用的具有构造函数参数对象。在参数设置简单的情况下,这可以更有效。

例如,下面的代码会创建地理编码参数,以限制要进行地理编码的国家/地区,并限制返回的最大结果。

GeocodeParameters geocodeParams = new GeocodeParameters();
geocodeParams.setCountryCode("France");
geocodeParams.setMaxResults(5);

Work online or offline

许多任务可以通过使用服务在线工作,也可以使用本地数据和资源离线工作。
例如,您可以使用默认的Esri地理编码服务,您自己的地理编码服务,定位器文件(.loc)或移动地图包(.mmpk)对地址进行地理编码。

//使用在线服务
final LocatorTask onlineLocator = new LocatorTask("http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer");

//使用离线定位器文件
final LocatorTask offlineLocator = new LocatorTask(localLocatorPath + ".loc");

// 使用移动地图包
final MobileMapPackage mmpk = new MobileMapPackage(localMmpkPath + ".mmpk");
    mmpk.addDoneLoadingListener(new Runnable() {
      @Override
      public void run() {
        if (mmpk.getLoadStatus() == LoadStatus.LOADED) {
          LocatorTask mmpkLocator = mmpk.getLocatorTask();
         
        }
      }
    });
mmpk.loadAsync();

Tasks and jobs

某些任务会公开具有多个阶段的操作(例如准备和下载地理数据库),并可以生成多个进度消息(例如完成百分比)。这些类型的任务始终绑定到ArcGIS 服务或本地服务器。一个例子是GeodatabaseSyncTask上的generateGeodatabaseAsync

这些任务不是直接返回结果,而是使用作业来监视状态,返回进度更新并返回结果。每个Job代表一个任务的特定操作。作业对于长时间运行的操作很有用,因为它们也可以暂停,恢复和取消。您的应用程序可以支持用户或主机操作系统终止正在运行的作业对象,然后在以后重新创建并恢复该作业。

要使用以下操作

  1. 通过初始化任务来创建任务以使用所需的数据或服务
  2. 定义任务的输入参数。
  3. 调用异步操作方法来获取作业,传入您定义的输入参数。
  4. 开始工作
  5. (可选)侦听作业状态的更改并检查作业消息,例如更新UI并向用户报告进度。
  6. 听取工作完成情况并从操作中获取结果。检查作业中的错误,如果成功,请使用结果。

例:

// 创建在线平铺服务任务
ExportTileCacheTask exportTilesTask = new ExportTileCacheTask("http://sampleserver6.arcgisonline.com/arcgis/rest/services/World_Street_Map/MapServer");

// 参数对象
ExportTileCacheParameters exportTilesParameters = new ExportTileCacheParameters();
exportTilesParameters.getLevelIDs().addAll(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
exportTilesParameters.setAreaOfInterest(
    new Envelope(-1652366.0, 2939253.0, 2537014.0, 8897484.0, SpatialReferences.getWebMercator()));

// 创建Job
final ExportTileCacheJob exportJob = exportTilesTask.exportTileCacheAsync(exportTilesParameters, exportedTpkPath);

// 监听 Job 进度,修改进度条
exportJob.addJobChangedListener(new Runnable() {
  @Override
  public void run() {
    List<Job.Message> messages = exportJob.getMessages();
    updateUiWithProgress(messages.get(messages.size() - 1).getMessage());
  }
});

// 监听 Job 结果 ,完成后使用结果
exportJob.addJobDoneListener(new Runnable() {
  @Override
  public void run() {
    // 发生错误
    if (exportJob.getError() != null) {
      dealWithException(exportJob.getError()); 
      return;
    }
// 执行成功
    if (exportJob.getStatus() == Job.Status.SUCCEEDED) {
      // 在地图中显示结果
      final TileCache exportedTileCache = exportJob.getResult();
      ArcGISTiledLayer tiledLayer = new ArcGISTiledLayer(exportedTileCache);
      mapView.getMap().getOperationalLayers().add(tiledLayer);
    }
  }
});

// 执行 Job
exportJob.start();

调用Job.getStatus将检索作业工作流中的当前点.开始工作定期接收工作变更事件;随着工作的进展,这种情况会随着频率的降低而发生;每个呼叫可能已为作业添加了多个JobMessage。对于成功和失败,一旦作业完成,就会调用作业监听器。已完成的作业,无论是成功还是失败,都无法重新启动。

Report job progress

job表示可能需要一些时间才能完成的异步运行操作。如前所述,您可以在作业完成,失败或被取消时监视作业状态的更改,但是中间的时间如何呢?用户可能会因等待长时间工作而无法获得有关其进度的反馈而感到不耐烦,jobs 提供了一种机制,用于报告它们所代表的运行操作的当前进度(完成百分比)。

当作业运行时,调用作业更改的侦听器(使用Job.addJobChangedListener方法侦听)。您可以从作业的Progress属性中获取作业的当前进度,该属性是一个整数,表示已完成操作的百分比。这允许您的应用程序使用UI元素(例如进度条)提供有关正在运行的作业状态的更多特定信息。

exportJob.start();
// Add a listener to display the % of the job done
exportJob.addProgressChangedListener(new Runnable() {
  @Override public void run() {
    updateUiWithProgress("Progress: " + String.valueOf(exportJob.getProgress()) +"%");
  }
});
// Add a listener to get the result when job is done
exportJob.addJobDoneListener(new Runnable() {
  @Override public void run() {
    TileCache exportedTileCacheResult = exportJob.getResult();
  }
});

Pause, resume, or cancel job

作业旨在有效地处理在作业运行时退出应用程序的用户,并处理由主机操作系统终止的应用程序。Jobs 还处理明确的暂停和取消操作。

Cancel a job

有时候不再需要工作的结果。例如,用户可以改变他们想要下载的图块缓存区域的想法,并且想要取消作业并重新开始。

在作业上调用Job.cancel会立即将其状态更改为Job.Status.FAILED,并将其他信息添加到作业错误对象。将调用作业完成侦听器。错误对象指示错误域和代码,允许您识别何时发生取消。

下面的代码显示,对于正在运行的ExportTileCacheJob,添加了JobDoneListener。在侦听器中,代码检查相应的错误代码和域,指示作业已被取消。此时,代码检查以防已创建切片缓存文件,如果已创建,则删除它。

// 添加监听
runningJob.addJobDoneListener(new Runnable() {
  @Override
  public void run() {
    // 经检测是否 error
    ArcGISRuntimeException jobError = runningJob.getError();
    if (jobError != null) {

      //检测是否是 failed
      if (runningJob.getStatus() == Job.Status.FAILED) {

        // 是否是用户取消
        if ((jobError.getErrorCode() == 18) &&
            (jobError.getErrorDomain() == ArcGISRuntimeException.ErrorDomain.ARCGIS_RUNTIME)) {

          // 取消后的UI刷行
          updateUiWithProgress("Export has been cancelled");

          // 检测是否已经缓存
          File tileCacheFile = new File(exportedTpkPath);
          if (tileCacheFile.exists()) {
            tileCacheFile.delete();
          }
          return;
        }
      }

      dealWithException(jobError); // 处理其他原因造船的错误
      return;
    }

    dealWithJobDoneSuccess(); // 成功后的处理
  }
});

虽然您可以通过重复使用相同的参数来启动新作业,但无法重新启动已取消的作业。取消作业不一定会停止任何服务器端进程,因为并非所有服务都支持服务器端的取消。

Pause and resume a job

job 可以是长时间运行的操作,因此无法保证在应用程序运行时它们将完成。您可以使用Job.pause显式暂停作业。例如,当应用程序切换到后台并且没有后台操作权限时。如果用户希望出于任何原因暂时停止网络访问,则暂停也可能是有用的。

暂停 job 将不会收到作业更改的消息。暂停作业不会阻止任何服务器端进程执行。当作业暂停时,未完成的请求可以完成,因此恢复作业可能会导致它与暂停时的状态不同。

您可以将 job 序列化为JSON,以便在您的应用程序处于后台运行时保持该作业,否则该过程将被终止。再次反序列化时,job 将处于Job.Status.PAUSED状态,无论其序列化时的状态如何,应重新启动以继续侦听完成。Job 更改的侦听器可以是您的应用程序更新作业JSON以存储的好地方。

下面的代码显示,对于现有正在运行的Job,将作业序列化为JSON。

mRunningJob.addJobChangedListener(new Runnable() {
  @Override
  public void run() {
    // Every time the job changes, update the stored JSON that represents the Job.
    mJobJson = mRunningJob.toJson();
  }
});

然后可以在适当时保存此JSON,例如,通过覆盖Activity中的onSaveInstanceState并将JSON String保存到Bundle参数。可以在适当的时候类似地检索JSON,例如在Activity的onRestoreInstanceState或onCreate方法中,具体取决于作业是应该被视为持久数据还是临时数据。

反序列化保存的 JSON数据

private void deserializeFromJson() {
  if ( (mJobJson != null) && (!mJobJson.isEmpty())) {
    mRunningJob = Job.fromJson(mJobJson);

    if (mRunningJob != null) {
      // 反序列化Job.Status.PAUSED状态的 job,
      mRunningJob.start();

    
      mRunningJob.addJobChangedListener(new Runnable() {
        @Override
        public void run() {
          dealWithJobChanged();
        }
      });

      mRunningJob.addJobDoneListener(new Runnable() {
        @Override
        public void run() {
          dealWithJobDone(); 
        }
      });
    }
  }
}