2013-03-16 23 views
12

如何查找Android中包內的所有課程?我使用PathClassLoader,但它總是返回一個空的枚舉?在Android中查找包中的所有課程

附加信息

嘗試了建議的反射方法。幾個關於反射庫的重要觀點。通過maven central可用的庫與Android不兼容,導致dex錯誤。我不得不包含源代碼並編譯dom4j,java-assist。

反射的問題,我的原始解決方案是Android中的PathClassLoader返回包的空枚舉。

這種方法的問題是,Android中的getResource總是返回空的枚舉。

final String resourceName = aClass.getName().replace(".", "/") + ".class"; 

for (ClassLoader classLoader : loaders) { 
     try { 
      final URL url = classLoader.getResource(resourceName); 
      if (url != null) { 
       final String normalizedUrl = url.toExternalForm().substring(0, url.toExternalForm().lastIndexOf(aClass.getPackage().getName().replace(".", "/"))); 
       return new URL(normalizedUrl); 
      } 
     } catch (MalformedURLException e) { 
      e.printStackTrace(); 
     } 
    } 
+0

是否可以先將apk修改,然後解析dex文件?如果你的情況可以接受,我可以寫一個答案。 – StarPinkER 2013-03-16 06:52:17

+0

那麼,我想暴露一些方法來動態插件,我不知道什麼類前手。 – user210504 2013-03-16 14:08:36

+0

你能詳細說一下嗎?不太瞭解你的場景 – StarPinkER 2013-03-17 00:38:47

回答

2

試試這個..

Reflections reflections = new Reflections("my.project.prefix"); 

    Set<Class<? extends Object>> allClasses 
         = reflections.getSubTypesOf(Object.class); 
+9

這在Android中不起作用,relections庫崩潰,不支持的dex類錯誤 – user210504 2013-03-16 22:43:58

+0

該方法的問題是 – user210504 2013-03-17 01:11:56

24

使用DexFile以列出您的APK所有類:

try { 
     DexFile df = new DexFile(context.getPackageCodePath()); 
     for (Enumeration<String> iter = df.entries(); iter.hasMoreElements();) { 
      String s = iter.nextElement(); 
     } 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 

其餘部分使用正則表達式或別的東西來篩選出您想要的類。

+0

我用[this](http://mindtherobot.com/blog/737/android-hacks-scan-android-classpath /)作爲起點。 – c0nstruct0r 2014-05-15 09:40:12

+3

自從Android Gradle Plugin 2.0.0以來,它無法正常工作 – anavarroma 2016-04-13 07:22:40

+0

我和@anavarroma有同樣的問題,這不再起作用,代碼運行但不會帶回任何類 – 2016-06-28 09:16:33

0

這是從http://mindtherobot.com/

代碼示例採取

假設我們有一個名爲Foo和幾個班用它裝飾的註釋:

@Retention(RetentionPolicy.RUNTIME) 
public @interface Foo { 
    String value(); 
} 

@Foo("person") 
public class Person { 

} 

@Foo("order") 
public class Order { 

} 

這裏有一個簡單的類,越過所有班你的應用程序在運行時,找到那些用@Foo標記並獲得註釋的值。不要將此代碼複製並粘貼到您的應用程序中 - 它有很多要修復和添加的內容,如下面的代碼所述。

public class ClasspathScanner { 
    private static final String TAG = ClasspathScanner.class.getSimpleName(); 

    private static Field dexField; 

    static { 
    try { 
     dexField = PathClassLoader.class.getDeclaredField("mDexs"); 
     dexField.setAccessible(true); 
    } catch (Exception e) { 
     // TODO (1): handle this case gracefully - nobody promised that this field will always be there 
     Log.e(TAG, "Failed to get mDexs field"); 
    } 
    } 

    public void run() { 
    try { 
     // TODO (2): check here - in theory, the class loader is not required to be a PathClassLoader 
     PathClassLoader classLoader = (PathClassLoader) Thread.currentThread().getContextClassLoader(); 

     DexFile[] dexs = (DexFile[]) dexField.get(classLoader); 
     for (DexFile dex : dexs) { 
     Enumeration<String> entries = dex.entries(); 
     while (entries.hasMoreElements()) { 
      // (3) Each entry is a class name, like "foo.bar.MyClass" 
      String entry = entries.nextElement(); 
      Log.d(TAG, "Entry: " + entry); 

      // (4) Load the class 
      Class<?> entryClass = dex.loadClass(entry, classLoader); 
      if (entryClass != null) { 
      Foo annotation = entryClass.getAnnotation(Foo.class); 
      if (annotation != null) { 
       Log.d(TAG, entry + ": " + annotation.value()); 
      } 
      } 
     } 
     } 
    } catch (Exception e) { 
     // TODO (5): more precise error handling 
     Log.e(TAG, "Error", e); 
    } 
    } 
} 
13

其實我找到了解決方案。感謝tao的回覆。

private String[] getClassesOfPackage(String packageName) { 
    ArrayList<String> classes = new ArrayList<String>(); 
    try { 
     String packageCodePath = getPackageCodePath(); 
     DexFile df = new DexFile(packageCodePath); 
     for (Enumeration<String> iter = df.entries(); iter.hasMoreElements();) { 
      String className = iter.nextElement(); 
      if (className.contains(packageName)) { 
       classes.add(className.substring(className.lastIndexOf(".") + 1, className.length())); 
      } 
     } 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 

    return classes.toArray(new String[classes.size()]); 
} 

測試在Android 5.0棒棒糖

+0

您應該返回類的完整名稱,比如'packagename.ClassName',因爲使用它的人想要以'Class.forName(className)'這樣的方式使用它。 – lovesh 2015-07-19 17:44:59

+1

@lovesh它取決於你的目標。其實你可以根據你的需要改變代碼。 – 2015-07-19 19:49:30

+0

聖牛。我希望我能找到更多的方法來獎勵這個答案。我爲此查找了**天**,並且只能找到它,因爲有人通過https://github.com/ronmamo/reflections/issues/115鏈接到它。而且非常簡單!我正在考慮回到所有其他SO帖子並鏈接到這裏。順便說一下,對於那些關心的人,如果你將'packageName'設置爲「com.myapp」,假設所有的軟件包都以此開始,那麼這個方法可以在應用程序中返回_all_包的所有類。修改它也很容易返回完全限定名並跳過內部類(對於'Class.forName()')。 – 2016-03-15 05:45:15

3

下面是爲您帶來不僅是類名,
也是Class<?>對象的解決方案。

雖然PathClassLoader.class.getDeclaredField("mDexs")失敗頻繁,
new DexFile(getContext().getPackageCodePath())似乎更穩定。

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); 
} 

,這是一個示例如何使用:

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(); 
1

我發現謝爾蓋·舒斯蒂科弗的相同溶液中,用包名衍生形式的上下文並處理內部類。不幸的是,它在Galaxy Nexus上工作時,它不適用於Nexus 6和Nexus 6p(未在其他設備上測試過)。

下面的代碼:

private HashMap<String, String> loadClassFullnames() { 
    final HashMap<String, String> ret = new HashMap<>(); 
    try { 
     final String thisPackage = context.getPackageName(); 
     final DexFile tmp = new DexFile(context.getPackageCodePath()); 
     for (Enumeration<String> iter = tmp.entries(); iter.hasMoreElements();) { 
      final String classFullname = iter.nextElement(); 
      if (classFullname.startsWith(thisPackage)) { 
       // TODO filter out also anonymous classes (es Class1$51) 
       final int index = classFullname.lastIndexOf("."); 
       final String c = (index >= 0) ? classFullname.substring(index + 1) : classFullname; 
       ret.put(c.replace('$', '.'), classFullname); 
      } 
     } 
    } catch (Throwable ex) { 
     Debug.w("Unable to collect class fullnames. Reason: " + ex.getCause() + "\n\rStack Trace:\n\r" + ((ex.getCause() != null)? ex.getCause().getStackTrace(): "none")); 
    } 

    return ret; 
} 

它縫上的Galaxy Nexus,該DexFile還包含應用程序包從(一個我們正在努力尋找!),而在Nexus 6 [P]沒有包匹配thisPackage被發現,而其他一些包... 有沒有人有同樣的問題?

相關問題