I have loaded a external apk through DexClassLoader.
in a method will be invoked has a GUI load from .xml layout.
But i can not view this GUI (from external apk) through invoke method !!
In conclusion, I want to load a GUI from an external APK file through invoke method.
Please help me !!
p/s: sorry about my english!
final File _optimizedDexOutputPath = getDir("outdex", Context.MODE_PRIVATE);
String _className;
String _methodToInvoke;
String _apkfilePath;
HashMap<String, String> hmData;
//Prepare
hmData = preWorkflow();
_className = hmData.get("className");
_apkfilePath = hmData.get("apkfilePath");
_methodToInvoke = hmData.get("methodToInvoke");
DexClassLoader dLoader = new DexClassLoader(_apkfilePath,
_optimizedDexOutputPath.getAbsolutePath(),
null, ClassLoader.getSystemClassLoader().getParent());
try {
Class<?> loadedClass = dLoader.loadClass(_className);
Object obj = (Object) loadedClass.newInstance();
Method m = loadedClass.getDeclaredMethod(_methodToInvoke);
m.invoke(obj);
} catch (IllegalAccessException e) {
e.getCause().printStackTrace();
} catch (InvocationTargetException e) {
e.getCause().printStackTrace();
}catch (NullPointerException e){
e.getCause().printStackTrace();
} catch(Exception ex) {
ex.printStackTrace();
}
In Android you don't have a unit 'GUI'. You are probably talking about an Activity. If you want to launch an Activity of another package there is no need for reflection. Simply send an Intent. http://developer.android.com/training/basics/intents/index.html
Related
Here I try to use getClassLoader().getResources() to get my .model file, however it returns null. I'm not sure where goes wrong!
And when I try to print out the urls, it gives me java.lang.TwoEnumerationsInOne#5fd1900, what does this means?
public Activity(MainActivity activity) {
MainActivity activity = new MainActivity();
try {
// Open stream to read trained model from file
InputStream is = null;
// this .model file is save under my /project/app/src/main/res/
Enumeration<URL> urls = Activity.class.getClassLoader().getResources("file.model");
// System.out.println("url:"+urls);
if (urls.hasMoreElements()) {
URL element = urls.nextElement();
is = element.openStream();
}
// deserialize the model
classifier = (J48) SerializationHelper.read(is);
is.close();
} catch (Exception e) {
e.printStackTrace();
}
}
In Android, resources put under src/main/res are not visible in the class path and can only be accessed via the android resources API. Try to put the file into src/main/resources.
I'm trying to build a plugin-System, where DexClassLoader is fetching code from other installed apks containing fragments(my plugins), and showing them in my host. This is working quite nice.
I also like to make the plugins hotswappable, this means I can change the code from a plugin, install it new and the host will notice and will load the new code. This also works, if I'm changing the code for the first time. (Although I thought it shouldn't, it seems I've got a wrong understanding of this code:
try {
requiredClass = Class.forName(fullName);
} catch(ClassNotFoundException e) {
isLoaded = false;
}
)
If i'm trying it a second time with the same plugin, the host shuts down at requiredClass = classLoader.loadClass(fullName); with something like
libc Fatal signal 7 (SIGBUS) at 0x596ed4d6 (code=2), thread 28814
(ctivityapp.host)
Does anybody has a deeper insight in the functionality of DexClassLoader and may tell me, what is happening here? I'm quite stuck at this.
Heres the full code of the method loading the foreign code:
/**
* takes the name of a package as String, and tries to load the code from the corresponding akp using DexclassLaoder.
* Checking if a package is a valid plugin must be done before calling this.
* The Plugin must contain a public class UI that extends Fragment and implements plugin as a starting point for loading
* #param packageName The full name of the package, as String
* #return the plugins object if loaded, null otherwise
*/
private Plugin attachPluginToHost(String packageName) {
try {
Class<?> requiredClass = null;
final ApplicationInfo info = context.getPackageManager().getApplicationInfo(packageName,0);
final String apkPath = info.sourceDir;
final File dexTemp = context.getDir("temp_folder", 0);
final String fullName = packageName + ".UI";
boolean isLoaded = true;
// Check if class loaded
try {
requiredClass = Class.forName(fullName);
} catch(ClassNotFoundException e) {
isLoaded = false;
}
if (!isLoaded) {
final DexClassLoader classLoader = new DexClassLoader(apkPath, dexTemp.getAbsolutePath(), null, context.getApplicationContext().getClassLoader());
requiredClass = classLoader.loadClass(fullName);
}
if (null != requiredClass) {
// Try to cast to required interface to ensure that it's can be cast
final Plugin plugin = Plugin.class.cast(requiredClass.newInstance());
installedPlugins.put(plugin.getName(), plugin);
return plugin;
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
Many thanks in advance!
Not that it really matters (As nobody is actually viewing this), or that I even understand what's going on, but deleting the corresponding file of the plugin in dexTemp.getAbsolutePath() before reloading it solves the problem.
PS: Tumbleweed-Badge, YAY!
I am receiving an exception when I try to get the field of a resource in an Android application. It seems like correct way of accessing a drawable id. Any suggestions what could be wrong?
04-09 22:45:59.816: W/System.err(5014): java.lang.NoSuchFieldException: lumiere
In my res/drawable folder I have "lumiere.jpg". From R.java:
public static final class drawable {
[...]
public static final int lumiere=0x7f02000a;
method call:
Class c = R.drawable.class;
field = c.getField(name);
i = new Integer(field.getInt(null));
I don't think this matters, being a static class method call, but just in case it does - the above code is in a plain Java class, not an Activity. Just thought I'd mention since access to resources seems restricted outside of Activities.
I believe you meant to use c.getDeclaredField(name). Also (and this might be the more "correct" way to do it), you can use the Resources object to get the ID when you have the name. See this question/answer.
I think you need to provide class's object when calling getInt method:
Integer i = null;
try {
Class c = R.drawable.class;
Field field = c.getField(name);
if(field != null){
i = new Integer(field.getInt(c)); //provide 'c' in getInt
}
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
Read this post
Unfortunately, it appears that you in fact cannot use this to access resources contained in R.java file from a plain Java class. When I copied the code into a class that is an Activity, it did not throw an exception and returned a value.
I'm aware of the performance differences between Parcelable (fast) and Serializable (slow). However, I need to store certain application information persistently, not just within one lifecycle, thus onSaveInstanceState and associated methods utilising Parcelable objects aren't appropriate.
So I turned my attention to Serializable. Primarily I have AbstractList types to store - which is fine, since they implement Serializable. However, many of the types I store inside these are Parcelable but not Serializable, e.g. RectF.
I thought "no problem", since I can easily generate a Parcel via Parcelable.writeToParcel(parcel, flags) then call marshall() on it to create a byte[] which I can serialize and deserialize. I figured I'd use generics; create a SerializableParcelable<Parcelable> implements Serializable class, allowing a one-fit solution for all Parcelable types I wish to serialize. Then I would e.g. store each RectF inside this wrapper within ArrayList, and lo-and-behold the list and its Parcelable contents are serializable.
However, the API docs state that marshall() mustn't be used for persistent storage:
public final byte[] marshall ()
Returns the raw bytes of the parcel.
The data you retrieve here must not be placed in any kind of persistent storage (on local disk, across a network, etc). For that, you should use standard serialization or another kind of general serialization mechanism. The Parcel marshalled representation is highly optimized for local IPC, and as such does not attempt to maintain compatibility with data created in different versions of the platform.
So now I'm stuck. I can either ignore this warning and follow the route I've outlined above, or else circumvent the issue by extending each individual Parcelable I want to serialize and creating bespoke serialization methods, which seems extremely wasteful of time and effort.
Does anyone know of a 'correct' shortcut to serialize a Parcelable object without using marshall()? Or should I plough on without heeding the warning specified? Perhaps an SQLite database is the way to go, but I'm unsure and would like your advice.
Many thanks.
For any object you need to serialize you can use objectOutPutStream .By using this you can write objects into the file system of the device.So this can used to save Parcelable objects also.
Below is the code to save object to File System.
public static void witeObjectToFile(Context context, Object object, String filename) {
ObjectOutputStream objectOut = null;
FileOutputStream fileOut = null;
try {
File file = new File(filename);
if(!file.exists()){
file.createNewFile();
}
fileOut = new FileOutputStream(file,false);
objectOut = new ObjectOutputStream(fileOut);
objectOut.writeObject(object);
fileOut.getFD().sync();
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException e) {
e.printStackTrace();
}finally {
if (objectOut != null) {
try {
objectOut.close();
} catch (IOException e) {
// do nowt
}
}
if (fileOut != null) {
try {
fileOut.close();
} catch (IOException e) {
// do nowt
}
}
}
}`
Inorder to read the Object use ObjectInputStream . Find the below code.
public static Object readObjectFromFile(Context context, String filename) {
ObjectInputStream objectIn = null;
Object object = null;
FileInputStream fileIn = null;
try {
File file = new File(filename);
fileIn = new FileInputStream(file);//context.getApplicationContext().openFileInput(filename);
objectIn = new ObjectInputStream(fileIn);
object = objectIn.readObject();
} catch (FileNotFoundException e) {
// Do nothing
}catch (NullPointerException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}finally {
if (objectIn != null) {
try {
objectIn.close();
} catch (IOException e) {
// do nowt
}
}
if(fileIn != null){
try {
fileIn.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return object;
}`
Regards,
Sha
I've hit a bit of a wall. Any help would be appreciated. I have an app that I want to use DexClassLoader to load another apk file.
Here is my code:
DexClassLoader dLoader = new DexClassLoader("/sdcard/download/test.apk","/sdcard/download",null,ClassLoader.getSystemClassLoader().getParent());
Class calledClass = dLoader.loadClass("com.test.classname");
Intent it=new Intent(this, calledClass);
it.setClassName("com.test", "com.test.classname");
startActivity(it);
Now I had already installed test.apk so when I ran the above code it
worked fine and launched the application. However I want to be able to
run this without test.apk being installed already (as that would
defeat the entire point of the application) . So I uninstalled it and
when I ran the my app again I get this error:
android.content.ActivityNotFoundException: Unable to find explicit
activity class {com.test/com.test.classname}; have you declared this
activity in your AndroidManifest.xml.
So I'm a bit stumped here. This activity is declared in the Manifest
of the apk I am trying to run. I can't declare it in my applications
Manifest. Any ideas?
Thanks,
Craig
Try using Android's PathClassLoader:
String packagePath = "com.mypackage";
String classPath = "com.mypackage.ExternalClass";
String apkName = null;
try {
apkName = getPackageManager().getApplicationInfo(packagePath,0).sourceDir;
} catch (PackageManager.NameNotFoundException e) {
// catch this
}
// add path to apk that contains classes you wish to load
String extraApkPath = apkName + ":/path/to/extraLib.apk"
PathClassLoader pathClassLoader = new dalvik.system.PathClassLoader(
apkName,
ClassLoader.getSystemClassLoader());
try {
Class<?> handler = Class.forName(classPath, true, pathClassLoader);
} catch (ClassNotFoundException e) {
// catch this
}
Although the question is old, I will answer because I struggled a bit to find a clear answer for your same question for myself. First, I would like to highlight that a clear requirement in your question is to load a class from an .apk that is not already installed on the device. Therefore, calling the package manager using getPackageManager() and providing it with the package path will clearly lead to NameNotFoundException because the .apk that has the package is not installed on the device.
So, the way to go about loading classes from an .apk file that is not installed on the device (i.e. you only have the .apk stored in a directory on your SDCARD) is by using DexClassLoader as follows:
1- Make sure you have the .apk file in a directory on your SDCARD. I've mine Offloadme.apk in the Download folder on my SDCARD.
2- Add read permission in your AndroidManifest.xml to allow your app to read from the manifest.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
3- Use the following definitions to define the path of the .apk, the class name inside the apk, and method name in that class that you would like to invoke:
final String apkFile =Environment.getExternalStorageDirectory().getAbsolutePath()+"/Download/Offloadme.apk";
String className = "com.khaledalanezi.offloadme.SimpleCalculator";
String methodToInvoke = "add";
4- Use the DexClassLoader to load the .apk and call the add method in the SimpleCalculator class using reflection as follows:
final File optimizedDexOutputPath = getDir("outdex", 0);
DexClassLoader dLoader = new DexClassLoader(apkFile,optimizedDexOutputPath.getAbsolutePath(),
null,ClassLoader.getSystemClassLoader().getParent());
try {
Class<?> loadedClass = dLoader.loadClass(className);
Object obj = (Object)loadedClass.newInstance();
int x =5;
int y=6;
Method m = loadedClass.getMethod(methodToInvoke, int.class, int.class);
int z = (Integer) m.invoke(obj, y, x);
System.out.println("The sum of "+x+" and "+"y="+z);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Note that in my simple example, I added two numbers using the add method available in the SimpleCalculator class loaded from the Offloadme.apk file stored on my SDCARD and I was able to print the correct answer which is 11.
You can't do that. Even if you're able to access classes from external file, Android still does not know them. And you don't run activities directly, but by requesting Android to run them, so they have to be registered/installed into system.