1. 程式人生 > >深度理解Android InstantRun原理以及原始碼分析

深度理解Android InstantRun原理以及原始碼分析

Instant Run官方介紹

簡單介紹一下Instant Run,它是Android Studio2.0以後新增的一個執行機制,能夠顯著減少你第二次及以後的構建和部署時間。簡單通俗的解釋就是,當你在Android Studio中改了你的程式碼,Instant Run可以很快的讓你看到你修改的效果。而在沒有Instant Run之前,你的一個小小的修改,都肯能需要幾十秒甚至更長的等待才能看到修改後的效果。

傳統的程式碼修改及編譯部署流程

1

構建整個apk → 部署app → app重啟 → 重啟Activity
而Instant Run則需要更少的時間。

Instant Run編譯和部署流程

2
只構建修改的部分 → 部署修改的dex或資源 → 熱部署,溫部署,冷部署

熱部署

Incremental code changes are applied and reflected in the app without needing to relaunch the app or even restart the current Activity. Can be used for most simple changes within method implementations.
方法內的簡單修改,無需重啟app和Activity

溫部署

The Activity needs to be restarted before changes can be seen and used. Typically required for changes to resources.
app無需重啟,但是activity需要重啟,比如資源的修改。

冷部署

The app is restarted (but still not reinstalled). Required for any structural changes such as to inheritance or method signatures.
app需要重啟,比如繼承關係的改變或方法的簽名變化等。

上述說這麼多概念,估計大家對Instant Run應該有了大體的認知了。那麼它的實現原理是什麼呢?其實,在沒有看案例之前,我基本上可以猜測到Instant Run的思路,基於目前比較火的外掛化框架,是比較容易理解Instant Run的。但Instant Run畢竟是Google官方的工具,具有很好的借鑑意義。

Demo案例

新建一個簡單的android studio專案,新建自己的MyApplication,在AndroidManifest檔案中設定:

4
首先,我們先反編譯一下APK的構成:
使用的工具:d2j-dex2jar 和jd-gui
3
裡面有2個dex檔案和一個instant-run.zip檔案。首先分別看一下兩個dex檔案的原始碼:
classes.dex的反編譯之後的原始碼:
5
裡面只有一個AppInfo,儲存了app的基本資訊,主要包含了包名和applicationClass。
classes2.dex反編譯之後的原始碼:
6
我們赫然發現,兩個dex中竟然沒有一句我們自己寫的程式碼??那麼程式碼在哪裡呢?你可能猜到,app真正的業務dex在instant-run.zip中。解壓instant-run.zip之後,如下圖所示:
7
反編譯之後,我們會發現,我們真正的業務程式碼都在這裡。
另外,我們再decode看一下AndroidManifest檔案
8
//TODO
我們發現,我們的application也被替換了,替換成了com.android.tools.fd.runtime.BootstrapApplication

看到這裡,那麼大體的思路,可以猜到:
1.Instant-Run程式碼作為一個宿主程式,將app作為資源dex載入起來,和外掛化一個思路
2.那麼InstantRun是怎麼把業務程式碼執行起來的呢?

InstantRun啟動app

首先BootstrapApplication分析,按照執行順序,依次分析attachBaseContext和onCreate方法。

1.attachBaseContext方法

<code class="language-java hljs  has-numbering">
    ...
    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">attachBaseContext</span>(Context context) {
        <span class="hljs-keyword">if</span> (!AppInfo.usingApkSplits) {
            String apkFile = context.getApplicationInfo().sourceDir;
            <span class="hljs-keyword">long</span> apkModified = apkFile != <span class="hljs-keyword">null</span> ? <span class="hljs-keyword">new</span> File(apkFile)
                    .lastModified() : <span class="hljs-number">0</span>L;
            createResources(apkModified);
            setupClassLoaders(context, context.getCacheDir().getPath(),
                    apkModified);
        }
        createRealApplication();

        <span class="hljs-keyword">super</span>.attachBaseContext(context);
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.realApplication != <span class="hljs-keyword">null</span>) {
            <span class="hljs-keyword">try</span> {
                Method attachBaseContext = ContextWrapper.class
                        .getDeclaredMethod(<span class="hljs-string">"attachBaseContext"</span>,
                                <span class="hljs-keyword">new</span> Class[] { Context.class });

                attachBaseContext.setAccessible(<span class="hljs-keyword">true</span>);
                attachBaseContext.invoke(<span class="hljs-keyword">this</span>.realApplication,
                        <span class="hljs-keyword">new</span> Object[] { context });
            } <span class="hljs-keyword">catch</span> (Exception e) {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException(e);
            }
        }
    }
    ...</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li></ul>

我們依次需要關注的方法有:
createResources → setupClassLoaders → createRealApplication → 呼叫realApplication的attachBaseContext方法

1.1.createResources

首先看createResources方法:

<code class="language-java hljs  has-numbering">
     <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">createResources</span>(<span class="hljs-keyword">long</span> apkModified) {
        FileManager.checkInbox();

        File file = FileManager.getExternalResourceFile();
        <span class="hljs-keyword">this</span>.externalResourcePath = (file != <span class="hljs-keyword">null</span> ? file.getPath() : <span class="hljs-keyword">null</span>);
        <span class="hljs-keyword">if</span> (Log.isLoggable(<span class="hljs-string">"InstantRun"</span>, <span class="hljs-number">2</span>)) {
            Log.v(<span class="hljs-string">"InstantRun"</span>, <span class="hljs-string">"Resource override is "</span>
                    + <span class="hljs-keyword">this</span>.externalResourcePath);
        }
        <span class="hljs-keyword">if</span> (file != <span class="hljs-keyword">null</span>) {
            <span class="hljs-keyword">try</span> {
                <span class="hljs-keyword">long</span> resourceModified = file.lastModified();
                <span class="hljs-keyword">if</span> (Log.isLoggable(<span class="hljs-string">"InstantRun"</span>, <span class="hljs-number">2</span>)) {
                    Log.v(<span class="hljs-string">"InstantRun"</span>, <span class="hljs-string">"Resource patch last modified: "</span>
                            + resourceModified);
                    Log.v(<span class="hljs-string">"InstantRun"</span>, <span class="hljs-string">"APK last modified: "</span> + apkModified
                            + <span class="hljs-string">" "</span>
                            + (apkModified > resourceModified ? <span class="hljs-string">">"</span> : <span class="hljs-string">"<"</span>)
                            + <span class="hljs-string">" resource patch"</span>);
                }
                <span class="hljs-keyword">if</span> ((apkModified == <span class="hljs-number">0</span>L) || (resourceModified <= apkModified)) {
                    <span class="hljs-keyword">if</span> (Log.isLoggable(<span class="hljs-string">"InstantRun"</span>, <span class="hljs-number">2</span>)) {
                        Log.v(<span class="hljs-string">"InstantRun"</span>,
                                <span class="hljs-string">"Ignoring resource file, older than APK"</span>);
                    }
                    <span class="hljs-keyword">this</span>.externalResourcePath = <span class="hljs-keyword">null</span>;
                }
            } <span class="hljs-keyword">catch</span> (Throwable t) {
                Log.e(<span class="hljs-string">"InstantRun"</span>, <span class="hljs-string">"Failed to check patch timestamps"</span>, t);
            }
        }
    }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li></ul>

該方法主要是判斷資源resource.ap_是否改變,然後儲存resource.ap_的路徑到externalResourcePath中

1.2.setupClassLoaders
<code class="language-java hljs  has-numbering">
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setupClassLoaders</span>(Context context, String codeCacheDir,
            <span class="hljs-keyword">long</span> apkModified) {
        List<String> dexList = FileManager.getDexList(context, apkModified);

        Class<Server> server = Server.class;
        Class<MonkeyPatcher> patcher = MonkeyPatcher.class;
        <span class="hljs-keyword">if</span> (!dexList.isEmpty()) {
            <span class="hljs-keyword">if</span> (Log.isLoggable(<span class="hljs-string">"InstantRun"</span>, <span class="hljs-number">2</span>)) {
                Log.v(<span class="hljs-string">"InstantRun"</span>, <span class="hljs-string">"Bootstrapping class loader with dex list "</span>
                        + join(<span class="hljs-string">'\n'</span>, dexList));
            }
            ClassLoader classLoader = BootstrapApplication.class
                    .getClassLoader();
            String nativeLibraryPath;
            <span class="hljs-keyword">try</span> {
                nativeLibraryPath = (String) classLoader.getClass()
                        .getMethod(<span class="hljs-string">"getLdLibraryPath"</span>, <span class="hljs-keyword">new</span> Class[<span class="hljs-number">0</span>])
                        .invoke(classLoader, <span class="hljs-keyword">new</span> Object[<span class="hljs-number">0</span>]);
                <span class="hljs-keyword">if</span> (Log.isLoggable(<span class="hljs-string">"InstantRun"</span>, <span class="hljs-number">2</span>)) {
                    Log.v(<span class="hljs-string">"InstantRun"</span>, <span class="hljs-string">"Native library path: "</span>
                            + nativeLibraryPath);
                }
            } <span class="hljs-keyword">catch</span> (Throwable t) {
                Log.e(<span class="hljs-string">"InstantRun"</span>, <span class="hljs-string">"Failed to determine native library path "</span>
                        + t.getMessage());
                nativeLibraryPath = FileManager.getNativeLibraryFolder()
                        .getPath();
            }
            IncrementalClassLoader.inject(classLoader, nativeLibraryPath,
                    codeCacheDir, dexList);
        }
    }
</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li></ul>

繼續看IncrementalClassLoader.inject方法:
IncrementalClassLoader的原始碼如下:

<code class="language-java hljs  has-numbering">
    <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">IncrementalClassLoader</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ClassLoader</span> {</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">boolean</span> DEBUG_CLASS_LOADING = <span class="hljs-keyword">false</span>;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> DelegateClassLoader delegateClassLoader;

    <span class="hljs-keyword">public</span> <span class="hljs-title">IncrementalClassLoader</span>(ClassLoader original,
            String nativeLibraryPath, String codeCacheDir, List<String> dexes) {
        <span class="hljs-keyword">super</span>(original.getParent());

        <span class="hljs-keyword">this</span>.delegateClassLoader = createDelegateClassLoader(nativeLibraryPath,
                codeCacheDir, dexes, original);
    }

    <span class="hljs-keyword">public</span> Class<?> <span class="hljs-title">findClass</span>(String className) <span class="hljs-keyword">throws</span> ClassNotFoundException {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.delegateClassLoader.findClass(className);
        } <span class="hljs-keyword">catch</span> (ClassNotFoundException e) {
            <span class="hljs-keyword">throw</span> e;
        }
    }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DelegateClassLoader</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">BaseDexClassLoader</span> {</span>

        <span class="hljs-keyword">private</span> <span class="hljs-title">DelegateClassLoader</span>(String dexPath, File optimizedDirectory,
                String libraryPath, ClassLoader parent) {
            <span class="hljs-keyword">super</span>(dexPath, optimizedDirectory, libraryPath, parent);
        }

        <span class="hljs-keyword">public</span> Class<?> <span class="hljs-title">findClass</span>(String name) <span class="hljs-keyword">throws</span> ClassNotFoundException {
            <span class="hljs-keyword">try</span> {
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">super</span>.findClass(name);
            } <span class="hljs-keyword">catch</span> (ClassNotFoundException e) {
                <span class="hljs-keyword">throw</span> e;
            }
        }
    }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> DelegateClassLoader <span class="hljs-title">createDelegateClassLoader</span>(
            String nativeLibraryPath, String codeCacheDir, List<String> dexes,
            ClassLoader original) {
        String pathBuilder = createDexPath(dexes);
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> DelegateClassLoader(pathBuilder, <span class="hljs-keyword">new</span> File(codeCacheDir),
                nativeLibraryPath, original);
    }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> String <span class="hljs-title">createDexPath</span>(List<String> dexes) {
        StringBuilder pathBuilder = <span class="hljs-keyword">new</span> StringBuilder();
        <span class="hljs-keyword">boolean</span> first = <span class="hljs-keyword">true</span>;
        <span class="hljs-keyword">for</span> (String dex : dexes) {
            <span class="hljs-keyword">if</span> (first) {
                first = <span class="hljs-keyword">false</span>;
            } <span class="hljs-keyword">else</span> {
                pathBuilder.append(File.pathSeparator);
            }
            pathBuilder.append(dex);
        }
        <span class="hljs-keyword">if</span> (Log.isLoggable(<span class="hljs-string">"InstantRun"</span>, <span class="hljs-number">2</span>)) {
            Log.v(<span class="hljs-string">"InstantRun"</span>, <span class="hljs-string">"Incremental dex path is "</span>
                    + BootstrapApplication.join(<span class="hljs-string">'\n'</span>, dexes));
        }
        <span class="hljs-keyword">return</span> pathBuilder.toString();
    }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setParent</span>(ClassLoader classLoader, ClassLoader newParent) {
        <span class="hljs-keyword">try</span> {
            Field parent = ClassLoader.class.getDeclaredField(<span class="hljs-string">"parent"</span>);
            parent.setAccessible(<span class="hljs-keyword">true</span>);
            parent.set(classLoader, newParent);
        } <span class="hljs-keyword">catch</span> (IllegalArgumentException e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(e);
        } <span class="hljs-keyword">catch</span> (IllegalAccessException e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(e);
        } <span class="hljs-keyword">catch</span> (NoSuchFieldException e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(e);
        }
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ClassLoader <span class="hljs-title">inject</span>(ClassLoader classLoader,
            String nativeLibraryPath, String codeCacheDir, List<String> dexes) {
        IncrementalClassLoader incrementalClassLoader = <span class="hljs-keyword">new</span> IncrementalClassLoader(
                classLoader, nativeLibraryPath, codeCacheDir, dexes);

        setParent(classLoader, incrementalClassLoader);

        <span class="hljs-keyword">return</span> incrementalClassLoader;
    }
    }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li><li>61</li><li>62</li><li>63</li><li>64</li><li>65</li><li>66</li><li>67</li><li>68</li><li>69</li><li>70</li><li>71</li><li>72</li><li>73</li><li>74</li><li>75</li><li>76</li><li>77</li><li>78</li><li>79</li><li>80</li><li>81</li><li>82</li><li>83</li><li>84</li><li>85</li><li>86</li><li>87</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li><li>61</li><li>62</li><li>63</li><li>64</li><li>65</li><li>66</li><li>67</li><li>68</li><li>69</li><li>70</li><li>71</li><li>72</li><li>73</li><li>74</li><li>75</li><li>76</li><li>77</li><li>78</li><li>79</li><li>80</li><li>81</li><li>82</li><li>83</li><li>84</li><li>85</li><li>86</li><li>87</li></ul>

inject方法是用來設定classloader的父子順序的,使用IncrementalClassLoader來載入dex。由於ClassLoader的雙親委託模式,也就是委託父類載入類,父類中找不到再在本ClassLoader中查詢。
呼叫之後的效果如下圖所示:
classloader
我們可以在MyApplication中,用程式碼驗證一下

<code class="language-java hljs  has-numbering">
     <span class="hljs-annotation">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span>() {
        <span class="hljs-keyword">super</span>.onCreate();
        <span class="hljs-keyword">try</span>{
            Log.d(TAG,<span class="hljs-string">"###onCreate in myApplication"</span>);
            String classLoaderName = getClassLoader().getClass().getName();
            Log.d(TAG,<span class="hljs-string">"###onCreate in myApplication classLoaderName = "</span>+classLoaderName);
            String parentClassLoaderName = getClassLoader().getParent().getClass().getName();
            Log.d(TAG,<span class="hljs-string">"###onCreate in myApplication parentClassLoaderName = "</span>+parentClassLoaderName);
            String pParentClassLoaderName = getClassLoader().getParent().getParent().getClass().getName();
            Log.d(TAG,<span class="hljs-string">"###onCreate in myApplication pParentClassLoaderName = "</span>+pParentClassLoaderName);
        }<span class="hljs-keyword">catch</span> (Exception e){
            e.printStackTrace();
        }
    }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li></ul>

執行結果:

<code class="language-txt hljs r has-numbering"> <span class="hljs-keyword">...</span>
<span class="hljs-number">06</span>-<span class="hljs-number">30</span> <span class="hljs-number">10</span>:<span class="hljs-number">43</span>:<span class="hljs-number">42.475</span> <span class="hljs-number">27307</span>-<span class="hljs-number">27307</span>/mobctrl.net.testinstantrun D/MyApplication: <span class="hljs-comment">###onCreate in myApplication classLoaderName = dalvik.system.PathClassLoader</span>
<span class="hljs-number">06</span>-<span class="hljs-number">30</span> <span class="hljs-number">10</span>:<span class="hljs-number">43</span>:<span class="hljs-number">42.475</span> <span class="hljs-number">27307</span>-<span class="hljs-number">27307</span>/mobctrl.net.testinstantrun D/MyApplication: <span class="hljs-comment">###onCreate in myApplication parentClassLoaderName = com.android.tools.fd.runtime.IncrementalClassLoader</span>
<span class="hljs-number">06</span>-<span class="hljs-number">30</span> <span class="hljs-number">10</span>:<span class="hljs-number">43</span>:<span class="hljs-number">42.475</span> <span class="hljs-number">27307</span>-<span class="hljs-number">27307</span>/mobctrl.net.testinstantrun D/MyApplication: <span class="hljs-comment">###onCreate in myApplication pParentClassLoaderName = java.lang.BootClassLoader</span></code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul>

由此,我們已經知道了,當前PathClassLoader委託IncrementalClassLoader載入dex。繼續回到BootstrapApplication的attachBaseContext方法繼續分析。

1.3.createRealApplication
<code class="language-java hljs  has-numbering">
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">createRealApplication</span>() {
        <span class="hljs-keyword">if</span> (AppInfo.applicationClass != <span class="hljs-keyword">null</span>) {
            <span class="hljs-keyword">if</span> (Log.isLoggable(<span class="hljs-string">"InstantRun"</span>, <span class="hljs-number">2</span>)) {
                Log.v(<span class="hljs-string">"InstantRun"</span>,
                        <span class="hljs-string">"About to create real application of class name = "</span>
                                + AppInfo.applicationClass);
            }
            <span class="hljs-keyword">try</span> {
                Class<? extends Application> realClass = (Class<? extends Application>) Class
                        .forName(AppInfo.applicationClass);
                <span class="hljs-keyword">if</span> (Log.isLoggable(<span class="hljs-string">"InstantRun"</span>, <span class="hljs-number">2</span>)) {
                    Log.v(<span class="hljs-string">"InstantRun"</span>,
                            <span class="hljs-string">"Created delegate app class successfully : "</span>
                                    + realClass + <span class="hljs-string">" with class loader "</span>
                                    + realClass.getClassLoader());
                }
                Constructor<? extends Application> constructor = realClass
                        .getConstructor(<span class="hljs-keyword">new</span> Class[<span class="hljs-number">0</span>]);
                <span class="hljs-keyword">this</span>.realApplication = ((Application) constructor
                        .newInstance(<span class="hljs-keyword">new</span> Object[<span class="hljs-number">0</span>]));
                <span class="hljs-keyword">if</span> (Log.isLoggable(<span class="hljs-string">"InstantRun"</span>, <span class="hljs-number">2</span>)) {
                    Log.v(<span class="hljs-string">"InstantRun"</span>,
                            <span class="hljs-string">"Created real app instance successfully :"</span>
                                    + <span class="hljs-keyword">this</span>.realApplication);
                }
            } <span class="hljs-keyword">catch</span> (Exception e) {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException(e);
            }
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">this</span>.realApplication = <span class="hljs-keyword">new</span> Application();
        }
    }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li></ul>

該方法就是用classes.dex中的AppInfo類的applicationClass常量中儲存的app真實的application。由上面的反編譯截圖可以知道,demo中的applicationClass就是mobctrl.net.testinstantrun.MyApplication。通過反射的方式,建立真是的realApplication。

1.4.呼叫realApplication的attachBaseContext方法

代理realApplication的生命週期,通過反射呼叫realApplication的attachBaseContext方法,以當前的Context為引數。
attachBaseContext方法執行結束之後,我們繼續往下看,到BootstrapApplication的onCreate方法

2.onCreate

原始碼如下:

<code class="language-java hljs  has-numbering">
     <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span>() {
        <span class="hljs-keyword">if</span> (!AppInfo.usingApkSplits) {
            MonkeyPatcher.monkeyPatchApplication(<span class="hljs-keyword">this</span>, <span class="hljs-keyword">this</span>,
                    <span class="hljs-keyword">this</span>.realApplication, <span class="hljs-keyword">this</span>.externalResourcePath);

            MonkeyPatcher.monkeyPatchExistingResources(<span class="hljs-keyword">this</span>,
                    <span class="hljs-keyword">this</span>.externalResourcePath, <span class="hljs-keyword">null</span>);
        } <span class="hljs-keyword">else</span> {
            MonkeyPatcher.monkeyPatchApplication(<span class="hljs-keyword">this</span>, <span class="hljs-keyword">this</span>,
                    <span class="hljs-keyword">this</span>.realApplication, <span class="hljs-keyword">null</span>);
        }
        <span class="hljs-keyword">super</span>.onCreate();
        <span class="hljs-keyword">if</span> (AppInfo.applicationId != <span class="hljs-keyword">null</span>) {
            <span class="hljs-keyword">try</span> {
                <span class="hljs-keyword">boolean</span> foundPackage = <span class="hljs-keyword">false</span>;
                <span class="hljs-keyword">int</span> pid = Process.myPid();
                ActivityManager manager = (ActivityManager) getSystemService(<span class="hljs-string">"activity"</span>);

                List<ActivityManager.RunningAppProcessInfo> processes = manager
                        .getRunningAppProcesses();
                <span class="hljs-keyword">boolean</span> startServer = <span class="hljs-keyword">false</span>;
                <span class="hljs-keyword">if</span> ((processes != <span class="hljs-keyword">null</span>) && (processes.size() > <span class="hljs-number">1</span>)) {
                    <span class="hljs-keyword">for</span> (ActivityManager.RunningAppProcessInfo processInfo : processes) {
                        <span class="hljs-keyword">if</span> (AppInfo.applicationId
                                .equals(processInfo.processName)) {
                            foundPackage = <span class="hljs-keyword">true</span>;
                            <span class="hljs-keyword">if</span> (processInfo.pid == pid) {
                                startServer = <span class="hljs-keyword">true</span>;
                                <span class="hljs-keyword">break</span>;
                            }
                        }
                    }
                    <span class="hljs-keyword">if</span> ((!startServer) && (!foundPackage)) {
                        startServer = <span class="hljs-keyword">true</span>;
                        <span class="hljs-keyword">if</span> (Log.isLoggable(<span class="hljs-string">"InstantRun"</span>, <span class="hljs-number">2</span>)) {
                            Log.v(<span class="hljs-string">"InstantRun"</span>,
                                    <span class="hljs-string">"Multiprocess but didn't find process with package: starting server anyway"</span>);
                        }
                    }
                } <span class="hljs-keyword">else</span> {
                    startServer = <span class="hljs-keyword">true</span>;
                }
                <span class="hljs-keyword">if</span> (startServer) {
                    Server.create(AppInfo.applicationId, <span class="hljs-keyword">this</span>);
                }
            } <span class="hljs-keyword">catch</span> (Throwable t) {
                <span class="hljs-keyword">if</span> (Log.isLoggable(<span class="hljs-string">"InstantRun"</span>, <span class="hljs-number">2</span>)) {
                    Log.v(<span class="hljs-string">"InstantRun"</span>, <span class="hljs-string">"Failed during multi process check"</span>, t);
                }
                Server.create(AppInfo.applicationId, <span class="hljs-keyword">this</span>);
            }
        }
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.realApplication != <span class="hljs-keyword">null</span>) {
            <span class="hljs-keyword">this</span>.realApplication.onCreate();
        }
    }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li></ul>

我們依次需要關注的方法有:
monkeyPatchApplication → monkeyPatchExistingResources → Server啟動 → 呼叫realApplication的onCreate方法

2.1 monkeyPatchApplication

該方法的目的可以總結為:替換所有當前app的application為realApplication。

<code class="language-java hljs  has-numbering">
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">monkeyPatchApplication</span>(Context context,
            Application bootstrap, Application realApplication,
            String externalResourceFile) {
        <span class="hljs-keyword">try</span> {
            Class<?> activityThread = Class
                    .forName(<span class="hljs-string">"android.app.ActivityThread"</span>);
            Object currentActivityThread = getActivityThread(context,
                    activityThread);

            Field mInitialApplication = activityThread
                    .getDeclaredField(<span class="hljs-string">"mInitialApplication"</span>);
            mInitialApplication.setAccessible(<span class="hljs-keyword">true</span>);
            Application initialApplication = (Application) mInitialApplication
                    .get(currentActivityThread);
            <span class="hljs-keyword">if</span> ((realApplication != <span class="hljs-keyword">null</span>) && (initialApplication == bootstrap)) {
                mInitialApplication.set(currentActivityThread, realApplication);
            }
            <span class="hljs-keyword">if</span> (realApplication != <span class="hljs-keyword">null</span>) {
                Field mAllApplications = activityThread
                        .getDeclaredField(<span class="hljs-string">"mAllApplications"</span>);
                mAllApplications.setAccessible(<span class="hljs-keyword">true</span>);
                List<Application> allApplications = (List<Application>) mAllApplications
                        .get(currentActivityThread);
                <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < allApplications.size(); i++) {
                    <span class="hljs-keyword">if</span> (allApplications.get(i) == bootstrap) {
                        allApplications.set(i, realApplication);
                    }
                }
            }
            Class<?> loadedApkClass;
            <span class="hljs-keyword">try</span> {
                loadedApkClass = Class.forName(<span class="hljs-string">"android.app.LoadedApk"</span>);
            } <span class="hljs-keyword">catch</span> (ClassNotFoundException e) {
                loadedApkClass = Class
                        .forName(<span class="hljs-string">"android.app.ActivityThread$PackageInfo"</span>);
            }
            Field mApplication = loadedApkClass
                    .getDeclaredField(<span class="hljs-string">"mApplication"</span>);
            mApplication.setAccessible(<span class="hljs-keyword">true</span>);
            Field mResDir = loadedApkClass.getDeclaredField(<span class="hljs-string">"mResDir"</span>);
            mResDir.setAccessible(<span class="hljs-keyword">true</span>);

            Field mLoadedApk = <span class="hljs-keyword">null</span>;
            <span class="hljs-keyword">try</span> {
                mLoadedApk = Application.class.getDeclaredField(<span class="hljs-string">"mLoadedApk"</span>);
            } <span class="hljs-keyword">catch</span> (NoSuchFieldException e) {
            }
            <span class="hljs-keyword">for</span> (String fieldName : <span class="hljs-keyword">new</span> String[] { <span class="hljs-string">"mPackages"</span>,
                    <span class="hljs-string">"mResourcePackages"</span> }) {
                Field field = activityThread.getDeclaredField(fieldName);
                field.setAccessible(<span class="hljs-keyword">true</span>);
                Object value = field.get(currentActivityThread);
                <span class="hljs-keyword">for</span> (Map.Entry<String, WeakReference<?>> entry : ((Map<String, WeakReference<?>>) value)
                        .entrySet()) {
                    Object loadedApk = ((WeakReference) entry.getValue()).get();
                    <span class="hljs-keyword">if</span> (loadedApk != <span class="hljs-keyword">null</span>) {
                        <span class="hljs-keyword">if</span> (mApplication.get(loadedApk) == bootstrap) {
                            <span class="hljs-keyword">if</span> (realApplication != <span class="hljs-keyword">null</span>) {
                                mApplication.set(loadedApk, realApplication);
                            }
                            <span class="hljs-keyword">if</span> (externalResourceFile != <span class="hljs-keyword">null</span>) {
                                mResDir.set(loadedApk, externalResourceFile);
                            }
                            <span class="hljs-keyword">if</span> ((realApplication != <span class="hljs-keyword">null</span>)
                                    && (mLoadedApk != <span class="hljs-keyword">null</span>)) {
                                mLoadedApk.set(realApplication, loadedApk);
                            }
                        }
                    }
                }
            }
        } <span class="hljs-keyword">catch</span> (Throwable e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException(e);
        }
    }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li><li>61</li><li>62</li><li>63</li><li>64</li><li>65</li><li>66</li><li>67</li><li>68</li><li>69</li><li>70</li><li>71</li><li>72</li><li>73</li><li>74</li><li>75</li><li>76</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li><li>61</li><li>62</li><li>63</li><li>64</li><li>65</li><li>66</li><li>67</li><li>68</li><li>69</li><li>70</li><li>71</li><li>72</li><li>73</li><li>74</li><li>75</li><li>76</li></ul>

具體做的事情可以總結為:

1.替換ActivityThread的mInitialApplication為realApplication
2.替換mAllApplications 中所有的Application為realApplication
3.替換ActivityThread的mPackages,mResourcePackages中的mLoaderApk中的application為realApplication。

2.2 monkeyPatchExistingResources

替換所有當前app的mAssets為newAssetManager。

<code class="language-java hljs  has-numbering">
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">monkeyPatchExistingResources</span>(Context context,
            String externalResourceFile, Collection<Activity> activities) {
        <span class="hljs-keyword">if</span> (externalResourceFile == <span class="hljs-keyword">null</span>) {
            <span class="hljs-keyword">return</span>;
        }
        <span class="hljs-keyword">try</span> {
            AssetManager newAssetManager = (AssetManager) AssetManager.class
                    .getConstructor(<span class="hljs-keyword">new</span> Class[<span class="hljs-number">0</span>]).newInstance(<span class="hljs-keyword">new</span> Object[<span class="hljs-number">0</span>]);
            Method mAddAssetPath = AssetManager.class.getDeclaredMethod(
                    <span class="hljs-string">"addAssetPath"</span>, <span class="hljs-keyword">new</span> Class[] { String.class });
            mAddAssetPath.setAccessible(<span class="hljs-keyword">true</span>);
            <span class="hljs-keyword">if</span> (((Integer) mAddAssetPath.invoke(newAssetManager,
                    <span class="hljs-keyword">new</span> Object[] { externalResourceFile })).intValue() == <span class="hljs-number">0</span>) {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException(
                        <span class="hljs-string">"Could not create new AssetManager"</span>);
            }
            Method mEnsureStringBlocks = AssetManager.class.getDeclaredMethod(
                    <span class="hljs-string">"ensureStringBlocks"</span>, <span class="hljs-keyword">new</span> Class[<span class="hljs-number">0</span>]);
            mEnsureStringBlocks.setAccessible(<span class="hljs-keyword">true</span>);
            mEnsureStringBlocks.invoke(newAssetManager, <span class="hljs-keyword">new</span> Object[<span class="hljs-number">0</span>]);
            <span class="hljs-keyword">if</span> (activities != <span class="hljs-keyword">null</span>) {
                <span class="hljs-keyword">for</span> (Activity activity : activities) {
                    Resources resources = activity.getResources();
                    <span class="hljs-keyword">try</span> {
                        Field mAssets = Resources.class
                                .getDeclaredField(<span class="hljs-string">"mAssets"</span>);
                        mAssets.setAccessible(<span class="hljs-keyword">true</span>);
                        mAssets.set(resources, newAssetManager);
                    } <span class="hljs-keyword">catch</span> (Throwable ignore) {
                        Field mResourcesImpl = Resources.class
                                .getDeclaredField(<span class="hljs-string">"mResourcesImpl"</span>);
                        mResourcesImpl.setAccessible(<span class="hljs-keyword">true</span>);
                        Object resourceImpl = mResourcesImpl.get(resources);
                        Field implAssets = resourceImpl.getClass()
                                .getDeclaredField(<span class="hljs-string">"mAssets"</span>);
                        implAssets.setAccessible(<span class="hljs-keyword">true</span>);
                        implAssets.set(resourceImpl, newAssetManager);
                    }
                    Resources.Theme theme = activity.getTheme();
                    <span class="hljs-keyword">try</span> {
                        <span class="hljs-keyword">try</span> {
                            Field ma = Resources.Theme.class
                                    .getDeclaredField(<span class="hljs-string">"mAssets"</span>);
                            ma.setAccessible(<span class="hljs-keyword">true</span>);
                            ma.set(theme, newAssetManager);
                        } <span class="hljs-keyword">catch</span> (NoSuchFieldException ignore) {
                            Field themeField = Resources.Theme.class
                                    .getDeclaredField(<span class="hljs-string">"mThemeImpl"</span>);
                            themeField.setAccessible(<span class="hljs-keyword">true</span>);
                            Object impl = themeField.get(theme);
                            Field ma = impl.getClass().getDeclaredField(
                                    <span class="hljs-string">"mAssets"</span>);
                            ma.setAccessible(<span class="hljs-keyword">true</span>);
                            ma.set(impl, newAssetManager);
                        }
                        Field mt = ContextThemeWrapper.class
                                .getDeclaredField(<span class="hljs-string">"mTheme"</span>);
                        mt.setAccessible(<span class="hljs-keyword">true</span>);
                        mt.set(activity, <span class="hljs-keyword">null</span>);
                        Method mt