您好,欢迎来到源码搜藏!分享精神,快乐你我!提示:担心找不到本站?在百度搜索“源码搜藏”,网址永远不丢失!
  • 首 页
  • 在线工具
  • 当前位置:首页 > 安卓源码 > 技术博客 >

    从Instant run谈Android替换Application和动态加载机制

    时间:2016-12-19 10:32 来源:互联网 作者:源码搜藏 浏览:收藏 挑错 推荐 打印

    背景 Android studio 2.0 Stable 版本中集成了 Install run 即时编译技术,官方描述可以大幅加速编译速度,我们团队在第一时间更新并使用,总体用下来感觉,恩也就那样吧,还不如不用的快。所以就去看了下 Install run 的实现方式,其中有一个整体框架的基础

    背景

    Android studio 2.0 Stable版本中集成了Install run即时编译技术,官方描述可以大幅加速编译速度,我们团队在第一时间更新并使用,总体用下来感觉,恩…也就那样吧,还不如不用的快。所以就去看了下Install run的实现方式,其中有一个整体框架的基础,也就是今天的文章的主题,Android替换Application和动态加载机制。

    Instant run

    Instant run的大概实现原理可以看下这篇Instant Run 浅析,我们需要知道Instant run使用的gradle plugin2.0.0,源码在这里,文中大概讲了下Instant run的实现原理,但是并没有深入细节,特别是替换Application和动态加载机制。

    关于动态加载,实际上Instant run提供了两种动态加载的机制:
    1.修改java代码需要重启应用加载补丁dex,而在Application初始化时替换了Application,新建了一个自定义的ClassLoader去加载所有的dex文件。我们称为重启更新机制
    2.修改代码不需要重启,新建一个ClassLoader去加载修改部分。我们称为热更新机制

    Application入口

    在编译时Instant run用到了Transform API修改字节码文件。其中AndroidManifest.xml文件也被修改,如下:
    /app/build/intermediates/bundles/production/instant-run/AndroidManifest.xml,其中的Application标签

    1
    2
    3
    4
    
    <application
            name="com.aa.bb.MyApplication"
            android:name="com.android.tools.fd.runtime.BootstrapApplication"
    		... />
    

     

    多了一个com.android.tools.fd.runtime.BootstrapApplication,在刚刚提到的gradle plugin中的instant-run-server目录下找到该文件。

    实际上BootstrapApplication是我们app的实际入口,我们自己的ApplicationMyApplication采用反射机制调用。
    我们知道ApplicationContextWrapper的子类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
    // android.app.Application
    public class Application extends ContextWrapper {
        // ...
        public application() {
            super(null);
        }
        // ...
    }
    // android.content.ContextWrapper
    public class ContextWrapper extends Context {
        Context mBase;
        // ...
        public ContextWrapper(Context base) {
            mBase = base;
        }
        protected void attachBaseContext(Context base) {
            if (mBase != null) {
                throw new IllegalStateException("Base context already set");
            }
            mBase = base;
        }
        // ...
        @Override
        public AssetManager getAssets() {
            return mBase.getAssets();
        }
        @Override
        public Resources getResources()
        {
            return mBase.getResources();
        }
        // ...
    }
    

    ContextWrapper一方面继承了Context,一方面又包含(composite)了一个Context对象(称为mBase),对Context的实现为转发给mBase对象处理。上面的代码表示,在attachBaseContext方式调用之前Application是没有用的,因为mBase是空的。所以我们看下BootstrapApplicationattachBaseContext方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    protected void attachBaseContext(Context context) {
            if (!AppInfo.usingApkSplits) {
                createResources(apkModified);
                //新建一个ClassLoader并设置为原ClassLoader的parent
                setupClassLoaders(context, context.getCacheDir().getPath(), apkModified);
            }
    		//通过Manifest中我们的实际Application即MyApplication名反射生成对象
            createRealApplication();
    		//调用attachBaseContext完成初始化
            super.attachBaseContext(context);
    
            if (realApplication != null) {
            //反射调用实际Application的attachBaseContext方法
                try {
                    Method attachBaseContext =
                            ContextWrapper.class.getDeclaredMethod("attachBaseContext", Context.class);
                    attachBaseContext.setAccessible(true);
                    attachBaseContext.invoke(realApplication, context);
                } catch (Exception e) {
                    throw new IllegalStateException(e);
                }
            }
        }
    

    初始化ClassLoader

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    //BootstrapApplication.setupClassLoaders
    private static void setupClassLoaders(Context context, String codeCacheDir, long apkModified) {
    		// /data/data/package_name/files/instant-run/dex/目录下的dex列表
            List<String> dexList = FileManager.getDexList(context, apkModified);
                ClassLoader classLoader = BootstrapApplication.class.getClassLoader();
                String nativeLibraryPath = (String) classLoader.getClass().getMethod("getLdLibraryPath")
                                    .invoke(classLoader);
                IncrementalClassLoader.inject(
                        classLoader,
                        nativeLibraryPath,
                        codeCacheDir,
                        dexList);
            }
        }
        
    //IncrementalClassLoader.inject
    public static ClassLoader inject(
                ClassLoader classLoader, String nativeLibraryPath, String codeCacheDir,
                List<String> dexes) {
            //新建一个自定义ClassLoader,dexPath为参数中的dexList
            IncrementalClassLoader incrementalClassLoader =
                    new IncrementalClassLoader(classLoader, nativeLibraryPath, codeCacheDir, dexes);
            //设置为原ClassLoader的parent
            setParent(classLoader, incrementalClassLoader);
    		return incrementalClassLoader;
        }
    

    动态加载

    新建一个自定义的ClassLoader名为IncrementalClassLoader,该ClassLoader很简单,就是BaseDexClassLoader的一个子类,并且将IncrementalClassLoader设置为原ClassLoader的parent,熟悉JVM加载机制的同学应该都知道,由于ClassLoader采用双亲委托模式,即委托父类加载类,父类找不到再自己去找。这样IncrementalClassLoader就变成了整个App的所有类的加载的ClassLoader,并且dexPath是/data/data/package_name/files/instant-run/dex目录下的dex列表,这意味着什么呢?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    //``BaseDexClassLoader``的``findClass``
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        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;
    }
    

    可以看到,查找Class的任务通过pathList完成;这个pathList是一个DexPathList类的对象,它的findClass方法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    public Class findClass(String name, List<Throwable> suppressed) {
       for (Element element : dexElements) {
           DexFile dex = element.dexFile;
    
           if (dex != null) {
               Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
               if (clazz != null) {
                   return clazz;
               }
           }
       }
       if (dexElementsSuppressedExceptions != null) {
           suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
       }
       return null;
    }
    

    这个DexPathList内部有一个叫做dexElements的数组,然后findClass的时候会遍历这个数组来查找Class。看到了吗,这个dexElements就是从dexPath来的,也就说是IncrementalClassLoader用来加载dexPath(/data/data/package_name/files/instant-run/dex/)下面的dex文件。感兴趣的同学可以看下,我们app中的所有第三方库和自己项目中的代码,都被打包成若干个slice dex分片,该目录下有几十个dex文件。每当修改代码用Instant run完成编译,该目录下的dex文件就会有一个或者几个的更新时间发生改变。

    正常情况下,apk被安装之后,APK文件的代码以及资源会被系统存放在固定的目录(比如/data/app/package_name/base-1.apk )系统在进行类加载的时候,会自动去这一个或者几个特定的路径来寻找这个类。而使用Install run则完全不管之前的加载路径,所有的分片dex文件和资源都在dexPath下,用IncrementalClassLoader去加载。也就是加载不存在APK固定路径之外的类,即动态加载。

    但是仅仅有ClassLoader是不够的。因为每个被修改的类都被改了名字,类名在原名后面添加$override,目录在app/build/intermediates/transforms/instantRun/debug/folders/4000。AndroidManifest中并没有注册这些被改了名字的Activity。> 因此正常情况下系统无法加载我们插件中的类;因此也没有办法创建Activity的对象。

    解决这个问题有两个思路,要么全盘接管这个类加载的过程;要么告知系统我们使用的插件存在于哪里,让系统帮忙加载;这两种方式或多或少都需要干预这个类加载的过程。
    引用自 – Android 插件化原理解析——插件加载机制

    动态加载的两种方案

    先来看下系统如何完成类的加载过程。
    Activity的创建过程

    1
    2
    3
    4
    
    java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
    activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
    StrictMode.incrementExpectedActivityCount(activity.getClass());
    r.intent.setExtrasClassLoader(cl);
    

    通过ClassLoader和类名加载,反射调用生成Activity对象,其中的ClassLoaderLoadedApk的一个对象r.packageInfo中获得的。LoadedApk对象是APK文件在内存中的表示。 Apk文件的相关信息,诸如Apk文件的代码和资源,甚至代码里面的ActivityService等组件的信息我们都可以通过此对象获取。
    r.packageInfo的来源:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    
    private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
            ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
            boolean registerPackage) {
            // 获取userid信息
        final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
        synchronized (mResourcesManager) {
        // 尝试获取缓存信息
            WeakReference<LoadedApk> ref;
            if (differentUser) {
                // Caching not supported across users
                ref = null;
            } else if (includeCode) {
                ref = mPackages.get(aInfo.packageName);
            } else {
                ref = mResourcePackages.get(aInfo.packageName);
            }
    
            LoadedApk packageInfo = ref != null ? ref.get() : null;
            if (packageInfo == null || (packageInfo.mResources != null
                    && !packageInfo.mResources.getAssets().isUpToDate())) {
                    // 缓存没有命中,直接new
                packageInfo =
                    new LoadedApk(this, aInfo, compatInfo, baseLoader,
                            securityViolation, includeCode &&
                            (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
    
            // 省略。。更新缓存
            return packageInfo;
        }
    }
    

    重要的是这个缓存mPackageLoadedApk对象packageInfo就是从这个缓存中取的,所以我们只要在mPackage修改里面的ClassLoader控制类的加载就能完成动态加载。

    在《Android 插件化原理解析——插件加载机制》一文中,作者已经提出两种动态加载的解决方案:

    『激进方案』中我们自定义了插件的ClassLoader,并且绕开了Framework的检测;利用ActivityThread对于LoadedApk的缓存机制,我们把携带这个自定义的ClassLoader的插件信息添加进mPackages中,进而完成了类的加载过程。

    『保守方案』中我们深入探究了系统使用ClassLoader findClass的过程,发现应用程序使用的非系统类都是通过同一个PathClassLoader加载的;而这个类的最终父类BaseDexClassLoader通过DexPathList完成类的查找过程;我们hack了这个查找过程,从而完成了插件类的加载。

    激进方案由于是一个插件一个Classloader也叫多ClassLoader方案,代表作DroidPlugin;保守方案也叫做单ClassLoader方案,代表作,Small、众多热更新框架如nuwa等。

    Instant run的重启更新机制

    绕了一大圈,终于能接着往下看了。接上面,我们继续看BootstrapApplicationonCreate方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    public void onCreate() {
            MonkeyPatcher.monkeyPatchApplication(
                        BootstrapApplication.this, BootstrapApplication.this,
                        realApplication, externalResourcePath);
                MonkeyPatcher.monkeyPatchExistingResources(BootstrapApplication.this,
                        externalResourcePath, null);
            super.onCreate();
            ...
    		//手机客户端app和Android Studio建立Socket通信,AS是客户端发消息,app		//是服务端接收消息作出相应操作。Instant run的通信方式。不在本文范围内
            Server.create(AppInfo.applicationId, BootstrapApplication.this);
    
            if (realApplication != null) {
            	//还记得这个realApplication吗,我们app中实际的Application
                realApplication.onCreate();
            }
        }
    

    上面代码,手机客户端app和Android Studio建立Socket通信,AS是客户端发消息,app是服务端接收消息作出相应操作,这是Instant run的通信方式,不在本文范围内。然后反射调用实际ApplicationonCreate方法。
    那么前面的两个MonkeyPatcher的方法是干嘛的呢

    先看MonkeyPatcher.monkeyPatchApplication

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    
    public static void monkeyPatchApplication(@Nullable Context context,
                                                  @Nullable Application bootstrap,
                                                  @Nullable Application realApplication,
                                                  @Nullable String externalResourceFile) {
            try {
                // Find the ActivityThread instance for the current thread
                Class<?> activityThread = Class.forName("android.app.ActivityThread");
                Object currentActivityThread = getActivityThread(context, activityThread);
    
                // Find the mInitialApplication field of the ActivityThread to the real application
                Field mInitialApplication = activityThread.getDeclaredField("mInitialApplication");
                mInitialApplication.setAccessible(true);
                Application initialApplication = (Application) mInitialApplication.get(currentActivityThread);
                if (realApplication != null && initialApplication == bootstrap) {
                //**2.替换掉ActivityThread.mInitialApplication**
                    mInitialApplication.set(currentActivityThread, realApplication);
                }
    
                // Replace all instance of the stub application in ActivityThread#mAllApplications with the
                // real one
                if (realApplication != null) {
                    Field mAllApplications = activityThread.getDeclaredField("mAllApplications");
                    mAllApplications.setAccessible(true);
                    List<Application> allApplications = (List<Application>) mAllApplications
                            .get(currentActivityThread);
                    for (int i = 0; i < allApplications.size(); i++) {
                        if (allApplications.get(i) == bootstrap) {
                        //**1.替换掉ActivityThread.mAllApplications**
                            allApplications.set(i, realApplication);
                        }
                    }
                }
    
                // Figure out how loaded APKs are stored.
    
                // API version 8 has PackageInfo, 10 has LoadedApk. 9, I don't know.
                Class<?> loadedApkClass;
                try {
                    loadedApkClass = Class.forName("android.app.LoadedApk");
                } catch (ClassNotFoundException e) {
                    loadedApkClass = Class.forName("android.app.ActivityThread$PackageInfo");
                }
                Field mApplication = loadedApkClass.getDeclaredField("mApplication");
                mApplication.setAccessible(true);
                Field mResDir = loadedApkClass.getDeclaredField("mResDir");
                mResDir.setAccessible(true);
    
                // 10 doesn't have this field, 14 does. Fortunately, there are not many Honeycomb devices
                // floating around.
                Field mLoadedApk = null;
                try {
                    mLoadedApk = Application.class.getDeclaredField("mLoadedApk");
                } catch (NoSuchFieldException e) {
                    // According to testing, it's okay to ignore this.
                }
    
                // Enumerate all LoadedApk (or PackageInfo) fields in ActivityThread#mPackages and
                // ActivityThread#mResourcePackages and do two things:
                //   - Replace the Application instance in its mApplication field with the real one
                //   - Replace mResDir to point to the external resource file instead of the .apk. This is
                //     used as the asset path for new Resources objects.
                //   - Set Application#mLoadedApk to the found LoadedApk instance
                for (String fieldName : new String[]{"mPackages", "mResourcePackages"}) {
                    Field field = activityThread.getDeclaredField(fieldName);
                    field.setAccessible(true);
                    Object value = field.get(currentActivityThread);
    
                    for (Map.Entry<String, WeakReference<?>> entry :
                            ((Map<String, WeakReference<?>>) value).entrySet()) {
                        Object loadedApk = entry.getValue().get();
                        if (loadedApk == null) {
                            continue;
                        }
    
                        if (mApplication.get(loadedApk) == bootstrap) {
                            if (realApplication != null) {
                            //**3.替换掉mApplication**
                                mApplication.set(loadedApk, realApplication);
                            }
                            if (externalResourceFile != null) {
                            //替换掉资源目录
                                mResDir.set(loadedApk, externalResourceFile);
                            }
    
                            if (realApplication != null && mLoadedApk != null) {
                            //**4.替换掉mLoadedApk**
                                mLoadedApk.set(realApplication, loadedApk);
                            }
                        }
                    }
                }
            } catch (Throwable e) {
                throw new IllegalStateException(e);
            }
        }
    

    这里做了三件事情:

    1.替换Application对象

    BootstrapApplication的作用就是加载realApplication也就是MyApplication,所以我们就要把所有Framework层的BootstrapApplication对象替换为MyApplication对象。包括:

    1
    2
    3
    
    baseContext.mPackageInfo.mApplication 代码3
    baseContext.mPackageInfo.mActivityThread.mInitialApplication 代码2
    baseContext.mPackageInfo.mActivityThread.mAllApplications 代码1
    

    2.替换资源相关对象mResDir,前面我们已经说过,正常情况下寻找资源都是在/data/app/package_name/base-1.apk目录下,而Instant run将资源也抽出来放在/data/data/package_name/files/instant-run/,加载目录也更改为后者

    3.替换mLoadedApk对象
    还记得前面的讲的LoadedApk吗,这里面有加载类的ClassLoader,由于BootstrapApplicationattachBaseContext方法中就将其已经替换为了IncrementalClassLoader,所以代码4处反射将BootstrapApplicationmLoadedApk赋值给了MyApplication,那么接下来MyApplication的所有类的加载都将由IncrementalClassLoader来负责。

    MonkeyPatcher.monkeyPatchExistingResources更新资源补丁,不在本文范围内就不讲了。

    这些工作做完之后调用MyApplicationonCreate方法BootstrapApplication就将控制权交给了MyApplication,这样在整个运行环境中,MyApplication就是正牌Application了,完成Application的替换。

    总结一下,刚才我们说了已经有两个动态加载的方案,激进方案和保守方案,而Instant run的重启更新机制更像后者–保守方案即单ClassLoader方案,首先,该种方案只有一个ClassLoader,只不过是通过替换Application达到的替换mLoadedApk进而替换ClassLoader的目的,并没有涉及到缓存mPackage然后dexList也是它自己维护的。

    Instant run 热更新机制

    Instant run哪里用到的热更新机制呢?还记得刚才我们提到的Socket通信吗,其中S端也就是手机客户端,接收到热更新的消息会执行下面的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    private int handleHotSwapPatch(int updateMode, @NonNull ApplicationPatch patch) {
           try {
               String dexFile = FileManager.writeTempDexFile(patch.getBytes());
               String nativeLibraryPath = FileManager.getNativeLibraryFolder().getPath();
               //新建一个ClassLoader,dexFile是刚更新的插件
               DexClassLoader dexClassLoader = new DexClassLoader(dexFile,
                       mApplication.getCacheDir().getPath(), nativeLibraryPath,
                       getClass().getClassLoader());
    
               // we should transform this process with an interface/impl
               Class<?> aClass = Class.forName(
                       "com.android.tools.fd.runtime.AppPatchesLoaderImpl", true, dexClassLoader);
               try {
                   PatchesLoader loader = (PatchesLoader) aClass.newInstance();
                   String[] getPatchedClasses = (String[]) aClass
                           .getDeclaredMethod("getPatchedClasses").invoke(loader);
                   //loader是PatchesLoader的一个实例,调用load方法加载插件
                   if (!loader.load()) {
                       updateMode = UPDATE_MODE_COLD_SWAP;
                   }
               } catch (Exception e) {
                   updateMode = UPDATE_MODE_COLD_SWAP;
               }
           } catch (Throwable e) {
               updateMode = UPDATE_MODE_COLD_SWAP;
           }
           return updateMode;
       }
    

    可以看到根据单个dexFile新建了一个ClassLoader,然后调用loader.load()方法,loaderPatchesLoader接口的实例,PatchesLoader接口的一个实现类AppPatchesLoaderImpl,该类中记录了哪些修改的类。看一下load方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    
    @Override
        public boolean load() {
            try {
            //遍历已记录的所有修改的类
                for (String className : getPatchedClasses()) {
                    ClassLoader cl = getClass().getClassLoader();
                    //我们刚才说的修改的类名后面都有$override
                    Class<?> aClass = cl.loadClass(className + "$override");
                    Object o = aClass.newInstance();
                    //1.**反射修改原类中的$change字段为修改后的值**
                    Class<?> originalClass = cl.loadClass(className);
                    Field changeField = originalClass.getDeclaredField("$change");
                    // force the field accessibility as the class might not be "visible"
                    // from this package.
                    changeField.setAccessible(true);
                    // If there was a previous change set, mark it as obsolete:
                    Object previous = changeField.get(null);
                    if (previous != null) {
                        Field isObsolete = previous.getClass().getDeclaredField("$obsolete");
                        if (isObsolete != null) {
                            isObsolete.set(null, true);
                        }
                    }
                    changeField.set(null, o);
                }
            } catch (Exception e) {
                return false;
            }
            return true;
        }
    

    Instant run的热更新原理可以概述为:
    1.第一次运行,应用transform API修改字节码。
    输出目录在app/build/intermediates/transforms/instantRun/debug/folders/1/,给所有的类添加$change字段,$changeIncrementalChange类型,IncrementalChange是个接口。如果$change不为空,去调用$changeaccess$dispatch方法,参数为方法签名字符串和方法参数数组,否则调用原逻辑。
    load方法中会去加载全部补丁类,并赋值给对应原类的$change
    这也验证了我们说它是多ClassLoader方案。

    2.所有修改的类有gradle plugin自动生成,类名在原名后面添加$override,复制修改后类的大部分方法,实现IncrementalChange 接口的access$dispatch方法,该方法会根据传递过来的方法签名,调用本类的同名方法。

    那么也就是说只要把原类的$change字段设置为该类,那就会调用该类的access$dispatch方法,就会使用修改后的方法了。上面代码1处就通过反射修改了原类中的$change为修改后补丁类中的值。AppPatchesLoaderImpl记录了所有被修改的类,也会被打进补丁dex。

    总结一下,可以看到Instant run热更新是多ClassLoader加载方案,每个插件dex都有一个ClassLoader,如果插件需要升级,直接重新创建一个自定的ClassLoader加载新的插件。但是目前来看,Instant run修改java代码大部分情况下都是重启更新机制,可能热更新机制还有bug。资源更新是热更新,重启对应Activity就可以。

    总结

    Instant run看下来真的有好多东西,其中就以替换Application和动态加载尤为重要,关于动态加载,完全可以根据Instant run的实现方式完成一个热修复和重启修复相结合的更新框架,用于线上bug的修复和功能更新,并且可以支持资源文件的更新,是无侵入性的更新框架,最重要的一点,这是官方支持的。但是,性能肯定会有所影响,实际开发中使用Instant run编译其实还有很多的问题,而且app初始化时使用的很多反射,这也直接导致app的启动速度降低好多。

    另外一点关于Application的替换是基于bazel(一种构建工具,类似于burk)中的StubApplication

    从Instant run谈Android替换Application和动态加载机制转载http://www.codesocang.com/anzhuoyuanma/boke/34086.html
    标签:网站源码