Android插件化原理(一)—— 插件类加载、类加载原理、(双亲委托机制)

Android插件化原理(三)——加载插件资源 讲解插件化换肤原理,如何加载插件资源 阅读详情

记录一下,这是2020年第一篇帖子,今年立了一个flag–经常写帖子。因为疫情的原因,只能每天在家养肚皮,躺床上为社会做贡献。实在是坐不住了,就开始写这篇文章吧。希望新的一年自己越来越厉害,也希望疫情早点过去


(一)什么是插件化

插件化技术最初源于免安装运行apk的想法,这个免安装的apk就可以理解为插件,而支持插件的app我们一般称之为宿主

(二)为什么要插件化

1.app功能模块越来越多,体积越来越大,维护变得困难;
2.模块之间的耦合度高,协同开发沟通成本越来越高;
3.方法数目可能超过65535,APP占用的内存越来越大;
4.增加功能只敢做加法。

(三)插件化和模块(模块)化的爱恨纠葛

  • 组件化开发主要是通过gradle配置,将一个app分成多个模块,每个模块都是一个组件,开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件等,但是最终发布的时候是将这些组件同意合并成一个apk,这就是组件化开发。
  • 插件化和组件化略有不同,插件化开发是将整个apk拆分成多个模块,这些模块包括一个宿主和多个插件,每个模块都是一个apk,最终打包的时候宿主apk和插件apk分开打包。
  • 插件化其实是建立在模块化基础上的,插件化在实施之前需要先进性模块化。

(四)核心-插件化原理(方案之一)

各大平台方案对比
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
各大平台插件化方案对比
不再一一介绍原理,本次主题是基于 360的DroidPlugin

接下来是插件化的核心:

  • 如何加载插件的类?
  • 如何加载插件的资源?
  • 如何调用插件的类(四大组件)?

一. 如何加载插件的类

1. 类加载器

毫无疑问,类是通过类加载器classLoader加载的,我们先看几种类加载器.

1.1 PathClassLoader和DexClassLoader区别

首先我们来看DexClassLoader:

DexClassLoader 用于加载.jar .apk .dex中的类,也可以用来加载不是应用程序中的代码

api-25-7.1 -->
DexClassLoader-api-25

api-26-8.0 -->

DexClassLoader-api-26

api-28-9.0 -->

DexClassLoader-api-28

可以发现,DexClassLoader里什么也没有做,只是重写了BaseDexClassLoader的构造函数,注意api28里面的这段标红注释:该参数在api 26之后就废弃了,后面会讲到。

再看BaseDexClassLoader

api-25-7.1 -->
在这里插入图片描述

api-26-8.0 & api-28-9.0 -->
BaseDexClassLoader-api26

我们把焦点放在optimizedDirectory上:应该被写入的dex所在的目录。通过构造三个版本的函数可以看出:8.0之前使用了optimizedDirectory参数,8.0及8.0之后不再使用该参数

我们再移步看一下PathClassLoader:PathClassLoader是系统类和系统应用的类加载器:
PathClassLoader
PathClassLoader8.0前后并没有什么区别,同样重写了四个参数的构造函数,optimizedDirectory为null。

总结:

  • 8.0之前,DexClassLoader供系统(谷歌工程师)使用,PathClassLoader提供给开发者使用;
  • 8.0及之后并无区别。后来谷歌工程师越来越觉得写两个加载器简直是闲得蛋疼,干脆就不再区分了。

区别: 在8.0之后并无区别,

1.2 BootClassLoader 和PathClassLoader
/**
*注意这个类派生自AppCompatActivity,而非Activity
*/
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findClassLoader();
    }

    private void findClassLoader() {
        ClassLoader classLoader = getClassLoader();
        while (classLoader !=null){
            Log.d(TAG, "findClassLoader: classLoader : "+classLoader);
            classLoader = classLoader.getParent();
        }
        Log.d(TAG, "findClassLoader: classLoader : "+ Activity.class.getClassLoader());
    }
}

让我的小助理运行一下代码,小手那么一点,运行结果如下所示:

在这里插入图片描述

通过代码,我们可以得知:
MainActivity 的classLoader是 PathClassLoader;Activity 的classLoader是BootClassLoader; PathClassLoader的parent是BootClassLoader。

其实他们两个的作用

  • BootClassLoader – 加载FramWork的class文件;
  • PathClassLoader – 加载应用内的class文件,包含implementation到的第三方库以及.jar、.aar等。

2. 类的生命周期

在这里插入图片描述

这里我们重点了解一下类加载阶段主要做的事情:

  • 通过一个类的全限定名来获取定义此类的二进制字节流;
  • 将这个字节流所代表的静态存储结构转化为方法区域的运行时数据结构。
  • 在java堆中生成一个代表这个类的Class对象,作为方法区域数据的访问入口。

其它阶段不是我们本章的重点,这里不再赘述,可参考Java类的生命周期


3 加载一个类

注:演示代码是基于Android API 9.0的
注:演示代码是基于Android API 9.0的
注:演示代码是基于Android API 9.0的

重要的话说三遍

3.1 「双亲委托」

首先通过代码演示如何加载一个类来请出一个传说中的大“人物”——双亲委托机制

       String dexPath = "";
       DexClassLoader dexClassLoader = new DexClassLoader(dexPath, this.getCacheDir().getAbsolutePath(),
                    null, getClassLoader());
       Class<?> clazz = dexClassLoader.loadClass("com.margin.plugin.ProxyActivity");

这样,一个类(插件中的类)就被加载成功了。那么,类究竟是如何加载的呢,请小助理位于ClassLoader中的loadClass把源码拿过来:

  • loadClass-1 :
    在这里插入图片描述

首先通过findLoadedClass检查是否已经加载过了;如果没有加载过,检查是否存在parent(注意这个parent父母即双亲,并不是指父类,而是上一级),如果双亲存在则调用parent的loadClass()方法,依次递归;

清楚此流程了,那么一个几乎所有小孩子都问过的问题来了——我是从哪里来的?不不不,是parent从哪里来的😳😳😳???

 DexClassLoader dexClassLoader = new DexClassLoader(dexPath,     this.getCacheDir().getAbsolutePath(),
                    null, getClassLoader());

我们在new DexClassLoader的时候传递的第四个参数就是parent,通过1.2和1.3得知,这个getClassLoader得到的是PathClassLoader,所以这里的classLoader层级关系为:

类加载器parent层级关系

这样,一层一层向上查找的过程,最终到了BootClassLoader的loadClass方法中,我们小助理再来上一下代码:

  • loadClass-2 : bcl-loadClass

注:BootClassLoader 是ClassLoader的一个内部类。

在这个方法中,仍然是先通过findLoadedClass判断是否已经加载过,如果没有,就不再找parent,而是直接调用findClass方法并返回class

在这里插入图片描述

双亲将加载到的class依次向下返回,第一层级的parent检查是否为null,如果为null则调用自己的findClass方法,将结果再次向下返回直至最初的DexClassLoader中的loadClass方法中(代码 loadClass-1),此时,如果双亲找到的class仍然为null,那么,就调用自己的findClass方法查找class

双亲委托机制的流程图

双亲委托机制

总结『双亲委托机制』

  • 1.首先通过findLoadedClass检查是否已经加载过了;
  • 2.如果没有加载过,检查是否存在parent(注意这个parent父母即双亲,并不是指父类,而是上一级),如果双亲存在则调用parent的loadClass()方法,依次递归调用,当到达顶部的时候不再检查双亲而是调用findClass方法并低层返回结果;
  • 3.如果最终都没有查找到或加载成功则调用自身的findClass并返回结果。

为什么使用双亲委托机制?

  • 1.避免重复加载,当双亲加载器已经加载了该类的时候,就没有必要子ClassLoader再次加载;
  • 2.安全性考虑,防止核心API库被随意篡改。

至此,双亲委托机制介绍完毕。

3.2 加载类
3.2.1 查找已加载的类findLoadedClass

cl-findLoadedClass2
vm-findLoadedClass

最终到达Native层,然后先这样再这样在那样,就完成了,哈哈哈哈哈哈

3.2.2 查找类findClass

查看BootClassLoader的findClass方法:
bcl-findClass-2
此处只是重写了ClassLoader的findClass方法,调用了Class的native方法。

我们在看DexClassLoader和PathClassLoader,他们的findClass由其父类BaseDexClassLoader重写:

  • 代码块1
    bdcl-findClass
    这里没有什么特别的,核心是通过pathList.findClass()查找class,pathList是DexPathList的实例,我们接着看这个方法:

  • 代码块2
    DexPathList-findClass

这个方法迭代dexElements,通过Element查找目标class,Element是DexPathList的一个内部类,咱们再看看其方法:

  • 代码块3
    Element-findClass

如果dexFile不为空,则调用dexFile.loadClassBinaryName返回class。dexFile是Element的一个变量,由构造器传入。那么我们就得看看Element从何而来,从上面可知,Element由dexElements而来,那么我们就得看dexElements是怎么来的。

DexPathList的过个构造函数中都有dexElements的创建:
DexPathList-constructor-dexElements

关键是这一行:

    this.dexElements = makeDexElements(splitDexPath(dexPath),optimizedDirectory,suppressedExceptions, definingContext, isTrusted);

可见dexElements由该方法创建的,我们接着查看该方法,在看一下,不妨先看一下第一个参数的方法splitDexPath方法:
DexPathList-splitDexPath

splitDexPath()方法主要作用:通过分隔符把给定的path分成一个个File,并将结果以List的形式返回,它去除不可读,不可用的、常规的文件等等。 知道了splitDexPath()的作用,我们再返回查看makeDexElements()方法:

       /**
     * Makes an array of dex/resource path elements, one per element of
     * the given array.
     */
    private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
            List<IOException> suppressedExceptions, ClassLoader loader) {
        return makeDexElements(files, optimizedDirectory, suppressedExceptions, loader, false);
    }


    private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
            List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
      Element[] elements = new Element[files.size()];
      int elementsPos = 0;
      /*
       * Open all files and load the (direct or contained) dex files up front.
       */
      for (File file : files) {
          if (file.isDirectory()) {
              // We support directories for looking up resources. Looking up resources in
              // directories is useful for running libcore tests.
              elements[elementsPos++] = new Element(file);
          } else if (file.isFile()) {
              String name = file.getName();

              DexFile dex = null;
              if (name.endsWith(DEX_SUFFIX)) {
                  // Raw dex file (not inside a zip/jar).
                  try {
                      dex = loadDexFile(file, optimizedDirectory, loader, elements);
                      if (dex != null) {
                          elements[elementsPos++] = new Element(dex, null);
                      }
                  } catch (IOException suppressed) {
                      System.logE("Unable to load dex file: " + file, suppressed);
                      suppressedExceptions.add(suppressed);
                  }
              } else {
                  try {
                      dex = loadDexFile(file, optimizedDirectory, loader, elements);
                  } catch (IOException suppressed) {
                      /*
                       * IOException might get thrown "legitimately" by the DexFile constructor if
                       * the zip file turns out to be resource-only (that is, no classes.dex file
                       * in it).
                       * Let dex == null and hang on to the exception to add to the tea-leaves for
                       * when findClass returns null.
                       */
                      suppressedExceptions.add(suppressed);
                  }

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

这个方法里面就是迭代splitDexPath所返回的List,通过file查找到dexFile,然后创建一个Element的实例并将dexFile放入,也就是说,一个dex对应一个Element,再将Element放入elements数组中,最后返回elements,这个elements就对应了一个apk中的所有文件。

class.dex

如果classLoader是PathClassLoader,elements就是应用内的所有类,如果classLoader是DexClassLoader,则需要看创建实例时传递的第一个参数path路径下的。

4. 加载插件中的类

1.创建一个插件:

  • step1:在项目中新建一个module:plugin,随便新建一个类:
public class Test {
    private static final String TAG = "Test";

    public static void sayHi() {
        Log.d(TAG, "sayHi: Hello , this is Plug-in");
    }
}
  • step2:将Test.class打包成dex文件:
    终端定位到:module下javac中的build/intermediates/javac/debug/classes
    然后执行命令: dx --dex --output=test.dex com/margin/plugin/Test.class
    执行完之后可以看到classes目录下多了一个test.dex文件。
    -step3:将dex文件放到sd卡中:

class.dex
-step4:在宿主app中编写代码并运行,别忘了在清单文件中添加文件读写权限,代码中不要忘了写运行时权限检查:

 private void luanchClass() {
        final String dexPath = "/sdcard/test.dex";
        PathClassLoader dexClassLoader = new PathClassLoader(dexPath, getClassLoader());
        try {
            //类的路径
            Class<?> testClazz = dexClassLoader.loadClass("com.margin.plugin.Test");
            //加载到类之后,反射调用其方法
            Method sayHiMethod = testClazz.getMethod("sayHi");
            sayHiMethod.invoke(null);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }

加载到类之后,反射调用方法,(别忘了添加文件访问权限),结果如下:
在这里插入图片描述

看到日志的那一瞬间,泪目了,终于类加载成功了,辛辛苦苦养了多年的猪,终于会拱白菜了😀😀😀。
可是,这就完了嘛?当然不是,如果是这么简单,我们就不会在前面长篇大论讲类加载远离了,而且这种方式每次调用都要使用类加载器加载及其不方便,怎么办呢,请看下文。

5.将插件与宿主的类合并

上面第4步,是使用时加载的方式,这种方式很不方便,所以我们换一种方式,加载合并插件,顾名思义,就是在app启动(当然时机是自定的)时将插件全部加载并合并到宿主中。

这种方式的核心思想就是,将插件的类合并到宿主中,具体步骤如下:

  • 新建一个类加载假期,用于加载插件;
  • 反射获取插件的类加载器的的pathList中的dexElements;
  • 获取当前宿主app的类加载器,然后同上一步反射获取其dexElements;
  • 将宿主和插件的dexElements数组合并;
  • 将合并后的dexElements反射设置到宿主的类加载器(DexPathList)中。
    代码如下:
  public static void loadClass(Context context) {
        //Element[] dexElements 是DexPathList的一个变量;
        //DexPathList pathList 是BaseDexClassesLoader的一个变量;
        try {

            //反射获取BaseDexClassLoader
            Class<?> baseDexClassLoaderClazz = Class.forName("dalvik.system.BaseDexClassLoader");

            //1.获取公共的 DexPathList pathList Field
            Field pathListField = baseDexClassLoaderClazz.getDeclaredField("pathList");
            pathListField.setAccessible(true);

            //2.获取公共的 Elementp[] elements Field
            Class<?> dexPathListClazz = Class.forName("dalvik.system.DexPathList");
            Field dexElementField = dexPathListClazz.getDeclaredField("dexElements");
            dexElementField.setAccessible(true);

            //3.创建新插件的类加载器,反射获取插件 类加载器的elements
            DexClassLoader dexClassLoader = new DexClassLoader(apkPath, context.getCacheDir().getAbsolutePath(),
                    null, context.getClassLoader());
            Object pluginPathList = pathListField.get(dexClassLoader);
            Object[] pluginElements = (Object[]) dexElementField.get(pluginPathList);

            //4.获取宿主baseDexClassLoader的elements
            PathClassLoader hostClassLoader = (PathClassLoader) context.getClassLoader();
            Object hostPathList = pathListField.get(hostClassLoader);
            Object[] hostDexElements = (Object[]) dexElementField.get(hostPathList);

            //5.将插件的elements合并到宿主elements中,然后反射重新设置宿主dexElements的值
            Object[] compoundElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(),
                    hostDexElements.length + pluginElements.length);
            System.arraycopy(hostDexElements, 0, compoundElements, 0, hostDexElements.length);
            System.arraycopy(pluginElements, 0, compoundElements, hostDexElements.length, pluginElements.length);

            dexElementField.set(hostPathList, compoundElements);


        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "loadClass: ", e);
        }
    }

在Applicaion中加载,别忘了把这个Applicaiton写到清单文件里

public class HostApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        LoadUtil.loadClass(this);
    }
}

然后就可以调用目标类了:

   /**
     * 将插件的类合并到宿主内后的加载方式
     */
    private void launchCompoundClass() {
        try {
            Class<?> testClazz = Class.forName("com.margin.plugin.Test");
            Method sayHiMethod = testClazz.getMethod("sayHi");
            sayHiMethod.invoke(null);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

然后运行就可以看到结果了,我就不再贴结果了。

至此,插件化第一步,类加载就完成了。

所有代码见 github: Magic-plug-in

Android插件化原理与方案详解 Android插件化种开发模式,它允许我们动态地加载和卸载APK,从而实现模块化开发,热更新等功能。本文将详细介绍Android插件化原理,以及几种主流的Android插件化方案。 阅读详情

相关推荐

Android插件化技术的原理与实现

Android插件化技术的原理与实现

zhuhai0613的博客 395

深入理解Android插件化技术

插件化技术可以说是Android高级工程师所必须具备的技能之,从2012年插件化概念的提出(Android版本),到2016年插件化的百花争艳,可以说,插件化技术引领着Android技术的进步。本篇文章转载自腾讯bugly,觉得写得不错,转载分享给大家。 插件化提要 可以说,插件化技术涉及得非常广泛,其中最核心的就是Android类加载机制和反射机制,相关原理请大家自行百度。

xiangzhihong8的专栏 3733

Android常用技术-热修复解析

Android学习是条漫长的道路,我们要学习的东西不仅仅只有表面的 技术,还要深入底层,弄明白下面的 原理,只有这样,我们才能够提高自己的竞争力,在当今这个竞争激烈的世界里立足。人生不可能帆风顺,有高峰自然有低谷,要相信,那些打不倒我们的,终将使我们更强大,要做自己的摆渡人。资源持续更新中,欢迎大家起学习和探讨。《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

2401_84009130的博客 932

Android插件化插件化基本原理

插件化简介 插件化概念 插件化开发就是将整个app拆分成很多模块,每个模块都是个apk,最终打包的时候将宿主apk和插件apk分开打包,插件apk通过动态下发到宿主apk。 插件化优点 宿主和插件分开编译 可并发开发,都是apk,开发互不影响(宿主需要给插件下发个context) 动态更新插件 按需下载模块 可解决方法数超过65536的问题 插件化难点 插件化需要解决的问题 插件中代码的加载与主工程的相互调用 插件中资源的加载与主工程的相互访问 四大组件生命周期的管理 解决方法 插件中代码的加载

qq_36390114的博客 6429

Android-插件化详解

什么是插件化插件化就是将整个app拆分成多个模块,这些模块包括个宿主和多个插件,每个模块都是个apk,最终打包的时候宿主apk和插件apk分开打包。那么这样做的好处是什么呢?1.减小app的体积,按需下载,用不到某些模块就不需要下载相应的插件apk2.减小模块之间的耦合度,降低协同开发的成本3.解决了应用之前的相互调用问题。

袁震的博客 2380

Android 插件化原理),通过dex文件调用插件app代码

Android插件化原理,从以下三个问题切入: 什么是插件化 如何实现插件类加载 如何实现插件资源的加载 什么是插件化 插件化技术最初是源于免安装运行APK的想法,这个免安装的APK就可以理解为插件,而支持插件的app,则称之为宿主;方面减小了安装包的大小,另方面可以实现 app 功能的动态扩展 插件化解决的问题 APP的功能越来越多,体积越来越大 模块之间的...

Rayman的博客 1256

安卓插件化原理

、发展历史 插件化技术最初源于免安装运行apk的想法,这个免安装的apk可以理解为插件。支持插件化的app可以在运行时加载和运行插件,这样便可以将app中些不常用的功能模块做成插件,方面减小了安装包的大小,另方面可以实现app功能的动态扩展。想要实现插件化,主要是解决下面三个问题: 插件中代码的加载和与主工程的互相调用 插件中资源的加载和与主工程的互相访问 四大组件生命周期的管理 二、插件...

miracle33的博客 996

Android插件化实现的原理,宿主app运行插件中的类 ()

分析下Android插件化实现的原理 这里的场景是通过Java层的Hook技术,实现Activity插件化,以api29为例,如果其他的api版本,需要根据具体代码做兼容. Hook技术,通常就是用反射,代理模式改变系统的调用流程,或者说拦截事件的传递,做些特定的处理.这样就可以在应用进程,通过hook技术改变系统进程的执行流程. 要实现Hook,就要先找到Hook的点,Hook点的选择...

lin20080410的专栏 1607

插件化知识点原理解析

插件化1.前言2.插件化的实现3.类加载3.1 ClassLoader的实现类 1.前言 插件化技术最初源于免安装运行 apk 的想法,这个免安装的 apk 就可以理解为插件,而支持插件的 app 我们般叫宿主。宿主可以在运行时加载和运行插件,这样便可以将 app 中些不常用的功能模块做成插件,方面减小 了安装包的大小,另方面可以实现 app 功能的动态扩展。 2.插件化的实现 我们如何去实现插件化呢? 首先我们要知道,插件apk是没有安装的,那我们怎么加载它呢?不知道。。。 没关系,这儿我

weixin_39432879的博客 918

Android插件化原理及基础实现【hook篇】

插件化的理念 将应用分为多个模块,分出宿主与插件 用户安装宿主,动态加载插件 插件化的优点 按需加载、可插拔、动态更新 减小apk体积,解决方法是超过65535的问题 插件分开开发与编译,提高效率,降低耦合度 插件化的缺点 提升项目复杂度 插件化框架 特性 dynamic-load-apk DynamicApk Small DroidPlugin VirtualAPK 作者 任玉刚 携程 wequick 360 滴滴 四大组件支持 只支持Activity 只支持Activity 只支持A

sese12138的博客 1784

你需要掌握的插件化知识

1 什么是插件化插件化技术最初源于免安装运行 apk 的想法,这个免安装的 apk 就可以理解为插件,而支持插件的 app 我们般叫宿主 2 插件化实现思路 1 如何加载插件的类? 2 如何加载插件的资源? 3 如何调用插件类? Q 1, 回答问题之前我们要先了解什么是双亲委托机制 类在加载的过程中首先回去 判断当前类是否已经被加载过,如果没有加载过并且自己的父加载器不为空,将会让自己的父加载器去处理,父加载同样会去按照这个逻辑处理。如果没有找到父加载器,才会自己去加载。 这样处理

BonnySoul的博客 1390

Android插件化原理(二)—启动插件Activity

插件化原理(二)——启动插件Activity

blankmargin的博客 1219

从零开始实现插件化框架(

什么是插件化 概念 插件化技术最初源于免安装运行 apk 的想法,这个免安装的 apk 就可以理解为插件,而支持插件的 app 我们般叫宿主。宿主可以在运行时加载和运行插件,这样便可以将 app 中些不常用的功能模块做成插件,方面减小了安装包的大小,另方面可以实现 app 功能的动态扩展。 我们知道计算机主板就是由系列的插槽组成的,我们需要什么功能,给它插上对应的芯片或显...

AndroidAlvin的博客 247

从零开始实现插件化框架((1),【Android面试题】

下面是辛苦给大家整理的学习路线个人可以走的很快,但群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们起学习成长!由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android

碧海朝天素 784

从零开始实现插件化框架(),头条android面试

super(dexPath, null, librarySearchPath, parent); } } // API 小于等于 26/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader(String dexPath, String optimizedDirectory, Str

m0_65146275的博客 167

Android加载类的方法及区别解析

Android 开发中,类加载个核心机制,了解不同的类加载方法及其区别对于插件化、热修复等高级技术的实现至关重要。

斗锋在干嘛的博客 582

初级Android程序员现状:工作8年,年薪8万,如何破局?

在下人称 Android “高手”,工作8年,年薪8万!为啥没有达到年薪百万?我思来想去,竟始终不得要领,我到底差在哪里了呢? 各位道友能否指点二呢? ——迷人且迷茫的老王 普通人和年薪百万的那群人,差距往往都不在硬实力上。学习经历、工作经验、增值充电,大家都是类似的。真正拉开差距的,其实都是软技能。 对于Android开发者来说,什么是软技能? 你不需要这些能力也能实现个app,但是想...

weixin_45258969的博客 417

StartActivity原理)——Android9.0 Activity启动原理, pauseActivity原理

Android9.0,Activity启动原理,startActivity原理

blankmargin的博客 687
上一篇: 设计模式--代理模式--深入理解动态代理
下一篇: Magic-Hybrid 混合开发架构原理解析
cry kid
博客等级 码龄10年 144粉丝 24原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值