ClassLoader解析以及應(yīng)用

[TOC]

Android ClassLoader淺析
Android中的dex区丑、apk、ClassLoader詳解

  1. 程序在運(yùn)行時把對應(yīng)的類加載到內(nèi)存中,在Android上來說就是把Dex文件中的類加載到內(nèi)存。
  2. 雙親委派機(jī)制
public abstract class ClassLoader {
    private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
    }
    
    protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
    }
    
    protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }
    
    public static ClassLoader getSystemClassLoader() {
        return SystemClassLoader.loader;
    }
    
    static private class SystemClassLoader {
        public static ClassLoader loader = ClassLoader.createSystemClassLoader();
    }
    
    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());
    }
    
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
    
    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) {
                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.
                    c = findClass(name);
                }
            }
            return c;
    }
    
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
}

1. ClassLoader 繼承關(guān)系

繼承關(guān)系
[圖片上傳失敗...(image-d446ce-1549854936566)]

  • PathClassLoader只能加載已經(jīng)安裝到Android系統(tǒng)中的apk文件(/data/app目錄)跑芳,是Android默認(rèn)使用的類加載器
  • DexClassLoader可以加載任意目錄下的dex/jar/apk/zip文件贬媒,比PathClassLoader更靈活聋亡,是實(shí)現(xiàn)熱修復(fù)的重點(diǎn)

源碼查看(基于API28 9.0)
在8.0以上际乘,optimizedDirectory參數(shù)傳的都是null坡倔,也就是說PathClassLoaderDexClassLoader沒差別,DexClassLoader可以加載未安裝的apk脖含,PathClassLoader也可以罪塔。

/**
 * Provides a simple {@link 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).
 *///
public class PathClassLoader extends BaseDexClassLoader {
    
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }

    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent){
        super(dexPath, null, librarySearchPath, parent);
   }
}
/**
 * A class loader that loads classes from {@code .jar} and {@code .apk} files
 * containing a {@code classes.dex} entry. This can be used to execute code not
 * installed as part of an application.
*///
public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
                          String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

具體實(shí)現(xiàn)還得看BaseDexClassLoader的構(gòu)造方法。

public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;

   public BaseDexClassLoader(String dexPath, File optimizedDirectory,
                          String librarySearchPath, ClassLoader parent) {
        this(dexPath, optimizedDirectory, librarySearchPath, parent, false);
    }

    // 參數(shù)dexPath:待加載的apk/dex/jar文件路徑养葵;
    // 參數(shù)optimizedDirectory:dex的輸出路徑征堪,將apk/dex/jar解壓出dex文件,復(fù)制到指定路徑关拒,用于dalvik運(yùn)行
    // 參數(shù)librarySearchPath:加載時候需要用到的lib庫佃蚜,這個一般不用,可以傳入Null
    // 參數(shù)parent:指定父加載器
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
                              String librarySearchPath, ClassLoader parent, boolean isTrusted) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
        ...
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        ...
        Class c = pathList.findClass(name, suppressedExceptions);
        ...
        return c;
    }

    @Override
    protected URL findResource(String name) {
        return pathList.findResource(name);
    }
    
    @Override
    public String findLibrary(String name) {
        return pathList.findLibrary(name);
    }
}

構(gòu)造方法中創(chuàng)建了一個DexPathList對象着绊,而BaseDexClassLoader中的各個findxxx()調(diào)用的是DexPathList對象.findxxx()

DexPathList的構(gòu)造方法和findxxx()方法谐算。

final class DexPathList {
    
    private Element[] dexElements;
    
    DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
        ...
        // 加載dexPath路徑下的dex和resource
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions, definingContext, isTrusted);
        ...
    }
    
    private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
        List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
     
      Element[] elements = new Element[files.size()];
      int elementsPos = 0;
      for (File file : files) {
          if (file.isDirectory()) {
              elements[elementsPos++] = new Element(file);
          } else if (file.isFile()) {
              String name = file.getName();

              DexFile dex = null;
              if (name.endsWith(DEX_SUFFIX)) {
                  // Raw dex file (not inside a zip/jar).
                  try {
                      dex = loadDexFile(file, optimizedDirectory, loader, elements);
                      if (dex != null) {
                          elements[elementsPos++] = new Element(dex, null);
                      }
                  } catch (IOException suppressed) {
                      System.logE("Unable to load dex file: " + file, suppressed);
                      suppressedExceptions.add(suppressed);
                  }
              } else {
                  try {
                      dex = loadDexFile(file, optimizedDirectory, loader, elements);
                  } catch (IOException suppressed) {
                      suppressedExceptions.add(suppressed);
                  }

                  if (dex == null) {
                      elements[elementsPos++] = new Element(file);
                  } else {
                      elements[elementsPos++] = new Element(dex, file);
                  }
              }
          } else {
              System.logW("ClassLoader referenced unknown path: " + file);
          }
      }
      return elements;
    }
}

挨個文件中尋找Class對象

 public Class<?> findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }

        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }

2. 熱更新實(shí)現(xiàn)原理

源碼

如果是.dex后綴的文件,會直接放到dexElements數(shù)組中归露,否則就從這個文件中尋找dex文件洲脂,找到后放到dexElements數(shù)組。
ClassLoader#loadClass就是遍歷dexElements數(shù)組剧包,從dex文件中找到要找的class文件恐锦。

那我們是不是在這個數(shù)組前安插一個自己的數(shù)組往果,是不是就可以了?

做法:
拿到我當(dāng)前應(yīng)用的dexElements數(shù)組,然后拿到已修復(fù)的dex或apk文件的dexElements數(shù)組一铅,把已修復(fù)的數(shù)組放到當(dāng)前應(yīng)用的數(shù)組前面陕贮。

public class HotfixApplication extends Application {
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

        File apk = new File(getCacheDir() + "/hotfix.dex");
        if (apk.exists()) {
            try {
            //1. 獲取當(dāng)前應(yīng)用的dexElements數(shù)組
                ClassLoader classLoader = getClassLoader();
                Class loaderClass = BaseDexClassLoader.class;
                Field pathListField = loaderClass.getDeclaredField("pathList");
                pathListField.setAccessible(true);
                Object pathListObject = pathListField.get(classLoader);
                Class pathListClass = pathListObject.getClass();
                Field dexElementsField = pathListClass.getDeclaredField("dexElements");
                dexElementsField.setAccessible(true);
                Object dexElementsObject = dexElementsField.get(pathListObject);

            //2. 獲取已修復(fù)dex或apk的dexElements數(shù)組
                PathClassLoader newClassLoader = new PathClassLoader(apk.getPath(), null);
                Object newPathListObject = pathListField.get(newClassLoader);
                Object newDexElementsObject = dexElementsField.get(newPathListObject);

            //3. 數(shù)組合并,把修復(fù)的數(shù)組放在自己的數(shù)組前面
                int oldLength = Array.getLength(dexElementsObject);
                int newLength = Array.getLength(newDexElementsObject);
                Object concatDexElementsObject = Array.newInstance(dexElementsObject.getClass().getComponentType(), oldLength + newLength);
                for (int i = 0; i < newLength; i++) {
                    Array.set(concatDexElementsObject, i, Array.get(newDexElementsObject, i));
                }
                for (int i = 0; i < oldLength; i++) {
                    Array.set(concatDexElementsObject, newLength + i, Array.get(dexElementsObject, i));
                }

                dexElementsField.set(pathListObject, concatDexElementsObject);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
}

3. 插件化原理

利用ClassLoader在新的apk中loadClass馅闽,利用反射獲取新的apk中的class

3.1 問題

Q: 怎么啟動插件apk中的Activity飘蚯?
A: 由于啟動Activity系統(tǒng)會校驗(yàn)清單文件,如果沒有該Activity會異常福也。我們可以創(chuàng)建一個代理的Activity,啟動這個代理Activity攀圈,然后在代理Activity中執(zhí)行真實(shí)對象的方法(這個真實(shí)對象需要有和Activity一樣的方法)暴凑。

Q: 怎樣獲取插件apk中的Resource文件
A: 我們要知道,Resource也是利用AssetManager獲取的赘来,所以我們要自定義一個AssetManager现喳,安放到Resource對象中。

public class ProxyActivity extends Activity {
    Object realActivity;

//    realActivity = ???;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        realActivity.onCreate(savedInstanceState);

    }

    @Override
    public AssetManager getAssets() {
        try {
            Class assetManagerClass = AssetManager.class;
            AssetManager assetManager = (AssetManager) assetManagerClass.newInstance();
            Method addAssetPath = assetManagerClass.getDeclaredMethod("adAssetPath", String.class);
            addAssetPath.invoke(assetManager, "apkPath");
            return assetManager;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return super.getAssets();
    }

    @Override
    public Resources getResources() {
        return new Resources(getAssets(), getResources().getDisplayMetrics(), getResources().getConfiguration());
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末犬辰,一起剝皮案震驚了整個濱河市嗦篱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌幌缝,老刑警劉巖灸促,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異涵卵,居然都是意外死亡浴栽,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門轿偎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來典鸡,“玉大人,你說我怎么就攤上這事坏晦÷茜瑁” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵昆婿,是天一觀的道長球碉。 經(jīng)常有香客問我,道長挖诸,這世上最難降的妖魔是什么汁尺? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮多律,結(jié)果婚禮上痴突,老公的妹妹穿的比我還像新娘搂蜓。我一直安慰自己,他們只是感情好辽装,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布帮碰。 她就那樣靜靜地躺著,像睡著了一般拾积。 火紅的嫁衣襯著肌膚如雪殉挽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天拓巧,我揣著相機(jī)與錄音斯碌,去河邊找鬼。 笑死肛度,一個胖子當(dāng)著我的面吹牛傻唾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播承耿,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼冠骄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了加袋?” 一聲冷哼從身側(cè)響起凛辣,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎职烧,沒想到半個月后扁誓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡阳堕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年跋理,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恬总。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡前普,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出壹堰,到底是詐尸還是另有隱情拭卿,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布贱纠,位于F島的核電站峻厚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏谆焊。R本人自食惡果不足惜惠桃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辜王,春花似錦劈狐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至汹来,卻和暖如春续膳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背收班。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工坟岔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人闺阱。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓炮车,卻偏偏與公主長得像,于是被迫代替她去往敵國和親酣溃。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345