Java Question [SOLVED] Question on the inner workings of an Android app

alwaysbusy

Expert
Licensed User
Longtime User
I have to write a very simple wrapper around a N5000 scanner library.

Situation:

They have provided me two library files with the same package name:
zltd_decoder.jar: package name com.zltd.decoder
zltd_common.jar: package name com.zltd.decoder (includes all the code from zltd_decoder.jar + some extra settings methods.

--> both contain the class: com.zltd.decoder.DecoderManager (but the common one contains also the extra methods)

On the device, there are two of their apps: one using the zltd_decoder.jar (demo), one using the ztld_common.jar (Scanner settings).

I wrote my wrapper using the zltd_common.jar (NOT using zltd_decoder.jar AT ALL), and all the extra methods can be accessed in Eclipse.

So far, so good.

Now if I run my app using my wrapper library and try to access one of the extra methods in zltd_common.jar, I get the following error:

B4X:
java.lang.NoSuchMethodError: No virtual method enableSymbology(IZ)V in class Lcom/zltd/decoder/DecoderManager; or its super classes (declaration of 'com.zltd.decoder.DecoderManager' appears in /system/framework/zltd_decoder.jar)

Note, in the error, it somehow points to framework/zltd_decoder.jar, although I have not used this library (I used ztld_common.jar).

So my question: although very improbable, is it somehow possible when I call a method in my wrapper Android picks the com.zltd.decoder.DecoderManager class from the demo app NOT containing the extra methods instead of the correct class embedded in my apk?

They are not willing to give me the source code of their libraries, else I would just recompile it with a different package name (like com.ab.n5000), to make sure it uses the correct DecoderManager class.
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
lthough very improbable, is it somehow possible when I call a method in my wrapper Android picks the com.zltd.decoder.DecoderManager class from the demo app NOT containing the extra methods instead of the correct class embedded in my apk?
I don't think so. You can uninstall the demo app and try it.

A good way to understand what happens is to decompile the APK with this tool: https://github.com/pxb1988/dex2jar
This will show you which classes are actually inside the APK.
 

alwaysbusy

Expert
Licensed User
Longtime User
I don't have access to the .apk files. They are pre-installed on the device and can't even be uninstalled. I have no idea why they created two libraries with the same package and class name, but different content: com.zltd.decoder.DecoderManager.

I think this is asking for trouble.

I personally like to use the dex2jar + d4j combo to decompile to java. It sometimes can decompile stuff where other decompilers don't succeed, but I have a look at JADX too for other projects.

This was supposed to be a 10 minutes routine job, but turns out a small nightmare...
 

alwaysbusy

Expert
Licensed User
Longtime User
When I decompile the .apk file B4J created, all methods are there:

upload_2017-5-16_9-58-39.png


Also tried to rename ztld_common.jar to ztld_decoder.jar but, as expected, this didn't make a difference. I could as well've called it gibberish.jar...
 
Last edited:

JordiCP

Expert
Licensed User
Longtime User
As a guess (assuming that your final jar only includes 'zltd_common.jar' file), I would check that it is located under a given folder and try to modify classpath accordingly so that it searches first where it is located.

(while googling I found an interesting StackOverflow post to check where classes have really been loaded from)
 

alwaysbusy

Expert
Licensed User
Longtime User
As a guess (assuming that your final jar only includes 'zltd_common.jar' file)
In my wrapper jar, it is not included. In the final apk file it is. I did (don't) know you can include the jar you are wrapping into a B4J library file. Can you elaborate a bit on this?

(while googling I found an interesting StackOverflow post to check where classes have really been loaded from
Interesting read, haven't got it working yet (Always returns null in my code, even with my own class: ABN5000.class).

B4X:
public String GetClassLocation() {
     //String path = DecoderManager.class.getProtectionDomain().getCodeSource().getLocation().getPath();
     BA.Log(mDecoderMgr.getClass().getSimpleName()); // <------ DecoderManager
     BA.Log(DecoderManager.class.getSimpleName()); // <------ DecoderManager
     URL url = getLocation(DecoderManager.class);
     if (url!=null) {
       File file = urlToFile(url);
       if (file!=null) {
         return file.getPath();
       }
     }
     return "";
   }
   
   protected static URL getLocation(final Class<?> c) {
    if (c == null) return null; // could not load the class

    // try the easy way first
    try {
    final URL codeSourceLocation =
    c.getProtectionDomain().getCodeSource().getLocation();
    if (codeSourceLocation != null) return codeSourceLocation;
    }
    catch (final SecurityException e) {
    BA.Log("Cannot access protection domain.");
    }
    catch (final NullPointerException e) {
    BA.Log("Protection domain or code source is null.");   // <------ comes here
    }

    // NB: The easy way failed, so we try the hard way. We ask for the class
    // itself as a resource, then strip the class's path from the URL string,
    // leaving the base path.

    // get the class's raw resource path
    final URL classResource = c.getResource(c.getSimpleName() + ".class");
    if (classResource == null) return null; // cannot find class resource  // <------ jumps out here

    final String url = classResource.toString();
    final String suffix = c.getCanonicalName().replace('.', '/') + ".class";
    if (!url.endsWith(suffix)) return null; // weird URL

    // strip the class's path from the URL string
    final String base = url.substring(0, url.length() - suffix.length());

    String path = base;

    // remove the "jar:" prefix and "!/" suffix, if present
    if (path.startsWith("jar:")) path = path.substring(4, path.length() - 2);

    try {
    return new URL(path);
    }
    catch (final MalformedURLException e) {
    e.printStackTrace();
    return null;
    }
   }

   /**
    * Converts the given {@link URL} to its corresponding {@link File}.
    * <p>
    * This method is similar to calling {@code new File(url.toURI())} except that
    * it also handles "jar:file:" URLs, returning the path to the JAR file.
    * </p>
    *
    * @param url The URL to convert.
    * @return A file path suitable for use with e.g. {@link FileInputStream}
    * @throws IllegalArgumentException if the URL does not correspond to a file.
    */
   public static File urlToFile(final URL url) {
    return url == null ? null : urlToFile(url.toString());
   }

   /**
    * Converts the given URL string to its corresponding {@link File}.
    *
    * @param url The URL to convert.
    * @return A file path suitable for use with e.g. {@link FileInputStream}
    * @throws IllegalArgumentException if the URL does not correspond to a file.
    */
   public static File urlToFile(final String url) {
    String path = url;
    if (path.startsWith("jar:")) {
    // remove "jar:" prefix and "!/" suffix
    final int index = path.indexOf("!/");
    path = path.substring(4, index);
    }
    try {
       /*
    if (PlatformUtils.isWindows() && path.matches("file:[A-Za-z]:.*")) {
    path = "file:/" + path.substring(5);
    }
    */
    return new File(new URL(path).toURI());
    }
    catch (final MalformedURLException e) {
    BA.Log("URL is not completely well-formed.");
    }
    catch (final URISyntaxException e) {
       BA.Log("URL is not completely well-formed.");
    }
    if (path.startsWith("file:")) {
    // pass through the URL as-is, minus "file:" prefix
    path = path.substring(5);
    return new File(path);
    }
    throw new IllegalArgumentException("Invalid URL: " + url);
   }
 

alwaysbusy

Expert
Licensed User
Longtime User
Ok, so it definitely does not use the correct embedded zltd_common.jar file, but another one from somewhere else:

Running this java code:
B4X:
public void DumpMethods() {
    try {
      Class c = DecoderManager.class;
      Method[] m = c.getDeclaredMethods();
      for (int i = 0; i < m.length; i++) {
            BA.Log(m[i].toString());
      }
    } catch (Throwable e) {
      System.err.println(e);
    }     
 }

Result:
B4X:
public void com.zltd.decoder.DecoderManager.addDecoderStatusListener(com.zltd.decoder.DecoderManager$IDecoderStatusListener)
public int com.zltd.decoder.DecoderManager.connectDecoderSRV()
public int com.zltd.decoder.DecoderManager.continuousShoot()
public int com.zltd.decoder.DecoderManager.disconnectDecoderSRV()
public void com.zltd.decoder.DecoderManager.dispatchScanKeyEvent(android.view.KeyEvent)
public void com.zltd.decoder.DecoderManager.enableLight(int,boolean)
public int com.zltd.decoder.DecoderManager.getDataTransferType()
public int com.zltd.decoder.DecoderManager.getFlashMode()
public int com.zltd.decoder.DecoderManager.getScanEfficientMode()
public [B com.zltd.decoder.DecoderManager.getScanImageBytes()
public int com.zltd.decoder.DecoderManager.getScanMode()
public int com.zltd.decoder.DecoderManager.getScanSceneMode()
public boolean com.zltd.decoder.DecoderManager.getScannerEnable()
public boolean com.zltd.decoder.DecoderManager.isContinuousShootState()
public boolean com.zltd.decoder.DecoderManager.isInKeyShoot()
public boolean com.zltd.decoder.DecoderManager.isKeyShootEnabled()
public boolean com.zltd.decoder.DecoderManager.isScanConnect()
public int com.zltd.decoder.DecoderManager.keyShoot(boolean)
public int com.zltd.decoder.DecoderManager.quitWithReason(int)
public void com.zltd.decoder.DecoderManager.removeDecoderStatusListener(com.zltd.decoder.DecoderManager$IDecoderStatusListener)
public void com.zltd.decoder.DecoderManager.setDataTransferType(int)
public void com.zltd.decoder.DecoderManager.setFlashLightOn(boolean)
public void com.zltd.decoder.DecoderManager.setFlashMode(int)
public void com.zltd.decoder.DecoderManager.setScanEfficientMode(int)
public void com.zltd.decoder.DecoderManager.setScanMode(int)
public void com.zltd.decoder.DecoderManager.setScanSceneMode(int)
public void com.zltd.decoder.DecoderManager.setScannerEnable(boolean)
public int com.zltd.decoder.DecoderManager.singleShoot()
public int com.zltd.decoder.DecoderManager.stopContinuousShoot()
public void com.zltd.decoder.DecoderManager.stopDecode()
public int com.zltd.decoder.DecoderManager.stopShootImmediately()
private com.zltd.decoder.IDecoderService com.zltd.decoder.DecoderManager.getIDecoderService()
public static com.zltd.decoder.DecoderManager com.zltd.decoder.DecoderManager.getInstance()

And indeed, it is right, the methods like enableSymbology() etc are missing.
 

alwaysbusy

Expert
Licensed User
Longtime User
Sorry to come back to this, but I've talked with the guys from Newland and they say if the .jar is embedded in your app, it should use that one, not the one in the /system/framework. But in B4A it does not so I was wondering if there is a way to do it?

I came accross a post that was talking about -Xbootclasspath (like we have in B4J) and other tricks to force some things. http://stackoverflow.com/questions/...-android-jar-by-editing-build-gradle-in-andro
 
Last edited:

Erel

B4X founder
Staff member
Licensed User
Longtime User
I came accross a post that was talking about -Xbootclasspath (like we have in B4J) and other tricks to force some things.
I don't think that is solves the same problem.

I haven't found any information whether it is possible or not to override a framework library. My guess is that it is not possible but I might be wrong.
Sorry to come back to this, but I've talked with the guys from Newland and they say if the .jar is embedded in your app, it should use that one, not the one in the /system/framework. But in B4A it does not so I was wondering if there is a way to do it?
Try to create a small app with Android Studio and this jar and see whether it works.
 
Top