Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android的類加載淺析

Android的類加載淺析

編輯:關於Android編程

類加載流程

在周志明寫的<<深入理解java虛擬機的一本書中>>已經詳細地介紹java加載類過程,在HotSpot虛擬機實現中是通過雙親委派機制來加載類的,那麼android又如何實現呢?在android系統中,有兩種常見的加載器實現:DexClassLoader和PathClassLoader,其都繼承了BaseDexClassLoader,而BaseDexClassLoader也繼承了java的ClassLoader,那麼這上面兩種實現有什麼差異呢?
其差異可根據官方文檔解釋:
DexClassLoader:A class loader that loads classes from .jar and .apk files containing a classes.dex entry. This can be used to execute code not installed as part of an application.
PathClassLoader:Provides a simple ClassLoader implementation that operates on a list of files and directories in the local file system, but does not attempt to load classes from the network. Android uses this class for its system class loader and for its application class loader(s).
通過查看這兩個源碼發現,發現當前類的代碼非常短,而具體細節實現都交給了BaseDexClassLoader,既然這樣,我就從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 {
        List suppressedExceptions = 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 Enumeration findResources(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和DexPathList這兩個類,但從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, List suppressed) {
    //遍歷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

  /**
     * See {@link #loadClass(String, ClassLoader)}.
     *
     * This takes a "binary" class name to better match ClassLoader semantics.
     *
     * @hide
     */
    public Class loadClassBinaryName(String name, ClassLoader loader, List suppressed) {
        return defineClass(name, loader, mCookie, this, suppressed);
    }

    private static Class defineClass(String name, ClassLoader loader, Object cookie,
                                     DexFile dexFile, List suppressed) {
        Class result = null;
        try {
        //調用jni層DexFile類的defineClassNative()去處理類的加載
            result = defineClassNative(name, loader, cookie, dexFile);
        } catch (NoClassDefFoundError e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        } catch (ClassNotFoundException e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        }
        return result;
    }

/art/runtime/native/dalvik_system_DexFile.cc

static jclass DexFile_defineClassNative(JNIEnv* env,
                                        jclass,
                                        jstring javaName,
                                        jobject javaLoader,
                                        jobject cookie,
                                        jobject dexFile) {
  std::vector dex_files;
  const OatFile* oat_file;
  if (!ConvertJavaArrayToDexFiles(env, cookie, /*out*/ dex_files, /*out*/ oat_file)) {
    VLOG(class_linker) << "Failed to find dex_file";
    DCHECK(env->ExceptionCheck());
    return nullptr;
  }

  ScopedUtfChars class_name(env, javaName);
  if (class_name.c_str() == nullptr) {
    VLOG(class_linker) << "Failed to find class_name";
    return nullptr;
  }
//類文件描述
  const std::string descriptor(DotToDescriptor(class_name.c_str()));
//類文件哈希地址
  const size_t hash(ComputeModifiedUtf8Hash(descriptor.c_str()));
  for (auto& dex_file : dex_files) {
 //獲取類類型定義句柄
    const DexFile::ClassDef* dex_class_def = dex_file->FindClassDef(descriptor.c_str(), hash);
    if (dex_class_def != nullptr) {
      ScopedObjectAccess soa(env);
      ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
      StackHandleScope<1> hs(soa.Self());
      Handle class_loader(
          hs.NewHandle(soa.Decode(javaLoader)));
      class_linker->RegisterDexFile(*dex_file, class_loader.Get());
  //處理加載類
      mirror::Class* result = class_linker->DefineClass(soa.Self(),
                                                        descriptor.c_str(),
                                                        hash,
                                                        class_loader,
                                                        *dex_file,
                                                        *dex_class_def);
      // Add the used dex file. This only required for the DexFile.loadClass API since normal
      // class loaders already keep their dex files live.
      class_linker->InsertDexFileInToClassLoader(soa.Decode(dexFile),
                                                 class_loader.Get());
      if (result != nullptr) {
        VLOG(class_linker) << "DexFile_defineClassNative returning " << result
                           << " for " << class_name.c_str();
        return soa.AddLocalReference(result);
      }
    }
  }
  VLOG(class_linker) << "Failed to find dex_class_def " << class_name.c_str();
  return nullptr;
}

/art/runtime/class_linker.cc

mirror::Class* ClassLinker::DefineClass(Thread* self,
                                        const char* descriptor,
                                        size_t hash,
                                        Handle class_loader,
                                        const DexFile& dex_file,
                                        const DexFile::ClassDef& dex_class_def) {
  StackHandleScope<3> hs(self);
  auto klass = hs.NewHandle(nullptr);

  // Load the class from the dex file.
  if (UNLIKELY(!init_done_)) {
    // finish up init of hand crafted class_roots_
    if (strcmp(descriptor, "Ljava/lang/Object;") == 0) {
      klass.Assign(GetClassRoot(kJavaLangObject));
    } else if (strcmp(descriptor, "Ljava/lang/Class;") == 0) {
      klass.Assign(GetClassRoot(kJavaLangClass));
    } else if (strcmp(descriptor, "Ljava/lang/String;") == 0) {
      klass.Assign(GetClassRoot(kJavaLangString));
    } else if (strcmp(descriptor, "Ljava/lang/ref/Reference;") == 0) {
      klass.Assign(GetClassRoot(kJavaLangRefReference));
    } else if (strcmp(descriptor, "Ljava/lang/DexCache;") == 0) {
      klass.Assign(GetClassRoot(kJavaLangDexCache));
    }
  } 
  if (klass.Get() == nullptr) {
    // Allocate a class with the status of not ready.
    // Interface object should get the right size here. Regular class will
    // figure out the right size later and be replaced with one of the right
    // size when the class becomes resolved.    
    klass.Assign(AllocClass(self, SizeOfClassWithoutEmbeddedTables(dex_file, dex_class_def)));
  }
  if (UNLIKELY(klass.Get() == nullptr)) {
    self->AssertPendingOOMException();
    return nullptr;
  }
  mirror::DexCache* dex_cache = RegisterDexFile(dex_file, class_loader.Get());
  if (dex_cache == nullptr) {
    self->AssertPendingOOMException();
    return nullptr;
  }
  klass->SetDexCache(dex_cache);
  SetupClass(dex_file, dex_class_def, klass, class_loader.Get());

  // Mark the string class by setting its access flag.
  if (UNLIKELY(!init_done_)) {
    if (strcmp(descriptor, "Ljava/lang/String;") == 0) {
      klass->SetStringClass();
    }
  }

  ObjectLock lock(self, klass);
  klass->SetClinitThreadId(self->GetTid());

  // Add the newly loaded class to the loaded classes table.
  mirror::Class* existing = InsertClass(descriptor, klass.Get(), hash);
  if (existing != nullptr) {
    // We failed to insert because we raced with another thread. Calling EnsureResolved may cause
    // this thread to block.
    return EnsureResolved(self, descriptor, existing);
  }

  // Load the fields and other things after we are inserted in the table. This is so that we don't
  // end up allocating unfree-able linear alloc resources and then lose the race condition. The
  // other reason is that the field roots are only visited from the class table. So we need to be
  // inserted before we allocate / fill in these fields. 
  LoadClass(self, dex_file, dex_class_def, klass);
  if (self->IsExceptionPending()) {
    VLOG(class_linker) << self->GetException()->Dump();
    // An exception occured during load, set status to erroneous while holding klass' lock in case
    // notification is necessary.
    if (!klass->IsErroneous()) {
      mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
    }
    return nullptr;
  }

  // Finish loading (if necessary) by finding parents
  CHECK(!klass->IsLoaded());
  if (!LoadSuperAndInterfaces(klass, dex_file)) {
    // Loading failed.
    if (!klass->IsErroneous()) {
      mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
    }
    return nullptr;
  }
  CHECK(klass->IsLoaded());
  // Link the class (if necessary)
  CHECK(!klass->IsResolved());
  // TODO: Use fast jobjects?
  auto interfaces = hs.NewHandle>(nullptr);

  MutableHandle h_new_class = hs.NewHandle(nullptr);
  if (!LinkClass(self, descriptor, klass, interfaces, &h_new_class)) {
    // Linking failed.
    if (!klass->IsErroneous()) {
      mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
    }
    return nullptr;
  }
  self->AssertNoPendingException();
  CHECK(h_new_class.Get() != nullptr) << descriptor;
  CHECK(h_new_class->IsResolved()) << descriptor;

  // Instrumentation may have updated entrypoints for all methods of all
  // classes. However it could not update methods of this class while we
  // were loading it. Now the class is resolved, we can update entrypoints
  // as required by instrumentation.
  if (Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled()) {
    // We must be in the kRunnable state to prevent instrumentation from
    // suspending all threads to update entrypoints while we are doing it
    // for this class.
    DCHECK_EQ(self->GetState(), kRunnable);
    Runtime::Current()->GetInstrumentation()->InstallStubsForClass(h_new_class.Get());
  }

  /*
   * We send CLASS_PREPARE events to the debugger from here.  The
   * definition of "preparation" is creating the static fields for a
   * class and initializing them to the standard default values, but not
   * executing any code (that comes later, during "initialization").
   *
   * We did the static preparation in LinkClass.
   *
   * The class has been prepared and resolved but possibly not yet verified
   * at this point.
   */
  Dbg::PostClassPrepare(h_new_class.Get());

  // Notify native debugger of the new class and its layout.
  jit::Jit::NewTypeLoadedIfUsingJit(h_new_class.Get());

  return h_new_class.Get();
}
//加載類
void ClassLinker::LoadClass(Thread* self,
                            const DexFile& dex_file,
                            const DexFile::ClassDef& dex_class_def,
                            Handle klass) {
  const uint8_t* class_data = dex_file.GetClassData(dex_class_def);
  if (class_data == nullptr) {
    return;  // no fields or methods - for example a marker interface
  }
  bool has_oat_class = false;
  if (Runtime::Current()->IsStarted() && !Runtime::Current()->IsAotCompiler()) {
    OatFile::OatClass oat_class = FindOatClass(dex_file, klass->GetDexClassDefIndex(),
                                               &has_oat_class);
    if (has_oat_class) {
      LoadClassMembers(self, dex_file, class_data, klass, &oat_class);
    }
  }
  if (!has_oat_class) {
    LoadClassMembers(self, dex_file, class_data, klass, nullptr);
  }
}

原來加載類是由class_linker去具體實現了,在android中,一般通過getClassLoader()獲取系統加載器,其系統加載器創建是在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;
    }

從上面知道,android獲取到的系統類加載器是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')

運行效果如圖:

這裡寫圖片描述

Demo地址:https://github.com/scau-beyondboy/ClassLoadDemo

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved