![]() |
VOOZH | about |
類加載流程在周志明寫的<<深入理解java虛擬機的一本書中>>已經詳細地介紹java加載類過程,在HotSpot虛擬機實現中是通過雙親委派機制來加載類的,那麼android又如何實現呢?在android系統中,有兩種常見的加載器實現:DexClassLoader和BaseDexClassLoader也繼承了java的ClassLoader,那麼這上面兩種實現有什麼差異呢?
BaseDexClassLoader源碼中去看其如何實現:/** * Base class for common functionality between various dex-based * {@link
ClassLoader} implementations. */ public class BaseDexClassLoader extends ClassLoader { private final DexPathList pathList; /** * Constructs an instance. * * @param dexPath the list of jar/apk files
containing classes and * resources, delimited by {@code File.pathSeparator}, which * defaults to {@code ":"} on Android * @param optimizedDirectory directory where optimized dex files * should be
written; may be {@code null} * @param librarySearchPath the list of directories containing native * libraries, delimited by {@code File.pathSeparator}; may be * {@code null} * @param parent the
parent class loader */ public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) { super(parent); this.pathList = new DexPathList(this, dexPath,
librarySearchPath, optimizedDirectory); } @Override protected Class findClass(String name) throws ClassNotFoundException { ListsuppressedExceptions = new ArrayList; Class c = pathList.findClass(name,
suppressedExceptions); if (c == null) { ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList); for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t); } throw cnfe; } return c; } /** * @hide */ public void addDexPath(String dexPath) { pathList.addDexPath(dexPath, null /*optimizedDirectory*/); } @Override protected URL
findResource(String name) { return pathList.findResource(name); } @Override protected EnumerationfindResources(String name) { return pathList.findResources(name); } @Override public String
findLibrary(String name) { return pathList.findLibrary(name); } /** * Returns package information for the given package. * Unfortunately, instances of this class don't really have this * information,
and as a non-secure {@code ClassLoader}, it isn't * even required to, according to the spec. Yet, we want to * provide it, in order to make all those hopeful callers of * {@code
myClass.getPackage.getName} happy. Thus we construct * a {@code Package} object the first time it is being requested * and fill most of the fields with dummy values. The {@code * Package} object is
then put into the {@code ClassLoader}'s * package cache, so we see the same one next time. We don't * create {@code Package} objects for {@code null} arguments or * for the default package. * *
There is a limited chance that we end up with multiple * {@code Package} objects representing the same package: It can * happen when when a package is scattered across different JAR * files which were loaded by different {@code ClassLoader} * instances. This is rather unlikely, and given that this whole * thing is more or less a workaround, probably not worth the * effort to address. * * @param name the name of the class * @return the package information for the class, or {@code null} * if there is no package information available for it */ @Override protected synchronized Package getPackage(String name) { if (name != null && !name.isEmpty) { Package pack = super.getPackage(name); if (pack == null) { pack = definePackage(name, "Unknown", "0.0", "Unknown", "Unknown", "0.0", "Unknown", null); } return pack; } return null; } /** * @hide */ public String getLdLibraryPath { StringBuilder result = new StringBuilder; for (File directory : pathList.getNativeLibraryDirectories) { if (result.length > 0) { result.append(':'); } result.append(directory); } return result.toString; } @Override public String toString { return getClass.getName + "[" + pathList + "]"; } }
原來BaseDexClassLoader只暴露一些信息接口,而具體實現交給了ClassLoader和ClassLoader來看,其加載機制的實現也遵循雙親委派機制,對於一個android類的加載過程,我們最關注就是類加載以及類查找兩個實現方法,其中類的加載方法實現在ClassLoader中,核心代碼:
protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class c =
findLoadedClass(name); if (c == null) { long t0 = System.nanoTime; try { //在父類加載器搜索範圍內去加載 if (parent != null) { c = parent.loadClass(name, false); } else { //如果沒有父加載器,則會調用啟動類加載器去嘗試加載 c =
findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not
found, then invoke findClass in order // to find the class. long t1 = System.nanoTime; //調用當前加載器去加載,不過findclass如何加載還要看具體子類的實現 c = findClass(name); // this is the defining class loader; record the
stats } } return c;
}這裡至始至終還不知道當前的應用類如何被加載,不過這裡還有一個關鍵方法還沒有跟蹤剖析就是findClass,從上面代碼中,我們知道安卓有個BaseDexClassLoader加載器父類,它裡面卻通過了DexPathList類的findClass來實現加載和查詢一個應用類的過程,因此需要進一步剖析DexPathList的findClass,其分析如下:
/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java:/** * Finds the named class in one of the dex files pointed at by * this instance. This will find the one in the earliest listed *
path element. If the class is found but has not yet been * defined, then this method will define it in the defining * context that this instance was constructed with. * * @param name of class to find
* @param suppressed exceptions encountered whilst finding the class * @return the named class or {@code null} if the class is not * found in any of the dex files */ public Class findClass(String
name, Listsuppressed) { //遍歷dex優化文件 for (Element element : dexElements) { DexFile dex = element.dexFile; if (dex != null) { //轉交給DexFile類去處理類的加載過程 Class clazz = dex.loadClassBinaryName(name,
definingContext, suppressed); if (clazz != null) { return clazz; } } } if (dexElementsSuppressedExceptions != null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null;
}
/libcore/dalvik/src/main/java/dalvik/system/DexFile.java
/art/runtime/native/dalvik_system_DexFile.cc
/art/runtime/class_linker.cc
ClassLoader中創建,核心代碼: /** *
Encapsulates the set of parallel capable loader types. */ private static ClassLoader createSystemClassLoader { String classPath = System.getProperty("java.class.path", "."); String librarySearchPath
= System.getProperty("java.library.path", ""); return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance); } //通過ClassLoader靜態內部類創建系統加載器 static private class
SystemClassLoader { public static ClassLoader loader = ClassLoader.createSystemClassLoader; } @CallerSensitive public static ClassLoader getSystemClassLoader { return SystemClassLoader.loader;
}PathClassLoader,而加載類方法最終實現是在class_linker。加載寄主APK的類現在我來做個實驗就是如何用DexClassLoader去加載寄主APK的類,這裡暫時不考慮資源和組件的加載,因為想要解決這些還需要考慮資源如何加載,由於這篇主要是圍繞著類加載展開,因此就不去涉及資源的加載,核心代碼如下://定義一個接口讓寄住和宿主APK共享,以解決無法直接通過引用地方式直接調用方法
public interface IDynamic { String getTip; Drawable getDrawable(Resources res, @DrawableRes int id); String getString(Resources res); public void startPluginActivity(Context context,Class cls);
public void startPluginActivity(Context context); } //寄主APK對其接口的實現 public class DynamicClass1 implements IDynamic { public DynamicClass1 { // Log.i(DynamicClass1.class.getName,"加載動態類"); } public
String getTip{ return "插件動態加載"; } @Override public Drawable getDrawable(Resources resources, @DrawableRes int i) { return null; } @Override public String getString(Resources resources) { return
resources.getString(R.string.test); } @Override public void startPluginActivity(Context context, Class Cls) { Intent intent = new Intent(context,Cls); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent); } @Override public void startPluginActivity(Context context) { Intent intent = new Intent(context,DynamicActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } @Override public String toString { return "插件"; } } //宿主APK從Assets拷貝dex到dirpath路徑 private void
copyFileToDexDir(Context context, String path, String dirpath, String apkName) { InputStream inputStream; BufferedInputStream buffer = null; File file; FileOutputStream out = null; try { inputStream
= context.getAssets.open(path); buffer = new BufferedInputStream(inputStream); byte content = new byte[1024]; file = new File(dirpath, apkName); out = new FileOutputStream(file); int length = -1;
while ((length = buffer.read(content)) != -1) { out.write(content, 0, length); } } catch (Exception e) { e.printStackTrace; } finally { StreamUtil.close(buffer); StreamUtil.close(out); } }
//宿主APK加載寄主APK的DynamicClass1類 private void dnyLoadPluCls { String path = "plugindemo1-debug.apk"; String dir = getFilesDir.getAbsolutePath; copyFileToDexDir(this, path, dir, path); String packName =
"com.scau.beyondboy.plugindemo1"; String apkPath = dir + File.separator + path; //獲取當前應用dex輸出目錄 String dexOutputDir = getApplicationInfo.dataDir; String libPath = getApplicationInfo.nativeLibraryDir;
loadDynRes(apkPath); DexClassLoader classLoader = new DexClassLoader(apkPath, dexOutputDir, libPath, this.getClassLoader); try { Class clazz = classLoader.loadClass(packName + ".DynamicClass1");
IDynamic object = (IDynamic) clazz.newInstance; String showTip = object.getTip; Toast.makeText(this, showTip, Toast.LENGTH_SHORT).show; } catch (Exception e) { e.printStackTrace; }
}
在這次實驗中,發現有個細節的地方,就是寄主APK和宿主APK依賴動態加載接口時,配置需要如下配置(才能兼容Android各個版本):
//宿主APK依賴配置 compile files('libs/plugininterface.jar') //寄主APK依賴配置 provided
files('libs/plugininterface.jar')