Reputation: 5013
I've used Reflections Library to Find All Subclasses of a given class in Java before.
This is a code snippet that I use in a simple java project :
Reflections reflections = new Reflections(PACKAGE_NAME);
Set<Class<? extends SuperClass>> subTypes =
reflections.getSubTypesOf(SuperClass.class);
for (Class<? extends SuperClass> subType : subTypes) {
log("Subclass = " + subType.getSimpleName());
}
When I run the same code in android project , "subType" List returns empty.
Can Anybody help me to get this work on Android ?
EDIT Jars I add for the whole thing to work are :
Upvotes: 5
Views: 3214
Reputation: 301
Here is a Kotlin solution that also works with interfaces. What you need is a real Context
instance, so be aware that you are not able to use this approach in unit-tests.
Note that DexFile
is deprecated due to API fragility with no useful replacement.
private inline fun <reified T> getAllSubclasses(
startsWithPackage: String,
context: Context,
): Set<Class<out T>> {
val classNames = DexFile(context.packageCodePath).entries()
val classLoader = context.classLoader
val superclass = T::class.java
val result = mutableSetOf<Class<out T>>()
classNames.iterator().forEach { className ->
if (className.startsWith(startsWithPackage) && className.contains("$").not()) {
val loadedClass = classLoader.loadClass(className)
if (loadedClass.superclass == superclass || loadedClass.interfaces.contains(superclass)) {
@Suppress("UNCHECKED_CAST")
result.add(loadedClass as Class<out T>)
}
}
}
return result
}
The method above could be used like that.
val subclasses = getAllSubclasses<SuperClass>("com.example", context)
Upvotes: 0
Reputation: 672
Here's my one-method-solution based on fantouch's answer.
Result will also contain inderect descendants.
public class Utils
{
// ...
public static ArrayList<Class> GetSubClasses( Context context, String packageName, Class targetSuperClass )
{
ArrayList<Class> subClasses = new ArrayList<>();
try
{
DexFile dex = new DexFile( context.getPackageCodePath() );
for ( Enumeration<String> iterator = dex.entries(); iterator.hasMoreElements(); )
{
String className = iterator.nextElement();
if ( !className.startsWith( packageName ) || className.contains("$") )
{
continue;
}
Class classObj = Class.forName( className );
Class superClass = classObj;
while ( true )
{
superClass = superClass.getSuperclass();
if ( superClass == null || superClass == Object.class )
{
break;
}
if ( superClass == targetSuperClass )
{
subClasses.add( classObj );
break;
}
}
}
}
catch ( Exception e )
{
e.printStackTrace();
}
return subClasses;
}
}
Upvotes: 0
Reputation: 1169
maybe you can try this:
public abstract class ClassScanner {
private static final String TAG = "ClassScanner";
private Context mContext;
public ClassScanner(Context context) {
mContext = context;
}
public Context getContext() {
return mContext;
}
void scan() throws IOException, ClassNotFoundException, NoSuchMethodException {
long timeBegin = System.currentTimeMillis();
PathClassLoader classLoader = (PathClassLoader) getContext().getClassLoader();
//PathClassLoader classLoader = (PathClassLoader) Thread.currentThread().getContextClassLoader();//This also works good
DexFile dexFile = new DexFile(getContext().getPackageCodePath());
Enumeration<String> classNames = dexFile.entries();
while (classNames.hasMoreElements()) {
String className = classNames.nextElement();
if (isTargetClassName(className)) {
//Class<?> aClass = Class.forName(className);//java.lang.ExceptionInInitializerError
//Class<?> aClass = Class.forName(className, false, classLoader);//tested on 魅蓝Note(M463C)_Android4.4.4 and Mi2s_Android5.1.1
Class<?> aClass = classLoader.loadClass(className);//tested on 魅蓝Note(M463C)_Android4.4.4 and Mi2s_Android5.1.1
if (isTargetClass(aClass)) {
onScanResult(aClass);
}
}
}
long timeEnd = System.currentTimeMillis();
long timeElapsed = timeEnd - timeBegin;
Log.d(TAG, "scan() cost " + timeElapsed + "ms");
}
protected abstract boolean isTargetClassName(String className);
protected abstract boolean isTargetClass(Class clazz);
protected abstract void onScanResult(Class clazz);
}
and this is a example how to use:
new ClassScanner(context) {
@Override
protected boolean isTargetClassName(String className) {
return className.startsWith(getContext().getPackageName())//I want classes under my package
&& !className.contains("$");//I don't need none-static inner classes
}
@Override
protected boolean isTargetClass(Class clazz) {
return AbsFactory.class.isAssignableFrom(clazz)//I want subclasses of AbsFactory
&& !Modifier.isAbstract(clazz.getModifiers());//I don't want abstract classes
}
@Override
protected void onScanResult(Class clazz) {
Constructor constructor = null;
try {
constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
constructor.newInstance();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}.scan();
Upvotes: 2
Reputation: 3246
There is an official API for dealing with dex files. See here for details. Basically, given path of the .dex file, you can enumerate all the files inside it. To get the URL of the classes.dex
, you can use
Thread.currentThread().getContextClassLoader().getResource("classes.dex")
(you may need to cut the trailing "!/classes.dex"
, can't check it right now). Then, you can iterate over the classes.dex contents and attempt to load all the classes you find this way. (Class.forName
), and, finally, check whatever class properties you need. Keep in mind, however, that classes.dex
contains all the classes, and "all" can be "quite a lot". There are also other performance issues to remember when dealing with .dex files. You should, however, be fine, as long as you don't attempt something needlessly inefficient. Especially since Reflections accomplishes its magic in a similar manner, as far as I remamber.
Upvotes: 0
Reputation: 8395
This might not work due to the compilation process used in Android. The source code is converted into .class files with the Java compiler. The next step turns the .class files into Android .dex files (Dalvik bytecode) and they are unlikely to keep all meta-data.
http://developer.android.com/tools/building/index.html
Upvotes: 1