diff --git "a/AdavancedPart/1.\347\203\255\344\277\256\345\244\215\345\256\236\347\216\260(\344\270\200).md" "b/AdavancedPart/1.\347\203\255\344\277\256\345\244\215\345\256\236\347\216\260(\344\270\200).md" index bf052c89..1abe582f 100644 --- "a/AdavancedPart/1.\347\203\255\344\277\256\345\244\215\345\256\236\347\216\260(\344\270\200).md" +++ "b/AdavancedPart/1.\347\203\255\344\277\256\345\244\215\345\256\236\347\216\260(\344\270\200).md" @@ -18,7 +18,7 @@ 在一般情况下,应用程序不需要创建`ClassLoader`对象,而是使用当前环境已经存在的`ClassLoader`。因为`Java`的`Runtime`环境在初始化时,其内部会创建一个`ClassLoader`对象用于加载`Runtime`所需的各种`Java`类。 每个`ClassLoader`必须有一个父类,在装载`Class`文件时,子`ClassLoader`会先请求父`ClassLoader`加载该`Class`文件,只有当其父`ClassLoader`找不到该`Class`文件时,子`ClassLoader`才会继续装载该类,这是一种安全机制。 -对于`Android`的应用程序,本质上虽然也是用`Java`开发,并且使用标准的`Java`编译器编译出`Class`文件,但最终的`APK`文件中包含的却是`dex`类型的文件。`dex`文件是将所需的所有`Class`文件重新打包,打包的规则不是简单的压缩,而是完全对`Class`文件内部的各种函数表、变量表等进行优化,并产生一个新的文件,这就是`dex`文件。由于`dex`文件是一种经过优化的`Class`文件,因此要加载这样特殊的`Class`文件就需要特殊的类装载器,这就是`DexClassLoader`,`Android SDK`中提供的D`exClassLoader`类就是出于这个目的。 +对于`Android`的应用程序,本质上虽然也是用`Java`开发,并且使用标准的`Java`编译器编译出`Class`文件,但最终的`APK`文件中包含的却是`dex`类型的文件。`dex`文件是将所需的所有`Class`文件重新打包,打包的规则不是简单的压缩,而是完全对`Class`文件内部的各种函数表、变量表等进行优化,并产生一个新的文件,这就是`dex`文件。由于`dex`文件是一种经过优化的`Class`文件,因此要加载这样特殊的`Class`文件就需要特殊的类装载器,这就是`DexClassLoader`,`Android SDK`中提供的`DexClassLoader`类就是出于这个目的。 总体来说,`Android` 默认主要有三个`ClassLoader`: @@ -75,7 +75,7 @@ public class DexClassLoader extends BaseDexClassLoader { } ``` 注释说的太明白了,这里就不翻译了,但是我们并没有找到加载的代码,去它的父类中查找, -因为家在都是从`loadClass()`方法中,所以我们去`ClassLoader`类中看一下`loadClass()`方法: +因为加载都是从`loadClass()`方法中,所以我们去`ClassLoader`类中看一下`loadClass()`方法: ```java /** * Loads the class with the specified name. Invoking this method is @@ -128,7 +128,7 @@ public class DexClassLoader extends BaseDexClassLoader { if (clazz == null) { ClassNotFoundException suppressed = null; try { - // 先检查父ClassLoader是否已经家在过该类 + // 先检查父ClassLoader是否已经加载过该类 clazz = parent.loadClass(className, false); } catch (ClassNotFoundException e) { suppressed = e; diff --git a/AdavancedPart/AOP.md b/AdavancedPart/AOP.md new file mode 100644 index 00000000..c6e0916a --- /dev/null +++ b/AdavancedPart/AOP.md @@ -0,0 +1,141 @@ +AOP +--- + + +AOP(Aspect Oriented Programing),面向切面编程。 +是OOP(Object Oriented Programing)面向对象编程的延续。 + +在OOP思想中,我们会把问题划分为各个模块,如语言、表情等。 +在划分这些模块的过程中,也会出现一些共同特征(如埋点)。它的逻辑被分散到了各个模块,导致了代码复杂度提高,可复用性降低。 + +而AOP,就是将各个模块中的通用逻辑抽离出来。 +我们将这些逻辑视为Aspect(切面),然后动态地把代码插入到类的指定方法、指定位置中。 + +一句话概括: 在运行时,动态的将代码切入到类的指定方法、指定位置上的编程思想就是面相切面的编程。 + + +一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。 + + +### AOP的实现方式 + +#### 静态AOP + +在编译器,切面直接以字节码的形式编译到目标字节码文件中。 + +1. AspectJ +AspectJ属于静态AOP,它是在编译时进行增强,会在编译时期将AOP逻辑织入到代码中。 + +由于是在编译器织入,所以它的优点是不影响运行时性能,缺点是不够灵活。 + +2. AbstractProcessor +自定义一个AbstractProcessor,在编译期去解析编译的类,并且根据需求生成一个实现了特定接口的子类(代理类) + +#### 动态AOP +1. JDK动态代理 +通过实现InvocationHandler接口,可以实现对一个类的动态代理,通过动态代理可以生成代理类,从而在代理类方法中,在执行被代理类方法前后,添加自己的实现内容,从而实现AOP。 + +2. 动态字节码生成 +在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中,没有接口也可以织入,但扩展类的实例方法为final时,则无法进行织入。比如Cglib + +CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB是一个好的选择。 + +3. 自定义类加载器 +在运行期,目标加载前,将切面逻辑加到目标字节码里。如:Javassist + +Javassist是可以动态编辑Java字节码的类库。它可以在Java程序运行时定义一个新的类,并加载到JVM中;还可以在JVM加载时修改一个类文件。 + +4. ASM +ASM可以在编译期直接修改编译出的字节码文件,也可以像Javassit一样,在运行期,类文件加载前,去修改字节码。 + + + +### AOP核心概念 + +###### 1.JoinPoint(连接点) + +程序执行过程中可以被“插入”的点。例如: + +- 方法被调用时 +- 方法执行时 +- 构造函数被调用时 +- 字段被读取/赋值时 +- 异常抛出时 + + +###### 2.PointCut(切点) + +对JoinPoint的筛选规则,告诉AOP“我要在哪些点下手”。 + +例如: 所有Activity的onCreate方法就是一个切点。 + +###### 3. Advice(通知/增强) + +在切点处要执行的代码。分5种: + +- @Before: 切点执行前 +- @After: 切点执行后(无论成功失败) +- @AfterReturning: 切点正常返回后 +- @AfterThrowing: 切点抛出异常后 +- @Around: 包裹切点(最强大,可控制是否执行原方法) + +###### 4. Aspect(切面) + +切点 + 通知 = 切面 + +一个Java类用@Aspect标记后就是切面。 + +##### 5. Weaving(织入) + +把切面代码插入到目标代码中的过程。 织入时机有三种: + +- 编译期织入(AspectJ主流方式) +- 类加载期织入 +- 运行期织入(JDK/CGLIB动态代理,Spring用的多) + +###### 6. Target(目标对象) + +被切面影响的对象 + + +### AOP在Android的实现原理 + +###### Java/Spring的AOP vs Android的AOP + +Spring主要用运行时动态代理(JDK Porxy/ CGLIB),但Android由于: + +- ART/Dalvik虚拟机限制 +- 性能要求高,运行时反射代价大 +- 类加载机制不同 + +所以Android几乎都用AspectJ编译期自己码织入。 + +###### 编译期织入原理 + + +``` + ┌─────────────┐ javac ┌─────────────┐ ajc(AspectJ) ┌──────────────┐ + │ .java │ ──────────> │ .class │ ──────────────> │ 织入后.class │ + └─────────────┘ └─────────────┘ └──────────────┘ + ↑ + ┌─────────────┐ + │ Aspect.java │ + └─────────────┘ +``` + +ajc(AspcetJ Compiler)会: + +1. 扫描所有.class,找匹配切点的位置 +2. 按通知类型,在该位置直接修改字节码插入代码 +3. 输出新的.class + +这意味着: AOP的开销在编译期,运行期几乎无额外开销(仅多几行字节码)。 + + + + + + + + + diff --git "a/AdavancedPart/ART\344\270\216Dalvik.md" "b/AdavancedPart/ART\344\270\216Dalvik.md" deleted file mode 100644 index 84fa0fea..00000000 --- "a/AdavancedPart/ART\344\270\216Dalvik.md" +++ /dev/null @@ -1,48 +0,0 @@ -ART与Dalvik -=== - - - -Dalvik ---- - -`Dalvik`是`Google`公司自己设计用于`Android`平台的`Java`虚拟机。它可以支持已转换为`.dex`(即`Dalvik Executable`)格式的`Java`应用程序的运行, -`.dex`格式是专为`Dalvik`设计的一种压缩格式,适合内存和处理器速度有限的系统。`Dalvik`经过优化,允许在有限的内存中同时运行多个虚拟机的实例, -并且每一个`Dalvik`应用作为一个独立的`Linux`进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。 -很长时间以来,`Dalvik`虚拟机一直被用户指责为拖慢安卓系统运行速度不如`IOS`的根源。 -`2014`年`6`月`25`日,`Android L`正式亮相于召开的谷歌`I/O`大会,`Android L`改动幅度较大,谷歌将直接删除`Dalvik`,代替它的是传闻已久的`ART`。 - - -ART ---- - -`Android 4.4`提供了一种与`Dalvik`截然不同的运行环境`ART`支持,`ART`源于`google`收购的`Flexycore`的公司。 -`ART`模式与`Dalvik`模式最大的不同在于,启用`ART`模式后,系统在安装应用的时候会进行一次预编译,将字节码转换为机器语言存储在本地, -这样在运行程序时就不会每次都进行一次编译了,执行效率也大大提升。 - -`ART`使用`AOT(Ahead Of Time)`(静态编译)而`Dalvik`使用`JIT(Just In Time)`(动态编译) -`JIT`方式会在程序执行时将`Dex bytecode`(`java`字节码)转换为处理器可以理解的本地代码,这种方式会将编译时间计入程序的执行时间,程序执行会显得慢一些。`AOT`方式会在程序执行之前(一般是安装时)就编译好本地代码,因此程序执行时少了编译的过程会显得快一些,但占用更多存储空间,安装时也会更慢。但没针对ART优化的程序反而会运行得更慢,随着`Android L`的普及这个问题迟早会解决。`ART`拥有改进的`GC`(垃圾回收)机制:`GC`时更少的暂停时间、`GC`时并行处理、某些时候`Collector`所需时间更短、减少内存不足时触发GC的次数、减少后台内存占用。 -在移除解释代码这一过程后,应用程序执行将更有效率,启动更快。总体的理念就是空间换时间。 - - -`AOT`的编译器分两种模式: -- 在开发机上编译预装应用; -- 在设备上编译新安装的应用,在应用安装时将`dex`字节码翻译成本地机器码。 - -ART优点: -- 系统性能的显著提升。 -- 应用启动更快、运行更快、体验更流畅、触感反馈更及时。 -- 更长的电池续航能力。 -- 支持更低的硬件。 - -ART缺点: -- 更大的存储空间占用,可能会增加10%-20%。 -- 更长的应用安装时间。 - - - - ---- - -- 邮箱 :charon.chui@gmail.com -- Good Luck! \ No newline at end of file diff --git a/AdavancedPart/ARouter.md b/AdavancedPart/ARouter.md new file mode 100644 index 00000000..f9d9cc9f --- /dev/null +++ b/AdavancedPart/ARouter.md @@ -0,0 +1,71 @@ +ARouter +--- + + +[ARouter](https://github.com/alibaba/ARouter) +一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦 + + + + +```java +Intent intent = new Intent(mContext, XxxActivity.class); +intent.putExtra("key","value"); +startActivity(intent); + +Intent intent = new Intent(mContext, XxxActivity.class); +intent.putExtra("key","value"); +startActivityForResult(intent, 666); +``` +在未使用ARouter路由框架之前的原生页面跳转方式。 + + +## 原生路由方案的缺点 + +1. 显式: 直接的类依赖,耦合严重 +2. 隐式: 会在Manifest文件中进行集中管理,写作困难 + + +## ARouter的优势 + +1. 使用注解,实现了映射关系自动注册与分布式路由管理 +2. 编译期间处理注解,并生成映射文件,没有使用反射,不影响运行时性能 +3. 映射关系按组分类,分级管理,按需初始化。 +4. 灵活的降级策略,每次跳转都会回调跳转结果,避免startActivity()一旦失败会抛出异常 +5. 自定义拦截器,自定义拦截顺序,可以对路由进行拦截,比如登录判断和埋点处理 +6. 支持依赖注入,可单独作为依赖注入框架使用,从而实现跨模块API调用 +7. 支持直接解析标准url进行跳转,并自动注入参数到目标页面中 +8. 支持多模块使用,支持组件化开发 + + + +## 基本使用 + +添加依赖并初始化后的基本使用: + +```java +// 在支持路由的页面上添加注解(必选) +// 这里的路径需要注意的是至少需要有两级,/xx/xx +@Route(path = "/test/activity") +public class YourActivity extend Activity { + ... +} + +// 1. 应用内简单的跳转(通过URL跳转在'进阶用法'中) +ARouter.getInstance().build("/test/activity").navigation(); + +// 2. 跳转并携带参数 +ARouter.getInstance().build("/test/1") + .withLong("key1", 666L) + .withString("key3", "888") + .withObject("key4", new Test("Jack", "Rose")) + .navigation(); +``` + + + + + + + + diff --git "a/AdavancedPart/AndroidRuntime_ART\344\270\216Dalvik.md" "b/AdavancedPart/AndroidRuntime_ART\344\270\216Dalvik.md" new file mode 100644 index 00000000..7efca459 --- /dev/null +++ "b/AdavancedPart/AndroidRuntime_ART\344\270\216Dalvik.md" @@ -0,0 +1,161 @@ +AndroidRuntime_ART与Dalvik +=== +在说Android Runtime之前,我们需要了解什么是运行时环境,还需要了解一些基本知识,即JVM和Dalvik VM的功能。 + +## Runtime + +用最简单的术语来说,它是操作系统使用的系统,它负责将您用Java之类的高级语言编写的代码转换为CPU/处理器能理解机器代码。 + +运行时包含在程序运行时执行的软件指令,即使它们实际上并不是该软件代码的一部分也是如此。 +CPU或更笼统的术语我们的计算机仅理解机器语言(二进制代码),因此要使其在CPU上运行,必须将代码转换为机器代码,这由翻译器完成。 +因此,以下是按顺序生成翻译器的过程: +- Assemblers + 它可以直接将汇编代码转换为机器代码,因此速度非常快。 +- Compilers + 它将代码转换为汇编代码,然后使用汇编程序将代码转换为二进制。使用此编译速度很慢,但是执行速度很快。但是编译器的最大问题是所生成的机器代码取决于平台。换句话说,在一台计算机上运行的代码可能不会在另一台计算机上运行。 +- Interpreters + 它在执行代码时翻译代码。由于翻译是在运行时进行的,因此执行速度很慢。 +### JVM +为了维持代码的平台独立性,JAVA开发了JVM,即Java虚拟机。它针对每个平台开发了JVM,这意味着JVM依赖于该平台。Java编译器将.java文件转换为.class文件,这称为字节码。该字节码被提供给JVM,该JVM将其转换为机器码。 + +### Android Runtime + +当我们构建应用程序并生成APK时,该APK的一部分是.dex文件。这些文件包含我们应用程序的源代码,包括我们在为软件解释器设计的低级代码(字节码)中使用的所有库。 + +当用户运行我们的应用程序时,写入的.dex文件中的字节码将由Android Runtime转换为机器码—一组指令,机器可以直接理解并由CPU处理。 + +![](https://raw.githubusercontent.com/CharonChui/Pictures/master/dex_local_code.png) + +Android Runtime同样也回管理内存及垃圾回收。 + + + +![](https://raw.githubusercontent.com/CharonChui/Pictures/master/jvm_dvm.png) + + + +可以使用各种策略将字节码编译为机器代码,所有这些策略都有其取舍。为了了解Android Runtime的工作原理,需要首先了解Dalvik。 + + + +## Android Runtime的发展 + +### Dalvik(<= Android K) + +`Dalvik`是`Google`公司自己设计用于`Android`平台的`Java`虚拟机。它可以支持已转换为`.dex`(即`Dalvik Executable`)格式的`Java`应用程序的运行, +`.dex`格式是专为`Dalvik`设计的一种压缩格式,适合内存和处理器速度有限的系统。`Dalvik`经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个`Dalvik`应用作为一个独立的`Linux`进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。 +很长时间以来,`Dalvik`虚拟机一直被用户指责为拖慢安卓系统运行速度不如`IOS`的根源。 +`2014`年`6`月`25`日,`Android L`正式亮相于召开的谷歌`I/O`大会,`Android L`改动幅度较大,谷歌将直接删除`Dalvik`,代替它的是传闻已久的`ART`。 + + + +早期,Android智能手机并不像现在那么强大。大多数手机的RAM很少,有些甚至只有200MB。 +难怪第一个被称为Dalvik的Android Runtime的实现正是为了优化此参数:RAM的使用。 + +因此,它没有在运行它之前将整个应用程序编译为机器代码,而是使用了称为Just In Time编译(简称JIT)的策略。 + +在这种策略下,编译器可以充当解释器。它在应用程序执行期间(在运行时)编译一小段代码。 + +而且由于Dalvik仅编译所需的代码并在运行时执行它,因此可以节省大量RAM。 + +使用Dalvik JIT编译器,每次运行该应用程序时,它会将Dalvik字节码的一部分动态转换为机器代码。随着执行的进行,更多的字节码将被编译和缓存。由于JIT仅编译部分代码,因此它具有较小的内存占用空间,并且在设备上使用的物理空间更少。 + +但是此策略有一个严重的缺点-因为所有这些都在运行时发生,因此显然会对运行时性能产生负面影响。 + +最终,引入了一些优化以使Dalvik更具性能。一些经常使用的已编译代码段已被缓存,不再重新编译。但这是非常有限的,因为在最初的日子里内存非常稀缺。 + +![](https://raw.githubusercontent.com/CharonChui/Pictures/master/dalvik_jit.png) + +几年来运行良好,但与此同时,手机的性能越来越高,RAM也越来越多。而且由于应用程序也越来越大,因此JIT性能影响变得越来越成问题。 + +这就是为什么在Android L中引入了新的Android Runtime:ART。 + + + +### ART(Android L) + +`Android 4.4`提供了一种与`Dalvik`截然不同的运行环境`ART`支持,`ART`源于`google`收购的`Flexycore`的公司。 +`ART`模式与`Dalvik`模式最大的不同在于,启用`ART`模式后,系统在安装应用的时候会进行一次预编译,将字节码转换为机器语言存储在本地,这样在运行程序时就不会每次都进行一次编译了,执行效率也大大提升。 + +`ART`使用`AOT(Ahead Of Time)`(静态编译)而`Dalvik`使用`JIT(Just In Time)`(动态编译) +`JIT`方式会在程序执行时将`Dex bytecode`(`java`字节码)转换为处理器可以理解的本地代码,这种方式会将编译时间计入程序的执行时间,程序执行会显得慢一些。`AOT`方式会在程序执行之前(一般是安装时)就编译好本地代码,因此程序执行时少了编译的过程会显得快一些,但占用更多存储空间,安装时也会更慢。但没针对ART优化的程序反而会运行得更慢,随着`Android L`的普及这个问题迟早会解决。`ART`拥有改进的`GC`(垃圾回收)机制:`GC`时更少的暂停时间、`GC`时并行处理、某些时候`Collector`所需时间更短、减少内存不足时触发GC的次数、减少后台内存占用。 +在移除解释代码这一过程后,应用程序执行将更有效率,启动更快。总体的理念就是空间换时间。 + +![](https://raw.githubusercontent.com/CharonChui/Pictures/master/dvm_art.png) + +`AOT`的编译器分两种模式: + +- 在开发机上编译预装应用; +- 在设备上编译新安装的应用,在应用安装时将`dex`字节码翻译成本地机器码。 + + + +这种方法极大地提高了运行时性能,因为运行本机机器代码甚至比即时编译快20倍。 + +ART优点: + +- 系统性能的显著提升。 +- 应用启动更快、运行更快、体验更流畅、触感反馈更及时。 +- 更长的电池续航能力。 +- 支持更低的硬件。 + +ART缺点: + +- 更大的存储空间占用,可能会增加10%-20%。字节码预先编译成机器码并存储到本地,机器码需要的存储空间更大。 +- 更长的应用安装时间,因为下载APK后,整个应用程序都需要转换为机器代码,而且由于所有应用程序都需要重新优化,因此执行系统更新还需要更长的时间。 +- Android L中的ART使用的内存比Dalvik多得多。 + + + +对于应用程序中经常运行的部分来说,对其进行预编译显然是有回报的,但现实是,用户很少打开应用程序的大多数部分,而对整个应用程序进行预编译几乎也没有回报。 + +这就是为什么在Android N中,Just In Time编译与称为配置文件引导编译(profile-guided complication)一起被引入到Android Runtime的原因。 + +### Profile-guided compilation(Android N) + +配置文件引导编译是一种策略,可以在运行Android应用程序时不断提高其性能。默认情况下,应用程序使用即时编译策略进行编译,但是当ART检测到某些热点功能时,这意味着它们经常运行,ART可以预编译并缓存这些方法以获得最佳性能。 + +![](https://raw.githubusercontent.com/CharonChui/Pictures/master/art_profile_guide.png) + +该策略可为应用程序的关键部分提供最佳性能,同时减少RAM使用量。由于事实证明,对于大多数应用程序来说,通常仅使用10%到20%的代码。 + +更改ART之后,不再影响应用程序安装和系统更新的速度。应用的关键部分的预编译仅在设备空闲和充电时进行,以最大程度地减少对设备电池的影响。 + + + +这种方法的唯一缺点是,为了获取配置文件数据并预编译常用的方法和类,用户必须实际使用应用程序。这意味着该应用程序的一些首次使用可能会有点慢,因为在这种情况下,将仅使用即时编译。 + +这就是为什么要改善在Android P中的初始用户体验的原因,Google在云中引入了个人资料。 + +### Profiles in the cloud(Android P) + +云中的配置文件背后的主要思想是,大多数人以非常相似的方式使用该应用程序。因此,为了在安装后立即提高性能,我们可以从已经使用过此应用程序的人那里收集配置文件数据。此汇总的配置文件数据用于为应用程序创建一个称为通用核心配置文件的文件。 + +![](https://raw.githubusercontent.com/CharonChui/Pictures/master/profiles_in_cloud.png) + +因此,当新用户安装该应用程序时,该文件将与该应用程序一起下载。 ART使用它来预编译大多数用户经常运行的类和方法。这样,新用户在下载应用程序后即可获得更好的性能。 + +这并不意味着不再使用旧策略。用户运行应用程序后,ART将收集用户特定的配置文件数据并重新编译设备闲置时该特定用户经常使用的代码。 + +而最好的部分是,我们开发应用程序的开发人员无需执行任何操作即可启用此功能。这一切都发生在Android Runtime中。 + +![](https://raw.githubusercontent.com/CharonChui/Pictures/master/android_runtime_change.png) + + + +- 最初Android使用Dalvik,它使用即时编译(JIT)来优化RAM(那时内存是非常紧缺的)的使用。 +- 为了提高Android L中的性能,引入了使用Ahead of time编译的ART。这样可以实现更好的运行时性能,但会导致更长的安装时间和更多的RAM使用率。 +- 因此,在Android N中,JIT被引入到ART中,并且配置文件引导的编译允许为经常运行的部分代码提供更好的性能。 +- 为了让用户在Android P中安装应用后立即获得最佳性能,Google在云端引入了配置文件,它通过添加随APK下载的通用核心配置文件来补充以前的优化,并允许ART预编译部分代码最常由以前的应用程序用户运行。 + + + + + +[Android Runtime-How Dalvik and ART works?](https://www.youtube.com/watch?v=0J1bm585UCc&t=27s) + + +--- + +- 邮箱 :charon.chui@gmail.com +- Good Luck! diff --git "a/AdavancedPart/Android\345\220\257\345\212\250\346\250\241\345\274\217\350\257\246\350\247\243.md" "b/AdavancedPart/Android\345\220\257\345\212\250\346\250\241\345\274\217\350\257\246\350\247\243.md" index 25d0e1be..5008840f 100644 --- "a/AdavancedPart/Android\345\220\257\345\212\250\346\250\241\345\274\217\350\257\246\350\247\243.md" +++ "b/AdavancedPart/Android\345\220\257\345\212\250\346\250\241\345\274\217\350\257\246\350\247\243.md" @@ -29,17 +29,37 @@ Android启动模式详解 - 如果设置了`singleTask`启动模式的`Activity`不是在新的任务中启动时,它会在已有的任务中查看是否已经存在相应的`Activity`实例,如果存在,就会把位于这个`Activity`实例上面的`Activity`全部结束掉, 即最终这个Activity实例会位于任务的堆栈顶端中。以`A`启动`B`来说,当`A`和`B`的`taskAffinity`不同时:第一次创建`B`的实例时,会启动新的`task`,然后将`B`添加到新建的`task`中;否则,将`B`所在`task`中位于`B`之上的全部`Activity`都删除,然后跳转到`B`中。 - `singleInstance` + 顾名思义,是单一实例的意思,即任意时刻只允许存在唯一的`Activity`实例,而且该`Activity`所在的`task`不能容纳除该`Activity`之外的其他`Activity`实例! -它与`singleTask`有相同之处,也有不同之处。 -相同之处:任意时刻,最多只允许存在一个实例。 -不同之处: - - `singleTask`受`android:taskAffinity`属性的影响,而`singleInstance`不受`android:taskAffinity`的影响。 - - `singleTask`所在的`task`中能有其它的`Activity`,而`singleInstance`的`task`中不能有其他`Activity`。 - - 当跳转到`singleTask`类型的`Activity`,并且该`Activity`实例已经存在时,会删除该`Activity`所在`task`中位于该`Activity`之上的全部`Activity`实例;而跳转到`singleInstance`类型的`Activity`,并且该`Activity`已经存在时, - 不需要删除其他`Activity`,因为它所在的`task`只有该`Activity`唯一一个`Activity`实例。 +它与`singleTask`有相同之处,也有不同之处。 +相同之处: 任意时刻,最多只允许存在一个实例。 +不同之处: + +- `singleTask`受`android:taskAffinity`属性的影响,而`singleInstance`不受`android:taskAffinity`的影响。 +- `singleTask`所在的`task`中能有其它的`Activity`,而`singleInstance`的`task`中不能有其他`Activity`。 +- 当跳转到`singleTask`类型的`Activity`,并且该`Activity`实例已经存在时,会删除该`Activity`所在`task`中位于该`Activity`之上的全部`Activity`实例;而跳转到`singleInstance`类型的`Activity`,并且该`Activity`已经存在时,不需要删除其他`Activity`,因为它所在的`task`只有该`Activity`唯一一个`Activity`实例。 + + +假设我们的程序中有一个Activity是允许其他程序调用的,如果想实现其他程序和我们的程序可以共享这个Activity的实例,应该如何实现呢? + +使用前面3种启动模式肯定是做不到的,因为每个应用程序都会有自己的返回栈,同一个Activity在不同的返回栈中入栈时必然创建了新的实例。 +而使用singleInstance模式就可以解决这个问题,在这种模式下,会有一个单独的返回栈来管理这个Activity,不管是哪个应用程序来访问这个Activity,都共用同一个返回栈,也就解决了共享Activity实例的问题。 + +假设现在有FirstActivity、SecondActivity、ThirdActivity三个Activity, SecondActivity的启动模式是SingleInstance。 +现在FirstActivity 启动SecondActivity,SecondActivity再启动ThirdActivity。 + +然后我们按下Back键进行返回,你会发现ThirdActivity竟然直接返回到了FirstActivity,再按下Back键又会返回到SecondActivity,再按下Back键才会退出程序,这是为什么呢?其实原理很简单,由于FirstActivity和ThirdActivity是存放在同一个返回栈里的,当在ThirdActivity的界面按下Back键时,ThirdActivity会从返回栈中出栈,那么FirstActivity就成为了栈顶Activity显示在界面上,因此也就出现了从ThirdActivity直接返回到FirstActivity的情况。然后在FirstActivity界面再次按下Back键,这时当前的返回栈已经空了,于是就显示了另一个返回栈的栈顶Activity,即SecondActivity。最后再次按下Back键,这时所有返回栈都已经空了,也就自然退出了程序。 + + +![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/activity_launch_mode_singleinstance.png?raw=true) + + + + + --- - 邮箱 :charon.chui@gmail.com -- Good Luck! \ No newline at end of file +- Good Luck! diff --git a/AdavancedPart/ApplicationId vs PackageName.md b/AdavancedPart/ApplicationId vs PackageName.md index 62829a21..35dc2f0a 100644 --- a/AdavancedPart/ApplicationId vs PackageName.md +++ b/AdavancedPart/ApplicationId vs PackageName.md @@ -7,7 +7,7 @@ ApplicationId vs PackageName 在`Android`官方文档中有一句是这样描述`applicationId`的:`applicationId : the effective packageName`,真是言简意赅,那既然`applicationId`是有效的包明了,`packageName`算啥? -所有`Android`应用都有一个包名。包名在设备上能唯一的标示一个应用,它在`Google Play`应用商店中也是唯一的。这就意味着一旦你使用一个包名发布应用后,你就永 远不能改变它的包名;如果你改了包名就会导致你的应用被认为是一个新的应用,并且已经使用你之前应用的用户将不会看到作为更新的新应用包。 +所有`Android`应用都有一个包名。包名在设备上能唯一的标识一个应用,它在`Google Play`应用商店中也是唯一的。这就意味着一旦你使用一个包名发布应用后,你就永 远不能改变它的包名;如果你改了包名就会导致你的应用被认为是一个新的应用,并且已经使用你之前应用的用户将不会看到作为更新的新应用包。 之前的`Android Gradle`构建系统中,应用的包名是由你的`manifest`文件中的根元素中的`package`属性定义的: @@ -86,4 +86,4 @@ buildTypes { --- - 邮箱 :charon.chui@gmail.com -- Good Luck! \ No newline at end of file +- Good Luck! diff --git "a/AdavancedPart/BroadcastReceiver\345\256\211\345\205\250\351\227\256\351\242\230.md" "b/AdavancedPart/BroadcastReceiver\345\256\211\345\205\250\351\227\256\351\242\230.md" index 1485641a..0a6a7f1a 100644 --- "a/AdavancedPart/BroadcastReceiver\345\256\211\345\205\250\351\227\256\351\242\230.md" +++ "b/AdavancedPart/BroadcastReceiver\345\256\211\345\205\250\351\227\256\351\242\230.md" @@ -5,7 +5,7 @@ BroadcastReceiver安全问题 - 保证发送的广播要发送给指定的对象 当应用程序发送某个广播时系统会将发送的`Intent`与系统中所有注册的`BroadcastReceiver`的`IntentFilter`进行匹配,若匹配成功则执行相应的`onReceive`函数。可以通过类似`sendBroadcast(Intent, String)`的接口在发送广播时指定接收者必须具备的`permission`或通过`Intent.setPackage`设置广播仅对某个程序有效。 -- 保证我接收到的广播室指定对象发送过来的 +- 保证我接收到的广播是指定对象发送过来的 当应用程序注册了某个广播时,即便设置了`IntentFilter`还是会接收到来自其他应用程序的广播进行匹配判断。对于动态注册的广播可以通过类似`registerReceiver(BroadcastReceiver, IntentFilter, String, android.os.Handler)`的接口指定发送者必须具备的`permission`,对于静态注册的广播可以通过`android:exported="false"`属性表示接收者对外部应用程序不可用,即不接受来自外部的广播。 `android.support.v4.content.LocalBroadcastManager`工具类,可以实现在自己的进程内进行局部广播发送与注册,使用它比直接通过sendBroadcast(Intent)发送系统全局广播有以下几个好处: @@ -46,4 +46,4 @@ protected void onDestroy() { --- - 邮箱 :charon.chui@gmail.com -- Good Luck! \ No newline at end of file +- Good Luck! diff --git "a/AdavancedPart/Componse\344\275\277\347\224\250.md" "b/AdavancedPart/Componse\344\275\277\347\224\250.md" new file mode 100644 index 00000000..6a2f0449 --- /dev/null +++ "b/AdavancedPart/Componse\344\275\277\347\224\250.md" @@ -0,0 +1,130 @@ +Compose使用 +=== + +传统 Android UI 开发采用命令式模型,即通过命令驱动视图变化:findViewById 查找控件、设置属性、处理交互逻辑。代码经常伴随着多个职责耦合在一起,结构混乱,易错难测。 + +Compose 则采用声明式模型:界面即状态的函数表达。当状态改变时,对应的 Composable 自动重新组合(Recompose)并刷新界面。这种模式更贴近现代前端(如 React/Vue)的理念。 + +无需关心视图更新逻辑,只要状态变化,界面自然重绘,大幅降低 UI 层复杂度。 + + +1.3 开发效率提升点 + +代码量平均减少 40%-60% + +无需 ViewHolder、Adapter 逻辑 + +状态与 UI 同步更新,避免 UI 状态丢失 + +支持实时预览(@Preview)、热重载、即时调试 + + +我们在公司项目中构建了性能对比 Benchmark(测试设备为 Pixel 6、Android 13): + +2.1 滚动列表对比:RecyclerView vs LazyColumn + +指标 RecyclerView LazyColumn (Compose) 差异 +平均帧率 +48 fps +58 fps ++20% +内存占用 +28 MB +22 MB +-21% +首次绘制耗时 +320 ms +210 ms +-34% + + +2.2 原因解析:Compose 更快的秘密 + +SlotTable:结构快照树 +Compose 编译器会将 Composable 函数转换为组装 SlotTable 的代码。SlotTable 是一种高效的数据结构,存储了 Composable 树的结构快照。当状态发生变化时,Compose 通过对比 SlotTable 的版本,精确地定位变化范围,从而进行最小代价的重组操作(recomposition)。这一过程通过 Composer 对 Slot 表的操作实现,避免了冗余 UI 节点更新。 + +重组与 Group 管理机制 +Compose 使用 Group(startGroup/endGroup)对 Composable 调用进行打包与标识,每个重组区域会通过重新执行对应的 Group 来进行更新,确保仅变更部分被执行。此机制在 RecomposeScopeImpl 中有体现,它能追踪每个状态依赖的作用域,从而提升重组精度。 + +无需 ViewHolder 回收 +传统 RecyclerView 需要手动管理视图缓存与回收,而 Compose 自动处理 Composition 节点生命周期。Compose Compiler 会生成高效的 Slot 操作指令,通过“skip、reuse”策略对 UI 层进行精准控制,避免重复创建与销毁。 + +Skia 图形引擎与 RenderNode +Compose 绘制层基于 Skia 引擎,使用 DrawModifier 直接对 Canvas 进行渲染。它不会像传统 View 那样层层嵌套测量布局与绘制流程,而是采用测量(MeasurePass)-> 布局(LayoutPass)-> 绘制(DrawPass)的管线逻辑,通过 LayoutNode 驱动 Compose UI 树的变化。同时 Compose Layout 使用 SubcomposeLayout 实现异步测量能力,提高复杂嵌套组件的性能表现。 + +渲染流程对比 +阶段 View System Compose +布局树管理 +View/ViewGroup 层级 +LayoutNode 节点 +渲染方式 +Choreographer + RenderThread +FrameClock + Skia 渲染 +状态追踪 +手动触发 invalidate +Snapshot 自动追踪 + Diff Patch +更新路径 +requestLayout → measure/layout +Recomposer + SlotTable 重组 + +注意:Compose 并非所有场景都一定更快,特别是复杂嵌套、过度组合场景仍需谨慎使用。 + +### 简介 + +Jetpack Compose 是用于构建 Android 界面的新款工具包。 + + +Jetpack Compose 是用于构建原生 Android 界面的新工具包。它使用更少的代码、强大的工具和直观的 Kotlin API,可以帮助您简化并加快 Android 界面开发,打造生动而精彩的应用。它可让您更快速、更轻松地构建 Android 界面。 + + +编写更少的代码会影响到所有开发阶段:作为代码撰写者,需要测试和调试的代码会更少,出现 bug 的可能性也更小,您就可以专注于解决手头的问题;作为审核人员或维护人员,您需要阅读、理解、审核和维护的代码就更少。 + +与使用 Android View 系统(按钮、列表或动画)相比,Compose 可让您使用更少的代码实现更多的功能。无论您需要构建什么内容,现在需要编写的代码都更少了。以下是我们的一些合作伙伴的感想: + +“对于相同的 Button 类,代码的体量要小 10 倍。”(Twitter) +“使用 RecyclerView 构建的任何屏幕(我们的大部分屏幕都使用它构建)的大小也显著减小。”(Monzo) +““只需要很少几行代码就可以在应用中创建列表或动画,这一点令我们非常满意。对于每项功能,我们编写的代码行更少了,这让我们能够将更多精力放在为客户提供价值上。”(Cuvva) +编写代码只需要采用 Kotlin,而不必拆分成 Kotlin 和 XML 部分:“当所有代码都使用同一种语言编写并且通常位于同一文件中(而不是在 Kotlin 和 XML 语言之间来回切换)时,跟踪变得更容易”(Monzo) + +无论您要构建什么,使用 Compose 编写的代码都很简洁且易于维护。“Compose 的布局系统在概念上更简单,因此可以更轻松地推断。查看复杂组件的代码也更轻松。”(Square) + + + +Compose 使用声明性 API,这意味着您只需描述界面,Compose 会负责完成其余工作。这类 API 十分直观 - 易于探索和使用:“我们的主题层更加直观,也更加清晰。我们能够在单个 Kotlin 文件中完成之前需要在多个 XML 文件中完成的任务,这些 XML 文件负责通过多个分层主题叠加层定义和分配属性。”(Twitter) + + +Compose 与您所有的现有代码兼容:您可以从 View 调用 Compose 代码,也可以从 Compose 调用 View。大多数常用库(如 Navigation、ViewModel 和 Kotlin 协程)都适用于 Compose,因此您可以随时随地开始采用。“我们集成 Compose 的初衷是实现互操作性,我们发现这件事情已经‘水到渠成’。我们不必考虑浅色模式和深色模式等问题,整个体验无比顺畅。”(Cuvva) + + +为现有应用设置 Compose + +首先,使用 Compose Compiler Gradle 插件配置 Compose 编译器。 + +然后,将以下定义添加到应用的 build.gradle 文件中: +``` +android { + buildFeatures { + compose = true + } +} +``` +在 Android BuildFeatures 代码块内将 compose 标志设置为 true 会在 Android Studio 中启用 Compose 功能。 + + +Compose vs HarmonyOS ArkUI 对比分析 + + +Jetpack Compose 和 HarmonyOS ArkUI 均采用声明式 UI 编程范式,面向多设备场景的响应式 UI 构建,二者在理念相通的同时,在架构设计、状态模型、渲染机制等方面有显著区别。 + + +![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/compose_vs_arkui.png?raw=true) + + +Jetpack Compose 是围绕可组合函数构建的。这些函数可让您以程序化方式定义应用的界面,只需描述应用界面的外观并提供数据依赖项,而不必关注界面的构建过程(初始化元素、将其附加到父项等)。如需创建可组合函数,只需将 @Composable 注解添加到函数名称中即可。 + + + +--- + +- 邮箱 :charon.chui@gmail.com +- Good Luck! \ No newline at end of file diff --git "a/AdavancedPart/ConstraintLaayout\347\256\200\344\273\213.md" "b/AdavancedPart/ConstraintLaayout\347\256\200\344\273\213.md" index d765862d..e3ccc9fc 100644 --- "a/AdavancedPart/ConstraintLaayout\347\256\200\344\273\213.md" +++ "b/AdavancedPart/ConstraintLaayout\347\256\200\344\273\213.md" @@ -12,8 +12,6 @@ ConstraintLaayout简介 implementation 'com.android.support.constraint:constraint-layout:1.1.0' ``` - - 然后会提示添加`ConstraintLayout`支持库。 @@ -95,7 +93,7 @@ implementation 'com.android.support.constraint:constraint-layout:1.1.0' * layout_constraintVertical_bias ``` - 下面这段代码就是让左边占30%,右边占70%(默认两边各占50%),这样左边就会短一些,如图5所示,此时代码是这样的: + 下面这段代码就是让左边占30%,右边占70%(默认两边各占50%),这样左边就会短一些,此时代码是这样的: ```xml