I am experimenting with implementing plug-in architecture in Android, more specifically, cross-apk class loading
assume I have the following:
apk A and apk B, with the same sharedUserId defined in AndroidManifest.xml
interface I is defined in apk A, and apk B contains a class IB that implements I
I tried the following approaches
in apk A, use createPackageContext to obtain context of B, then call Context.getClassLoader and load the desired class name. However, due to the fact that this creates two class loaders, and therefore I cannot cast the class IB loaded in B into interface I; which means I need to use reflection...
pass apk B to DexClassLoader (path to apk B obtained by ApplicationInfo.sourceDir) and failed with "Class resolved by unexpected DEX", probably because there is a duplicate interface I in apk B as well...
modify build xml and follow the approach in Custom Class Loading in Dalvik to create a separate jar containing the class IB in B which implements I and put the jar into apk B's assets directory. This approach looks promising, as I can load class IB and cast it to I without problems. However, it creates the need for copying the jar, and I am not sure what will happen when NDK shared library is involved.
My question is, is there an approach that involves no modification of build.xml/ant.properties and works with NDK so library?
Not sure about other approaches, but for approach 2, you can exclude interface I when you were packing B to apk. Just exclude I.class file generated is enough.
You may find this post by Fred Chung helpful and may give you some hint. http://android-developers.blogspot.kr/2011/07/custom-class-loading-in-dalvik.html
Related
I have two app projects, A and B, based on a library project C.
All the java code is contained by library project C. Projects A and B only differ in their AndroidManifests, varying their functionality through setting different flag variables.
To give a simplified example, A gives access to a class ImportExport, and its method ImportExport.import(). The other, app B, doesn't give access to such functionaliy.
I would like to make the ImportExport.import() code nonexistent in the compiled app B. Is there a way I could add an empty ImportExport.import() method definition to app B, that would override the library's definition of ImportExport.import()?
If necessary, I'm also happy with overriding the whole class ImportExport.
I need to make a reference to a builtin Android Java Class in NDK c++ code.
You can do it by
cls_tm = (*env)->FindClass(env, "javax/crypto/Cipher");
I am worried someone can tamper with apk, extract the java code then create their own class with the package name javax.crypto.Cipher, and read all the sensitive data I am passing to it. I am new to Java and Android so I wanted to know if it is possible to create your own package with same name as built in packages like javax.crypto.Cipher?
It is possible to create classes with the same name. However, they do not take the place of existing classes.
Every class is loaded by a class loader. Class loaders form a hierarchy, with the "bootstrap" class loader at the very top. The class loader that loads your app's classes is created by the Android app framework; it is a child of the "system" class loader, which is a child of the bootstrap loader.
When your app references a class, it asks its class loader to find it by name. Each loader will either return a class that it defined, or ask its parent to find it. (The default behavior is to ask the parent first, but an individual loader can override this.)
javax.crypto.Cipher is part of core.jar, which is loaded by the bootstrap class loader. So unless your application's class loader decides to replace Cipher with its own version, you will get the system version.
(The JNI FindClass call is actually a bit strange. Depending on where you are when you call it, it can actually end up in the system class loader rather than your app's loader. See this section in JNI Tips for an explanation.)
Suppose you really did want to replace Cipher. You can provide your own version, and your app code will happily use it. However, when you try to pass it to some other code in core.jar, your app will fail. This is because classes loaded in the VM aren't unique by name, but rather by the combination of name and class loader. So you can't pass a Cipher+MyAppLoader into something that expects a Cipher+bootstrap.
In any event, if somebody modified your APK, they would have to re-sign it; since they don't have your private key, it wouldn't look like an app that came from you.
If somebody modified a device and replaced the system Cipher with their own version, they can do whatever they want.
I make sereval Android apps with similar code, but I need to stay flexible for customizing the apps. My solution right now is, that I have a Library-Project, where I can override functionality in the Project by extending classes. For the following example, I call the Library Project PL. P1 is a customized Project using PL and P2 is another customized Project using PL.
My Problem is now, that in PL I would like to make an instance from StudentPL.java. In P1, there is no need to make any changes to StudentPL, so the compiler should take this class, but in P2, there I have StudentP2.java, that extends from StudentPL.java. So the instance made in PL, should be an instance of StudentP2.java.
My idea was now, that I make a package called mirror, that is directly in the src-Folder of the Project PL. There I make the class mirror.Student.java that extends from StudentPL.java. In the Library-Project, I make now an instance of Student.java. For Project P1, there are no changes needed. In P2 I make the same folder mirror in src Folder and I make the same class mirror.Student.java that extends from StudentP2.java now. The Idea is, that the ClassLoader now loads mirror.Student.java from P2 and ignores mirror.Student.java from PL, but this results in the following error:
Dex Loader] Unable to execute dex: Multiple dex files define Lmirror/Student;
Conversion to Dalvik format failed: Unable to execute dex: Multiple dex files define Lmirror/Student
Here is a UML of what I'm talking about:
http://www.koenix-band.ch/images/other/Stackoverflow.gif
Maybe I should overwrite the ClassLoader, but I have no idea how to do this? Does anyone have an idea to solve this problem? Maybe someone has another idea, how i could customize the apps.
Michael
In my opinion it would be better to make your MainActivityPL abstract with a setStudent or similar method. The bulk of the activity and all its logic would still be shared from your library, but it would allow your other projects an option to use different student objects by extending the StudentPL class.
I've searched around SO for this and found a few things, but I'm still not sure I fully understand, so I ask you for clarifications.
Here is what I need:
Have a project that has specific function: interrogate web service, display results in different views
Have a second, third and forth project that has exactly the same functionality as the first one, but only different graphic elements like splash screen image, icon, name, package name.
So, I have ProjectCore with activities and functionality. Project1 with a car icon and car image for splashscreen. Project2 with airplane icon and airplane image for splashscreen. Something like that. Each projects has a class with constants like'appId, appName, appServerURL"... All the web service call, data display is in Core as it's the same for all prohects, only the read is made from Constants class.
I was thinking of this approach
Make ProjectCore a Library project with a package like com.domain.core and dummy images
Make Project1, add reference to ProjectCore in it and with a package like com.domain.code.project1 and in resources folder, put images with same name as in core project
Make Project2 on the same principle like project1
Will this approach work ?
Thanks.
Later Edit. I've tried as mentioned before. For instance in Core project I had in drawable a file called splash.png. In Project1's and Project2's drawable folder I've put spash.png file with other images. This works fine. Running the Project1 and Project2 on my phone, started each app with it's own image. So far so good.
Then, because I have different constants I need to use in my App, I went into Core library project and added:
public class C {
public static String SomeConstant = "Project core!";
}
Here comes the problem, I need to have different constant values across Project1 and Project2. Because on Core project, the class is in com.domain.core.utils for instance... I can't add the same package in Project1 and Project2. How do I add the classes so I can update their values and be used on each project with particlar values ?
public class C {
public static String SomeConstant = "Project 1 constant!";
}
public class C {
public static String SomeConstant = "Project 2 constant!";
}
Thank you!
You want to create your functionality in a Library project and then have all of your Branded/OEM/3rdParty projects extend from this, overriding images and string resources where necessary.
When you need to use "Constants" you should instead have a single "run once" portion of your code (such as a splash screen) load these strings from resource files:
public static final String CONSTANT_ONE;
public void onCreate() { CONSTANT_ONE = getResources().getString(R.String.CONSTANT_ONE); }
EDIT
I'm unsure on how initialising a final value on onCreate() will perform. If final doesn't work well and you're worried about changing the variable during program execution then make the variable private (so only that class can assign to it) and then create a public static String getConstantOne() function.
Yes. Library projects are ideal for this, especially if only resources differ. I've used the exact approach that you've outlined with success...
Yes this should work fine. I did something a bit similar and I found occasionally you may have some circumstances where you want to call out from your library project to your application project. In these cases I used interfaces/abstract classes defined in the library project but implemented in application project...
General question:
Is it possible to use a ClassLoader to replace a pre-loaded (by the system, e.g. found in Android's %android%/frameworks/base/preloaded-classes file) class?
Specific:
I am attempting to use the DexClassLoader to replace a class found in android.net.* before creating a WebView in my application. I can get a Class object, but getMethods() for example gives me an array I'd expect in the unmodified/original class implementation. Is this due to the preloaded-classes system?
Basic setup & pseudo code:
Modify android.net.* class, adding a few test methods/etc.
Compile and end up with classes.dex
jar cf mytest.jar classes.dex
Include mytest.jar in APK assets
Create DexClassLoader and get Class via loadClass()
getMethods() on Class object returns an array I'd expect to see without modifications present in #1
I can provide more details on the setup I'm using and code if needed.
No you can not. WebView is part of the boot class path, and thus the base class loader. There is nothing you can do to make it use classes in another class loader. In fact, it has already been loaded and linked to the classes it uses before your app is even launched (as part of the zygote process pre-initialization).