精华内容
下载资源
问答
  • 插件化篇 - 插件化技术实现原理
    千次阅读
    2019-04-10 17:54:06

    插件化技术最初源于免安装运行 apk 的想法,这个免安装的 apk 可以理解为插件。支持插件化的 app 可以在运行时加载和运行插件,这样便可以将 app 中一些不常用的功能模块做成插件,一方面减小了安装包的大小,另一方面可以实现 app 功能的动态扩展。想要实现插件化,主要是解决下面三个问题:

    • 插件中代码的加载和与主工程的互相调用。
    • 插件中资源的加载和与主工程的互相访问。
    • 四大组件生命周期的管理。

     

     

    1. 插件化发展

    第一代:dynamic-load-apk 最早使用 ProxyActivity 这种静态代理技术,由 ProxyActivity 去控制插件中 PluginActivity 的生命周期。该种方式缺点明显,插件中的 activity 必须继承 PluginActivity,开发时要小心处理 context。而 DroidPlugin 通过 Hook 系统服务的方式启动插件中的 Activity,使得开发插件的过程和开发普通的 app 没有什么区别,但是由于 hook 过多系统服务,异常复杂且不够稳定。

    第二代:为了同时达到插件开发的低侵入性 (像开发普通 app 一样开发插件) 和框架的稳定性,在实现原理上都是趋近于选择尽量少的 hook,并通过在 manifest 中预埋一些组件实现对四大组件的插件化。另外各个框架根据其设计思想都做了不同程度的扩展,其中 Small 更是做成了一个跨平台,组件化的开发框架。

    第三代:VirtualApp 比较厉害,能够完全模拟 app 的运行环境,能够实现 app 的免安装运行和双开技术。Atlas 是阿里今年开源出来的一个结合组件化和热修复技术的一个 app 基础框架,其广泛的应用与阿里系的各个 app,其号称是一个容器化框架。

     

     

    2. 基本原理

     

    • 2.1 类加载

    外部 apk 中类的加载:

    Android 中常用的有两种类加载器,DexClassLoader 和 PathClassLoader,它们都继承于BaseDexClassLoader。

        public class DexClassLoader extends BaseDexClassLoader {
    
            public DexClassLoader(String dexPath, String optimizedDirectory,
                String libraryPath, ClassLoader parent) {
                super(dexPath, new File(optimizedDirectory), libraryPath, parent);
            }
        }
    
        public class PathClassLoader extends BaseDexClassLoader {
    
            public PathClassLoader(String dexPath, ClassLoader parent) {
                super(dexPath, null, null, parent);
            }
    
            public PathClassLoader(String dexPath, String libraryPath,
                ClassLoader parent) {
                super(dexPath, null, libraryPath, parent);
            }
        }

    区别在于调用父类构造器时,DexClassLoader 多传了一个 optimizedDirectory 参数,这个目录必须是内部存储路径,用来缓存系统创建的 Dex 文件。而 PathClassLoader 该参数为 null,只能加载内部存储目录的 Dex 文件。所以我们可以用DexClassLoader 去加载外部的 apk,用法如下:

    /**
         * 第一个参数为 apk 的文件目录
         * 第二个参数为内部存储目录
         * 第三个为库文件的存储目录
         * 第四个参数为父加载器
         */
        new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent)

    双亲委托机制:

    ClassLoader 调用 loadClass 方法加载类:

    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { 
           //首先从已经加载的类中查找
            Class<?> clazz = findLoadedClass(className);    
        if (clazz == null) {
                ClassNotFoundException suppressed = null;     
               try {   
                    //如果没有加载过,先调用父加载器的loadClass
                    clazz = parent.loadClass(className, false);
                } catch (ClassNotFoundException e) {
                    suppressed = e;
                }      
            if (clazz == null) {        
                    try {           
           
                      //父加载器都没有加载,则尝试加载
                        clazz = findClass(className);
                    } catch (ClassNotFoundException e) {
                        e.addSuppressed(suppressed);       
                         throw e;
                    }
                }
            }    
                return clazz;
        }

    可以看出 ClassLoader 加载类时,先查看自身是否已经加载过该类,如果没有加载过会首先让父加载器去加载,如果父加载器无法加载该类时才会调用自身的 findClass 方法加载,该机制很大程度上避免了类的重复加载。

    DexClassLoader 的 DexPathList:

    DexClassLoader 重载了 findClass 方法,在加载类时会调用其内部的 DexPathList 去加载。DexPathList 是在构造DexClassLoader 时生成的,其内部包含了 DexFile。DexPathList 的 loadClass 会去遍历 DexFile 直到找到需要加载的类:

        public Class findClass(String name, List<Throwable> suppressed) { 
           //循环dexElements,调用DexFile.loadClassBinaryName加载class
            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;
        }

    有一种热修复技术正是利用了 DexClassLoader 的加载机制,将需要替换的类添加到 dexElements 的前面,这样系统会使用先找到的修复过的类。 

     

    • 2.2 单 DexClassLoader 与多 DexClassLoader

    通过给插件 apk 生成相应的 DexClassLoader 便可以访问其中的类,这边又有两种处理方式,有单 DexClassLoader 和多DexClassLoader 两种结构。

    多 DexClassLoader:

    对于每个插件都会生成一个 DexClassLoader,当加载该插件中的类时需要通过对应 DexClassLoader 加载。这样不同插件的类是隔离的,当不同插件引用了同一个类库的不同版本时,不会出问题。RePlugin 采用的是该方案。

    单 DexClassLoader:

    将插件的 DexClassLoader 中的 pathList 合并到主工程的 DexClassLoader 中。这样做的好处时,可以在不同的插件以及主工程间直接互相调用类和方法,并且可以将不同插件的公共模块抽出来放在一个 common 插件中直接供其他插件使用。Small 采用的是这种方式。

    互相调用

    插件调用主工程:

    • 在构造插件的 ClassLoader 时会传入主工程的 ClassLoader 作为父加载器,所以插件是可以直接可以通过类名引用主工程的类。

    主工程调用插件:

    • 若使用多 ClassLoader 机制,主工程引用插件中类需要先通过插件的 ClassLoader 加载该类再通过反射调用其方法。插件化框架一般会通过统一的入口去管理对各个插件中类的访问,并且做一定的限制。
    • 若使用单 ClassLoader 机制,主工程则可以直接通过类名去访问插件中的类。该方式有个弊病,若两个不同的插件工程引用了一个库的不同版本,则程序可能会出错,所以要通过一些规范去避免该情况发生。

     

    • 2.3 资源加载

    Android 系统通过 Resource 对象加载资源,下面代码展示了该对象的生成过程:

    //创建AssetManager对象 
    AssetManager assets = new AssetManager();
     //将apk路径添加到AssetManager中
      if (assets.addAssetPath(resDir) == 0){              
        return null;  
    }
     //创建Resource对象
    
    r = new Resources(assets, metrics, getConfiguration(), compInfo);

    因此,只要将插件 apk 的路径加入到 AssetManager 中,便能够实现对插件资源的访问。具体实现时,由于 AssetManager 并不是一个 public 的类,需要通过反射去创建,并且部分 Rom 对创建的 Resource 类进行了修改,所以需要考虑不同 Rom 的兼容性。

    资源路径的处理:

    和代码加载相似,插件和主工程的资源关系也有两种处理方式。

    • 合并式:addAssetPath 时加入所有插件和主工程的路径。
    • 独立式:各个插件只添加自己 apk 路径。

    合并式由于 AssetManager 中加入了所有插件和主工程的路径,因此生成的 Resource 可以同时访问插件和主工程的资源。但是由于主工程和各个插件都是独立编译的,生成的资源 id 会存在相同的情况,在访问时会产生资源冲突。

    独立式时,各个插件的资源是互相隔离的,不过如果想要实现资源的共享,必须拿到对应的 Resource 对象。

    Context 的处理:

    通常我们通过 Context 对象访问资源,光创建出 Resource 对象还不够,因此还需要一些额外的工作。 对资源访问的不同实现方式也需要不同的额外工作。以 VirtualAPK 的处理方式为例:

    第一步:创建 Resource

    if (Constants.COMBINE_RESOURCES) {
        //插件和主工程资源合并时需要hook住主工程的资源
        Resources resources = ResourcesManager.createResources(context, apk.getAbsolutePath());
        ResourcesManager.hookResources(context, resources);  
          return resources;
    } else {  
          //插件资源独立,该resource只能访问插件自己的资源
        Resources hostResources = context.getResources();
        AssetManager assetManager = createAssetManager(context, apk);  
            return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
    }

    第二步:hook 主工程的 Resource 

    对于合并式的资源访问方式,需要替换主工程的 Resource,下面是具体替换的代码。

    public static void hookResources(Context base, Resources resources) { 
       try {
                ReflectUtil.setField(base.getClass(), base, "mResources", resources);
                Object loadedApk = ReflectUtil.getPackageInfo(base);
                ReflectUtil.setField(loadedApk.getClass(), loadedApk, "mResources", resources);
    
                Object activityThread = ReflectUtil.getActivityThread(base);
                Object resManager = ReflectUtil.getField(activityThread.getClass(), activityThread, "mResourcesManager");       
         if (Build.VERSION.SDK_INT < 24) {
                    Map<Object, WeakReference<Resources>> map = (Map<Object, WeakReference<Resources>>) ReflectUtil.getField(resManager.getClass(), resManager, "mActiveResources");
                    Object key = map.keySet().iterator().next();
                    map.put(key, new WeakReference<>(resources));
                } else {                // still hook Android N Resources, even though it's unnecessary, then nobody will be strange.
                    Map map = (Map) ReflectUtil.getFieldNoException(resManager.getClass(), resManager, "mResourceImpls");
                    Object key = map.keySet().iterator().next();
                    Object resourcesImpl = ReflectUtil.getFieldNoException(Resources.class, resources, "mResourcesImpl");
                    map.put(key, new WeakReference<>(resourcesImpl));
                }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    注意下上述代码 hook 了几个地方,包括以下几个 hook 点:

    • 替换了主工程 context 中 LoadedApk 的 mResource 对象。
    • 将新的 Resource 添加到主工程 ActivityThread 的 mResourceManager 中,并且根据 Android 版本做了不同处理。

    第三步:关联 resource 和 Activity

    Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
    activity.setIntent(intent);
    //设置Activity的mResources属性,Activity中访问资源时都通过mResources
    
    ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", plugin.getResources());

    上述代码是在 Activity 创建时被调用的 (后面会介绍如何 hook Activity 的创建过程),在 activity 被构造出来后,需要替换其中的mResources 为插件的 Resource。由于独立式时主工程的 Resource 不能访问插件的资源,所以如果不做替换,会产生资源访问错误。

    做完以上工作后,则可以在插件的 Activity 中放心的使用 setContentView,inflater 等方法加载布局了。

    资源冲突

    合并式的资源处理方式,会引入资源冲突,原因在于不同插件中的资源 id 可能相同,所以解决方法就是使得不同的插件资源拥有不同的资源 id。资源 id 是由 8 位 16 进制数表示,表示为 0xPPTTNNNN。PP 段用来区分包空间,默认只区分了应用资源和系统资源,TT 段为资源类型,NNNN 段在同一个 APK 中从 0000 递增。

     

    所以思路是修改资源 id 的 PP 段,对于不同的插件使用不同的 PP 段,从而区分不同插件的资源。具体实现方式有两种

    • 修改 aapt 源码,编译期修改 PP 段。
    • 修改 resources.arsc 文件,该文件列出了资源 id 到具体资源路径的映射。

    具体实现可以分别参考 Atlas 框架和 Small 框架。推荐第二种方式,不用入侵原有的编译流程。

     

     

    3. 四大组件支持

    Android 开发中有一些特殊的类,是由系统创建的,并且由系统管理生命周期。如常用的四大组件,Activity,Service,BroadcastReceiver 和 ContentProvider。 仅仅构造出这些类的实例是没用的,还需要管理组件的生命周期。其中以 Activity 最为复杂,不同框架采用的方法也不尽相同。下面以 Activity 为例详细介绍插件化如何支持组件生命周期的管理,大致分为两种方式:

    • ProxyActivity 代理。
    • 预埋 StubActivity,hook 系统启动 Activity 的过程。

     

    • 3.1 ProxyActivity 代理

    ProxyActivity 代理的方式最早是由 dynamic-load-apk 提出的,其思想很简单,在主工程中放一个 ProxyActivy,启动插件中的Activity 时会先启动 ProxyActivity,在 ProxyActivity 中创建插件 Activity,并同步生命周期。

    1. 首先需要通过统一的入口 (如 PluginManager) 启动插件 Activity,其内部会将启动的插件 Activity 信息保存下来,并将intent 替换为启动 ProxyActivity 的 intent。
    2. ProxyActivity 根据插件的信息拿到该插件的 ClassLoader 和 Resource,通过反射创建 PluginActivity 并调用其 onCreate 方法。
    3. PluginActivty 调用的 setContentView 被重写了,会去调用 ProxyActivty 的 setContentView。由于 ProxyActivity 重写了getResource 返回的是插件的 Resource,所以 setContentView 能够访问到插件中的资源。同样 findViewById 也是调用ProxyActivity 的。
    4. ProxyActivity 中的其他生命周期回调函数中调用相应 PluginActivity 的生命周期。

    代理方式的关键总结起来有下面两点:

    1. ProxyActivity 中需要重写 getResouces,getAssets,getClassLoader 方法返回插件的相应对象。生命周期函数以及和用户交互相关函数,如 onResume,onStop,onBackPressedon,KeyUponWindow,FocusChanged 等需要转发给插件。
    2. PluginActivity 中所有调用 context 的相关的方法,如 setContentView,getLayoutInflater,getSystemService 等都需要调用 ProxyActivity 的相应方法。

    该方式虽然能够很好的实现启动插件 Activity 的目的,但是由于开发式侵入性很强,dynamic-load-apk 之后的插件化方案很少继续使用该方式,而是通过 hook 系统启动 Activity 的过程,让启动插件中的 Activity 像启动主工程的 Activity 一样简单。

     

    • 3.2 hook 方式
    1. Activity1 调用 startActivity,实际会调用 Instrumentation 类的 execStartActivity 方法,Instrumentation 是系统用来监控Activity 运行的一个类,Activity 的整个生命周期都有它的影子。
    2. 通过跨进程的 binder 调用,进入到 ActivityManagerService 中,其内部会处理 Activity 栈。之后又通过跨进程调用进入到Activity2 所在的进程中。
    3. ApplicationThread 是一个 binder 对象,其运行在 binder 线程池中,内部包含一个 H 类,该类继承于类 Handler。ApplicationThread 将启动 Activity2 的信息通过 H 对象发送给主线程。
    4. 主线程拿到 Activity2 的信息后,调用 Instrumentation 类的 newActivity 方法,其内通过 ClassLoader 创建 Activity2 实例。

    下面介绍如何通过 hook 的方式启动插件中的 Activity,需要解决以下两个问题:

    • 插件中的 Activity 没有在 AndroidManifest 中注册,如何绕过检测。
    • 如何构造 Activity 实例,同步生命周期。

    解决方法有很多种,以 VirtualAPK 为例,核心思路如下:

    • 先在 Manifest 中预埋 StubActivity,启动时 hook 住,将 Intent 替换成 StubActivity。
    • 通过插件的 ClassLoader 反射创建插件 Activity。
    • 之后 Activity 的所有生命周期回调都会通知给插件 Activity。

    下面具体分析整个过程涉及到的代码:

    替换系统 Instrumentation

    VirtualAPK 在初始化时会调用 hookInstrumentationAndHandler,该方法 hook 了系统的 Instrumentaiton 类,由上文可知该类和Activity 的启动息息相关。

    private void hookInstrumentationAndHandler() { 
       try {  
             //获取Instrumentation对象
            Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation(this.mContext);   
                  //构造自定义的VAInstrumentation
            final VAInstrumentation instrumentation = new VAInstrumentation(this, baseInstrumentation); 
                         //设置ActivityThread的mInstrumentation和mCallBack
            Object activityThread = ReflectUtil.getActivityThread(this.mContext);
            ReflectUtil.setInstrumentation(activityThread, instrumentation);
            ReflectUtil.setHandlerCallback(this.mContext, instrumentation); 
             this.mInstrumentation = instrumentation;
        } catch (Exception e) {
            e.printStackTrace();
        }
     }

    该段代码将主线程中的 Instrumentation 对象替换成了自定义的 VAInstrumentation 类。在启动和创建插件 activity 时,该类都会偷偷做一些手脚。

    hook activity 启动过程

    VAInstrumentation 类重写了 execStartActivity 方法:

    public ActivityResult execStartActivity(
        Intent intent) {
    //转换隐式intent
        mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent); 
       if (intent.getComponent() != null) {  
          //替换intent中启动Activity为StubActivity
            this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
        }        
    
        //调用父类启动Activity的方法
    public void markIntentIfNeeded(Intent intent) { 
       if (intent.getComponent() == null) {  
             return;
        }
    
        String targetPackageName = intent.getComponent().getPackageName();
        String targetClassName = intent.getComponent().getClassName();    // search map and return specific launchmode stub activity
        if (!targetPackageName.equals(mContext.getPackageName()) && mPluginManager.getLoadedPlugin(targetPackageName) != null) {
            intent.putExtra(Constants.KEY_IS_PLUGIN, true);
            intent.putExtra(Constants.KEY_TARGET_PACKAGE, targetPackageName);
            intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName);
            dispatchStubActivity(intent);
        }
    }

    execStartActivity 中会先去处理隐式 intent,如果该隐式 intent 匹配到了插件中的 Activity,将其转换成显式。之后通过markIntentIfNeeded 将待启动的的插件 Activity 替换成了预先在 AndroidManifest 中占坑的 StubActivity,并将插件 Activity 的信息保存到该 intent 中。其中有个 dispatchStubActivity 函数,会根据 Activity 的 launchMode 选择具体启动哪个 StubActivity。VirtualAPK 为了支持 Activity 的 launchMode 在主工程的 AndroidManifest 中对于每种启动模式的 Activity 都预埋了多个坑位。

    hook Activity 的创建过程

    上一步欺骗了系统,让系统以为自己启动的是一个正常的 Activity。当构建 Activity 时,再将插件的 Activity 换回来,此时调用的是 VAInstrumentation 类的 newActivity 方法。

    @Override
    public Activity newActivity(ClassLoader cl, String className, Intent intent){
        try {
            cl.loadClass(className);
        } catch (ClassNotFoundException e) {
            //通过LoadedPlugin可以获取插件的ClassLoader和Resource
            LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);
                    //获取插件的主Activity
            String targetClassName = PluginUtil.getTargetActivity(intent);
                    if (targetClassName != null) { 
                       //传入插件的ClassLoader构造插件Activity
                Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
                activity.setIntent(intent);
                        //设置插件的Resource,从而可以支持插件中资源的访问
                try {
                    ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", plugin.getResources());
                } catch (Exception ignored) { 
                                       // ignored.
                }  
              return activity;
            }
        }    return mBase.newActivity(cl, className, intent);
    }

    由于 AndroidManifest 中预埋的 StubActivity 并没有具体的实现类,所以此时会发生 ClassNotFoundException。之后在处理异常时取出插件 Activity 的信息,通过插件的 ClassLoader 反射构造插件的 Activity。 

    一些额外操作

    插件 Activity 构造出来后,为了能够保证其正常运行还要做些额外的工作,VAInstrumentation 类做了一些处理:

    @Override
    public void callActivityOnCreate(Activity activity, Bundle icicle) { 
       final Intent intent = activity.getIntent();
           if (PluginUtil.isIntentFromPlugin(intent)) {
            Context base = activity.getBaseContext();
                   try {
                LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);
                ReflectUtil.setField(base.getClass(), base, "mResources", plugin.getResources());
                ReflectUtil.setField(ContextWrapper.class, activity, "mBase", plugin.getPluginContext());
                ReflectUtil.setField(Activity.class, activity, "mApplication", plugin.getApplication());
                ReflectUtil.setFieldNoException(ContextThemeWrapper.class, activity, "mBase", plugin.getPluginContext());
                   // set screenOrientation
                ActivityInfo activityInfo = plugin.getActivityInfo(PluginUtil.getComponent(intent)); 
                  if (activityInfo.screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
                    activity.setRequestedOrientation(activityInfo.screenOrientation);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
        mBase.callActivityOnCreate(activity, icicle);
    }

    这段代码主要是将 Activity 中的 Resource,Context 等对象替换成了插件的相应对象,保证插件 Activity 在调用涉及到 Context的方法时能够正确运行。

    经过上述步骤后,便实现了插件 Activity 的启动,并且该插件 Activity 中并不需要什么额外的处理,和常规的 Activity 一样。那问题来了,之后的 onResume,onStop 等生命周期怎么办呢?答案是所有和 Activity 相关的生命周期函数,系统都会调用插件中的 Activity。原因在于 AMS 在处理 Activity 时,通过一个 token 表示具体 Activity 对象,而这个 token 正是和启动 Activity 时创建的对象对应的,而这个 Activity 被我们替换成了插件中的 Activity,所以之后 AMS 的所有调用都会传给插件中的 Activity。

    小结

    VirtualAPK 通过替换了系统的 Instrumentation,hook 了 Activity 的启动和创建,省去了手动管理插件 Activity 生命周期的繁琐,让插件 Activity 像正常的 Activity 一样被系统管理,并且插件 Activity 在开发时和常规一样,即能独立运行又能作为插件被主工程调用。

    其他插件框架在处理 Activity 时思想大都差不多,无非是这两种方式之一或者两者的结合。在 hook 时,不同的框架可能会选择不同的 hook 点。如 360 的 RePlugin 框架选择 hook 了系统的 ClassLoader,在判断出待启动的 Activity 是插件中的时,会调用插件的 ClassLoader 构造相应对象。另外 RePlugin 为了系统稳定性,选择了尽量少的 hook,因此它并没有选择 hook 系统的startActivity 方法来替换 intent,而是通过重写 Activity 的 startActivity,因此其插件 Activity 是需要继承一个类似 PluginActivity的基类的。不过 RePlugin 提供了一个 Gradle 插件将插件中的 Activity 的基类换成了 PluginActivity,用户在开发插件 Activity 时也是没有感知的。

     

    • 3.3 其他组件

    四大组件中 Activity 的支持是最复杂的,其他组件的实现原理要简单很多,简要概括如下:

    • Service:Service 和 Activity 的差别在于,Activity 的生命周期是由用户交互决定的,而 Service 的生命周期是我们通过代码主动调用的,且 Service 实例和 manifest 中注册的是一一对应的。实现 Service 插件化的思路是通过在 manifest 中预埋StubService,hook 系统 startService 等调用替换启动的 Service,之后在 StubService 中创建插件 Service,并手动管理其生命周期。
    • BroadCastReceiver:解析插件的 manifest,将静态注册的广播转为动态注册。
    • ContentProvider:类似于 Service 的方式,对插件 ContentProvider 的所有调用都会通过一个在 manifest 中占坑的ContentProvider 分发。

     

     

     

     

    更多相关内容
  • 插件化开发-动态加载

    热门讨论 2015-07-29 14:14:41
    插件化开发-动态加载已安装apk和未安装的apk
  • 【Android 插件化插件化简介 ( 组件化与插件化 )

    千次阅读 多人点赞 2021-05-29 21:27:36
    一、组件化与插件化、 二、插件化示例、 三、插件化标准引入、

    Android 插件化系列文章目录

    【Android 插件化】插件化简介 ( 组件化与插件化 )
    【Android 插件化】插件化原理 ( JVM 内存数据 | 类加载流程 )
    【Android 插件化】插件化原理 ( 类加载器 )
    【Android 插件化】“ 插桩式 “ 插件化框架 ( 原理与实现思路 )
    【Android 插件化】“ 插桩式 “ 插件化框架 ( 类加载器创建 | 资源加载 )
    【Android 插件化】“ 插桩式 “ 插件化框架 ( 注入上下文的使用 )
    【Android 插件化】“ 插桩式 “ 插件化框架 ( 获取插件入口 Activity 组件 | 加载插件 Resources 资源 )
    【Android 插件化】“ 插桩式 “ 插件化框架 ( 运行应用 | 代码整理 )






    一、组件化与插件化



    组件化 是将应用分成若干 Module 模块 , 每个模块称为一个组件 ;

    组件化 项目中 , 分为两种模式 , " 集成模式 " " 组件模式 " ;

    在开发过程中的 " 组件模式 " 下这些组件可以 独立运行 , 在 " 集成模式 " 下 , 这些组件 相互依赖拼装成一个 APK 安装包 ;


    组件化开发的弊端 :

    多个模块必须是并发开发 , 模块之间相互依赖 , 如果修改了一个模块 , 那就必须重新打包 ;

    插件化开发 , 解决了上述问题 ;


    插件化 将应用拆分成若干模块 , 其中有 1 1 1" 宿主 " 模块 , 若干 " 插件 " 模块 ;

    最终打包时 , 将 " 宿主 " 模块 和 " 插件 " 模块 分开进行打包 ;

    " 宿主 " 模块" 插件 " 模块 都各自是一个单独 apk 安装文件 ;


    插件化 中 , " 宿主 " 模块 和 " 插件 " 模块 可以分开进行编译 , 二者之间互不影响 , 各个模块可以并发进行开发 , " 宿主 " 模块 可以 动态更新插件 ,





    二、插件化示例



    如像 支付宝 等类似的特大型应用 , 内部提供了几百个小的应用模块 , 不可能在开发时就将其集成进去 , 这些软件的本体只有 100 MB 左右 , 不可能将所有的应用都纳入进去 ;

    分辨原生组件与 Web 组件 : 在大型应用中 , 有些应用是使用 WebView 嵌入前端小程序 , 有些是远程组件 , 在 " 开发者选项 " 中 , 打开 " 显示布局边界 " 选项 , 如果是 WebView 就只有一个框 , 如果是原生组件 , 对应的 TextView , ImageView 等都有各自的边界 , 借助布局边界显示可分辨出该第三方小程序是 前端 WebView 界面还是原生应用 ;

    打开 " 显示布局边界 " 选项 :

    在这里插入图片描述

    支付宝主界面效果 : 主界面是原生界面 ;

    在这里插入图片描述

    饿了么界面时 WebView 界面 , 主要内容是 前端小程序 开发的 ;

    在这里插入图片描述

    财富管理模块的股票模块 , 是原生应用 ;

    在这里插入图片描述

    支付宝的主体框架是 Android 原生应用 , 其中的第三方功能 , 小程序 , 基本都是 Web 前端页面 ;

    目前这类应用的开发趋势是使用 Web 应用 替换 原生应用 ;


    股票模块这类 原生应用 , 一般不会在支付宝开发时 , 打包在其中 , 而是通过 插件化 机制 , 动态部署其插件 apk ;

    这类软件第一次打开时 , 需要下载该模块的 插件 apk , 并安装 , 此时会卡顿以下 , 之后可以流畅访问 ;





    三、插件化标准引入



    插件化 中 , " 宿主 " 模块 和 " 插件 " 模块 可以分开进行编译 , 二者之间互不影响 , 各个模块可以并发进行开发 , " 宿主 " 模块 可以 动态更新插件 ;

    " 宿主 " 模块是当前运行的应用 , " 插件 " 模块是下载的插件模块编译后打包的 apk 文件 ;

    在不安装 插件 apk 的情况下 , 调用该 插件中的功能 , 如 Activity , Service , 代码逻辑等 ;


    不是任意 apk 文件都可以接入到 " 宿主 " 插件中 , 要接入的 apk 必须符合一定的标准 ;

    在 apk 插件没有安装 , 因此不存在上下文 , 调用插件中的 Activity 界面时 , 需要将上下文传给插件的 Activity ,

    展开全文
  • Android插件化方案实践

    千次阅读 2020-07-27 10:18:59
    插件化概述 1、插件化和组件化的区别 组件化是将一个app拆分为多个模块进行协作开发,每个模块都是一个单独的组件,这些组件可以相互依赖,也可以单独调试运行。但是最终发布的时候,这些组件会合并在一起,组成一...

    一、插件化概述

     

    1、插件化和组件化的区别

    组件化是将一个app拆分为多个模块进行协作开发,每个模块都是一个单独的组件,这些组件可以相互依赖,也可以单独调试运行。但是最终发布的时候,这些组件会合并在一起,组成一个整体的apk,这就是组件化开发。

    插件化开发和组件化是有所不同的,插件化开发就是将一个app拆分成多个模块,但是每一个模块都是一个apk,最终打包的时候将宿主apk和插件apk分开打包,独立分发。宿主apk发布到市场,插件apk通过动态下发到手机存储空间,然后进行插装操作,宿主apk就能够加载到这个插件包,完成整个业务流程链路的闭合。

     

    2、插件化的优势

    提高编译速度

    开发过程中,每个插件都是独立编译运行的,会是当的提高我们的开发速度。

    业务模块完全解耦

    每个业务模块都是完全独立的,可以随意删除和增加某一部分的功能。

    利于团队开发

    每个团队负责自己的功能,减少沟通的成本。

    动态更新,按需下载

    不需要重新安装即可实现功能的升级,对于一些不常用的功能,可以让用户按需下载,缩减整个程序包的大小。

    解决65535的限制

    每个业务都是一个独立的apk,所以完全可以解决分包的问题。

     

    3、主流的插件化方案

          插件化的实现思路有很多,市面上开源框架也比较多,其中以滴滴公司研发的一款插件化框架VirtualAPK做的相对较优秀,功能也比较强大。

    特性

    DynamicLoadApk

    DynamicAPK

    Small

    DroidPlugin

    VirtualAPK

    支持四大组件

    只支持Activity

    只支持Activity

    只支持Activity

    全支持

    全支持

    组件无需在宿主manifest中预注册

    ×

    插件可以依赖宿主

    ×

    支持PendingIntent

    ×

    ×

    ×

    Android特性支持

    大部分

    大部分

    大部分

    几乎全部

    几乎全部

    兼容性适配

    一般

    一般

    中等

    插件构建

    部署aapt

    Gradle插件

    Gradle插件

    最近腾讯推出了一个由Kotlin实现的插件化框架Shadow,这款框架号称是全动态、零反射、无Hack实现的新一代插件化技术,因为没有用到实战项目中,所以暂时还没有做深入研究。

     

    二、插件化实现原理

    通过上面的概述,可以知道插件化和组件化不同之处在于每一个插件都是一个可以独立安装运行的apk,在用户使用的过程中,只会安装宿主apk,其他的插件会通过网络等方式的下发到手机的内部存储中。所以想实现插件化,就必须将手机中的插件和宿主做关联,达到可以相互通信的效果,但是宿主模块和插件是不能直接进行相互通信的,下面我们会说明原因,所以我们需要一个中间件来作为媒介,这个中间件就是插件SDK。

    所以在实现插件化的方案时,项目的结构搭建应该是分三部分:

    宿主模块:app(主工程)

    中间件:pluginSDK(插件化工具包)

    插件模块:loginPlugin、registPlugin ……(拆分的业务插件)

    因为宿主和插件模块只能做业务逻辑相关的功能,所以要想实现插件化,宿主必须通过pluginSDK来加载和调用插件,那么在pluginSDK中就必须解决以下几个问题:

    • 插件的类加载
    • 插件的生命周期
    • 插件的路由管理
    • 插件的资源管理
    • 插件的事件处理

     

    1、插件的类加载

    如果想在主模块中去使用用插件,就必须将插件加载到主模块中,这里就会用到类加载器。我们知道Android中的虚拟机是Dalvik,它不是标准的Java虚拟机,所以在类加载机制上,和Java中的类加载器是有一些区别的。

    Java虚拟机运行的class字节码,Android虚拟机运行的是dex字节码。

       

    Android中使用的类加载器是PathClassLoader和DexClassLoader,PathClassLoader只能加载已经安装到手机的dex,而DexClassLoader可以加载未安装的dex,所以我们可以通过DexClassLoader加载器来加载插件apk,在pluginSDK的PluginManager类中来实现插件加载初始化的方法

    上图中的代码就是pluginSDK加载插件的核心实现,是对调用插件的初始化操作。这里主要是在宿主模块中调用这个初始化方法来加载插件,并获取插件中的dexClassLoader、packageInfo、resources三个对象,这三个对象我们会后续提供给代理类来使用

     

    2、插件的生命周期

    上面提到宿主模块和插件是不能直接进行相互通信的,这是因为插件中的Activity虽然继承了Activity,但是它只能看作是一个普通的类,并不是真正意义上的Activity,因为它不具备Activity最经典的两个特性:【生命周期】和【上下文】。这是为什么呢?因为我们都知道,插件是直接通过宿主app的动态加载来使用的,插件本身并没有经过Android系统安装的过程,那也就是说,插件本身没有经过AMS的处理,Context对象就是在AMS的main函数中返回的,并且四大组件也是由AMS来统一调度的,所以说没有经过AMS的处理的Activity是没有“灵魂”的,那怎么办呢?我们可以通过“代理伪装”的方式来强制赋予插件Activity生命周期的能力。

    先在pluginSDK中创建一个PluginInterface接口,接口类中定义Activity具有的所有的生命周期函数和参数,用来模拟插件Activity的生命周期。

    然后再在pluginSDK中创建一个BaseActivity来实现这个接口,并实现attach方法。attach方法中接收的是一个Activity实例,这样只要传入我们后续提到的ProxyActivity代理类实例,Context对象也就具备了。最后将插件Activity都继承这个BaseActivity即可。

     

    3、应用的路由管理

    插件加载完成,我们最常用的操作就是路由跳转,我们需要从宿主模块跳转到各个插件中去。如果用Intent的方式,那我们必须在宿主模块中的AndroidManifest中去注册插件中的Activity,这么做肯定是不合理的,如果引入第三方的路由框架,一个是违背了插件化框架设计的原则,另一个是会限制功能的开发,这显然也是不可取的。所以我们要在pluginSDK中创建一个ProxyActivity,让这个代理类来实现路由的跳转。

    首先我们应该先实现从宿主模块到ProxyActivity的跳转逻辑

    这里需要注意的是,我们获取跳转的Activity的时候是通过activities[0]来拿到的,这就要求我们在开发插件的时候必须将启动Activity放到该插件的AndroidManifest中的第一个节点,这算是一个隐性的规范。

    然后再继续实现ProxyActivity类中的路由逻辑

    根据插件Activity类的名称,可以去加载到该类的实例,因为BaseActivity已经实现了PluginInterface接口,也就意味这插件Activity属于PluginInterface的子类,所以可以通过结构引用的方式通过接口来调用插件Activity的生命周期函数,达到唤起插件Activity的作用。

     

    4、插件的资源管理

    解决了上面的几个,其实就可以从宿主模块中实现跳转到插件中了,但是插件中的Activity并不能正常的进行UI显示和事件的监听,是因为插件不具备上下文对象,也就无法使用任何与上下文相关的api,就拿setContentView(R.layout.activity_login)这个类来说,他的本质实现应该是this.setContentView(R.layout.activity_ login),所以他也是需要上下文对象的,前面为了解决这个问题,我们已经将代理类ProxyActivity的实例传递给BaseActivity,而插件Activity又继承自这个BaseActivity,所以也就意味着插件Activity中的上下文对象是ProxyActivity代理类的上下文,那这里肯定是有问题的,插件类用代理类的上下文去获取资源肯定是不正确的,我们必须要在代理类中拿到插件类的资源并给到插件类才行。

    上面我们已经在PluginManager中初始化并获取到了插件类的三个对象,分别是:dexClassLoader、packageInfo、resources,那我们可以在代理类中去重写getResources()方法并返回PluginManager.getInstance().getResources(),并且我们还需要在BaseActivity中重写setContentView()方法,插件中才能被调用执行,UI资源才能被加载成功。

     

    5、插件的事件处理

    通过上面的操作,我们已经可以从宿主模块跳转到对应的插件中了,但是如果在插件中的做一些与上下文相关的操作(如:findViewById),还是要调用BaseActivity中对应的方法,所以需要重写大量的Activity的方法来给子类做支持,如下图,that就是代理类传递给BaseActivity的上下文对象。这一系类的操作本质就是插件不具备上下文对象,我们只能强制给插件赋予这个属性。

     

    三、探究最后

    关于android的插件化原理,本次的研究大概就是以上几点,本文是从apk动态加载的层面对插件化技术的一个研究,通过代理和反射获取到插件的实例,距离商用的技术方案,还需要大量的细节优化和测试。对于市面上的插件化技术方案,各个大厂也是研发了各自优秀的技术方案,比如滴滴公司的VirtualAPK插件化方案尤为优秀。

    动态加载技术会围绕着插件Activity不具备生命周期这个特征做大量的手动映射和管理,开发侵入性比较强,而VirtualAPK通过替换了系统的Instrumentation,hook了Activity的启动和创建,省去了手动管理插件Activity生命周期的繁琐,让插件Activity像正常的Activity一样被系统管理,并且插件Activity在开发时和常规一样,即能独立运行又能作为插件被主工程调用,这种方案的实现难度无疑会更大。未来更多的插件化技术会被研究和开发出来,如果插件化技术稳定发展到了一定的程度,很可能会影响App以后的开发方式。

     

    参考:

    《Activity启动流程》
    《Android apk动态加载机制的研究》
    《Hook机制-AMS&PMS》

     

    展开全文
  • 插件化篇 - 插件化框架对比

    千次阅读 2019-04-10 15:52:23
    来看看现有插件化框架的对比。 目录: MulitDex 引起的问题 插件化需要解决的问题与方案 插件化实现方案分析对比 1.MulitDex 引起的问题 在应用安装到手机上的时候 dex 文件的安装是复杂的,有可能会因为第...

    来看看现有插件化框架的对比。

    目录:

    1. MulitDex 引起的问题
    2. 插件化需要解决的问题与方案
    3. 插件化实现方案分析对比

     

     

    1. MulitDex 引起的问题

    在应用安装到手机上的时候 dex 文件的安装是复杂的,有可能会因为第二个 dex 文件太大导致 ANR。使用了 mulitDex 的 App 有可能在 4.0(api level 14) 以前的机器上无法启动,因为 Dalvik linearAlloc bug(Issue 22586) 。使用了 mulitDex 的 App 在 runtime期间有可能因为 Dalvik linearAlloc limit (Issue 78035) Crash。该内存分配限制在 4.0 版本被增大,但是 5.0 以下的机器上的 Apps 依然会存在这个限制。

    主 dex 被 dalvik 虚拟机执行时候,哪些类必须在主 dex 文件里面这个问题比较复杂。build tools 可以搞定这个问题。但是如果你代码存在反射和 native 的调用也不保证 100% 正确。

    对于 davilk 和 art 虚拟机 Mulitdex 的不同: ART 模式相比原来的 Dalvik,会在安装 APK 的时候,使用 Android 系统自带的dex2oat 工具把 APK 里面的 .dex 文件转化成 OAT 文件。

    这里说一下罗迪的快速加载 Mulitdex 方案:art 虚拟机对 dex 优化需要很长时间,加载插件 dex 跳过优化实现加速,跳过会影响类加载的性能。

     

     

    2. 插件化需要解决的问题与方案

     

    • 2.1 实现插件化需要解决的技术点
    • 问题 1:资源如何加载,资源冲突问题如何解决。
    • 问题 2:代码如何加载访问。
    • 问题 3:插件的管理后台包括的内容。
    • 问题 4:插件的增量更新问题。
    • 问题 5:加载插件中的 so 库。

     

    • 2.2 解决方案

    针对问题 1:

    原理:资源 id 是在编译时生成的,其生成的规则是 0xPPTTNNNN,PP 段是用来标记 apk 的,默认情况下系统资源 PP 是01,应用程序的 PP 是 07。TT 段,是用来标记资源类型的,比如图标、布局等,相同的类型 TT 值相同,但是同一个 TT 值不代表同一种资源,例如这次编译的时候可能使用 03 作为 layout 的 TT,那下次编译的时候可能会使用 06 作为TT的值,具体使用那个值,实际上和当前 APP 使用的资源类型的个数是相关联的。NNNN 则是某种资源类型的资源 id,默认从1 开始,依次累加。

    那么我们要解决资源 id 问题,就可从 TT 的值开始入手,只要将每次编译时的 TT 值固定,即可以让资源 id 达到分组的效果,从而避免重复。例如将宿主程序的 layout 资源的 TT 固定为 33,将插件程序资源的 layout 的 TT 值固定为 03 (可不对插件程序的资源 id 做任何处理,使其使用编译出来的原生的值),即可解决资源 id 重复的问题了。

    固定资源 id 的 TT 值的办法非常简单,提供一份 public.xml,在 public.xml 中指定什么资源类型以什么 TT 值开头即可。比如:

    public.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <public type="mipmap" name="ic_launcher" id="0x7f030001" />
        <public type="drawable" name="ic_notice" id="0x7f02002b" />
    
    
        <!--<public-padding type="drawable" name="abc_" start="0x7f120000" end="0x7f12ffff" />-->
    </resources>

    build.gradle:

    afterEvaluate {
        for (variant in android.applicationVariants) {
            def scope = variant.getVariantData().getScope()
            String mergeTaskName = scope.getMergeResourcesTask().name
            def mergeTask = tasks.getByName(mergeTaskName)
    
            mergeTask.doLast {
                copy {
                    int i=0
                    from(android.sourceSets.main.res.srcDirs) {
                        include 'values/public.xml'
                        rename 'public.xml', (i++ == 0? "public.xml": "public_${i}.xml")
                    }
    
                    into(mergeTask.outputDir)
                }
            }
        }
    }

    这样就能使用 public.xml 固定资源 id,public.xml 用来告诉 Android 资源打包工具 aapt,将类型为 drawable 的资源 ic_notice 的id 固定为 0x7f02002b。注意,在开发应用程序时,一般是不需要用到 public.xml 文件的,因为我们的资源基本上都是在内部使用的,不会导出来给第三方应用程序使用。只在内部使用的资源,不管它的 id 如何变化,我们都可以通过 R.java 文件定义的常量来正确地引用它们。只有系统定义的资源包才会使用到 public.xml 文件,因为它定义的资源是需要提供给第三方应用程序使用的。当然还有做插件化的时候,处理资源冲突问题。

    还有一个方法是通过定制过的 aapt 在编译时指定插件的 PP 段的值来实现分组,重写过的 aapt 指定 PP 段来实现 id 分组。

    • 方案 1:将插件 apk 资源解压,通过操作文件的方式来使用,这个只是理论上可行,实际使用的时候还是有很多的问题。
    • 方案 2:重写 Context 的 getResource()、getAsset() 之类的方法,资源冲突需要扩展 aapt 实现。
    • 方案 3:打包后执行一个脚本修改资源 id。

     

    针对问题 2:

    原理说明:主要就是 classloader 加载 dex,代理模式就是本身宿主中有 Activity,通过欺骗系统来创建 Activity,欺骗系统的部分 hook 的有深有浅 (对比 DroidPlugin 和 Small),让这个 Activity 有生命周期,而动态加载模式就是运行时动态创建并编译一个 Activity 类,需要使用动态创建类的工具实现动态字节码操作。

    • 方案 1:简单加载模式。
    • 方案 2:Activity 代理模式。
    • 方案 3:动态加载模式。

     

    针对问题 3:

    • 提供插件信息查询和下载,包括回滚到某一版本。
    • 管理使用插件的 apk,可以向不同版本 apk 提供不同插件。
    • MD5 校检插件的合法性。

     

     

    3. 插件化实现方案分析对比

     

    • AndroidDynamicLoader

    AndroidDynamicLoader 的作者是大众点评的屠毅敏,应该是最早的动态加载实现方案了。作者在介绍这个框架时形容宿主 App就好像浏览器,但它加载的并不是网页,而是运行在 Android 系统上的插件。这个方案主要是在插件中使用 Fragment,在宿主中使用 Activity 去动态加载插件中的 Fragment。
    github 链接:https://github.com/mmin18/AndroidDynamicLoader

    • 23Code

    23Code 并不是一个开源的项目,它是一个完整的 apk。在应用市场可以下载得到,它内部展示了一些有趣的动效,通过下载的方式,可以直接运行起来,这就是插件化的真实案例。

    • Altas

    altas 是阿里的伯奎分享出来的一个插件化的方案。
    分享视频链接:http://v.youku.com/v_show/id_XNTMzMjYzMzM2.html
    对应的 PPT:http://club.alibabatech.org/resource_detail.htm?topicId=84

    这里的分享主要是将一些思路,业务方面的场景,解决的问题。后来 github 上有了对应的开源项目叫 OpenAltas,后改名ACDD。
    ACDD 的链接:https://github.com/bunnyblue/ACDD

    官方对这个库的说明是非代理 Android 动态热部署框架,而且在视频介绍中,伯奎也讲到,这个方案,就是把宿主当做一个容器,动态加载对应的插件。视频中也有讲到,要做到让插件无感知地运行在这个容器中,需要 hook 很多系统层面的东西,比如:ActivityThread,LoadedApk,ContextImpl,PackageParser,ActivityManagerNative 等,即当插件需要系统的服务来提供对应功能的时候,将这个行为拦截掉,宿主就可以在插件和 Android 系统中间做些手脚了。在当时,Altas 是最为先进插件化技术了,为淘宝客户端提供了很多加载其他业务插件的能力。

    • Dynamic-load-apk

    Dynamic-Load-Apk 简称 DL,这个开源框架作者是任玉刚,他的实现方式是,在宿主中埋一个代理 Activity,更改 ClassLoader后找到加载插件中的 Activity,使用宿主中的 Activity 作为代理,回调给插件中 Activity 所以对应的生命周期。这个思路与AndroidDynamicLoader 有点像,都是做一个代理,只不过 Dynamic-load-apk 加载的插件中的 Activity。
    项目地址:https://github.com/singwhatiwanna/dynamic-load-apk
    项目说明:http://blog.csdn.net/singwhatiwanna/article/details/40283117

    • DroidPlugin

    DroidPlugin 是张勇实现的一套插件化方案,它的原理也是 Hook 客户端一侧的系统 API,可能与 Altas 所 Hook 的点不同,细想应该是基本相同的。
    项目地址:https://github.com/DroidPluginTeam/DroidPlugin

    项目中 DOC 文件夹有很多关于项目的讲解说明。
    张勇本人对 DroidPlugin 的讲解:http://www.infoq.com/cn/presentations/the-realization-principle-and-application-of-droidplugin?utm_campaign=rightbar_v2&utm_source=infoq&utm_medium=presentations_link&utm_content=link_text
    维术对这个框架的讲解:http://weishu.me/2016/01/28/understand-plugin-framework-overview/

    维术的这几篇 blog 很详细滴讲解了 DroidPlugin 是如何实现四大组件的插件化的,还有几篇有规划但是没有写的方面,希望后面能补全。

    • VirtualApp

    VirtualApp 作者是高中生罗迪,据说这个 Android 大牛初三的时候就开始研究双开、插件化的技术,相当了不起。项目的思路与DroidPlugin 相似,不过他没有提供 Service 的代理,而是使用 ContentProvider 来代替 Service 在宿主中作为真正的运行体。
    项目地址:https://github.com/asLody/VirtualApp

    • DynamicAPK

    DynamicAPK 是携程推出的动态加载方案。
    项目说明:http://www.infoq.com/cn/articles/ctrip-android-dynamic-loading
    项目地址:https://github.com/CtripMobile/DynamicAPK

    与 ACDD 一样修改了aapt,使得插件与宿主的资源不会出现相同 id 的问题。

     

    整体对比如下:

     

     

     

     

    展开全文
  • 四大组件的插件化插件化技术的核心知识点,而Activity插件化更是重中之重,Activity插件化主要有三种实现方式,分别是反射实现、接口实现和Hook技术实现。反射实现会对性能有所影响,主流的插件化框架没有采用此...
  • android技术特别成熟了,热修复,组件化......等框架已经层出不穷,如果还仅限于使用框架上,技术永远很难...这一篇文章教大家手写出插件化框架,插件化技术是Android工程师必备的技术之一,我们要懂其思想,知其原理。
  • 插件化简介 插件化概念 插件化开发就是将整个app拆分成很多模块,每个模块都是一个apk,最终打包的时候将宿主apk和插件apk分开打包,插件apk通过动态下发到宿主apk。 插件化优点 宿主和插件分开编译 可并发开发,都...
  • GoLang 插件化开发

    千次阅读 2020-10-05 06:42:54
    Golang 插件化开发 Golang官方提供了plugin模块,该模块可以支持插件开发. 目前很多思路都是在开发过程中支持插件话,当主体程序写完后,不能够临时绑定插件.但是本文将带领你进行主体程序自动识别并加载、控制插件调用...
  • 安卓插件化框架Shadow原理分析

    万次阅读 热门讨论 2021-11-02 16:21:57
    这样的代码在安卓12以下都是OK的,但是如果安卓14把mInstrumentation设置为了私有变量,那我们的整个插件化方案都会失效。 private fun checkInstrumentation(context: Context) { if (state.isHookInstrumentation)...
  • Android插件化主流框架和实现原理

    万次阅读 多人点赞 2020-03-02 23:03:07
    这几年移动开发业界兴起的「 插件化技术 」的旋风,各个大厂都推出了自己的插件化框架,各种开源框架都评价自身功能优越性,令人目不暇接。随着公司业务快速发展,项目增多,开发资源却有限,如何能在有限资源内满足...
  • Android组件化和插件化的概念

    千次阅读 多人点赞 2020-11-10 15:43:17
    一、什么是组件化和插件化 组件化开发 就是将一个app分成多个模块,每个模块都是一个组件(Module),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件等,但是最终发布的时候是将这些组件合并统一...
  • 目录 前言 一、模块化 二、组件化 1、概念 ...三、插件化 ...谈到热修复相信大家应该比较熟悉,因为它是...插件化和热修复同出一门,俩者都属于动态更新,而模块化和组件化是基础。相信看完本篇的内容,对于这些模糊...
  • android插件化

    千次阅读 2020-09-02 17:52:35
    插件化是一种动态升级app功能的解决方案,不同于热修复(仅仅是修复功能),类似于RN、Weex(目的类似)。都是为了在不发版本的情况下,可以让用户用上最新的功能。不过RN、Weex还额外支持跨平台。相对于RN和Weex,...
  • Android插件化开发之(一)实现

    千次阅读 2021-12-13 13:43:04
    简单、明细的说明Android插件化开发的实现方式,帮助有需要者更深入的了解插件化开发,让大家的开发兼容性更强,代码质量更高!!!拿走,不谢。
  • android架构设计之插件化、组件化

    万次阅读 多人点赞 2018-01-08 10:21:50
    如今移动app市场已经是...app类型更加丰富,有电子商务、有视频、有社交、有工具等等,基本上涵盖了各行各业每个角落,为了更加具有竞争力app不仅功能上有创性,内容也更加多元,更加饱满,所以出现了巨大的工...
  • 闲话不多说,下面开始扯正题,最近有个同事问我“模块化、组件化,插件化还有热更新他们之间有什么关系和区别?“ 概述 随着产品的业务不断的增加,我们的APP中代码就会越来越多,这时侯为了方便我们多个成员之间...
  • 模块化、组件化和插件化的区别

    千次阅读 2019-10-14 10:26:53
    单工程模式 移动开发诞生,我们开发移动项目,我相信大多用的是单工程单任务的开发模式,二话不说,直接就开始写起,是不是这样呢? new Project -> 分包 -> 写起。我相信都经历过,也写的比较爽,...模块 A...
  • 插件化系统开发

    千次阅读 2018-09-10 17:25:54
    转载:https://blog.csdn.net/mailtogst/article/details/2073696 ... 何为插件化系统,插件化系统有何优势 当一个软件项目开发结束并交付使用后,假如需要增加一些新的功能时,我们希望在不修改原有的应用程序情...
  • Android 插件化框架

    千次阅读 2019-08-23 15:57:26
    流程:创建app,集成small,宿主module中初始small strictSplitResources = false :是否严格执行资源分割 创建插件module名字为APP.main 包名为com.example.appmain 创建assets文件夹和bundle.json文件记录...
  • Android插件化浅析

    千次阅读 2016-07-03 13:21:13
    插件化是2016年移动端最火爆的几个名词之一,目前淘宝、百度、腾讯等都有成熟的动态加载框架,包括apkplug, 本篇博客就来探讨一下插件化设计。本博客主要从以下几个方面对插件化进行解析: Ø 为什么会提出插件化...
  • 插件化开发小结

    千次阅读 2018-06-04 11:06:58
    引言先简单介绍一下Android插件化。很早之前已经有公司在研究这项技术,淘宝做得比较早,但淘宝的这项技术一直是保密的。直到2015年才陆续出现很多框架,Android插件化分成很多技术流派,实现的方式都不太一样。发展...
  • 什么是模块化,组件化, 插件化

    万次阅读 2018-06-01 09:31:27
    最近毕竟火的也就是组件的应用了,可以大大加快项目的开发进度,对此本人亲自做了demo,有想了解的朋友请关注:https://github.com/wangpengfei1992/ModularizedDemo 废话不多说,解释一下这几个的概念和区别的...
  • 安卓插件化课程-序章_分享+记录-CSDN博客前言:目前安卓领域,插件化十分的流行,本以为这一类的文章会有很多,但是百度一搜,基本上讲的插件化都是皮毛,没有涉及到核心。所以就想写一系列的文章来一步一步深入的...
  • Android插件化作为每个合格的Android程序员都必须会的技术,被各大厂广泛使用。随着各大厂对移动互联网的垄断,我们渐渐发现app集成的功能越来越多。比如如下几个app(携程、淘宝、支付宝): 可以看到每一个app...
  • 三、Service插件化的实现 1、实现步骤 有了上述源码的理解分析以及上一篇所实现Activity插件化的加持,对于Service插件化的实现那简直不要太简单。大致可以分为如下几个步骤: 第一步复用Activity插件化中所实现的...
  • 深入理解Android插件化技术

    万次阅读 2018-07-11 06:20:33
    插件化提要 可以说,插件化技术涉及得非常广泛,其中最核心的就是Android的类加载机制和反射机制,相关原理请大家自行百度。 插件化发展历史 插件化技术最初源于免安装运行apk的想法,这个免安装的apk可以理解...
  • 博客地址 http://blog.csdn.net/sbsujjbcy/article/details/47446733
  • 模块化到组件化再到插件化

    千次阅读 2018-08-16 21:42:36
    控制反转,依赖注入: 耦合结构和解耦结构 目前我的项目的结构如下图所示,因为intent跳转和...模块开发 将一个程序按照其功能做拆分,分成相互独立的模块,以便于每个模块只包含与其功能相关的内容。模块我们...
  • 满帮集团插件化框架Phantom使用演示 Phantom插件化演示(请star支持) 演示demo下载 注意:请将插件apk拷贝至sdcard下。 Phantom介绍 Phantom 是满帮集团开源的一套稳定、灵活、兼容性好的 Android 插件化方案...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 741,495
精华内容 296,598
关键字:

插件化