I'm trying to load classes from external JAR file placed on sdcard. Many people used DexClassLoader successfuly.
My steps:
1) Create classes.dex file from jar file:
dx --dex --output=classes.dex file.jar
2) Add generated classes.dex file to jar
3) Create DexClassLoader:
ClassLoader classLoader = new DexClassLoader(context.getDir("dex",
Context.MODE_PRIVATE).getAbsolutePath(), jarfile.getAbsolutePath(), null,
context.getClassLoader());
4) When I see what's in dex inside:
try {
DexFile dx = DexFile.loadDex(jarFile.getAbsolutePath(), File.createTempFile("opt", "dex",
context.getCacheDir()).getPath(), 0);
// Print all classes in the DexFile
for(Enumeration<String> classNames = dx.entries(); classNames.hasMoreElements();) {
String className = classNames.nextElement();
System.out.println("class: " + className);
}
} catch (IOException e) {
e.printStackTrace();
}
DexOpt: --- BEGIN 'file.jar' (bootstrap=0) ---
DexOpt: --- END 'file.jar' (success) ---
DEX prep '/mnt/sdcard/tmp/file.jar': unzip in 0ms, rewrite 83ms
class: com.test.TestClass
5) Load class:
Class<?> class = classLoader.loadClass("com.test.TestClass");
Here I get exception!:
java.lang.ClassNotFoundException: Didn't find class "com.test.TestClass" on path: /data/data/com.myapp/cache/app_dex
I see that it creates app_dex directory but it's empty.
Please, help!!!
The only way I could load classes from jar is by DexFile class:
DexFile dexFile = DexFile.loadDex(jar.getAbsolutePath(),
File.createTempFile("opt", "dex", context.getCacheDir()).getPath(), 0);
....
Class<?> currClass = dexFile.loadClass(className, context.getClassLoader());
Very interesting why I can't do this with DexClassLoader.
Does anybody use it in Android 4.2.2 or 4.4.2 ?
Thanks!
It seems you set incorrect order
ClassLoader classLoader = new DexClassLoader(context.getDir("dex",
Context.MODE_PRIVATE).getAbsolutePath(), jarfile.getAbsolutePath(), null,
context.getClassLoader());
replace first parameter with second
More info https://developer.android.com/reference/dalvik/system/DexClassLoader#public-constructors_1
Related
I have developed a SDK for android applications.We have many clients using this SDK in there applications.Now i have updated my SDK.I am looking for a way that these changes can reflect in there application without updating there app on play store.Urgent help needed.Any help will be appreciated.Thanks in advance.
Well you can dynamically load a jar file from your SD card using DexLoader class...which you can update when ever you want..on your storage... below is working code..
final String libPath = Environment.getExternalStorageDirectory() + "/test.jar";
final File tmpDir = getDir("dex", 0);
final DexClassLoader classloader = new DexClassLoader(libPath, tmpDir.getAbsolutePath(), null, this.getClass().getClassLoader());
final Class<Object> classToLoad = (Class<Object>) classloader.loadClass("com.test.android.MainActivity");
final Object myInstance = classToLoad.newInstance();
final Method doSomething = classToLoad.getMethod("doSomething");
doSomething.invoke(myInstance);
and in your library file code can be like this
public class MainActivity {
public void doSomething() {
Log.e(MainActivity .class.getName(), "MainActivity : doSomething() called.");
}}
tell me if you need any assistance
there is no such way for your situation. But there is one thing you can do to enable it for next update. Android can dynamically load compiled code with DexClassLoader. So you compile a new DEX file, and then force your SDK to download and use it.
// Internal storage where the DexClassLoader writes the optimized dex file to
final File optimizedDexOutputPath = getDir("outdex", Context.MODE_PRIVATE);
DexClassLoader cl = new DexClassLoader(dexInternalStoragePath.getAbsolutePath(),
optimizedDexOutputPath.getAbsolutePath(),
null,
getClassLoader());
Class libProviderClazz = null;
try {
// Load the library.
libProviderClazz =
cl.loadClass("com.example.dex.lib.LibraryProvider");
// Cast the return object to the library interface so that the
// caller can directly invoke methods in the interface.
// Alternatively, the caller can invoke methods through reflection,
// which is more verbose.
LibraryInterface lib = (LibraryInterface) libProviderClazz.newInstance();
lib.showAwesomeToast(this, "hello");
} catch (Exception e) { ... }
I'm writing a word game in Android. It's my first app so my knowledge is almost non-existent.
What I would like to do is use JWI to access the WordNet dictionary. This requires specifying the WordNet dictionary's file path.
From what I've read, Android "assets" are not available via a simple file path, but what JWI requires to initialize the WordNet dictionary API is a URL to the disk location of the dictionary files.
So, what is the best course of action? Should I copy the assets at startup-time into a known folder on the android device? I can't think of a better way but that seems entirely stupid to me.
Any help gratefully received.
I have the same problem (for a jetty webapp however and not android) and tried those two approaches, however unsuccessfully:
JWNL.initialize(this.getClass().getClassLoader().getResourceAsStream("wordnet_properties.xml");
dict = Dictionary.getInstance();
Here it successfully loads wordnet_properties.xml but it cannot access the dictionary which is pointed to by the properties file.
Using the dictionary folder directly:
String dictPath = "models/en/wordnet/dict/";
URL url = this.getClass().getClassLoader().getResource(dictPath);
System.out.println("loading wordnet from "+url);
dict = new RAMDictionary(url, ILoadPolicy.NO_LOAD);
Here I get the dictionary URL to be jar:file:/home/myusername/.m2/repository/package/1.0-SNAPSHOT/commons-1.0-SNAPSHOT.jar!/models/en/wordnet/dict/. WordNet however doesn't accept the jar protocol and gives me the error:
java.lang.IllegalArgumentException: URL source must use 'file' protocol
at edu.mit.jwi.data.FileProvider.toFile(FileProvider.java:693)
at edu.mit.jwi.data.FileProvider.open(FileProvider.java:304)
at edu.mit.jwi.DataSourceDictionary.open(DataSourceDictionary.java:92)
at edu.mit.jwi.RAMDictionary.open(RAMDictionary.java:216)
My next investigation will be to create a subclass to RAMDictionary or something similar, please tell me if you have found a solution in the meantime.
P.S.: I just wrote the developer a mail asking for help after I tried to rewrite the FileProvider to use resources instead but after one or two hours I gave up because the code calls so much other code that also only works with files. I will keep you up to date!
P.P.S.: I received an answer from the developer saying that it is principially not possible with streams because they don't offer random access which is necessary. However, he offered to implement a solution to load it all in RAM, if really necessary, but that would use up about 500 MB and I guess that is too much for android apps so I guess it is still best to unpack it somewhere.
P.S.: Here is my unpacking solution (you can replace the System.out.println statements with logger statements if you use logging or remove them if you don't like them):
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/** Allows WordNet to be run from within a jar file by unpacking it to a temporary directory.**/
public class WordNetUnpacker
{
static final String ID = "178558556719"; // minimize the chance of interfering with an existing directory
static final String jarDir = "models/en/wordnet/dict";
/**If running from within a jar, unpack wordnet from the jar to a temp directory (if not already done) and return that.
* If not running from a jar, just return the existing wordnet directory.
* #see getUnpackedWordNetDir(Class)*/
static File getUnpackedWordNetDir() throws IOException
{return getUnpackedWordNetDir(WordNetUnpacker.class);}
/**If running from within a jar, unpack wordnet from the jar to a temp directory (if not already done) and return that.
* If not running from a jar, just return the existing wordnet directory.
* #param clazz the class in whose classloader the wordnet resources are found.
* #see getUnpackedWordNetDir()**/
static File getUnpackedWordNetDir(Class clazz) throws IOException
{
String codeSource = clazz.getProtectionDomain().getCodeSource().getLocation().getPath();
System.out.println("getUnpackedWordNetDir: using code source "+codeSource);
if(!codeSource.endsWith(".jar"))
{
System.out.println("not running from jar, no unpacking necessary");
try{return new File(WordNetUnpacker.class.getClassLoader().getResource(jarDir).toURI());}
catch (URISyntaxException e) {throw new IOException(e);}
}
try(JarFile jarFile = new JarFile(codeSource))
{
String tempDirString = System.getProperty("java.io.tmpdir");
if(tempDirString==null) {throw new IOException("java.io.tmpdir not set");}
File tempDir = new File(tempDirString);
if(!tempDir.exists()) {throw new IOException("temporary directory does not exist");}
if(!tempDir.isDirectory()) {throw new IOException("temporary directory is a file, not a directory ");}
File wordNetDir = new File(tempDirString+'/'+"wordnet"+ID);
wordNetDir.mkdir();
System.out.println("unpacking jarfile "+jarFile.getName());
copyResourcesToDirectory(jarFile, jarDir, wordNetDir.getAbsolutePath());
return wordNetDir;
}
}
/** Copies a directory from a jar file to an external directory. Copied from Stack Overflow. */
public static void copyResourcesToDirectory(JarFile fromJar, String jarDir, String destDir) throws IOException
{
int copyCount = 0;
for (Enumeration<JarEntry> entries = fromJar.entries(); entries.hasMoreElements();)
{
JarEntry entry = entries.nextElement();
if(!entry.getName().contains("models")) continue;
if (entry.getName().startsWith(jarDir) && !entry.isDirectory()) {
copyCount++;
File dest = new File(destDir + "/" + entry.getName().substring(jarDir.length() + 1));
File parent = dest.getParentFile();
if (parent != null) {
parent.mkdirs();
}
FileOutputStream out = new FileOutputStream(dest);
InputStream in = fromJar.getInputStream(entry);
try {
byte[] buffer = new byte[8 * 1024];
int s = 0;
while ((s = in.read(buffer)) > 0) {
out.write(buffer, 0, s);
}
} catch (IOException e) {
throw new IOException("Could not copy asset from jar file", e);
} finally {
try {
in.close();
} catch (IOException ignored) {}
try {
out.close();
} catch (IOException ignored) {}
}
}
}
if(copyCount==0) System.out.println("Warning: No files copied!");
}
}
You can just copy all dict files from "assets" to the internal directory of your app. Just do it once, on the first app launch.
Since then you can use JWI in a causual way like this:
String path = getFilesDir() + "/dict";
URL url = new URL("file", null, path);
IDictionary dict = new Dictionary(url);
I tried really hard, but always get a Class Not Found exception, from reading this answer
https://stackoverflow.com/a/3024261
I took my jar run the
dx --dex --output=C:\classes.dex C:\MyAndroidLib.jar
and got a dex file
then I run the
apt add C:\MyLib.jar C:\classes.dex
to create a jar with the dex file.
then I wrote the following code.
DexClassLoader classLoader = new DexClassLoader(
destPath, dirPath +"/" , null, getClass().getClassLoader());
Class<?> classToLoad = classLoader.loadClass("ClassImpl");
on debug I can see that the dex is inside the classLoader(under the mDexs member)
and the ClassImpl is the only class I got inside.
but I keep getting the class not found exception.
Anyone got a working sample of dynamic class loading from external jar ?
Someone knows whats my problem?
I did not use aapt... Only dex. And I got my class loaded, and my methods called.
Use this code for see inside the dex file:
DexFile dexfile = DexFile.loadDex(url_jar_path,
File.createTempFile("opt", "dex", context.getCacheDir()).getPath(), 0);
// Print all classes in the DexFile
Enumeration<String> classNames = dexfile.entries();
String classname = "";
while (true) {
if (! classNames.hasMoreElements()) {
break;
}
classname = classNames.nextElement();
}
Use this code for see inside the class:
Class class = dexclassloader.loadClass(ruta_clase_en_jar);
String name;
Method[] method_array = clase.getMethods();
for (Method i: method_array) {
name = i.getName ();
}
I'm using reflection to call a method that is outside of the target API level of my Android application:
try {
Method m = Class.forName("android.content.Context")
.getDeclaredMethod("getExternalCacheDir");
Object result = m.invoke(this);
if (result instanceof File) {
Log.v("MyApp", "external cache: "
+ ((File) result).getAbsolutePath());
cacheDirectory = (File) result;
} else {
Log.v("MyApp", "non-file cache: " + result);
}
} catch (Exception e) {
// ...
}
I can optimize this without any problems through Proguard, but it warns me:
Note: com.example.MyApp accesses a declared method 'getExternalCacheDir()' dynamically
Maybe this is library method 'android.content.Context { java.io.File getExternalCacheDir(); }'
Maybe this is library method 'android.content.ContextWrapper { java.io.File getExternalCacheDir(); }'
Maybe this is library method 'android.test.mock.MockContext { java.io.File getExternalCacheDir(); }'
Note: there were 1 accesses to class members by means of introspection.
You should consider explicitly keeping the mentioned class members
(using '-keep' or '-keepclassmembers').
Is this an actual problem, or is Proguard just informing me of a potential problem?
This is similar to the answer Paul Lammertsma posted. Also take a look at the author of ProGuard Eric Lafortune's answer at: How to suppress 'Maybe this is program method' warnings from ProGuard
You can avoid it by explicitly mentioning the method in the
configuration:
-keep class com.foo.OtherClass { com.foo.OtherClass getInstance(); }
Alternatively, you can suppress notes on a class:
-dontnote com.foo.MyClass
The problem resided in a library project that I was using, and the proguard.cfg from that project wasn't being inspected by Proguard.
By adding the following lines to my own projects proguard.cfg, I was able to make the notice disappear:
-keep class android.content.Context {
public java.io.File getExternalCacheDir();
}
I wanted to see the source code for an apk file created which I did by using dex2jar to transform the dex file in to a .class file, and then use a jar decompiler (such as the free jd-gui) to plain text java.
Now for viewing that source code I added the classes.dex.dex2jar as an external jar in the project and started viewing the .class files of the external jar added. Now the issue is, in the code there are some statements like:
1 local1 = new 1(this);
2 local2 = new 2(this);
3 local3 = new 3(this);
Can anyone help how to resolve this.
This is equivalent to creating 3 inner classes, like so:
public class test000021_innerclass {
void X() {
innerClass1 c1 = new innerClass1(this);
innerClass2 c2 = new innerClass2(this);
innerClass3 c3 = new innerClass3(this);
}
private class innerClass1 {
public innerClass1(test000021_innerclass test000021_innerclass) {
}
}
private class innerClass2 {
public innerClass2(test000021_innerclass test000021_innerclass) {
}
}
private class innerClass3 {
public innerClass3(test000021_innerclass test000021_innerclass) {
}
}
}