Deploying app with EMDK as a single APK (instant run disabled) causes a crash?

Hi all,

 

I'm working on an Android app for a client that is designed to run on the TC8000 and WT6000. The app is complete and all is working as expected when the app is debugged on emulator or device using Android Studio's instant run feature. But when instant run is disabled, or the APK is built and signed, the app crashes when the EMDK wrapper tries to get the barcode manager. The exact error is:

 

W/System.err: java.lang.ArrayIndexOutOfBoundsException: length=9; index=317143120

W/System.err:     at com.symbol.emdk.EMDKManager.getInstance(EMDKManager.java:211)

W/System.err:     at com.logistex.mobileclient.EMDKWrapper.onOpened(EMDKWrapper.java:74)

...

D/EMDKManager: length=9; index=317143120

 

The EMDKWrapper.java file looks like this:

 

package com.logistex.mobileclient;

 

import com.logistex.mobileclient.Comms.eventScannedData;

import com.symbol.emdk.EMDKManager;

import com.symbol.emdk.EMDKManager.EMDKListener;

import com.symbol.emdk.EMDKResults;

import com.symbol.emdk.barcode.BarcodeManager;

import com.symbol.emdk.barcode.ScanDataCollection;

import com.symbol.emdk.barcode.Scanner;

import com.symbol.emdk.barcode.ScannerConfig;

import com.symbol.emdk.barcode.ScannerException;

import com.symbol.emdk.barcode.ScannerInfo;

import com.symbol.emdk.barcode.ScannerResults;

import com.symbol.emdk.barcode.StatusData;

 

import android.content.Context;

import android.os.AsyncTask;

import android.widget.TextView;

 

import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

 

public class EMDKWrapper implements EMDKListener, Scanner.StatusListener, Scanner.DataListener

{

  EMDKManager emdkManager = null;

   private BarcodeManager barcodeManager = null;

   private Scanner scanner = null;

   // List of supported scanner devices
   private List<ScannerInfo> deviceList;

   // Provides current scanner index in the device Selection Spinner
   private int scannerIndex = 0;

 

   private TextView statusOutputView;

   private eventScannedData scanTarget;

 

  EMDKWrapper(Context appContext)

  {

  EMDKResults results = EMDKManager.getEMDKManager(appContext, this);

   if (results.statusCode != EMDKResults.STATUS_CODE.SUCCESS)

  {

   //Failed to request the EMDKManager
   }

 

  }

 

   public void RegisterFragmentScanTarget(eventScannedData scanTarg)

  {

   scanTarget = scanTarg;

  }

 

   public void RegisterStatusOutputView(TextView statusOutput)

  {

   statusOutputView = statusOutput;

  }

 

   void release()

  {

   //Release the EMDKmanager on Application exit.
   if (emdkManager != null)

  {

   emdkManager.release();

   emdkManager = null;

  }

  }

 

   @Override
   public void onOpened(EMDKManager emdkManager)

  {

   this.emdkManager = emdkManager;

   //The EMDK Manager is ready and now you can call other EMDK APIs.
   System.out.println("EMDK Manager = " + emdkManager);

 

   barcodeManager = (BarcodeManager) this.emdkManager.getInstance(EMDKManager.FEATURE_TYPE.BARCODE);

 

  System.out.println("Barcode Manager = " + barcodeManager);

   // Get the supported scanner devices
   enumerateScannerDevices();

   try
   {

  deInitScanner();

  initializeScanner();

  setProfile();

  } catch (ScannerException e)

  {

  System.out.println("EMDK Exception: " + e);

  }

  }

 

   // Sets the user selected Barcode scanning Profile
   public void setProfile()

  {

   try
   {

   // cancel any pending asynchronous read calls before applying profile
  // and start reading barcodes
   if (scanner.isReadPending())

   scanner.cancelRead();

 

  ScannerConfig config = scanner.getConfig();

  config.decoderParams.code11.enabled = true;

  config.decoderParams.code39.enabled = true;

  config.decoderParams.code128.enabled = true;

  config.decoderParams.upca.enabled = true;

  config.decoderParams.ean8.enabled = true;

  config.decoderParams.ean13.enabled = true;

  config.readerParams.readerSpecific.cameraSpecific.illuminationMode = ScannerConfig.IlluminationMode.ON;

  config.scanParams.decodeHapticFeedback = true;

 

   // Set the Scan Tone selected from the Scan Tone Spinner
   config.scanParams.audioStreamType = ScannerConfig.AudioStreamType.RINGER;

   scanner.setConfig(config);

 

   // Starts an asynchronous Scan. The method will not turn
  // ON the
  // scanner. It will, however, put the scanner in a state
  // in which
  // the scanner can be turned ON either by pressing a
  // hardware
  // trigger or can be turned ON automatically.

   scanner.triggerType = Scanner.TriggerType.HARD;

 

  } catch (ScannerException e)

  {

  e.printStackTrace();

  }

  }

 

   public void ScannerOn()

  {

   if (scanner == null)

   return;

   try
   {

   if (scanner.isReadPending())

   scanner.cancelRead();

   scanner.read();

  }

   catch (ScannerException e)

  {

  e.printStackTrace();

  }

  }

 

   public void ScannerOff()

  {

   if (scanner == null)

   return;

   try
   {

   if (scanner.isReadPending())

   scanner.cancelRead();

  }

   catch (ScannerException e)

  {

  e.printStackTrace();

  }

  }

 

   @Override
   public void onClosed()

  {

   /* EMDKManager is closed abruptly.
  * Call EmdkManager.release() to free the resources used by the
  * current EMDK instance.
  * */

   deInitScanner();

   if (emdkManager != null)

  {

   emdkManager.release();

   emdkManager = null;

  }

  }

 

   @Override
   public void onData(ScanDataCollection scanDataCollection)

  {

   new AsyncDataUpdate().execute(scanDataCollection);

  }

 

   public void processScan(ScanDataCollection scanDataCollection, TextView updateField)

  {

   new AsyncDataUpdate().execute(scanDataCollection);

  }

 

   @Override
   public void onStatus(StatusData statusData)

  {

   new AsyncStatusUpdate().execute(statusData);

  }

 

   private void enumerateScannerDevices()

  {

   if (barcodeManager != null)

  {

  List<String> friendlyNameList = new ArrayList<String>();

   int Index = 0;

 

   deviceList = barcodeManager.getSupportedDevicesInfo();

 

   if (deviceList.size() != 0)

  {

 

  Iterator<ScannerInfo> it = deviceList.iterator();

   while (it.hasNext())

  {

  ScannerInfo scnInfo = it.next();

  friendlyNameList.add(scnInfo.getFriendlyName());

   if (scnInfo.isDefaultScanner())

  {

   scannerIndex = Index;

  }

  ++Index;

  }

  } else
   {

  System.out.println("Status: Failed to get the list of supported scanner devices! Please close and restart the application.");

  }

  }else
   {

  System.out.println("Status: Barcode manager is null.");

  }

  }

 

   private void initializeScanner() throws ScannerException

  {

 

   if (deviceList.size() != 0)

  {

   scanner = barcodeManager.getDevice(deviceList.get(scannerIndex));

  } else
   {

  Program.Toast("Status: Failed to get the specified scanner device! Please close and restart the application.", 1);

  }

 

   if (scanner != null)

  {

 

   // Add data and status listeners
   scanner.addDataListener(this);

   scanner.addStatusListener(this);

 

   try
   {

   // Enable the scanner
   scanner.enable();

 

  } catch (ScannerException e)

  {

  Program.Toast("Barcode scanner exception: " + e.getMessage(), 1);

  }

  }

  }

 

   // Disable the scanner instance
   private void deInitScanner()

  {

   if (scanner != null)

  {

   try
   {

   scanner.cancelRead();

 

   scanner.removeDataListener(this);

   scanner.removeStatusListener(this);

   scanner.disable();

 

  } catch (ScannerException e)

  {

  }

   scanner = null;

  }

  }

 

   // Update the scan data on UI
   int dataLength = 0;

 

   // AsyncTask that configures the scanned data on background
  // thread and updated the result on UI thread with scanned data and type of
  // label
   private class AsyncDataUpdate extends
   AsyncTask<ScanDataCollection, Void, String>

  {

 

   @Override
   protected String doInBackground(ScanDataCollection... params)

  {

   // Status string that contains both barcode data and type of barcode
  // that is being scanned
   String statusStr = "";

 

  ScanDataCollection scanDataCollection = params[0];

 

   // The ScanDataCollection object gives scanning result and the
  // collection of ScanData. So check the data and its status
   if (scanDataCollection != null && scanDataCollection.getResult() == ScannerResults.SUCCESS)

  {

 

  ArrayList<ScanDataCollection.ScanData> scanData = scanDataCollection.getScanData();

 

   // Iterate through scanned data and prepare the statusStr
   for (ScanDataCollection.ScanData data : scanData)

  {

   // Get the scanned data
   String barcodeData = data.getData();

   // Get the type of label being scanned
   ScanDataCollection.LabelType labelType = data.getLabelType();

   // Concatenate barcode data and label type
   statusStr = barcodeData + " " + labelType;

  }

  }

 

   // Return result to populate on UI thread
   return statusStr;

  }

 

   @Override
   protected void onPostExecute(String result)

  {

   // Update the dataView EditText on UI thread with barcode data and
  // its label type
   if (dataLength++ > 50)

  {

   // Clear the cache after 50 scans
   dataLength = 0;

  }

   scanTarget.ScannedDataDelegate(result);

  setProfile();

  }

 

   @Override
   protected void onPreExecute()

  {

  }

 

   @Override
   protected void onProgressUpdate(Void... values)

  {

  }

 

 

  }

 

   // AsyncTask that configures the current state of scanner on background
  // thread and updates the result on UI thread
   private class AsyncStatusUpdate extends AsyncTask<StatusData, Void, String>

  {

 

   @Override
   protected String doInBackground(StatusData... params)

  {

  String statusStr = "";

   // Get the current state of scanner in background
   StatusData statusData = params[0];

  StatusData.ScannerStates state = statusData.getState();

   // Different states of Scanner
   switch (state)

  {

   // Scanner is IDLE
   case IDLE:

  statusStr = "The scanner enabled and its idle";

   break;

   // Scanner is SCANNING
   case SCANNING:

  statusStr = "Scanning..";

   break;

   // Scanner is waiting for trigger press
   case WAITING:

  statusStr = "Waiting for trigger press..";

   break;

   // Scanner is not enabled
   case DISABLED:

  statusStr = "Scanner is not enabled";

   break;

   default:

   break;

  }

 

   // Return result to populate on UI thread
   return statusStr;

  }

 

   @Override
   protected void onPostExecute(String result)

  {

   // Update the status text view on UI thread with current scanner
  // state
   if (statusOutputView != null)

   statusOutputView.setText(result);

  }

 

   @Override
   protected void onPreExecute()

  {

  }

 

   @Override
   protected void onProgressUpdate(Void... values)

  {

  }

 

 

  }

}

 

The issue occurs at this line in onOpened:

 

 

   barcodeManager = (BarcodeManager) this.emdkManager.getInstance(EMDKManager.FEATURE_TYPE.BARCODE);

 

The println after this line shows the barcodeManager is null and the program crashes when it is accessed later.

 

As I mentioned, this only happens when I'm installing a single APK. Instant run seems to split the APK into multiple parts which fixes the issue. However, it is a requirement that the app can be installed via a single APK, as the process will need to be repeated for many devices.

 

I've tried many ways of including the EMDK and changing the build.gradle to try and solve it, but the results are always the same - working via instant run but not otherwise. The only other logged line I've found that might be relevant is this:

 

W/ResourcesManager: Asset path '/system/framework/com.symbol.emdk.jar' does not exist or contains no resources.

 

Which is logged before the EMDK is connected, just before:

 

D/com.symbol.emdk.EMDKServiceConnection: The EMDK Service will be connected soon (asynchronus call)!

 

Is there something obvious I'm getting wrong here? How can I build a single APK that includes the EMDK?

 

Thanks