Android开发笔记 onGithub

笔记,参考7.2中所列参考文章所写,DEMO地址在PluginTestDemoApplication

1.综述

2015年是Android插件化技术突飞猛进的一年,随着业务的发展各大厂商都碰到了Android Native平台的瓶颈。 从技术上讲,业务逻辑的复杂导致代码量急剧膨胀,各大厂商陆续出到65535方法数的天花板;同时,运营为王的时代对于模块热更新提出了更高的要求。 在业务层面上,功能模块的解耦以及维护团队的分离也是大势所趋;各个团队维护着同一个App的不同模块,如果每个模块升级新功能都需要对整个app进行升级,那么发布流程不仅复杂而且效率低下;在讲究小步快跑和持续迭代的移动互联网必将遭到淘汰。 H5和Hybird可以解决这些问题,但是始终比不上native的用户体验;于是,国外的FaceBook推出了react-native;而国内各大厂商几乎都选择纯native的插件化技术。可以说,Android的未来必将是react-native和插件化的天下。 react-native资料很多,但是讲述插件化的却凤毛菱角;

插件化技术听起来高深莫测,实际上要解决的就是两个问题:

  • 1.代码加载
  • 2.资源加载
  • 参考 Android插件化原理解析——概要

1.1 需要解决的问题

  • 1.代码的加载

    类的加载可以使用Java的ClassLoader机制,但是对于Android来说,并不是说类加载进来就可以用了,很多组件都是有“生命”的;因此对于这些有血有肉的类,必须给它们注入活力,也就是所谓的组件生命周期管理;

    另外,如何管理加载进来的类也是一个问题。假设多个插件依赖了相同的类,是抽取公共依赖进行管理还是插件单独依赖?这就是ClassLoader的管理问题;

  • 2.资源的加载

    资源加载方案大家使用的原理都差不多,都是用AssetManager的隐藏方法addAssetPath;但是,不同插件的资源如何管理?是公用一套资源还是插件独立资源?共用资源如何避免资源冲突?对于资源加载,有的方案共用一套资源并采用资源分段机制解决冲突(要么修改aapt要么添加编译插件);有的方案选择独立资源,不同插件管理自己的资源。

1.2 Android中加载类 和 Java加载类的区别

Android中许多组件类(如Activity、Service等)是需要在Manifest文件里面注册后才能工作的(系统会检查该组件有没有注册),所以即使动态加载了一个新的组件类进来,没有注册的话还是无法工作;

Res资源是Android开发中经常用到的,而Android是把这些资源用对应的R.id注册好,运行时通过这些ID从Resource实例中获取对应的资源。如果是运行时动态加载进来的新类,那类里面用到R.id的地方将会抛出找不到资源或者用错资源的异常,因为新类的资源ID根本和现有的Resource实例中保存的资源ID对不上;

2.实现思路

目前国内开源的较成熟的插件方案有DL和DroidPlugin;但是DL方案仅仅对Framework的表层做了处理,严重依赖that语法,编写插件代码和主程序代码需单独区分;而DroidPlugin通过Hook增强了Framework层的很多系统服务,开发插件就跟开发独立app差不多;就拿Activity生命周期的管理来说,DL的代理方式就像是牵线木偶,插件只不过是操纵傀儡而已;而DroidPlugin则是借尸还魂,插件是有血有肉的系统管理的真正组件;DroidPlugin Hook了系统几乎所有的Sevice,欺骗了大部分的系统API;掌握这个Hook过程需要掌握很多系统原理,因此学习DroidPlugin对于整个Android FrameWork层大有裨益。

以DroidPlugin为例的方式

  • 1.构造DexClassLoader : 既然我们知道如果想启动插件apk就需一个Classloader,那么我们换一种想法,能不能我们将我们的插件apk的信息告诉系统的这个Classloader,然后让系统的Classloader来帮我们加载及创建呢?答案是肯定之前我们说过讲过android中的Classloader主要分析PathClassLoader和DexClassLoader,系统通过PathClassLoader来加载系统类和主dex中的类。而DexClassLoader则用于加载其他dex文件中的类。他们都是继承自BaseDexClassLoader。(如果没有看过的建议先看看 插件化知识详细分解及原理 之ClassLoader及dex加载过程)

  • 2.拿到宿主apk里ClassLoader中的pathList对象和我们Classloader的pathList,进行合并

    • 2.1 通过PathClassLoader拿到宿主应用的dexPathList,通过DexClassLoader拿到插件的dexPathList。
    • 2.2 拿到宿主和插件的dex列表
    • 2.3 将宿主和插件的dexPathList合并
  • 3.hook住startActivity方法,使用占坑的方式,也就是说我们可以提前在AndroidManifest中固定写死一个Activity,这个Activity只不过是一个傀儡,我们在启动我们插件apk的时候使用它去系统层校检合法性,然后等真正创建Activity的时候再通过hook思想拦截Activity的创建方法,提前将信息更换回来创建真正的插件apk。

    • 3.1 通过反射,拿到AMS的代理对象
    • 3.2 自定义InvocationHandler拦截startActivty方法
    • 3.3 带系统检查完Activity合法性后重新执行原Activity

3.准备工作

3.1 理解应用的启动过程

  • 参考 Android应用程序启动过程源代码分析

下面是大致的流程,详细内容可以看Activity的笔记或者罗升阳的《Android源码分析》

应用程序是由Launcher启动起来的,其实Launcher本身也是一个应用程序。

启动一个应用时会调用 startActivitySafely -> startActivity -> startActivityForResult

最终会调用InstrumentationexecStartActivity方法。

execStartActivity中会调用ActivityManagerNative.getDefault().startActivity ,实际上就是调用了AMS的startActivity方法。

execStartActivity中还会调用checkStartActivityResult方法对start的结果进行检查。

AMS的startActivity->startActivityAsUser->mActivityStarter.startActivityMayWait,最终调用 ActivityStackSupervisorrealStartActivityLocked方法。

realStartActivityLocked方法中,会调用ActivityThreadscheduleLaunchActivity方法。

scheduleLaunchActivity方法中 有一行代码 sendMessage(H.LAUNCH_ACTIVITY, r);

ActivityThread内部的H类中调用

public void handleMessage(Message msg) {if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));switch (msg.what) {case LAUNCH_ACTIVITY: {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");final ActivityClientRecord r = (ActivityClientRecord) msg.obj;r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);handleLaunchActivity(r, null);Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);} break;}
复制代码

handleLaunchActivity->performLaunchActivity

performLaunchActivity中通过Instrumentation.newActivity创建Activity,并创建Application和Context

3.2 理解Activity和Service的启动过程

  • Android应用程序的Activity启动过程简要介绍和学习计划

performLaunchActivity方法中

java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);复制代码

实际是通过r.packageInfo这个LoadedApk类型的对象获得。

LoadedApk保存了Apk文件的相关信息。

H类里通过getPackageInfoNoCheck方法获得LoadedApk,针对代码的信息会存到mPackages表里

apk被安装之后,apk的文件代码以及资源会被系统存放在固定的目录比如/data/app/package_name/base.apk)中,系统在进行类加载的时候,会自动去这一个或者几个特定的路径来寻找这个类。(可以在Android Studio的Device File Explorer去查看)

在startActivityLocked方法内部进行了一系列重要的检查:比如权限检查,Activity的exported属性检查等等;我们上文所述的,启动没有在Manifestfest中显示声明的Activity抛异常也是这里发生的:

3.3 理解DexClassLoader 和 PathClassLoader

3.3.1 DexClassLoader

构造函数:

public DexClassLoader (String dexPath, String dexOutputDir, String libPath, ClassLoader parent)复制代码
dexPath:dex文件路径列表,多个路径使用”:”分隔
dexOutputDir:经过优化的dex文件(odex)文件输出目录
libPath:动态库路径(将被添加到app动态库搜索路径列表中)
parent:这是一个ClassLoader,这个参数的主要作用是保留java中ClassLoader的委托机制(优先父类加载器加载classes,由上而下的加载机制,防止重复加载类字节码)
复制代码

DexClassLoader是一个可以从包含classes.dex实体的.jar或.apk文件中加载classes的类加载器。可以用于实现dex的动态加载、代码热更新等等。这个类加载器必须要一个app的私有、可写目录来缓存经过优化的classes(odex文件)

3.3.2 PathClassLoader

PathClassLoader提供两个常用构造方法

public PathClassLoader (String path, ClassLoader parent)
1
public PathClassLoader (String path, String libPath, ClassLoader parent)
复制代码
path:文件或者目录的列表
libPath:包含lib库的目录列表
parent:父类加载器
复制代码

3.3.3区别

  • DexClassLoader:能够加载未安装的jar\apk dex。
  • PathClassLoader:Android系统通过PathClassLoader来加载系统类和主dex中的类。

参考:

  • DexClassLoader和PathClassLoader的区别

  • 插件化知识详细分解及原理 之ClassLoader及dex加载过程

  • Android动态加载基础 ClassLoader工作机制

  • Android 插件化原理解析——插件加载机制

3.4 理解反射和Hook

首先我们得找到被Hook的对象,我称之为Hook点;什么样的对象比较好Hook呢?自然是容易找到的对象。什么样的对象容易找到?静态变量和单例;在一个进程之内,静态变量和单例变量是相对不容易发生变化的,因此非常容易定位,而普通的对象则要么无法标志,要么容易改变。我们根据这个原则找到所谓的Hook点。

  • 1.寻找Hook点,原则是静态变量或者单例对象,尽量Hook pulic的对象和方法,非public不保证每个版本都一样,需要适配。

  • 2.选择合适的代理方式,如果是接口可以用动态代理;如果是类可以手动写代理也可以使用cglib。

  • 3.偷梁换柱——用代理对象替换原始对象

  • Android插件化原理解析——Hook机制之动态代理

  • Android插件化原理解析——Hook机制之Binder Hook


4.其他

4.1 热门热修复框架

目前比较有名的插件化框架: 任玉刚的:dynamic-load-apk,这个项目使用的是一种代理的方式去实现 https://github.com/singwhatiwanna/dynamic-load-apk

360的:DroidPlugin,这个项目是通过hook系统类来实现 https://github.com/Qihoo360/DroidPlugin

目前比较火的热修复框架: 阿里的:andfix,用于对方法的修复,可以立即生效,不支持资源及类替换 https://github.com/alibaba/AndFix

腾讯的:tinker,除了不支持立即生效,全部支持 https://github.com/Tencent/tinker

美团的:robust,不开源

  • 2017 Android插件化框架总结

7.2 参考文章

  • 苹果核 - Android插件化实践(1)--动态代理

总述

  • Android插件化原理解析——概要

Hook

  • Android插件化原理解析——Hook机制之动态代理
  • Android插件化原理解析——Hook机制之Binder Hook
  • Android 插件化原理解析——Hook机制之AMS&PMS

生命周期管理

  • Android 插件化原理解析——Activity生命周期管理
  • Android插件化原理解析——广播的管理
  • Android 插件化原理解析——Service的插件化
  • Android插件化原理解析——ContentProvider的插件化

插件的加载

  • Android 插件化原理解析——插件加载机制
  • Android动态加载基础 ClassLoader工作机制

下面这几篇文章是参考上面的文章所写,主要参考上面的几篇文章,下面的文章作为补充

  • 插件化知识详细分解
  • Android插件化完美实现代码资源加载及原理讲解 附可运行demo
  • Android插件化资源的使用及动态加载 附demo
  • 插件化知识详细分解及原理 之Binder机制
  • 插件化知识详细分解及原理 之代理,hook,反射
  • 插件化知识详细分解及原理 之ClassLoader及dex加载过程
  • 插件化知识详细分解及原理 之应用的启动过程

Android 插件化原理入门笔记相关推荐

  1. Android 插件化原理学习 —— Hook 机制之动态代理

    前言 为了实现 App 的快速迭代更新,基于 H5 Hybrid 的解决方案有很多,由于 webview 本身的性能问题,也随之出现了很多基于 JS 引擎实现的原生渲染的方案,例如 React Nat ...

  2. Android 插件化原理解析——Activity生命周期管理

    之前的 Android插件化原理解析 系列文章揭开了Hook机制的神秘面纱,现在我们手握倚天屠龙,那么如何通过这种技术完成插件化方案呢?具体来说,插件中的Activity,Service等组件如何在A ...

  3. Android 插件化原理解析——Hook机制之AMSPMS

    在前面的文章中我们介绍了DroidPlugin的Hook机制,也就是代理方式和Binder Hook:插件框架通过AOP实现了插件使用和开发的透明性.在讲述DroidPlugin如何实现四大组件的插件 ...

  4. Android插件化原理—ClassLoader加载机制

    前面<Android 插件化原理学习 -- Hook 机制之动态代理>一文中我们探索了一下动态代理 hook 实现了 启动没有在 AndroidManifest.xml 中显式声明的 Ac ...

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

    Android插件化原理,从以下三个问题切入: 什么是插件化 如何实现插件类的加载 如何实现插件资源的加载 什么是插件化 插件化技术最初是源于免安装运行APK的想法,这个免安装的APK就可以理解为插件 ...

  6. Android插件化原理(一)Activity插件化

    title: " Android插件化原理(一)Activity插件化" date: 2018-5-28 00:16 photos: https://s2.ax1x.com/201 ...

  7. android插件化原理

    最近几年移动开发业界兴起了「 插件化技术 」的旋风,各个大厂都推出了自己的插件化框架,各种开源框架都评价自身功能优越性,令人目不暇接.随着公司业务快速发展,项目增多,开发资源却有限,如何能在有限资源内 ...

  8. Android插件化原理解析——概要

    2015年是Android插件化技术突飞猛进的一年,随着业务的发展各大厂商都碰到了Android Native平台的瓶颈: 从技术上讲,业务逻辑的复杂导致代码量急剧膨胀,各大厂商陆续出到65535方法 ...

  9. Android 插件化原理 完胜360插件框架 技术实战

    性能优化 Android 性能优化 (一)APK高效瘦身 http://blog.csdn.net/whb20081815/article/details/70140063 Android 性能优化 ...

最新文章

  1. EXCEL保存提示“隐私问题警告:此文档中包含宏……”解决办法
  2. 量子通信产业化初试,中国筹建千亿级国家实验室
  3. python怎么安装numpy库-Python NumPy库的安装和使用
  4. 【每日SQL打卡】​​​​​​​​​​​​​​​DAY 14丨重新格式化部门表【难度中等】
  5. Android 8.0学习(8)---内核文件系统优化
  6. [luogu1880] [NOI1995]石子合并
  7. 如何在 FaceTime 通话中共享您的屏幕?
  8. MySQL多字节字符集造成主从数据不一致问题
  9. 通过Everything 快速搭建局域网内文件服务器
  10. 五个金念什么_四个“金”字读什么?
  11. 网络安全-终端安全检测和防御技术
  12. Dynamics CRM 365零基础入门学习(一)Dynamics介绍以及开发工具配置
  13. 示波器FFT频谱分析的使用方法和注意点
  14. 0x50 动态规划(练习)20:干草堆(题解)
  15. android休眠唤醒屏幕,Android屏幕的休眠和唤醒
  16. HDU 3068 2017 Multi-University Training Contest - Team 1 1006 Fuction: 循环节+最后一步
  17. Hive hql 经典5道面试题
  18. HttpRestful工具类
  19. 阿里P8架构师谈:消息中间件介绍、典型使用场景、以及使用原则
  20. PS CC2018 命令大全

热门文章

  1. 音视频开发基础知识(2)——最通俗易懂的视频编解码理论知识
  2. 银行数字化转型导师坚鹏:银行数据治理和数据质量问题解决
  3. QCustomPlot 属性介绍
  4. 微博php sdk,新浪微博PHP版SDK的导致20007错误
  5. 新手使用charles时碰到的坑(保姆级教程)
  6. c++之指针和引用作为函数参数传递时的区别
  7. 北大计算机研究生的ACM感言
  8. 淘宝客户端多应用内置
  9. Python解释器、PyCharm的安装
  10. iOS马甲包混淆方案怎么做?