├── Android编译过程 ├── java文件简要编译流程.png ├── jit-workflow.png ├── 构建流程.png ├── 理解Android编译过程.md └── 页错误.png ├── Glide源码解析(1)-- 加载流程 ├── ActiveResources.get.png ├── ByteBufferFileLoader.loadData.png ├── DecodeHelper.getLoadData.png ├── DecodeJob.getNextGenerator.png ├── DecodeJob.notifyEncodeAndRelease.png ├── DecodeJob.onResourceDecoded.png ├── DecodeJob.ready.png ├── DecodePath.decodeResourceWithList.png ├── Engine.getjob.png ├── Glide.Glide.png ├── Glide.mdj ├── RequestBuilder.into.png ├── ResourceCacheGenerator.startNext.png ├── SourceGenerator.startNext.png ├── StreamLocalUriFetcher.loadResourceFromUri.png ├── decodeJob.runGenerators.png ├── decodeJob.runWrapped.png ├── decodeJob.willDecodeFromCache.png ├── engine.loadFromActiveResources.png ├── engine.loadFromCache.png ├── engine.onResourceReleased.png ├── engine.start.png ├── engine.startJob.png ├── glide.md └── 时序图-load.png ├── Glide源码解析(2)-- 缓存和对象池 ├── DiskCacheWrapper.get.png ├── DiskLruCache.open.png ├── DiskLruCacheWrapper.put.png ├── DownSampler.decode.png ├── Glide-缓存和对象池.md ├── GroupedLinkedMap.get.png ├── LruArrayPool.get.png ├── LruBitmapPool.clearMemory.png ├── LruBitmapPool.get.png ├── LruBitmapPool.getDirtyOrNull.png ├── LruBitmapPool.getMaxSize.png ├── LruBitmapPool.put.png ├── LruBitmapPool.trimMemory.png ├── MemorySizeCalculator.getMaxSize.png ├── MemorySizeCalculator.png ├── MemorySzieCalculator.isLowMemoryDevice.png └── SizeConfigStrategy.get.png ├── LICENSE ├── README.md ├── RecycleView性能优化(持续更新中) └── RecycleView性能优化.md ├── Shadow源码解析笔记 ├── Shadow源码解析.md └── pic │ └── 0.png ├── _config.yml ├── 一般攻击和网络攻击 ├── attack-surfaces.png ├── osi.png └── 了解攻击.md ├── 内存公有划分与boot ├── partition-layout.png └── 内存公有划分与boot.md ├── 剖析Activity、Window、ViewRootImpl和View之间的关系 ├── Session.windowAddedLocked.png ├── SurfaceView.png ├── SurfaceView.updateWindow.png ├── ViewRootImpl与WMS.png ├── ViewTree.png ├── WMS.createSurfaceControl.png ├── WMS.relayoutWIndow.png ├── WindowManagerGlobal.addView.png ├── addwindow.png ├── startActivity.png └── 剖析Activity、Window、ViewRootImpl和View之间的关系.md ├── 架构分层详解 ├── Android-Property-Service.png ├── Android-logging-system.png ├── Framework-Managers.png ├── Linux-Changes-by-Android.png └── 深入理解Android层级.md ├── 架构和权限模型 ├── Android架构和权限模型.md ├── android架构组成.png ├── ls-l.png ├── ps.png ├── sdcard.png └── uid.png ├── 真的理解Context? ├── Activity.attach.png ├── Context使用.png ├── DecorView的Context.png ├── FragmentHostCallback.context.png ├── LayoutInflater.rinflate.png ├── createContext.png ├── need_new_task.png └── 真的理解Context?.md └── 绘图基础--Canvas与Drawable ├── Canvas、Bitmap.md ├── CustomView.draw.png ├── Drawable.createFromResourcesStream.png ├── DrawableInflater.inflateFromTag.png ├── GradientDrawable.draw.png ├── GradientDrawable.ensureValidRect.png ├── ImageView.updateDrawable.png ├── MyView.onDraw.png ├── ResourcesImpl.loadDrawableForCookie.png ├── SurfaceView.internalLockCanvas.png ├── ViewRootImpl.drawSoftware.png ├── getdrawable.png └── 效果图.png /Android编译过程/java文件简要编译流程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Android编译过程/java文件简要编译流程.png -------------------------------------------------------------------------------- /Android编译过程/jit-workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Android编译过程/jit-workflow.png -------------------------------------------------------------------------------- /Android编译过程/构建流程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Android编译过程/构建流程.png -------------------------------------------------------------------------------- /Android编译过程/理解Android编译过程.md: -------------------------------------------------------------------------------- 1 | ## Androd运行环境 2 | 3 | 标准Java编译器使用JIT技术(见注1)将源代码编译成字节码(.class文件),紧接着进行第二轮编译(是对字节码进行压缩)出的字节码可以在特定的JVM运行。对于Android会有两个不同的虚拟机: 4 | 5 | - DALVIK:对于Android 4.4及以下版本使用的虚拟机,它使用JIT编译技术,但是在应用打开时进行编译,将应用启动速度变慢。 6 | - ART:应用AOT(ahead-of-time)技术在应用安装时将一部分的字节码转化成机器码,因此应用的启动速度变快。同时ART对GC技术进行了优化,默认采用CMS(并发标记和删除,见注2)缩短用户等待的时间。ART也存在缺点:会使安装速度变慢,因为缓存了机器码导致应用占用空间变大。 7 | 8 | ## Android编译过程 9 | 10 | ![java文件简要编译流程](./java文件简要编译流程.png) 11 | 12 | 更详细的编译补充过程见注3。 13 | 14 | #### 编译字节码 15 | 16 | Android应用需要JDK来编译Java文件,通过javac命令来编译我们所写的代码,包括运行时的预编译类和三方库,输出字节码。这个步骤是一般的Java步骤,接下来才是Android特色的东西。 17 | 18 | #### 混淆(ProGuard Tool) 19 | 20 | 这个步骤是选择性开启的。Proguard Tool获取上述步骤输出的字节码,将其压缩(移除没人调用的方法)和混淆(将类名映射为无意义的名字)。 21 | 22 | 混淆大量减少包名,同时让破解者的成本大大提升。 23 | 24 | 不管用不用Proguard,这个步骤最后输出的也是.class文件(使用了就是混淆过的.class文件)。 25 | 26 | #### .class字节码转化dex字节码 27 | 28 | 取到上一步输出的字节码将其转化为dex字节码(Dalvik Executable)。dex字节码是经过优化后的代码,能在Dalvik和ART运行时执行。 29 | 30 | #### 打包Apk并签名 31 | 32 | 将上一步输出的dex字节码和资源文件通过aapt一并压缩到apk内,通过jarsigner(标准签名工具)或zipalign(将字节码对齐)等工具进行签名。 33 | 34 | #### 运行apk 35 | 36 | Zygote进程负责启动app。Zygote进程包含了所有应用公用的核心仓库,使用linux系统的fork命令来创建(非常快)一个自己的复制进程用于运行apk,这种方法会比启动新一个新进程再加载系统文件快得多。 37 | 38 | #### 执行机器码 39 | 40 | Android运行时会读取上一步输出apk中的dex字节码,为了获得更快的执行速度,需要重新编译成机器码,可以成为ELF文件,在Android平台上是OAT文件。OAT文件会通过页映射到进程的内存里,然后把CPU执行入口地址改为该进程的内存地址开始执行(见注4),具体可以参考《程序员的自我修养—链接、装载与库》。 41 | 42 | 在Dalvki上这步在应用启动时使用JIT技术实现; 43 | 44 | 而在ART上,这步发生在app安装时,所以当启动应用时,速度会更快。 45 | 46 | 到这里位置,应用就启动主Activity并显示到屏幕上,具体可参考应用启动流程。 47 | 48 | ## 总结 49 | 50 | 从编写Java代码开始,经过一系列转换和编译,最终生成Android能执行的机器码,这个中间发生的东西还有很多,这里给了个大体流程。 51 | 52 | ## 注 53 | 54 | #### 1.JIT 55 | 56 | Android 运行时 (ART) 包含一个具备代码分析功能的即时 (JIT) 编译器,该编译器可以在 Android 应用运行时持续提高其性能。JIT 编译器补充了 ART 当前的预先 (AOT) 编译器的功能,有助于提高运行时性能,节省存储空间,以及加快应用及系统更新速度。相较于 AOT 编译器,JIT 编译器的优势也更为明显,因为它不会在应用自动更新期间或重新编译应用(在无线下载 (OTA) 更新期间)时拖慢系统速度。 57 | 58 | - 用户打开应用,会另ART加载.dex文件:如果有.oat文件(.dex文件的AOT二进制文件),ART则会使用该文件;若果没有则使用JIT解释执行.dex文件 59 | - 针对未编译的应用启动JIT 60 | - 将JIT数据转存在只限应用访问的目录 61 | - AOT编译守护进程通过解析该文件来推动编译。 62 | 63 | 图片来自Android官方文档 64 | 65 | ![jit-workflow](./jit-workflow.png) 66 | 67 | #### 2.CMS 68 | 69 | CMS是以获取最短回收停顿时间为目标的收集器。整个过程分为初始标记,并发标记,重新标记和并发清除,其中1和3都需要阻塞主线程来标记GC Root能关联的对象,而并发标记和并发清除是耗时最长的步骤。其缺点有:较为占用CPU,无法清除浮动垃圾和无法整理空间碎片。 70 | 71 | #### 3.构建流程 72 | 73 | 图片来自Android官方文档 74 | 75 | 76 | 77 | ![构建流程](./构建流程.png) 78 | 79 | #### 4.装载 80 | 81 | 图片来自《程序员的自我修养—链接、装载与库》 82 | 83 | ![页错误](./页错误.png) 84 | 85 | 左边的ELF文件就是Android下执行的OAT文件,它按读写权限划分,具体分为.text代码段,.data数据段;中间是进程的虚拟地址空间,在Android下是Zygote fork出来的进程;右边是物理内存空间。 86 | 87 | 整个流程大概是,创建进程的虚拟地址空间并映射物理空间,建立ELF文件与虚拟空间的映射关系,开始执行。 88 | 89 | 具体请参考《程序员的自我修养—链接、装载与库》。 90 | 91 | ### 参考资料 92 | 93 | [Understanding the Android CompilationProcess -- By Bhavya Doshi](http://www.theappguruz.com/blog/android-compilation-process) 94 | 95 | [ART和Dalvik](https://source.android.com/devices/tech/dalvik/) 96 | 97 | [How Android Apps are Built and Run](https://github.com/dogriffiths/HeadFirstAndroid/wiki/How-Android-Apps-are-Built-and-Run) 98 | 99 | [Build and Run](https://developer.android.com/studio/build/index.html) 100 | 101 | 《程序员的自我修养—链接、装载与库》 102 | 103 | 《深入理解Java虚拟机》 -------------------------------------------------------------------------------- /Android编译过程/页错误.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Android编译过程/页错误.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/ActiveResources.get.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/ActiveResources.get.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/ByteBufferFileLoader.loadData.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/ByteBufferFileLoader.loadData.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/DecodeHelper.getLoadData.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/DecodeHelper.getLoadData.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/DecodeJob.getNextGenerator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/DecodeJob.getNextGenerator.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/DecodeJob.notifyEncodeAndRelease.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/DecodeJob.notifyEncodeAndRelease.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/DecodeJob.onResourceDecoded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/DecodeJob.onResourceDecoded.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/DecodeJob.ready.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/DecodeJob.ready.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/DecodePath.decodeResourceWithList.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/DecodePath.decodeResourceWithList.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/Engine.getjob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/Engine.getjob.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/Glide.Glide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/Glide.Glide.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/RequestBuilder.into.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/RequestBuilder.into.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/ResourceCacheGenerator.startNext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/ResourceCacheGenerator.startNext.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/SourceGenerator.startNext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/SourceGenerator.startNext.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/StreamLocalUriFetcher.loadResourceFromUri.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/StreamLocalUriFetcher.loadResourceFromUri.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/decodeJob.runGenerators.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/decodeJob.runGenerators.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/decodeJob.runWrapped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/decodeJob.runWrapped.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/decodeJob.willDecodeFromCache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/decodeJob.willDecodeFromCache.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/engine.loadFromActiveResources.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/engine.loadFromActiveResources.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/engine.loadFromCache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/engine.loadFromCache.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/engine.onResourceReleased.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/engine.onResourceReleased.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/engine.start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/engine.start.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/engine.startJob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/engine.startJob.png -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/glide.md: -------------------------------------------------------------------------------- 1 | ## 概述 2 | 3 | 目前最新版Glide 4.6.1已发布,跟之前版本对比,变动颇大。 4 | 5 | 源码不少,所以分模块分析,文章也分开来写,条理比较清晰。 6 | 7 | 大体流程分析 -> 加载算法分析 -> 缓存池使用 -> 生命周期和设计思想 -> 查遗补漏 8 | 9 | 本章主要解析图片加载库的最核心部分,图片加载逻辑的流程。 10 | 11 | 图片加载需要考虑和兼顾各个方面: 12 | 13 | - 大图片如何加载,如何避免OOM 14 | - 图片加载过程需要显示holder 15 | - 如何分配缓存 16 | - 线程池和对象池的设计和使用 17 | - 网络资源的分配 18 | - 图片的生命周期等 19 | 20 | Glide作为一个非常优秀的图片加载框架,对上述问题都解决得相当漂亮。 21 | 22 | 在阅读本文之前,建议先阅读[Glide官方指南](https://bumptech.github.io/glide/)。 23 | 24 | ## 核心模块 25 | 26 | ### 注册组件 27 | 28 | - [`ModelLoader`](https://muyangmin.github.io/glide-docs-cn/javadocs/400/com/bumptech/glide/load/model/ModelLoaderFactory.html),用于加载自定义的 Model(Url,Uri任意的 POJO )和 Data(InputStreams, FileDescriptors)。 29 | - [`ResourceDecoder`](https://muyangmin.github.io/glide-docs-cn/javadocs/400/com/bumptech/glide/load/ResourceDecoder.html), 用于对新的 Resources(Drawables, Bitmaps)或新的Data 类型(InputStreams,FileDescriptors)进行解码。 30 | - [`Encoder`](https://muyangmin.github.io/glide-docs-cn/javadocs/400/com/bumptech/glide/load/Encoder.html), 用于向 Glide 的磁盘缓存写 Data (InputStreams, FileDesciptors)。 31 | - [`ResourceTranscoder`](https://muyangmin.github.io/glide-docs-cn/javadocs/400/com/bumptech/glide/load/resource/transcode/ResourceTranscoder.html),用于在不同的资源类型之间做转换,例如,从 BitmapResource 转换为 DrawableResource 。 32 | - [`ResourceEncoder`](https://muyangmin.github.io/glide-docs-cn/javadocs/400/com/bumptech/glide/load/ResourceEncoder.html),用于向 Glide 的磁盘缓存写 Resources(BitmapResource, DrawableResource)。 33 | 34 | ### 剖析请求 35 | 36 | 每个请求的加载都是从 37 | 38 | - Model(如String)有模型加载器(ModelLoader)处理为Data(如InputStream) 39 | - Data通过ResourceDecoder处理为资源Resource, 40 | - 可选的转码步骤,Resource通过资源转码器ResourceTranscoder处理为转码后资源Transcoded Resource。 41 | 42 | 编码器Encoder在步骤2之前往磁盘缓存写入数据,资源编码器ResourceEncoder在步骤3之前往磁盘缓存里写入数据。 43 | 44 | ### 从普通调用说起 45 | 46 | Glide新版本的调用有所改变,可以通过注解生成的`GlideApp`来作为入口,原先的`Glide`也能用,只是API有所变化,这里用GlideApp作为例子。 47 | 48 | ```java 49 | GlideApp.with(imageview.getContext()).load(path).placeholder(R.drawable.placeholder).centerCrop().override(width, height).into(imageview); 50 | ``` 51 | 52 | 这种流式调用非常爽快,最终调用落在GlideRequest.into方法中。 53 | 54 | `RequestBuilder.into` 55 | 56 | ![RequestBuilder.into](./RequestBuilder.into.png) 57 | 58 | target是一个重要的概念——Glide用于显示placeholder、决定尺寸大小、加载资源和响应生命周期的接口。一般实现是ImageViewTarget及其子类,通过ImageViewTargetFactory.buildTarget生成,默认是DrawableImageViewTarget,它包装一个ImageView,当加载失败和初始化的时候,会显示placeHolder,资源加载成功则直接显示。 59 | 60 | Request构建完后,它进行了一次比较,用于处理类似RecycleView快速滚动时,重用ImageView时显示出旧request加载的图片,若不等则调用clear方法并重新开始加载,若相等则用旧request加载,recycle掉新request,这个request是通过setTag来实现的。 61 | 62 | 下面是真正开始加载的流程 63 | 64 | ![时序图-load](./时序图-load.png) 65 | 66 | 从调用上看,最后落到的是Engine.load方法。 67 | 68 | ### 图片加载 69 | 70 | Engine.load方法所做的事 71 | 72 | - 检查当前列表中是否存在活跃资源,存在则返回;不活跃的资源则被移进内存缓存 73 | - 检查是否能使用内存缓存,存在则返回 74 | - 检查是否有正在加载中的图片,有则返回 75 | - 开始一个新的加载过程 76 | 77 | 活跃资源是指一个请求至少被加载成功一次且未被释放(有另一个View正在展示这张图片),一旦消费者把资源释放掉,则会移入缓存。如果从缓存中加载资源,则资源会变活跃。如果资源从缓存中移除,会被重用。对于消费者而言,没有强制要求释放资源,所以活跃资源都以弱引用的方式持有。 78 | 79 | #### loadFromActiveResource 80 | 81 | 上述第一步 82 | 83 | `engine.loadFromActiveResources` 84 | 85 | ![engine.loadFromActiveResources](./engine.loadFromActiveResources.png) 86 | 87 | 主要看loadFromActiveResources方法返回是否为空,不为空加载流程直接返回结果。 88 | 89 | 查看方法源码,调用了ActiveResource.get方法 90 | 91 | `ActiveResources.get` 92 | 93 | ![ActiveResources.get](./ActiveResources.get.png) 94 | 95 | activeEngineResources声明如下,ResourceWeakReference是一个WeakReference> 96 | 97 | ```java 98 | final Map activeEngineResources = new HashMap<>(); 99 | ``` 100 | 101 | 通过key来获取是否存在活跃的WeakReference,印证了加载的第一步。 102 | 103 | 再看一下活跃资源是怎么被移除了,通过ActiveResource.deactive方法,调用时机是 104 | 105 | `engine.onResourceReleased`![engine.onResourceReleased](./engine.onResourceReleased.png) 106 | 107 | 资源被释放时将其移除活跃状态并置入缓存,得证。 108 | 109 | #### loadFromCache 110 | 111 | 上述第二步 112 | 113 | `engine.loadFromCache` 114 | 115 | ![engine.loadFromCache](./engine.loadFromCache.png) 116 | 117 | cache列表是一个LruCache,是一个LinkedHashmap,简单来说就是拥有双端指针的有顺序的HashMap,一个指针指向用得多的头另一个用得少的尾,超出size则删除尾项,这里不展开。 118 | 119 | 而cache存在时,则把资源从cache移除,放进活跃队列。 120 | 121 | #### getJob 122 | 123 | 上述第三步 124 | 125 | `Engine.getjob` 126 | 127 | ![Engine.getjob](./Engine.getjob.png) 128 | 129 | 很简单,检查当前正在加载的任务中,存在则加上回调并返回。 130 | 131 | #### startEngineJob 132 | 133 | 上述第四步 134 | 135 | `engine.startJob` 136 | 137 | ![engine.startJob](./engine.startJob.png) 138 | 139 | 新建engineJob和decodeJob,往当前正在加载列表里添加任务,开始执行! 140 | 141 | EngineJob实际上是一个线程池的持有者,它控制任务执行和回调函数,而真正执行任务的是DecodeJob。 142 | 143 | `engine.start` 144 | 145 | ![engine.start](./engine.start.png) 146 | 147 | start方法里调用了decodeJob.wllDecodeFromCache,比较关键。 148 | 149 | `decodeJob.willDecodeFromCache` 150 | 151 | ![decodeJob.willDecodeFromCache](./decodeJob.willDecodeFromCache.png) 152 | 153 | getNextStage里调用diskCacheStrategy.decodeCachedResource来判断,这也是我们可以指定不同diskCacheStrategy的方便之处,默认使用的是DiskCacheStrategy.AUTOMATIC,decodeCachedResource默认返回true。 154 | 155 | 所以Engine.start方法使用的是diskCacheExecutor线程池来执行任务,默认线程数为1(核心线程和最大线程池数都为1,具体查看GlideExecutor.newDiskCacheExecutor)。 156 | 157 | 真正任务执行时DecodeJob,实际上是个Runnable,看一下run方法,其中核心功能是runWrapper。 158 | 159 | `DecodeJob.runWrapped` 160 | 161 | ![decodeJob.runWrapped](./decodeJob.runWrapped.png) 162 | 163 | RunReason是任务执行的状态,一共三种,INITIALIZE,SWITCH_TO_SOURCE_SERVICE,DECODE_DATA,初始态是INITIALIZE。它跟DecodeJob.Stage的状态不同,后者是标记解码的来源。 164 | 165 | `DecodeJob.runGenerators` 166 | 167 | ![decodeJob.runGenerators](./decodeJob.runGenerators.png) 168 | 169 | DecodeJob.runGenerators方法通过DataFetcherGenerator的startNext返回false作为循环条件,根据stage的状态,选择不同的Generator来解码数据。 170 | 171 | ![DecodeJob.getNextGenerator](./DecodeJob.getNextGenerator.png) 172 | 173 | 上面stage共有三种有效状态和一个结束态,下面一个个来看,先是ResourceCacheGenerator。(注:当加载GIF图片时,默认采用的DiskCacheStrategy.NONE,所以直接从SourceGenerator开始)。 174 | 175 | ##### ResourceCacheGenerator 176 | 177 | 从缓存数据中取到已缩放/已转码的资源。举个例子,如果传入是String类型的绝对路径,原图片大小1.5MB(1920 * 1080),这里取到的会是压缩且裁剪过的图,大约10KB左右(180 * 180)。 178 | 179 | `ResourceCacheGenerator.startNext` 180 | 181 | ![ResourceCacheGenerator.startNext](./ResourceCacheGenerator.startNext.png) 182 | 183 | 通过源文件的绝对路径currentKey从DiskLruCacheWrapper(默认缓存文件夹大小为250MB,目前只用了InternalCacheDiskCache)中取到缓存文件cacheFile,通过DecodeHelper.getModelLoaders取到能加载这个cacheFile的loader,一共有三种分别是ByteBufferFileLoader,FileLoader,UnitModelLoader,按顺序处理,任一个处理完毕之后返回。 184 | 185 | 最后一步通过modelLoader.buildLoadData包装出ModelLoader.LoadData对象,其fetcher为ByteBufferFileLoader,再看loadData方法。 186 | 187 | `ByteBufferFileLoader.loadData` 188 | 189 | ![ByteBufferFileLoader.loadData](./ByteBufferFileLoader.loadData.png) 190 | 191 | 从File内读取数据,根据结果回调成功或失败,失败则使用其他loader继续加载,其他的FileLoader和UnitModelLoader的处理感兴趣的可以自己看下。 192 | 193 | 成功和失败的回调在DecodeJob里 194 | 195 | `DecodeJob.onDataFetcherReady` 196 | 197 | ![DecodeJob.ready](./DecodeJob.ready.png) 198 | 199 | 注意,某个loader失败并不会在回调到最外层,而是在runGenerators里根据stage是否到FINISHED状态再回调,未未FINISHED则继续进行下一步处理。 200 | 201 | ##### DataCacheGenerator 202 | 203 | 从缓存中获取原始数据,对于本地资源而言没用到。 204 | 205 | ##### SourceGenerator 206 | 207 | 从原始数据使用已注册的ModelLoaders生成DataFetchers,同时生成加载生成的Model。 208 | 209 | 已注册的ModelLoaders可以从Glide的构造方法里找到,也可以调用append自己添加,第一个参数是load的类型,第二个参数是中间数据类型,最后一个是ModelLoaderFactory,工厂方法通过build生成ModelLoader。 210 | 211 | `Glide.Glide` 212 | 213 | ![Glide.Glide](./Glide.Glide.png) 214 | 215 | 继续使用选择String作为例子。 216 | 217 | `SourceGenerator.startNext` 218 | 219 | ![SourceGenerator.startNext](./SourceGenerator.startNext.png) 220 | 221 | 看一下loadData怎么来的 222 | 223 | `DecodeHelper.getLoadData` 224 | 225 | ![DecodeHelper.getLoadData](./DecodeHelper.getLoadData.png) 226 | 227 | 取到的modelLoader是StringLoader类型,LoadData是由StringLoader.buildLoadData生成,而StringLoader.uriLoader是由MultiModelLoaderFactory.build方法生成的内部类MultiModelLoader,调用MultiModelLoader.buildLoadData方法,真正返回一个LoadData。 228 | 229 | LoadData又持有MultiFetcher,当SourceGenerator.startNext调用loadData.fetch.loadData方法,实际是循环MultiFetcher里的Fetcher,最终由StreamLocalUriFetcher.loadData通过ContextResolver.openInputStream返回一个InputStream。 230 | 231 | `StreamLocalUriFetcher.loadResourceFromUri` 232 | 233 | ![StreamLocalUriFetcher.loadResourceFromUri](./StreamLocalUriFetcher.loadResourceFromUri.png) 234 | 235 | 将结果通过onDataReady回调给SourceGenerator,由于getDataSource是LOCAL,对于DiskCacheStrategy.isDataCacheable只有Remote才返回true,所以回调到DecodeJob.onDataFetcherReady -> decodeFromRetrievedData -> decodeFromData -> decodeFromFetcher -> runLoadPath -> LoadPath.load -> loadWithExceptionList -> decode -> decodeResourceWithList -> notifyEncodeAndRelease(结束)。 236 | 237 | `DecodePath.decodeResourceWithList` 238 | 239 | ![DecodePath.decodeResourceWithList](./DecodePath.decodeResourceWithList.png) 240 | 241 | 循环decodePaths列表里的ByteBufferGifDecoder,ByteBufferBitmapDecoder和BitmapDrawableDecoder分别调用decode方法,其中任意处理了,就可以返回。 242 | 243 | 若图片不是GIF,则走到ByteBufferBitmapDecoder.handles方法默认返回true,则调用其decode方法即downsampler.decode方法,这就是压缩/裁剪图片的核心方法,原来Glide也是通过原生Android提供的API的实现的!先不管具体算法实现,算法太长,先挖坑后面另开篇章讲。 244 | 245 | 看解码完毕之后的操作 246 | 247 | `DecodeJob.onResourceDecoded` 248 | 249 | ![DecodeJob.onResourceDecoded](./DecodeJob.onResourceDecoded.png) 250 | 251 | 这里走的是ResourceCacheKey,然后init,encoder是BitmapEncoder。 252 | 253 | DecodeJob.decodeFromData结束后,返回非空的Resource后,调用notifyEncodeAndRelease完成最后的工作。 254 | 255 | `DecodeJob.notifyEncodeAndRelease` 256 | 257 | ![DecodeJob.notifyEncodeAndRelease](./DecodeJob.notifyEncodeAndRelease.png) 258 | 259 | resource参数是包装类LazyBitmapDrawable(调用get方法时才生成BitmapDrawable),通过LockedResource处理返回了BitmapDrawable,回调到外层target.onResourceReady,调用fetcher.cleanup做清理工作。 260 | 261 | 当图片加载完成后调用DecodeJob.notifyEncodeAndRelease -> encode -> DiskLruCacheWrapper.put -> DataCacheWriter.write -> BitmapEncoder.encode,再次进行压缩后再将文件根据key/value形式缓存起来。 262 | 263 | 这样整个加载流程就走完了。 264 | 265 | ## 总结 266 | 267 | Glide是一个优秀且复杂的图片加载框架。本文并没有完全解决开头所提的每个问题,只是对整个图片加载的流程做了大体的概括,更具体的细节需要一点点阅读源码。 268 | 269 | 后面的章节会分析图片加载算法。 -------------------------------------------------------------------------------- /Glide源码解析(1)-- 加载流程/时序图-load.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(1)-- 加载流程/时序图-load.png -------------------------------------------------------------------------------- /Glide源码解析(2)-- 缓存和对象池/DiskCacheWrapper.get.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(2)-- 缓存和对象池/DiskCacheWrapper.get.png -------------------------------------------------------------------------------- /Glide源码解析(2)-- 缓存和对象池/DiskLruCache.open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(2)-- 缓存和对象池/DiskLruCache.open.png -------------------------------------------------------------------------------- /Glide源码解析(2)-- 缓存和对象池/DiskLruCacheWrapper.put.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(2)-- 缓存和对象池/DiskLruCacheWrapper.put.png -------------------------------------------------------------------------------- /Glide源码解析(2)-- 缓存和对象池/DownSampler.decode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(2)-- 缓存和对象池/DownSampler.decode.png -------------------------------------------------------------------------------- /Glide源码解析(2)-- 缓存和对象池/Glide-缓存和对象池.md: -------------------------------------------------------------------------------- 1 | ## 缓存相关 2 | 3 | Glide除了内存缓存和磁盘缓存,还有Bitmap对象池和ArrayPool的使用,其中大多使用LRU的思想。 4 | 5 | ### 预备计算工作 6 | 7 | 先判断设备是否低内存设备 8 | 9 | `MemorySzieCalculator.isLowMemoryDevice` 10 | 11 | ![MemorySzieCalculator.isLowMemoryDevice](./MemorySzieCalculator.isLowMemoryDevice.png) 12 | 13 | 获取Glide框架能使用的最大内存大小,通过取到每个应用最大能用的内存大小 * 比例参数 14 | 15 | `MemorySizeCalculator.getMaxSize` 16 | 17 | ![MemorySizeCalculator.getMaxSize](./MemorySizeCalculator.getMaxSize.png) 18 | 19 | 计算屏幕的字节大小 = 宽 * 高 * 4字节(ARGB 8888),计算Glide使用内存大小和Bitmap池大小,若两者相加大于Glide框架能使用的最大内存则乘比例参数,否则返回。 20 | 21 | `MemorySizeCalculator` 22 | 23 | ![MemorySizeCalculator](./MemorySizeCalculator.png) 24 | 25 | ### Bitmap缓存池 26 | 27 | 缓存池实现BitmapPool接口,用于重用Bitmap对象,真正实现类是LruBitmapPool。 28 | 29 | 需要实现的方法比较少,一个个看。 30 | 31 | `LruBitmapPool.getMaxSize` 32 | 33 | ![LruBitmapPool.getMaxSize](./LruBitmapPool.getMaxSize.png) 34 | 35 | 前两个比较简单,maxSize就是上面算出来的值,setSizeMultiplier把maxSize乘一个因子,根据结果将对象释放一部分。evict方法很简单,移除队尾最少使用的Bitmap并recycle,计算CurrentSize是否比maxSize大,大则继续。 36 | 37 | `LruBitmapPool.put` 38 | 39 | ![LruBitmapPool.put](./LruBitmapPool.put.png) 40 | 41 | 先检查Bitmap状态,是否可变,大小是否很大,config内容。都可以把Bitmap添加到队列里,计算大小,并evict。 42 | 43 | `LruBitmapPool.get` 44 | 45 | ![LruBitmapPool.get](./LruBitmapPool.get.png) 46 | 47 | 两个方法大同小异,所谓getDirty比get效率高,就是get方法多做了一步eraseColor。 48 | 49 | `LruBitmapPool.getDirtyOrNull` 50 | 51 | ![LruBitmapPool.getDirtyOrNull](./LruBitmapPool.getDirtyOrNull.png) 52 | 53 | strategy在Android 4.4及以上是SizeConfigStrategy,否则是AttributeStrategy,区别在于Bitmap大小计算方式不尽相同,这里以SizeConfigStrategy为例,看get方法。 54 | 55 | `SizeConfigStrategy.get` 56 | 57 | ![SizeConfigStrategy.get](./SizeConfigStrategy.get.png) 58 | 59 | SizeConfigStrategy拥有一个keyPool,可以简单看做size为20的ArrayDeque。一开始findBestKey从这个空队列中创建一个Key,Key由Bitmap的大小和Config组成,通过Key从groupedMap中取到大小相等,Config相同的Bitmap。 60 | 61 | findBestKey方法内有几个变量,其中sortedSizes是个以Config为key,NavigableMap为值的HashMap,这个NavigableMap又是以Bitmap大小为key,这个大小的Bitmap的数量为值的TreeMap。这个设计用于,当需要取特定Config的Bitmap缓存时,可以在O(1)时间复杂度内取到TreeMap对象,接着取到特定大小的Bitmap则需要O(lgN)的时间复杂度,加起来是O(lgN)的时间复杂度。 62 | 63 | 从缓存中拿的result不为空时,要先减掉相应config切相应大小的Bitmap计数,最后调用reconfigure进一步减少Bitmap宽高,返回给外层。注意这里获取到的Bitmap的像素值是不靠谱的,需要调用者进行重绘,这里主要是重用Bitmap对象,所以方法名为getDirty。 64 | 65 | LruBitmapPool.getDirtyOrNull减少当前计数结束。 66 | 67 | `LruBitmapPool.clearMemory` 68 | 69 | ![LruBitmapPool.clearMemory](./LruBitmapPool.clearMemory.png) 70 | 71 | 循环删除缓存列表直到0。 72 | 73 | `LruBitmapPool.trimMemory` 74 | 75 | ![LruBitmapPool.trimMemory](./LruBitmapPool.trimMemory.png) 76 | 77 | 简单,不解释。 78 | 79 | 总结一下对象池的作用。频发的GC是会影响帧率和应用的流畅体验的,因为GC是需要stop the world来执行的。GC发生的场景很多,例如分配新对象,定时触发等,而分配大而连续的内存和很多零碎的生命周期短的小对象则必会触发GC。对象池减少了生成和回收对象的频率,也就是减少了GC的频率,由于是按需生成且存在缓存,所以原则上,只要及时清理缓存,是不会造成内存浪费的。 80 | 81 | ### 内存缓存 82 | 83 | 具体实现类时LruResourceCache,继承了LruCache,实现了MemoryCache接口,是对添加或删除Resource内存缓存的抽象,其实现就是LruCache的思想,不细说。 84 | 85 | 多出了ResourceRemovedListener接口用于当Bitmap被移除出缓存进行通知,调用ResourceRecycler.recycle进行回收。 86 | 87 | 总结一下内存缓存池的作用,在Resouce被release时会以键值对的形式放入内存缓存池,如果这时有同样key的可以直接从内存缓存池中取出(即State为Active的Resource)。当放入内存缓存池时,大小超出限制,则取出队尾的Resource,并调用其recycle方法,对于BitmapResource而言,recycle方法就是调用bitmapPool.put方法。 88 | 89 | ### 磁盘缓存 90 | 91 | 磁盘缓存默认实现是DiskLruCacheWrapper,由InternalCacheDiskCacheFactory.build生成,默认大小为250MB,位置为/data/data/包名/image_manager_disk_cache。 92 | 93 | DiskLruCacheWrapper实现DiskCache接口,是读写磁盘的抽象。先看get和put方法的实现。 94 | 95 | `DiskLruCacheWrapper.put` 96 | 97 | ![DiskLruCacheWrapper.put](./DiskLruCacheWrapper.put.png) 98 | 99 | 先获取写锁,同时只能有一个写操作,在finally中释放。 100 | 101 | 构建出DiskLruCache,开始读取journal,即对磁盘缓存的操作记录,构建出lruEntries。刚开始只存在一个空白的journal文件。 102 | 103 | ![DiskLruCache.open](./DiskLruCache.open.png) 104 | 105 | 方法开始先检查有没有journal.bkp和journal并存,以journal为准(何为jornal请翻上面的注释)。 106 | 107 | 若diskCache.get方法返回不为null,则表示已有缓存,直接返回。 108 | 109 | 不存在则构造一个Editor对象,为它赋值DIRTY表示正在写入。 110 | 111 | 由上篇可知,图片加载完成后会调用DiskLruCacheWrapper.put -> DataCacheWriter.write -> BitmapEncoder.encode方法,将Bitmap写入文件。这个文件以key.tmp值作为名字。 112 | 113 | 最后调用commit,若写入成功将文件重命名为key,在journal记录一条CLEAN的操作,失败则删除旧文件,记录一条REMOVE的操作。 114 | 115 | `DiskLruCacheWrapper.get`![DiskCacheWrapper.get](./DiskCacheWrapper.get.png) 116 | 117 | 知道了DiskLruCache的put原理,get方法就很简单了,根据key取到响应的文件,由于键值对是1:1关系,所以去index为0的File,存在则返回。 118 | 119 | ### LruArrayPool 120 | 121 | 默认大小4MB,用于存放byte/int数组的对象池。例如用于BitmapFactory.Options.inTempStorage的值,是指定解码过程的缓冲区大小。 122 | 123 | `DownSampler.decode` 124 | 125 | ![DownSampler.decode](./DownSampler.decode.png) 126 | 127 | 在decode方法内,通过byteArrayPool.get一个64KB的byte数组,赋值给BitmapFactory.Options.inTempStorage,在finally中调用byteArrayPool.put放回池内。 128 | 129 | `LruArrayPool.get` 130 | 131 | ![LruArrayPool.get](./LruArrayPool.get.png) 132 | 133 | 先根据arrayClass(byte[].class/int[].class)和size生成Key,用Key去取相对应的byte[]/int[],若result不为null,再根据ArrayAdapter(ByteArrayAdapter/IntArrayAdapter)计算大小,减少currentSize的大小,decrementArrayOfSize将相对应的类型和大小的数组的数量减1。 134 | 135 | `GroupedLinkedMap.get` 136 | 137 | ![GroupedLinkedMap.get](./GroupedLinkedMap.get.png) 138 | 139 | 当Key对应的Entry不存在时则新建一个,否则将key插入Key池内,将Entry作为链表头。LinkedEntry内的values是一个List,用于存放Key所对应数组(byte[]/int[]),是在GroupLinkedMap.put方法中add进去的。 140 | 141 | 总结一下,ArrayPool是一个全局的数组池,它可以放置不同类型的数组,具有限定大小的同时,使用LRU策略保持顺序。 142 | 143 | ## 总结 144 | 145 | 内存缓存和磁盘缓存的使用,大大提升了加载的速度,而对象池的使用则减少了大量的内存分配和回收的操作,减少GC的发生,提升了吞吐率。 -------------------------------------------------------------------------------- /Glide源码解析(2)-- 缓存和对象池/GroupedLinkedMap.get.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(2)-- 缓存和对象池/GroupedLinkedMap.get.png -------------------------------------------------------------------------------- /Glide源码解析(2)-- 缓存和对象池/LruArrayPool.get.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(2)-- 缓存和对象池/LruArrayPool.get.png -------------------------------------------------------------------------------- /Glide源码解析(2)-- 缓存和对象池/LruBitmapPool.clearMemory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(2)-- 缓存和对象池/LruBitmapPool.clearMemory.png -------------------------------------------------------------------------------- /Glide源码解析(2)-- 缓存和对象池/LruBitmapPool.get.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(2)-- 缓存和对象池/LruBitmapPool.get.png -------------------------------------------------------------------------------- /Glide源码解析(2)-- 缓存和对象池/LruBitmapPool.getDirtyOrNull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(2)-- 缓存和对象池/LruBitmapPool.getDirtyOrNull.png -------------------------------------------------------------------------------- /Glide源码解析(2)-- 缓存和对象池/LruBitmapPool.getMaxSize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(2)-- 缓存和对象池/LruBitmapPool.getMaxSize.png -------------------------------------------------------------------------------- /Glide源码解析(2)-- 缓存和对象池/LruBitmapPool.put.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(2)-- 缓存和对象池/LruBitmapPool.put.png -------------------------------------------------------------------------------- /Glide源码解析(2)-- 缓存和对象池/LruBitmapPool.trimMemory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(2)-- 缓存和对象池/LruBitmapPool.trimMemory.png -------------------------------------------------------------------------------- /Glide源码解析(2)-- 缓存和对象池/MemorySizeCalculator.getMaxSize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(2)-- 缓存和对象池/MemorySizeCalculator.getMaxSize.png -------------------------------------------------------------------------------- /Glide源码解析(2)-- 缓存和对象池/MemorySizeCalculator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(2)-- 缓存和对象池/MemorySizeCalculator.png -------------------------------------------------------------------------------- /Glide源码解析(2)-- 缓存和对象池/MemorySzieCalculator.isLowMemoryDevice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(2)-- 缓存和对象池/MemorySzieCalculator.isLowMemoryDevice.png -------------------------------------------------------------------------------- /Glide源码解析(2)-- 缓存和对象池/SizeConfigStrategy.get.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Glide源码解析(2)-- 缓存和对象池/SizeConfigStrategy.get.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > “The only truly secure system is one that is powered off, cast in a block of concrete and sealed in a lead-lined room with armed guards–and even then I have my doubts.” – Eugene H. Spafford 2 | 3 | [1.Android整体架构和权限模型](https://github.com/ChenSiLiang/securdroid/blob/master/%E6%9E%B6%E6%9E%84%E5%92%8C%E6%9D%83%E9%99%90%E6%A8%A1%E5%9E%8B/Android%E6%9E%B6%E6%9E%84%E5%92%8C%E6%9D%83%E9%99%90%E6%A8%A1%E5%9E%8B.md) 4 | 5 | [2.Android架构分层详解](https://github.com/ChenSiLiang/securdroid/blob/master/%E6%9E%B6%E6%9E%84%E5%88%86%E5%B1%82%E8%AF%A6%E8%A7%A3/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Android%E5%B1%82%E7%BA%A7.md) 6 | 7 | [3.Android内存公有划分与boot](https://github.com/ChenSiLiang/securdroid/blob/master/%E5%86%85%E5%AD%98%E5%85%AC%E6%9C%89%E5%88%92%E5%88%86%E4%B8%8Eboot/%E5%86%85%E5%AD%98%E5%85%AC%E6%9C%89%E5%88%92%E5%88%86%E4%B8%8Eboot.md) 8 | 9 | [4.攻击定义和了解网络攻击](https://github.com/ChenSiLiang/securdroid/tree/master/%E4%B8%80%E8%88%AC%E6%94%BB%E5%87%BB%E5%92%8C%E7%BD%91%E7%BB%9C%E6%94%BB%E5%87%BB) 10 | 11 | [5.剖析Activity、Window、ViewRootImpl和View之间的关系](https://github.com/ChenSiLiang/securdroid/blob/master/%E5%89%96%E6%9E%90Activity%E3%80%81Window%E3%80%81ViewRootImpl%E5%92%8CView%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/%E5%89%96%E6%9E%90Activity%E3%80%81Window%E3%80%81ViewRootImpl%E5%92%8CView%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB.md) 12 | 13 | [6.绘图基础--Canvas和Drawable](https://github.com/ChenSiLiang/securdroid/blob/master/%C2%A0%E7%BB%98%E5%9B%BE%E5%9F%BA%E7%A1%80--Canvas%E4%B8%8EDrawable/Canvas%E3%80%81Bitmap.md) 14 | 15 | [7.理解Android编译过程](https://github.com/ChenSiLiang/securdroid/blob/master/Android%E7%BC%96%E8%AF%91%E8%BF%87%E7%A8%8B/%E7%90%86%E8%A7%A3Android%E7%BC%96%E8%AF%91%E8%BF%87%E7%A8%8B.md) 16 | 17 | [8.真的理解Context?](https://github.com/ChenSiLiang/android-toy/blob/master/%E7%9C%9F%E7%9A%84%E7%90%86%E8%A7%A3Context%EF%BC%9F/%E7%9C%9F%E7%9A%84%E7%90%86%E8%A7%A3Context%EF%BC%9F.md) 18 | 19 | [9.Glide源码解析(1)-- 加载流程](https://github.com/ChenSiLiang/android-toy/blob/master/Glide%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%EF%BC%881%EF%BC%89--%20%E5%8A%A0%E8%BD%BD%E6%B5%81%E7%A8%8B/glide.md) 20 | 21 | [10.Glide源码解析(2)-- 缓存和对象池](https://github.com/ChenSiLiang/android-toy/blob/master/Glide%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%EF%BC%882%EF%BC%89--%20%E7%BC%93%E5%AD%98%E5%92%8C%E5%AF%B9%E8%B1%A1%E6%B1%A0/Glide-%E7%BC%93%E5%AD%98%E5%92%8C%E5%AF%B9%E8%B1%A1%E6%B1%A0.md) 22 | 23 | [11.RecycleView性能优化(持续更新中)](https://github.com/ChenSiLiang/android-toy/blob/master/RecycleView%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%EF%BC%88%E6%8C%81%E7%BB%AD%E6%9B%B4%E6%96%B0%E4%B8%AD%EF%BC%89/RecycleView%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96.md) 24 | 25 | [12.Shadow源码解析笔记]() 26 | 27 | ### cool website 28 | 29 | [can I use?](http://caniuse.com/#home)-Browser support tables for modern web technologies(浏览器支持的现代网络技术表) 30 | 31 | [mobilehtml5](http://mobilehtml5.org/)-HTML5 compatibility on mobile and tablet browsers with testing on real devices(移动设备和平板的浏览器的H5兼容性). 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /RecycleView性能优化(持续更新中)/RecycleView性能优化.md: -------------------------------------------------------------------------------- 1 | ## 卡顿原因 2 | 3 | ### RecyclerView: notifyDataSetChanged 4 | 5 | 数据需要全局刷新时,可以使用notifyDataSetChanged;对于增加或减少数据,可以使用如下方法实现局部刷新。 6 | 7 | ```java 8 | void onNewDataArrived(List news) { 9 | List oldNews = myAdapter.getItems(); 10 | DiffResult result = DiffUtil.calculateDiff(new MyCallback(oldNews, news)); 11 | myAdapter.setNews(news); 12 | result.dispatchUpdatesTo(myAdapter); 13 | } 14 | ``` 15 | 16 | ### 嵌套RecycleView 17 | 18 | 常见于竖直滚动的RecycleView嵌套一个横向滚动的RecycleView。对于单个RecycleView而言,都拥有独立的itemView对象池,对于嵌套的情况,可以设置共享对象池,如下: 19 | 20 | ```java 21 | class OuterAdapter extends RecyclerView.Adapter { 22 | RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool(); 23 | 24 | ... 25 | 26 | @Override 27 | public void onCreateViewHolder(ViewGroup parent, int viewType) { 28 | // inflate inner item, find innerRecyclerView by ID… 29 | LinearLayoutManager innerLLM = new LinearLayoutManager(parent.getContext(), 30 | LinearLayoutManager.HORIZONTAL); 31 | innerRv.setLayoutManager(innerLLM); 32 | innerRv.setRecycledViewPool(mSharedPool); 33 | return new OuterAdapter.ViewHolder(innerRv); 34 | 35 | } 36 | 37 | ``` 38 | 39 | 更近一步可以调用 `setInitialPrefetchItemCount(int)` 来优化嵌套时预加载性能,例如横向RecycleView上有3.5个item需要显示,可以调用`LinearLayoutManager.setInitialPrefetchItemCount(4)`,默认的数值是2。 40 | 41 | ### RecyclerView: Too much inflation / Create taking too long 42 | 43 | 减少view type的数量,如果只是样式上相差不大,可以再bind方法里进行改变,因为创建不同的view type需要额外的inflate调用导致卡顿。 44 | 45 | ### RecyclerView: Bind taking too long 46 | 47 | [onBindViewHolder(VH, int)](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#onBindViewHolder(VH, int))的实现应该非常简单,通过读取POJO类的数据来设置ViewHolder,不应该有其他额外的逻辑实现。 48 | 49 | ### RecyclerView or ListView: layout / draw taking too long 50 | 51 | ### Layout性能 52 | 53 | 通过Systemtrace工具可以检测Layout的性能,如果耗时太长或者调用次数过多,需要考察一下是否过度使用RelativeLayout或者嵌套多层LinearLayout,每层Layout都会导致其child多次的measure/layout,其时间复杂度是O(n²)。有如下方法可以优化 54 | 55 | - 重新组织层级 56 | - 自定义控件重写layout方法 57 | - 使用 [ConstraintLayout](https://developer.android.com/training/constraint-layout/index.html) 58 | 59 | ### Bitmap传递 60 | 61 | Android以OpenGL Texture的形式来展示bitmap,当bitmap第一次展示时,它会以width * height大小的Texture形式传递到GPU上,所以要保证bitmap的大小不会大于其展示大小,要知道上传过程是阻塞主线程的。一般传递一张1920 * 1080的Texture不会超过10ms。 62 | 63 | ### 对象分配和垃圾回收 64 | 65 | 虽然Android 5.0上使用ART来减少GC停顿时间,但仍然会造成卡顿。尽量避免在循环内创建对象导致GC。要知道,创建对象需要分配内存,而这个时机会检查内存是否足够来决定需不需要进行GC。 66 | 67 | ## RecycleView的优化技巧 68 | 69 | ### TextView 70 | 71 | 避免使用textAllCaps,直接使用String.toUpperCase 72 | 73 | ### 单个监听器 74 | 75 | 对于每个ViewHolder都要设置监听器怎么办? 76 | 77 | 全局new一个Listener,通过view.getId与view.getTag取到对应View的id和数据,避免对每个新创建的Viewholder都new出一个监听器。 78 | 79 | ### 值相同避免再次刷新 80 | 81 | 例如TextView的text相同,就不需要再调用setText方法,对比的损耗往往小于绘制。 82 | 83 | ### 避免半透明绘制 84 | 85 | 尽量不要对ViewHolder使用有透明度改变的绘制。 86 | 87 | ### 自定义View 88 | 89 | 使用StaticLayout和DynamicLayout代替TextView,使用自定义View代替LayoutInflater.inflate(xml)文件,或者使用ConstraintLayout降低层级深度。 90 | 91 | ### 缓存池 92 | 93 | 缓存自定义View,下载好网络上的POJO类数据避免重复下载。 94 | 95 | ### 预加载 96 | 97 | ```java 98 | // 通过复写指定预加载的像素值。 99 | LinearLayoutManager.getExtraLayoutSpace(); 100 | ``` 101 | 102 | 和 103 | 104 | ```java 105 | // 设置预加载itemview数目。 106 | RecycleView.setItemViewCacheSize(size); 107 | ``` 108 | 109 | ### 参考资料 110 | 111 | Android 7.0源码 112 | 113 | [Android 官方文档Performance -- Slow Rendering](https://developer.android.com/topic/performance/vitals/render.html) 114 | 115 | [Recycler View – pre cache views](https://androiddevx.wordpress.com/2014/12/05/recycler-view-pre-cache-views/) 116 | 117 | [RecyclerView item optimizations Or how StaticLayout can help you](https://medium.com/@programmerr47/recyclerview-item-optimizations-cae1aed0c321) 118 | 119 | [RecyclerView Prefetch](https://medium.com/google-developers/recyclerview-prefetch-c2f269075710) 120 | 121 | [优化ListView有哪些方法](https://www.zhihu.com/question/19703384) 122 | 123 | -------------------------------------------------------------------------------- /Shadow源码解析笔记/Shadow源码解析.md: -------------------------------------------------------------------------------- 1 | ## 0 引言 2 | 3 | 插件化一直以来都被视为Android中一门高深莫测的学问,它需要解决一系列难题: 4 | 5 | - 四大组件的调用 6 | - 如果使用插件的资源 7 | - 尽可能减少hook系统API,降低兼容难度 8 | - 尽量避免宿主的体积增量 9 | 10 | 腾讯最近开源的Tencent Shadow分享了很多设计细节和解决思路。对比之前的插件化框架,其优势在于零反射,无入侵性且零增量。对于有些未接触过或对插件化比较陌生的同学,整个流程可能比较难以一下看懂。下面先分析整体执行流程,再研究其中的代码细节是如何解决上述难题的。 11 | 12 | 先阅读Github的文档有助于理解。而编译代码的过程已省去,按照Github上的步骤可以顺利完成。 13 | 14 | ## 1 由宿主应用开始 15 | 16 | 本地启动的顺序 17 | 18 | - HostApplication内进行恢复上次runtime和得到插件目录。其中PluginHelper.init方法指定了pluginManagerFile、pluginZipFile并从assets拷贝到"/data/…/files/"目录下 19 | - MainAcitiviy -> PluginLoadActivity -> PluginManager.enter 此时还是在host内 20 | - SplashActivity -> 其他Activity 此时是业务方app的实现 21 | 22 | 因此重点在于1、2步。 23 | 24 | 从尾到头进行回溯。 25 | 26 | 最终是调用 27 | 28 | ```java 29 | DynamicPluginManager.enter(Context context, long fromId, Bundle bundle, EnterCallback callback) 30 | ``` 31 | 32 | ## 2 Plugin Manager 33 | 34 | DynamicPluginManager.enter方法做了三件事 35 | 36 | - updateManagerImpl比对上次更新时间,若不相等,则以最新文件进行更新,创建SamplePluginManager(在manager包内的pluginManagerFile内构造出ManagerFactoryImpl再构造出来dynamic包内的SamplePluginManager),属于动态化的子功能。 37 | - SamplePluginManager.enter启动activity(前一半在宿主进程内) 38 | - 根据最新文件更新PluginManager 39 | 40 |  重点看第2步: 41 | 42 | - 调用callback.showLoading 43 | 44 | - installPlugin从压缩包中解压插件,更新数据库,安装插件的runtime,loader,.so 45 | 46 | - startPluginActivity -> BinderPluginLoader.convertActivityIntent 该过程详细看下一章 47 | 48 | - loadPlugin -> bindPluginProcessService 49 | 50 | - loadRuntime 跨进程load runtime,hack class loader 51 | - loadPluginLoader 跨进程传uuid,插件进程根据uuid初始化pluginLoader,将其传回客户端 52 | - PluginLoader.loadPlugin(partKey) 跨进程调用mDynamicPluginLoader.loadPlugin(_arg0)。先初始化PluginServiceManager,再调用LoadPluginBloc.loadPlugin 53 | - 在HostApplication.loadPlugin加载插件到classloader中,调用createShadowApplication初始化插件application,将所包含的信息注册保存 54 | 55 | - 跨进程调用mDynamicPluginLoader.callApplicationOnCreate 56 | - 跨进程调用mDynamicPluginLoader.convertActivityIntent 确认启动的类名在插件中存在,将类信息放到intent内,在host进程内启动对应Activity。 57 | 58 | - 调用callback.closeLoading 59 | 60 | ## 3 FastPluginManager.convertActivityIntent 61 | 62 | | 宿主进程 | Binder | 传递跨进程对象 | 插件进程 | 作用 | 63 | | --------------------------------- | ----------------- | -------------------- | -------------------- | --------------------------------- | 64 | | PluginManagerThatUseDynamicLoader | PpsBinder | << PpsController | PluginProcessService | | 65 | | BinderUuidManager | UuidManagerBinder | >> UuidManagerBinder | PluginProcessService | 根据uuid和APPID获取对应的插件信息 | 66 | | BinderPluginLoader | PpsBinder | << PluginLoaderImpl | PluginProcessService | 转换intent | 67 | 68 | 插件加载是重点,我们从loadPlugin作为起点开始看。 69 | 70 | ### 3. 1 PluginLoader.loadPlugin 71 | 72 | ```java 73 | // 位于宿主进程 74 | FastPluginManager.loadPlugin(partKey); // loadPluginLoaderAndRuntime 75 | // 位于插件进程 76 | LoadPluginBloc.loadPlugin(){ 77 | // 在这里初始化PluginServiceManager 78 | // 加载插件到ClassLoader中. 79 | LoadApkBloc.loadPlugin 80 | // 获取宿主包信息包括activity、meta_data、service、provider和native路径等 81 | PackageManager.getPackageArchiveInfo 82 | // 解析插件apk,将插件内的信息放到pluginInfo内 83 | ParsePluginApkBloc.parse 84 | // new PluginPackageManager 85 | PluginPackageManager(hostPackageManager, packageInfo, allPluginPackageInfo) 86 | // 创建资源resource 87 | CreateResourceBloc.create 88 | // 创建shadow Application 89 | CreateApplicationBloc.createShadowApplication 90 | // 解析插件apk 91 | ParsePluginApkBloc.parse 92 | // 将插件信息放到各个map内,包括类名,Activity,services,provider 93 | ComponentManager.addPluginApkInfo(pluginInfo); 94 | // 添加键值对 95 | pluginPartsMap[] 96 | // 添加键值对 97 | PluginPartInfoManager.addPluginInfo(); 98 | } 99 | // 调用shadow applciation.oncreate方法 100 | pluginLoader.callApplicationOnCreate(partKey); 101 | DynamicPluginLoader.convertActivityIntent(); 102 | // 最后启动插件内的plashActivity 103 | ``` 104 | 105 | #### 3.1.1 loadPluginLoaderAndRuntime 106 | 107 | - bind PPS拿到插件进程PluginProcessService的接口PpsController,不赘述 108 | - 跨进程loadRuntime 109 | - 在插件进程的PPS内,使用mUuidManager(即从宿主进程传递来的binder)反过来调用宿主进程的PluginManagerThatUseDynamicLoader.getRuntime,构造一个InstalledApk返回到PPS内 110 | - 使用InstalledApk内的path来调用DynamicRuntime.loadRuntime 111 | - 关键是DynamicRuntime.hackParentToRuntime,将RuntimeClassLoader(这个就是下面说的DexClassLoader)插入到BootClassLoader与之间。 112 | - 保存结果,可以用作runtime恢复 113 | 114 | 更具体的原理可以参考作者的文章[Shadow的全动态设计原理解析](),对应container动态化。 115 | 116 | #### 3.1.2 loadPluginLoader 117 | 118 | - 跟上一步骤类似取到InstalledApk,调用new LoaderImplLoader().load 119 | 120 | - 使用ApkClassLoader.getInterface获取LoaderFactory,ApkClassLoader主要有两个功能: 121 | - 实现不正常的双亲委派逻辑,既能和parent隔离类加载,也能通过白名单复用一些类 122 | - 封装了加载对象强转成接口类型的方法 123 | - LoaderFactory.build返回PluginLoaderImpl。LoaderFactory不是为了返回多种Loader实现,只是为了让构造参数能在编译期进行检查,而不是通过反射方法构造的,在参数不对应时编译期检查不出来。 124 | 125 | 更具体的原理可以参考作者的文章[Shadow的全动态设计原理解析](),对应关于动态加载接口实现的实践经验。 126 | 127 | #### 3.1.3 LoadApkBloc.loadPlugin 128 | 129 | - 先通过Logger.classLoader.parent取到宿主的hostParentClassLoader。 130 | - 如果dependson为null,则new PluginClassLoader,这个就是下面说的DexClassLoader 131 | - loadClass方法判断如果hostParentClassLoader!=null || 是白名单 || other 走双亲委派模型,否则先尝试自己加载,再走双亲委派模型。 132 | 133 | > 所有的插件框架中,Activity的加载都是这样的,new一个DexClassLoader加载插件apk。然后从插件ClassLoader中load指定的插件Activity名字,newInstance之后强转为Activity类型使用。实际上Android系统自身在启动Activity时也是这样做的。所以这就是插件机制能动态更新Activity的基本原理。 134 | > 135 | > ... 136 | > 137 | > 因此,我们可以通过修改ClassLoader的parent,为ClassLoader新增一个parent。将原本的`BootClassLoader <- PathClassLoader`结构变为`BootClassLoader <- DexClassLoader <- PathClassLoader`,插入的DexClassLoader加载了ContainerActivity就可以使得系统在向PathClassLoader查找ContainerActivity时能够正确找到实现。 138 | 139 | 更具体的原理可以参考作者的文章[Shadow的全动态设计原理解析](),对应loader动态化。 140 | 141 | #### 3.1.4 CreateApplicationBloc.createShadowApplication 142 | 143 | 集合上述加载的部件 144 | 145 | - 使用工厂模式生产的ShadowPluginLoader 146 | - 宿主的资源Resource 147 | - 插件的包信息 148 | - 宿主的包信息 149 | - 添加ComponentManager,主要功能是管理组件和宿主中注册的壳子之间的配对关系,这个在第一步生成ShadowPluginLoader会new ComponentManager 150 | 151 | 初始化 152 | 153 | - 注册静态自定义广播接收器的类名,该类名是loader包里的广播类 154 | - 设置业务名pluginInfo.businessName和partKey 155 | - 设置ShadowRemoteViewCreatorImp 156 | 157 | #### 3.1.5 ComponentManager.addPluginApkInfo(pluginInfo) 158 | 159 | Activity和Service需要更新packageNameMap,pluginInfoMap,pluginComponentInfoMap,其中packageNameMap是<类名,包名>,pluginInfoMap,pluginComponentInfoMap。 160 | 161 | 另外 162 | 163 | - 插件内的Activity,更新componentMap 164 | - 插件内的Providers,通过mPluginContentProviderManager.addContentProviderInfo更新providerAuthorityMap 165 | 166 | #### 3.1.6 callApplicationOnCreate 167 | 168 | - 手动调用ShadowApplication.attachBaseContext(mHostAppContext) 169 | - 调用mPluginContentProviderManager.createContentProviderAndCallOnCreate,调用contentProvider.attachInfo 170 | - 手动调用ShadowApplication.onCreate 171 | 172 | #### 3.1.7 convertActivityIntent(pluginIntent) 173 | 174 | 如果请求的是插件内的组件,则写入下列信息 175 | 176 | - 类名、包名 177 | - 参数,业务名 178 | - partkey,pluginloader,版本号,进程ID 179 | 180 | 最后启动Activity。 181 | 182 | ### 3.2 FastPluginManager.installPlugin 183 | 184 | 回过头来看loadPlugin之前的installPlugin。 185 | 186 | ```java 187 | // 解压插件 188 | installPluginFromZip 189 | // odex优化 190 | oDexPluginLoaderOrRunTime 191 | //插件apk的so解压 192 | extractSo 193 | // odex优化 194 | oDexPlugin 195 | ``` 196 | 197 | #### 3.2.1 installPluginFromZip 198 | 199 | ![0](/Users/doushabao/information/学习资料/笔记/shadow/pic/0.png) 200 | 201 | - 将插件zip解压到名为zip的MD5的文件夹内,例如/data/user/0/包名/files/ShadowPluginManager/UnpackedPlugin/test-dynamic-manager/c35f2f6aa2c138b28e59e9a0d947cea0/plugin-debug.zip/"压缩包内容名",其中从ShadowPluginManager开始为shadow新增的文件夹名,从UnpackedPlugin后为某个特定插件的名字,例如这里是test-dynamic-manager。 202 | 203 | - 读取配置文件config.json,里面包括版本信息,pluginLoader信息,plugins信息 204 | 205 | ```json 206 | {"compact_version":[1,2,3],"pluginLoader":{"apkName":"sample-loader-debug.apk","hash":"D1CBB814545373A66537465BAD8F37B5"},"plugins":[{"partKey":"sample-plugin-app","apkName":"sample-plugin-app-debug.apk","businessName":"sample-plugin-app","hostWhiteList":["com.tencent.shadow.sample.host.lib"],"hash":"504495C8C3016B51B6E65EA9816C7955"}],"runtime":{"apkName":"sample-runtime-debug.apk","hash":"AE00182BE7F8D3D459B1BD4F0AE06E47"},"UUID":"684E32CC-4F8C-4FA2-A60F-66EAB532EDC1","version":4,"UUID_NickName":"1.1.5"} 207 | ``` 208 | 209 | - 将config插入数据库,包括loader.apk, runtime.apk, plugin-app.apk 210 | 211 | #### 3.2.2 oDexPluginLoaderOrRunTime 212 | 213 | 在低于API 26时,使用DexClassLoader时需要指定生成odex的optimizedDirectory,odex是经过优化后的dex文件。 214 | 215 | odex文件有两个优点: 216 | 217 | - 体积比dex更小,它是应用启动前已优化的部分程序。 218 | - 更安全,令攻击者更难读取应用内容 219 | 220 | 指定odex目录后创建DexclassLoader,其构造方法会最终调用DexPathList.makeDexElements加载dex文件,最终将odex路径赋值给RuntimeClassLoader,ApkClassLoader。 221 | 222 | #### 3.2.3 extractSo 223 | 224 | 将插件依赖的so文件复制到 "lib/" 目录下,更新数据库关于so的path。 225 | 226 | #### 3.2.4 oDexPlugin 227 | 228 | 最后插件下的目录为: 229 | 230 | - md5: 存zip和解压文件 231 | - lib:存.so 232 | - oDex:存从apk解压出来的dex和odex文件 233 | 234 | 至此,启动插件内Activity的整体流程结束。回头看一下作者文章[Shadow解决Activity等组件生命周期的方法解析]() 里讲的: 235 | 236 | > 我们的选择是在宿主PathClassLoader上给它加一个parent ClassLoader。因为PathClassLoader也是一个有正常“双亲委派”逻辑的ClassLoader,它加载什么类都会先问自己parent ClassLoader先加载。 237 | 238 | 也就对应了3.1.5节内的类加载器的实现。 239 | 240 | 再看一下壳子Activity复用的实现,作者对比了RePlugin框架需要在宿主中注册大量Activity,而Shadow用代理Activity通过Intent的参数确定构造哪个Activity,令壳子能被复用。这也是runtime包的动态实现功能之一。 241 | 242 | 打开宿主AndroidManifest文件,注册了三个launchMode分别为standard/singleInstance/singleTask的Activity,都继承于PluginContainerActivity。 243 | 244 | 主要实现的是这两点 245 | 246 | - 插件Activity不继承Activity,通过AOP替换父类为ShadowActivity,继承了PluginActivity 247 | - 插件Activity控制壳子Activity生命周期的调用 248 | - 在壳子PluginContainerActivity构造时,持有了被委托的接口delegate,可以调用插件的伪生命周期方法。接着调用了delegate.setDelegator(this),为ShadowActivityDelegate赋值了壳子Activity的引用 249 | - 在ShadowActivityDelegate.onCreate方法内,通过PluginClassLoader.loadClass创建插件PluginActivity,再为插件PluginActivity赋值了壳子Activity的引用。ShadowActivityDelegate作为一个转调或者信使的功能。 250 | - 由插件Activity直接指挥壳子Activity什么时候调用`super.onCreate()` 251 | 252 | 因此整个sample里的Activity启动流程为 253 | 254 | MainActivity(Host) -> (上面一大堆加载) -> PluginDefaultProxyActivity(Host) -> PluginContainerActivity(host) -> ShadowActivityDelegate(Host) -> SplashActivity(:plugin) 255 | 256 | ## 4 同名View的方法解析 257 | 258 | > Android系统中存在一个错误的设计,导致LayoutInflater在inflate的时候使用的View类并不是简单的从ClassLoader中读取的,而是自行做了一层缓存,以View的类名作为Key保存了Class对象。这个缓存存储在一个静态域上,因此在同一个进程中,不能存在两个同名的View都使用LayoutInflater构造。这个问题常见于插件框架对于support包中的RecycleView支持上。 259 | 260 | 具体查看作者原文[Shadow解决插件和宿主有同名View的方法解析]()。 261 | 262 | 对应LayerInflater.createViewFromtag方法: 263 | 264 | - 先用factory创建View 265 | - 若没有,则走自己的createView方法 266 | 267 | 而上述问题出在第二步。翻查Android源码,最新版本已经修复了这个问题,通过verifyClassLoader来判断是否需要移除并置空缓存。但我们仍旧需要兼容旧版本,所以可以通过设置factory来进行自定义加载。 268 | 269 | 在Shadow内的实现,在壳子Activity内重写getLayerInflater方法,返回设置了factory2的自定义LayerInflater,具体请查看ShadowLayoutInflater,其中ShadowFactory2.onCreateView也是类似官方的修复一样来解决这个问题。 270 | 271 | 再看一下同名资源的处理,插件与宿主的是资源隔离的。因此插件只能访问到自己的资源,而宿主无从更新自身代码,也无需访问插件资源。 272 | 273 | ## 5 PackageManager 274 | 275 | 最终调用的方法顺序是 276 | 277 | CodeConverterExtension.redirectMethodCallToStaticMethodCall 278 | 279 | ->PackageManagerTransform. setupPackageManagerTransform 280 | 281 | ->PackageManagerInvokeRedirect.getPluginPackageManager 282 | 283 | 前两部是查找getPackageManager,生成静态的方法调用PackageManagerInvokeRedirect,内部的实现是通过classloader找到对应的PluginPartInfo,再取到相应的资源。 284 | 285 | 具体查看作者原文[Shadow对PackageManager的处理方法]()。 286 | 287 | ## 6 三大组件的调用 288 | 289 | 其他三大组件并没有Activity复杂的生命周期,比较简单。 290 | 291 | ### 6.1 Service 292 | 293 | 启动流程为ShadowContext.startService -> ComponentManager.startService -> PluginServiceManager.stopPluginService,由3.1.5可知,插件Service已经在ComponentManager中注册了,先调用newServiceInstance通过pluginclassloader新建出Service,设置例如宿主Context、插件资源和loader之类,调用其onCreate方法。 294 | 295 | 其他方法类似。 296 | 297 | ### 6.2 ContentProvider 298 | 299 | 通过宿主PluginContainerContentProvider来转发方法。 300 | 301 | ### 6.3 BroadcastReveiver 302 | 303 | 对于动态广播直接注册使用就可以了。 304 | 305 | 对于静态广播,手动将静态广播放到ShadowAcpplication内,在onCreate被调用时进行注册,具体看3.1.4小节。 306 | 307 | ## 7 总结 308 | 309 | 本文分析了插件化框架Shadow如何解决一系列难题,特别是从插件加载到启动Activity的整个流程。宿主仅在AndroidManifest注册了壳子Activity,真正位于dynamic包内。壳子Activity双向持有插件Activity的引用,来实现插件Activity伪生命周期的调用(插件Activity并不真正继承Activity类),仅通过插入一层classloader而不需要过分地hack系统API实现。其中,修改LayerInflater实现同名View和通过AOP修改PackageManager的设计思路十分巧妙,值得学习。 310 | 311 | ## 8 Reference 312 | 313 | - Shadow源码 314 | - 作者shifujun设计细节分享 315 | - Android官网 bounde service 316 | - Android source code 317 | - Android内核—binder架构分析 318 | - Android LayoutInflater Factory 源码解析 319 | - 【Android 修炼手册】常用技术篇 -- Android 插件化解析 320 | - 关于odex,配置ART 321 | 322 | ## 9 转载或引用 323 | 324 | 转载或引用请注明出处。 -------------------------------------------------------------------------------- /Shadow源码解析笔记/pic/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/Shadow源码解析笔记/pic/0.png -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /一般攻击和网络攻击/attack-surfaces.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/一般攻击和网络攻击/attack-surfaces.png -------------------------------------------------------------------------------- /一般攻击和网络攻击/osi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/一般攻击和网络攻击/osi.png -------------------------------------------------------------------------------- /一般攻击和网络攻击/了解攻击.md: -------------------------------------------------------------------------------- 1 | 最好的安全者往往是最危险的攻击者。 2 | 3 | 彻底了解攻击手段的多样性是攻击或防御成功的关键,无论对于Android或者是其他系统,情况大抵类似。而每个黑客的目的就是通过研究未披露的漏洞,分析其用于攻击的可能性,相反,白客就需要了解每种可能入侵系统的方式。所以,了解攻击手段是首要任务。 4 | 5 | ### 攻击向量 6 | 7 | 攻击向量是实施攻击的方法,简单来说,就是接触到恶意代码的行为。攻击向量通过三项标准来评估漏洞和攻击的可行性: 8 | 9 | - 认证 10 | - 可访问性 11 | - 难度 12 | 13 | 例如,给目标发送电子邮件是一种高级的攻击向量,它不需要任何认证手段,只需要简单点击就能执行恶意代码。另一个例子是网络监控,认证同样也是不必要烦人,但依赖于监听网络提供的服务是否有漏洞。 14 | 15 | 进一步细分攻击向量: 16 | 17 | - 点击url 18 | - api漏洞 19 | 20 | 上述邮件的例子就是通过点击一个url来执行恶意代码。另一个就是通过API漏洞实施攻击,例如sql注入漏洞。 21 | 22 | 任何能利用来执行恶意代码的**手段**都可以称为向量。 23 | 24 | ### 攻击面 25 | 26 | 攻击往往只会发生在系统最脆弱的侧面。在物理层面上看,攻击面会是目标暴露出来未有防御的一面(有时大型攻击面会是烧服务器,光纤等)。从技术角度来看,攻击面是描述了代码未被发现的脆弱性。在邮件的攻击中,攻击面可能攻击是邮件服务器中的解析器,又或是在目标机器上呈现某种信息。在对浏览器的攻击中,所有与浏览器相关的技术都可能是攻击面,例如HTTP,HTML,CSS,SVG。 27 | 28 | 记住,攻击面不仅仅是依赖漏洞才存在,只要是攻击者能执行的任何代码都被视为攻击面。 29 | 30 | 与攻击向量的不同在于,以Android设备作为攻击目标,无线网络可能是一个攻击面,相反,某个特定的功能或API可能被视为攻击向量。 31 | 32 | 下一节讨论的网络概念中,因特网被分为几个层次,数据在每层传递时候都暴露了不一样的攻击面。![attack-surfaces](./attack-surfaces.png) 33 | 34 | 系统最外层的攻击面可以是由两个web server通信端口组成。如果攻击向量是某个未加密的请求,带有不受信任的数据发送到指定的php应用内。那对于服务器和客户端的潜在攻击面就容易成功。 35 | 36 | 最后注意一地那,一个攻击面可能由多个攻击向量组成,一个对于图片加载API的攻击面可能通过电子邮件,网页和应用的即时信息这些攻击向量来达到。 37 | 38 | ### 攻击面分类 39 | 40 | 攻击面与系统组成成正相关,系统的代码,硬件,用户,操作系统都需要考虑。市面上很多Android设备的功能都是一应俱全的,同时也导致了攻击面的多样化,因此下面来对攻击面进行分类。 41 | 42 | #### 属性 43 | 44 | 一般会有下面几个关键属性可供参考。 45 | 46 | - 攻击向量 47 | 48 | 49 | - 提权 - 攻击面可能需要root权限执行命令 50 | - 内存安全 - 对于C和C++并不是内存安全语言可能更脆弱 51 | - 复杂性 - 复杂的代码,算法和协议更可能存在漏洞 52 | 53 | 下面从最吸引人,最致命的攻击面开始 54 | 55 | #### 远程攻击 56 | 57 | 远程攻击不会发生在目标物理地址的附近,而是通过计算机网络进行攻击。 58 | 59 | 从点击一个有毒的url(或者在地址栏敲入一个有毒的地址),网络蠕虫就可以开始增殖了。除此之外,攻击者还可以在网络中处于有利位置(假扮服务器)来实施中间人攻击,例如谷歌镜像。 60 | 61 | 要实施远程攻击就需要了解更多网络的概念。 62 | 63 | #### 网络概念 64 | 65 | 家用机和移动设备存在于网络的最外层,在这些网络节点之间是路由器。当一台移动设备链接到某个网页时,一连串使用各种协议的数据包就通过网络来定位,通信,与服务器交换数据。蜂窝网络与此类似,通过与最近的信号塔通信,当用户走动的时候,信号塔也随之改变,信号塔是移动射击接入网络的第一站。 66 | 67 | ##### OSI模型 68 | 69 | ![osi](./osi.png) 70 | 71 | - 物理层:两台计算机交流0和1,部分以太网和WIFI在这层 72 | - 数据链路层:添加了数据纠错能力,剩余的以太网和WIFI,还有想LLC和ARP协议 73 | - 网络层:提供路由机制让数据包知道具体的目的地 74 | - 传输层:增加了可靠性传输 75 | - 会话层:为主机间建立对话 76 | - 表现层:兼容数据差异性 77 | - 应用层:DNS,DHCP,FTP,SNMP,HTTP,SMTP等 78 | 79 | #### 网络配置与防御 80 | 81 | 现代网络不同于1980年,那时互联网是公开透明的,主机之间可以互联,彼此都是可信的。通过几次血的教训,防火墙开始建立起来了。1999年NAT开始用来创建一个私有网络;在2013年,ipv4的地址快要消耗殆尽,NAT缓解了压力。简单地说,NAT路由器扮演了一个透明代理的角色,它连接了WAN(外网)和LAN(局域网),同时也成为了一种防火墙,外网只能通过NAT路由器发送响应,而不知道内网的组成。 82 | 83 | #### 邻接 84 | 85 | 网络中,两个相连的主机关系称为邻接。攻击者可以通过攻占LAN内的主机实现邻接,说好的有防火墙呢?有VPN啊。又或者直接侵占NAT路由器。当攻击者成功后,它就成为目标与外网之间的中间人。 86 | 87 | 在内网中,网络点之间是直接相连的,没有路由器的参与因此IP也无从谈起,更不用说协议验证。数据传输的方式往往是在广播,根据MAC地址送达,而某些局域网配置是可以监听网络请求的。 88 | 89 | 事实上,没有任何验证协议的网络是很容易被欺骗的。例如在某个欺诈攻击中,攻击者伪造了数据包的源地址,伪装发送给其他主机,骗到真实的回复。这种攻击大多数发生在网络层以下,通过ARP或ARP缓存欺诈,只存在于数据链路层。 90 | 91 | 对于ARP欺诈最有效的防御措施是使用静态ARP表。其他类似的攻击还有DNS和DHCP的。 92 | 93 | ### 移动技术 94 | 95 | 对于移动设备而言,网络攻击除了上述形式外,也另有花样。攻击面会是SMS和MMS服务。这些点到点类型的服务通过蜂窝网络传递,所以没有网络邻接和用户交互的行为。 96 | 97 | 一种有意思的攻击方法是,基于WAP发送富文本短信。WAP协议是基于SMS协议,可扩展的推送服务。其中有一种实现是Service Loading request(或称WAP push),当这种消息推送到手机上时,不需要用户任何的操作,手机会自动处理下发的信息,例如打开默认浏览器打开某个url。 98 | 99 | 2012年,研究员Borgaonkar在三星设备上实施过这种攻击。他通过WAP推送信息唤醒手机的USSD服务(比如输入*#会触发一些预设定的命令),下发一个链接`tel://url`,浏览器打开该链接后会自动打开拨号界面。通过拨号界面修改手机的PUK码,由于SIM卡得PUK一旦输错超过10次,该SIM卡就会报废,这种攻击的目的在于破坏SIM卡。 100 | 101 | ### 总结 102 | 103 | 攻击=攻击面+攻击向量 104 | 105 | 攻击向量通过认证,可访问性和难度进行评估。攻击面大多选取系统薄弱处,例如无线网络的底层协议。 106 | 107 | -------------------------------------------------------------------------------- /内存公有划分与boot/partition-layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/内存公有划分与boot/partition-layout.png -------------------------------------------------------------------------------- /内存公有划分与boot/内存公有划分与boot.md: -------------------------------------------------------------------------------- 1 | 需要最高权限的理由很多,例如本文的目的在于不通过授权的方式更改Android设备的安全设置。也有很多人想更改默认系统行为,修改自定义主题和开机动画。Rooting可以卸载厂商预装软件,备份恢复整个系统或者加载自定义的kernel镜像。很多典型的管理型app都需要root权限,如防火墙,广告拦截和超频器。 2 | 3 | 不论root的理由是什么,都降低了设备的安全性,root可以通过最高权限获取用户数据,更可怕的是,当手机遗失,被偷掉或被卖掉,是存在被破解的可能的。 4 | 5 | ### 设备内存划分 6 | 7 | 设备的内存是根据逻辑储存单元来划分的,通过boot加载脚本来指定划分层级。根据不同厂商,机型,划分的层级会各有差异,但某些特定层级是公有的。 8 | 9 | ![partition layout](./partition-layout.png) 10 | 11 | 12 | 13 | #### Boot Process 14 | 15 | boot通常是设备启动时运行的第一个程序。对于大多数设备而言,boot启动了底层级的初始化,如设置计时器,内部RAM,启动多媒体等。boot本身由多个部分组成,这里我们把它当做一个整体看待。 16 | 17 | boot初始化硬件设施完成后,战场从引导分区进阶到内存级别,轮到Android Kernel接过boot的最后一棒,它完成了Android 系统执行所需要的一切任务,包括初始化内存和I/O机制,中断处理,CPU调度,设备驱动等。最后,它把根目录挂载,启动user-space进程init,就陷入了沉睡中。 18 | 19 | init进程是其他user-space进程的父亲。它开始动手了,根据/init.rc,这本由创造它的程序员写成的天书,开始初始化user-space的组件,包括Android核心服务如rild电话服务,VPN和adbd。其中一个更广为人知的孩子Zygote,创建了Dalvik虚拟机并繁育了第一个Java孩子,System Server。最终Android Framework的服务也被开启了。 20 | 21 | 整个开天辟地过程就结束了。 22 | 23 | 我们来看一下天书init,rc所写 24 | 25 | ```xml 26 | on init 27 | ... 28 | # setup the global environment 29 | export PATH 30 | /sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin 31 | export LD_LIBRARY_PATH /vendor/lib:/system/lib 32 | export ANDROID_BOOTLOGO 1 33 | export ANDROID_ROOT /system 34 | export ANDROID_ASSETS /system/app 35 | export ANDROID_DATA /data 36 | export EXTERNAL_STORAGE /mnt/sdcard 37 | ... 38 | on property:androVM.inited=1 39 | class_start core 40 | class_start main 41 | ... 42 | service servicemanager /system/bin/servicemanager 43 | class core 44 | user system 45 | group system 46 | critical 47 | onrestart restart zygote 48 | onrestart restart media 49 | onrestart restart surfaceflinger 50 | onrestart restart drm 51 | ... 52 | service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server 53 | class main 54 | socket zygote stream 666 55 | onrestart write /sys/android_power/request_state wake 56 | onrestart write /sys/power/state on 57 | onrestart restart media 58 | onrestart restart netd 59 | ... 60 | ``` 61 | 62 | 系统boot完成后,会发出一个[ACTION_BOOT_COMPLETED](https://developer.android.com/reference/android/content/Intent.html#ACTION_BOOT_COMPLETED)的广播,任何注册接受这个广播的接收器都能收到。 63 | 64 | #### 刷机模式 65 | 66 | 按住某几个特殊的键可进入刷机模式。它是储存在低层级的一个叫flashing的进程所启动,支持USB连接,通信的。 67 | 68 | ### boot的锁 69 | 70 | 为了防止中断用户篡改系统固定,boot是被厂商加锁的。消费者对厂商的不信任,对预装软件的厌恶,对追求极致性能的欲望,助长着刷机风气的日渐嚣张,许多厂商被迫放出boot的解锁步骤,甚至很多大厂纷纷出产一键root工具。 71 | 72 | 解锁boot的难度愈发降低,只需简单得工具,一台手机都能轻松地获取root权限。root权限带来了严重的安全隐患,由于其特殊性,root可以获取所有应用的用户数据,账户信息,秘密文件,交易详情,私密图片等,所以应用的开发者对各种敏感数据需要进行加密,普通用户在换新手机的时候,不要忘了将旧手机恢复出厂设置并清除用户数据,使得别有用心的攻击者难以得逞。 73 | 74 | ### 备份恢复 75 | 76 | 备份恢复系统是Android标准机制,在不需要擦除用户数据的前提下,进行系统更新。这样的恢复操作必须进行重启。 77 | 78 | 恢复镜像通常存储在recovery层级里,作为最小的Linux镜像,它的功能十分有限,通常是一个ZIP包里面的一些元数据和更新脚本。 79 | 80 | ### 总结 81 | 82 | Root机器能获取设备的至高权限,同时也存在着被攻击的隐患。本文主要介绍了内存划分的几个概念和boot机制 83 | 84 | -------------------------------------------------------------------------------- /剖析Activity、Window、ViewRootImpl和View之间的关系/Session.windowAddedLocked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/剖析Activity、Window、ViewRootImpl和View之间的关系/Session.windowAddedLocked.png -------------------------------------------------------------------------------- /剖析Activity、Window、ViewRootImpl和View之间的关系/SurfaceView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/剖析Activity、Window、ViewRootImpl和View之间的关系/SurfaceView.png -------------------------------------------------------------------------------- /剖析Activity、Window、ViewRootImpl和View之间的关系/SurfaceView.updateWindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/剖析Activity、Window、ViewRootImpl和View之间的关系/SurfaceView.updateWindow.png -------------------------------------------------------------------------------- /剖析Activity、Window、ViewRootImpl和View之间的关系/ViewRootImpl与WMS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/剖析Activity、Window、ViewRootImpl和View之间的关系/ViewRootImpl与WMS.png -------------------------------------------------------------------------------- /剖析Activity、Window、ViewRootImpl和View之间的关系/ViewTree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/剖析Activity、Window、ViewRootImpl和View之间的关系/ViewTree.png -------------------------------------------------------------------------------- /剖析Activity、Window、ViewRootImpl和View之间的关系/WMS.createSurfaceControl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/剖析Activity、Window、ViewRootImpl和View之间的关系/WMS.createSurfaceControl.png -------------------------------------------------------------------------------- /剖析Activity、Window、ViewRootImpl和View之间的关系/WMS.relayoutWIndow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/剖析Activity、Window、ViewRootImpl和View之间的关系/WMS.relayoutWIndow.png -------------------------------------------------------------------------------- /剖析Activity、Window、ViewRootImpl和View之间的关系/WindowManagerGlobal.addView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/剖析Activity、Window、ViewRootImpl和View之间的关系/WindowManagerGlobal.addView.png -------------------------------------------------------------------------------- /剖析Activity、Window、ViewRootImpl和View之间的关系/addwindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/剖析Activity、Window、ViewRootImpl和View之间的关系/addwindow.png -------------------------------------------------------------------------------- /剖析Activity、Window、ViewRootImpl和View之间的关系/startActivity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/剖析Activity、Window、ViewRootImpl和View之间的关系/startActivity.png -------------------------------------------------------------------------------- /剖析Activity、Window、ViewRootImpl和View之间的关系/剖析Activity、Window、ViewRootImpl和View之间的关系.md: -------------------------------------------------------------------------------- 1 | 本文梳理了Activity、View、Window、ViewRoot、Surface、AMS、WMS之间的关系,由于跳转间的流程纷繁复杂,一旦陷入代码细节就难以自拔,下文中分析省略掉很多细节,想了解的可以阅读源码或者阅读相对应的书籍。 2 | 3 | ### 概念定义 4 | 5 | ContextImpl:Context实现类。 6 | 7 | PhoneWindow:Window唯一实现类。Window是一个抽象概念,是添加到WindowManager的根容器。 8 | 9 | ViewRootImpl:ViewRootImpl是View的根,它控制了View的测量和绘制,同时持有WindowSession通过Binder与WMS通信,同时持有IWindow作为WSM的回调接口,用于例如touch事件的回调。 10 | 11 | ![ViewRootImpl与WMS](./ViewRootImpl与WMS.png) 12 | 13 | WindowManagerImpl:WindowManager和ViewManager的实现类,通过WindowManagerGlobal与WMS通信。 14 | 15 | DecorView:继承FrameLayout,是视图树的根布局。 16 | 17 | ![ViewTree](./ViewTree.png) 18 | 19 | 使用AS自带的tools/layout inspector可以看出,整个DecorView包含了三部分:navigationBarBackground为导航栏,statusBarBackground为状态栏,LinearLayout为当中内容部分,展开LinearLayout.FrameLayout,可以得到action_bar_container即actionbar或toolbar和content(R.id.content)即真正setContentView的目标。 20 | 21 | 下文中但凡遇到抽象类/接口,都直接用实现类替代,而 -> 符号代表由函数跳转到另一函数。 22 | 23 | ### 从启动Activity说起 24 | 25 | 第一个部分是启动Activity到创建出ViewRootImpl。 26 | 27 | ![startActivity](./startActivity.png) 28 | 29 | 从ContextImpl开始,省略掉AMS里相关跳转到最后ActivityThread.performLaunchActivity -> Activity.attach中创建出PhoneWindow。 30 | 31 | 继续下一步调用方法 ActivityThread.handleResumeActivity -> WindowManagerImpl.addView创建出ViewRootImpl。 32 | 33 | ![WindowManagerGlobal.addView](./WindowManagerGlobal.addView.png) 34 | 35 | ViewRootImpl的构造方法内创建了WindowSession(Binder),通过它与WindowManagerService进行通信。 36 | 37 | 小结:启动Activity会创建ViewRootImpl和PhoneWindow,建立起与WMS的连接。 38 | 39 | ### 与WMS通信 40 | 41 | 第二步是ViewRootImpl与WMS通信。 42 | 43 | ![addwindow](./addwindow.png) 44 | 45 | 接上第一步中在ViewRootImpl构造方法中通过WindowSession -> Binder.openSession构造出WindowSession。 46 | 47 | 由第一步7中WindowManagerImpl.addView -> … ->WMS.relayoutWindow根据Window测量的大小相对应创建出SurfaceControl,通过SurfaceControl.getSurface将测量结果写入outSurface内,此处的outSurface就是ViewRootImpl.mSurface,注意此处只有大小,还未有指向native surface的指针mNativeObject。 48 | 49 | ![WMS.createSurfaceControl](./WMS.createSurfaceControl.png) 50 | 51 | 由第一步7中WindowManagerImpl.addView -> … ->WindowState.attch,创建出WindowToken用来标识Window类型,如子窗体(1000~1999),应用窗体(1~99)和系统窗体(2000~2999)。再创建WindowState——WMS端的Window对象,它持有Session与WindowManager通信,更重要的是调用Session.windowAddedLocked创建出SurfaceSession。 52 | 53 | ![Session.windowAddedLocked](./Session.windowAddedLocked.png) 54 | 55 | SurfaceSession构造方法里调用了nativeCreate,从这里开始就是native的世界,不是本文重点,但简单概括一下流程是通过创建SurfaceComposerClient与SurfaceFlinger进行交互,锁定一块共享内存,通过writeParcel返回给ViewRootImpl.mSurface,同时拥有了native surface的地址。 56 | 57 | 小结:当Activity准备显示时,会测量Window和添加Window,创建出WMS服务对应的WindowState,Surface和native Surface。 58 | 59 | ### 绘制 60 | 61 | 绘制四要素:bitmap(一块内存保存像素),canvas(画布用于画像素),paint(画笔),path(画的对象)。 62 | 63 | 应用无论是使用View/Canvas绘制(软件绘制,Skia),或者使用硬件加速绘制,最底层都是与Surface(OpenGL)进行交互。 64 | 65 | 再回到Activity的生命周期onCreate,调用setContentView创建一个不可见的DecorView,当ActivityThread.handleResumeActivity -> Activity.makeVisible设置DecorView为可见。 66 | 67 | 其中绘制的起点是ViewRootImpl.performTraversals -> ViewRootImpl.performMeasure -> ViewRootImpl.performLayout - > ViewRootImpl.performDraw调用作为根视图DecorView的measure,layout,draw方法来遍历视图树。 68 | 69 | 值得一提的是FrameBuffer的知识点,开始绘制时,会调用Surface.lockCanvas,由SurfaceFlinger锁定一块共享内存传递给Canvas,内存共享的是设备显存,在上面绘制相当于在屏幕上绘画。绘制结束调用Surface.unlockCanvasAndPost,从Suface上detach掉canvas,释放Surface。 70 | 71 | ### 触类旁通之SurfaceView 72 | 73 | SurfaceView会创建一个Z轴靠下的新Window,通过挖洞(重叠区域变透明)使自己可见。 74 | 75 | 观察一下SurfaceView的内部结构,似乎和ViewRootImpl差不多,同时持有IWindowSession,Surface和MyWindow(同ViewRootImple.WindowSession) 76 | 77 | ![SurfaceView](./SurfaceView.png) 78 | 79 | relayoutWindow,addWindow,Surface一气呵成,流程比较简单,注意一下SurfaceHolder,一般使用SurfaceView时候都是操作SurfaceHolder.Callback,它作为内部类一开始就创建出来了,而在native surface创建完毕之后调用SurfaceHolder.Callback.surfaceCreated。 80 | 81 | ![SurfaceView.updateWindow](./SurfaceView.updateWindow.png) 82 | 83 | ### 总结 84 | 85 | Activity启动时除了通过ViewRootImpl读取各个参数确定Window的大小,位置等等,通过WMS创建出相应大小的Surface和一块共享内存,等待DecorView通过Canvas绘制画面。 86 | 87 | ### 参考资料 88 | 89 | Android 7.1.1 源码 90 | 91 | Android官方文档 92 | 93 | 《Android开发艺术探索》 94 | 95 | 《深入理解Android 卷1》 96 | 97 | 其他优秀的中英文文章 -------------------------------------------------------------------------------- /架构分层详解/Android-Property-Service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/架构分层详解/Android-Property-Service.png -------------------------------------------------------------------------------- /架构分层详解/Android-logging-system.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/架构分层详解/Android-logging-system.png -------------------------------------------------------------------------------- /架构分层详解/Framework-Managers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/架构分层详解/Framework-Managers.png -------------------------------------------------------------------------------- /架构分层详解/Linux-Changes-by-Android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/架构分层详解/Linux-Changes-by-Android.png -------------------------------------------------------------------------------- /架构分层详解/深入理解Android层级.md: -------------------------------------------------------------------------------- 1 | 了解各个层级的组成,对于评估应用安全性或者进行某种攻击,是非常必要的。这个章节讨论的话题是,应用组成部件的安全性,运行时以及进程间通信的机制。 2 | 3 | ### Android应用层 4 | 5 | - AndroidManifest 6 | - 唯一包名和版本号 7 | - Activity, Service, BroadCast Receiver, Instrumentation定义 8 | - 需要权限的声明 9 | - 依赖第三库的声明 10 | - 优先安装位置以及UI信息 11 | 12 | 13 | - Intent:应用内通信的关键。需要关注其携带内容信息,目标组件,标志flag和权限需求。 14 | - Activities:界面元素,更需要关注ActivityManager。 15 | - Broadcat Receivers:应用间通信的另一种方式。需要关注其权限需求。 16 | - Services:除了应用启动的Services外,还需要关注系统的公用Service如蓝牙和短信服务。 17 | - ContentProviders:获取公用数据的接口,需要关注权限需求。 18 | 19 | ### Android Framework 20 | 21 | 应用层和运行时的粘合剂,提供UI,公用数据,信息传递功能和与应用无关的代码。例如android.\*,android.content或java.\*等。 22 | 23 | 除此之外,Framework还通过各种manager来管理服务,而下表列出的manager都是由system_server启动。 24 | 25 | ![Framework Managers](./Framework-Managers.png) 26 | 27 | 通过ps命令,可以看到manager的PPID都是system_server的PID。 28 | 29 | ### Dalvik虚拟机 30 | 31 | Dalvik是基于寄存器而不是基于栈的。它专门为嵌入式系统设计,本省是高效而快速的,但是,虚拟机毕竟是底层CPU寄存器的抽象,这导致了效率的损耗,这也是谷歌竭力来消除这部分影响的原因。 32 | 33 | 为了高效,DEX文件在被虚拟机解析之前就被已经被优化了。对于Android应用内加载DEX文件,这仅会发生在应用第一次加载的时候(在ART是应用安装时加载)。 34 | 35 | #### Zygote 36 | 37 | Android设备启动时执行的程序之一,第一个作用是负责启动各种附加服务和Android Framework需要的libraries。 38 | 39 | Zygote通过创建自身的复制,作为每个Dalvik进程的loder。这种优化策略避免重复加载资源的额外损耗。因此,核心库,类文件和heap架构的Dalvik进程实例都是一样的。 40 | 41 | 第二个作用是启动system_server进程。system_server进程运行了拥有特权AID的核心服务,也就是启动上面表格中的Android Framework Manager。 42 | 43 | ### User-Space Native Code 44 | 45 | 是Android的重要组成部分,由Libraries和系统核心服务组成。 46 | 47 | #### Libraries 48 | 49 | 通过JNI,Android Frameword中高层级的功能可以调用低层级的实现。例如本地数据存储库SQlite;嵌入式浏览器引擎Webkit等,一般存放在`/system/lib` 目录下。 50 | 51 | #### 核心服务 52 | 53 | 核心服务是指初始化底层操作系统环境和原生Android组件,这些环境包括初始化时启动init进程,和提供各种关键功能例如,debuggerd和adbd。 54 | 55 | ##### init 56 | 57 | 在Android系统上,user-space进程通过Linux内核启动的第一个命令就是init。和其他Linux系统相似,init程序执行一系列命令进行user-space环境初始化,其执行文位于`/init.rc`。(总的流程:开机->Boot Process->init->Zygote->System Server->System Process->User Process) 58 | 59 | 通过`cat /init.rc`或`cat /etc/init.rc`查看 60 | 61 | ```xml 62 | ... 63 | service servicemanager /system/bin/servicemanager 64 | class core 65 | user system 66 | group system 67 | critical 68 | onrestart restart zygote 69 | onrestart restart media 70 | onrestart restart surfaceflinger 71 | onrestart restart drm 72 | 73 | service vold /system/bin/vold 74 | class core 75 | socket vold stream 0660 root mount 76 | ioprio be 2 77 | 78 | service netd /system/bin/netd 79 | class main 80 | socket netd stream 0660 root system 81 | socket dnsproxyd stream 0660 root inet 82 | socket mdns stream 0660 root system 83 | ... 84 | ``` 85 | 86 | init执行的任务包括 87 | 88 | - 初始化必须的Service 89 | - 根据参数,指定每个Service下应该运行的用户和用户组 90 | - 通过Property Service设置全局环境变量 91 | - 注册指定事件的监听,例如修改系统属性或挂载文件系统 92 | 93 | ##### Property Service 94 | 95 | 在init进程中,提供持久化,内存对应的键值对形式的配置参数。包括网络接口参数,音频参数和安全相关设置都依赖这些配置参数。 96 | 97 | ![Android Property Service](./Android-Property-Service.png) 98 | 99 | 通过`getprop`或`setprop`获取和设置。 100 | 101 | ```xml 102 | [androVM.inited]: [1] 103 | [androVM.vbox_dpi]: [320] 104 | [androVM.vbox_graph_mode]: [768x1280-16] 105 | [dalvik.vm.heapgrowthlimit]: [64m]ces.txt] 106 | [dev.bootcomplete]: [1] 107 | ... 108 | [dhcp.eth1.dns3]: [] 109 | [genyd.ac.online.cached]: [1] 110 | [genyd.battery.full.cached]: [50000000] 111 | [genyd.device.id]: [000000000000000] 112 | ... 113 | [init.svc.vold]: [running] 114 | [init.svc.zygote]: [running] 115 | [keyguard.no_require_sim]: [true] 116 | [net.bt.name]: [Android] 117 | ... 118 | [net.tcp.buffersize.wifi]: [524288,1048576,2097152,262144,524288,1048576] 119 | [persist.service.adb.enable]: [1] 120 | ... 121 | [ro.allow.mock.location]: [0] 122 | [ro.boot.hardware]: [vbox86] 123 | [ro.bootloader]: [unknown] 124 | ``` 125 | 126 | 以ro打头的是只读属性,不能被修改。 127 | 128 | ##### Volume Daemon 129 | 130 | 又被称为vold,负责挂载和卸载Android文件系统。例如,当插入SD卡时,vold进程捕获到相关事件,将卡挂载到合适的路径(/mnt/sdcard )。当SD卡被拔掉或被手动拒绝,vold会把目标卡卸载。 131 | 132 | ### Kernel 133 | 134 | 接下来会讨论Android对标准的Linux Kernel做了一些修改。 135 | 136 | ![Linux Changes by Android](./Linux-Changes-by-Android.png) 137 | 138 | #### Binder 139 | 140 | 最重要的特性之一,提供IPC机制,允许同步调用另一个进程中的方法。由于Binder机制的复杂性和篇幅所限,这边先不作讨论。 141 | 142 | #### ashemem 143 | 144 | 匿名共享内存(Anonymous Shared Memory)。ashemen是一个基于文件的,引用计数的共享内存接口。由于ashmem目的就是当系统可用内存不足时自动收缩和回收内存,在Android核心组件中颇为流行,如Surface Flinger, Audio Flinger,System Server和DalvikVM。 145 | 146 | #### Logger 147 | 148 | Android kernel仍然使用基于Linux的kelnel-logging机制,同事它也使用另外被称为logger的子系统。logger通过logcat命令,用于查看日志信息,其中包括四种类型:main,radio,event和sustem。 149 | 150 | ![Android logging system](./Android-logging-system.png) 151 | 152 | 其中main输入的日志最多,它也是应用日志输出的来源。应用通过android.util.Log指定日志的优先级,Log.i代表information,Log.d代表debug,Log.e代表error(分级与syslog一致)。 153 | 154 | 系统进程打印的日志也传达出很多信息,它们通过android.util.Slog的native方法println_native,调用native code输出日志。 155 | 156 | ### 总结 157 | 158 | 回头看Android设计和架构一眼,这是多复杂的一个系统啊。 159 | 160 | 基于最小特权法则,应用只能申请它所需要的最小权限,提高安全性的同时,也增加了复杂性,再加上系统安全的基石——进程隔离和降权操作,无论是开发者还是攻击者,都加大了开发的难度。当攻击者想谋划一次攻击前,必须花时间完全理解这其中的复杂性。 161 | 162 | 仁者见仁,智者见智。 163 | 164 | 165 | ​ 166 | ​ -------------------------------------------------------------------------------- /架构和权限模型/Android架构和权限模型.md: -------------------------------------------------------------------------------- 1 | ### Android架构 2 | 3 | 对于Android架构一般的理解会是,”在Linux上的Java“。然而,不准确,这样的描述直接忽略掉平台的复杂架构。 4 | 5 | 纵观全局,整个架构由五部分组成,包括Android应用层,Android Framework,Dalvik虚拟机,native code和Linux kernel。 6 | 7 | ![android架构组成](./android架构组成.png) 8 | 9 | #### 职能简介 10 | 11 | - 应用层:允许开发者扩展与提升设备的功能,无需关注底层实现细节。 12 | - Framework:提供丰富的API调用Android设备上的功能,是应用层与Dalvik虚拟机之间的粘合剂。包括管理UI元素,数据存储与应用间传递数据 13 | 14 | 上述两层都是使用Java开发并在Dalvik虚拟机上执行的。 15 | 16 | - Dalvik:为操作系统特别设计的高性能抽象层,执行Dalvik Executable(DEX)文件。依赖于native层的libraries 17 | - Native:包括系统服务如vold,Dbus;网络服务如dhcpd;其他库如WebKit何OpenSSL。其中一些服务会与内核级服务,驱动程序通信,另一则在调用上简化了底层实代码。 18 | 19 | Android基于Linux内核做出很多额外的改动,例如获取相机,WIFI,网络,Binder和安全等。 20 | 21 | ### 安全模型 22 | 23 | Android使用两套分离但互相配合的权限模型。 24 | 25 | 在低层级,Linux内核强制使用用户和用户组权限。这种权限模型继承自Linux,限制获取文件系统和Android特定资源的权限。一般被称为**Android's sandBox**(沙箱)。 26 | 27 | 在Android运行时,通过Dalvik虚拟机和Android Framework实现第二套模型,在用户安装应用时启用,限制了应用能获取的权限。一部分的权限直接对应了底层操作系统特定用户,用户组的权限。 28 | 29 | #### Android‘s SandBox 30 | 31 | Android基于Linux,而Linux继承了Unix著名的进程独立和最小特权法则。需要注意的是,进程作为独立的用户运行时,既不能与其他用户通信,又无法访问内存区域。 32 | 33 | 所以沙箱可以理解为几个概念:标准的Linux进程隔离,大多数应用的单独用户ID(UID),严格的文件系统权限。 34 | 35 | Andoird定义了一系列的AndroidIDs(AIDs)来代替传统的用户和用户组的密码验证,通过补充组获取权限或共享资源,例如在sdcard_rw组的成员拥有读写/sdcard文件的权限。 36 | 37 | 除了文件系统的权限,补充组也用于获取其他权限。例如AID_INET组,允=允许用户打开AF_INET和AF_INET6套接字。 38 | 39 | 当应用运行时,UID,GID和补充组分配给一个新进程,操作系统不仅会在内核层级强制使用限制,还会在应用运行时进行控制,这就是Android沙箱。 40 | 41 | 在Android模拟器中,通过`ps`命令可以查看进程的PID。 42 | 43 | ![ps](./ps.png) 44 | 45 | 通过`cd /proc/1`进入PID为1的进程内,`cat status`查看UID和GID。 46 | 47 | ![uid](./uid.png) 48 | 49 | 50 | 51 | #### Android权限 52 | 53 | Android权限模型是多方面的,有API权限,文件系统权限和IPC权限。正如前面所说,高层级的权限会对应底层OS的功能,包括打开套接字,蓝牙设备,和特定的文件路径。 54 | 55 | Android高层级权限声明在AndroidManifest内。应用安装时,PackageManager会将应用权限从Manifest中读取出来,存储在/data/system/packages.xml中。 56 | 57 | 通过`cat /data/system/packages.xml`可以找到系统Phone的UID及权限。 58 | 59 | ```xml 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 | 这些权限会在应用进程实例化时(例如设置补充GIDs时)授予。 87 | 88 | 用户组的权限存储在/etc/permissions/platform.xml中,用于确定应用的补充组GIDs。 89 | 90 | 通过`cat /etc/permission/platform.xml`查看。下面列举存储权限。 91 | 92 | ```xml 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | ``` 108 | 109 | 这些权限会以下列两种方式之一强制执行:运行时调用方法时进行判断,底层操作系统或内核检查。 110 | 111 | ##### API权限 112 | 113 | API权限包括用于控制在API/frameword中高层级的功能和第三方框架。例如公用API权限READ_PHONE_STATE,在文档中定义为“仅允许读取电话状态”。需要该权限的应用要在调用任何与电话状态相关的API之前获取授权。相关方法在`TelephoneYManager`中,像getDeviceSoftwareVersion, getDeviceId等。 114 | 115 | ##### 文件系统权限 116 | 117 | Android应用沙箱由Unix严格的文件系统权限控制。UIDs和GIDs授予了访问各自文件系统内的存储空间。 118 | 119 | 通过`ls -l /data/data`查看,UIDs和GIDs(第二,第三列)在列表中是独一无二的,而且再看权限只有特定UIDs和GIDs才能访问其中内容。 120 | 121 | ![ls-l](./ls-l.png) 122 | 123 | 正如前面提到的,特定的补充GIDs用于获取共享资源,例如SD卡或外置存储。 124 | 125 | 通过`ls -l /mnt/`查看sdcard读写权限。 126 | 127 | ![sdcard](./sdcard.png) 128 | 129 | -------------------------------------------------------------------------------- /架构和权限模型/android架构组成.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/架构和权限模型/android架构组成.png -------------------------------------------------------------------------------- /架构和权限模型/ls-l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/架构和权限模型/ls-l.png -------------------------------------------------------------------------------- /架构和权限模型/ps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/架构和权限模型/ps.png -------------------------------------------------------------------------------- /架构和权限模型/sdcard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/架构和权限模型/sdcard.png -------------------------------------------------------------------------------- /架构和权限模型/uid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/架构和权限模型/uid.png -------------------------------------------------------------------------------- /真的理解Context?/Activity.attach.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/真的理解Context?/Activity.attach.png -------------------------------------------------------------------------------- /真的理解Context?/Context使用.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/真的理解Context?/Context使用.png -------------------------------------------------------------------------------- /真的理解Context?/DecorView的Context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/真的理解Context?/DecorView的Context.png -------------------------------------------------------------------------------- /真的理解Context?/FragmentHostCallback.context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/真的理解Context?/FragmentHostCallback.context.png -------------------------------------------------------------------------------- /真的理解Context?/LayoutInflater.rinflate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/真的理解Context?/LayoutInflater.rinflate.png -------------------------------------------------------------------------------- /真的理解Context?/createContext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/真的理解Context?/createContext.png -------------------------------------------------------------------------------- /真的理解Context?/need_new_task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/真的理解Context?/need_new_task.png -------------------------------------------------------------------------------- /真的理解Context?/真的理解Context?.md: -------------------------------------------------------------------------------- 1 | ## 概述 2 | 3 | 在使用getContext方法的时候有没有想过,在不同的场景下,取到的Context到底有什么不同,View,Fragment,Activity和Application的getContext又究竟是怎么样? 4 | 5 | 下面来分析一下。 6 | 7 | ## 获取Context 8 | 9 | ### DecorView 10 | 11 | DecorView的Context是Application Context。 12 | 13 | ActivityThread.addView -> PhoneWindow.generateDecor 14 | 15 | ![DecorView的Context](./DecorView的Context.png) 16 | 17 | ### View 18 | 19 | 一般View是从LayoutInflater类中inflate生成的,查看inflate方法,会调用rinflate 20 | 21 | ![LayoutInflater.rinflate](./LayoutInflater.rinflate.png) 22 | 23 | 发现是入参Context,在inflate方法内赋值,其实最后就是LayoutInflater.from的参数Context。 24 | 25 | 所以结论是普通View是LayoutInflater.from的参数Context。 26 | 27 | 但是对于xml这样的布局文件里面的View又是怎么样的呢? 28 | 29 | 看一下Activity.setContentView方法的调用过程是: 30 | 31 | Activity.setContentView -> PhoneWindow.setContentView -> LayoutInflater.inflate方法,而这个LayoutInflater是在PhoneWindow的构造方法内创建的,回到Activity.attach方法,看到构造方法的参数是Activity的Context。![Activity.attach](./Activity.attach.png) 32 | 33 | 于是增加一个结论,在xml中解析的View的Context属于Activity。 34 | 35 | 可能有人问,那Fragment也是吗?看一下Fragment.onCreateView中参数有LayoutInflater,跟踪一下 36 | 37 | Fragment.onCreateView <- Fragment.performCreateView -> FragmentManagerImpl.moveToState <- Fragment.performGetLayoutInflater <- Fragment.getLayoutInflater <- FragmentHostCallback.onGetLayoutInflater 38 | 39 | 结果是用了FramgentHostCallback构造方法的参数Context![FragmentHostCallback.Context](./FragmentHostCallback.Context.png) 40 | 41 | 结论也是Activity的Context。 42 | 43 | #### FragmentActivity 44 | 45 | 一般使用FragmentActivity.this和FragmentActivity.getContext方法取到Context,最终取到的都是Activity的Context,不再赘述。 46 | 47 | ### Fragment 48 | 49 | 通过Fragment.getContext取到Context,结果是取到FragmentHostCallback.getContext也是Activity的Context。 50 | 51 | ### Application 52 | 53 | 取到的是Application Context。 54 | 55 | ## Applicaiton与Activity的区别 56 | 57 | 所以最终的目的是区分Application与Activity的Context有什么不同。所以看它们各自的实现,Application与Activity都继承于ContextWrapper,ContextWrapper就是包装了一下抽象类Context,在构造方法里传入Context对象,根本在于构建Application和Activity的地方。 58 | 59 | 追踪一下发现Context构建都在ContextImpl类内,Application对应createAppContext,而Activity则对应createActivityContext,都在ActivityThread中调用各自创建Context方法进行初始化。![createContext](./createContext.png) 60 | 61 | 对比ContextImpl的构建,前三个参数都一致,Activity的Context多了activityToken和classLoder,其中activityToken对应ActivityRecord类,接着调用Context.setResource方法设置Activity的配置和逻辑显示相关的信息Display。![need_new_task](./need_new_task.png) 62 | 63 | 看一下这个熟悉的错误,这个是使用Application的Context启动Activity时报的错误,这个mSourceRecord就是AcitivityRecord对象,对于Application而言为null,所以需要指定New_Task这个标志。 64 | 65 | 两者之间更重要的一个区别是:生命周期。 66 | 67 | 对于Application的Context而言,在整个应用的生命周期内都不会改变;而对于不同的Activity,其Context有可能不同,例如添加一个Dialog必须附着在Activity上,所以使用Application就会报错。 68 | 69 | 下图来源于[Stackoverflow](https://stackoverflow.com/questions/4128589/difference-between-activity-Context-and-Application-Context) 70 | 71 | ![Context使用](./Context使用.png) 72 | 73 | ## 总结 74 | 75 | 除了Application,DecorView和getApplicationContext方法会取到Application Context外,其他方法getContext都会取到Activity Context或者传入的Context。 76 | 77 | 一般来说,Application Context存在于整个应用的生命周期中,不会随场景变化而改变,所以对于打开不同的Activity,Activity Context可能存在不同,而且生命周期跟Activity的生命周期一致。 -------------------------------------------------------------------------------- /绘图基础--Canvas与Drawable/Canvas、Bitmap.md: -------------------------------------------------------------------------------- 1 | ### 概述 2 | 3 | 画2D图形有两种方法: 4 | 5 | - 把图片和动画画到View对象里,整个绘图过程由系统的视图树处理,我们只需要定义好图形。适合于不需要动态改变的简单图形,如静态图片和定义好的动画 6 | - 在Canvas上绘图。在类的ondraw方法内获取Canvas,或者调用Canvas.drawXXX方法。适合重复重绘自己的图形,如视频。既可以通过自定义View通过invalidate,在UI线程里回调onDraw方法,也可以通过SurfaceView启动别的线程调用invalidate。 7 | 8 | ### 用Canvas绘图 9 | 10 | Canvas实际上是封装了各种draw方法的类,调用draw方法把图形绘制到底层的Surface上,即绘制在Window上。 11 | 12 | - 在onDraw方法里使用Canvas绘制比较简单,不再赘述。 13 | - 在SurfaceView通过lockCanvas获取Canvas,同上。 14 | - 自定义Canvas,根据文档所写,创建Canvas时,需要设置一个Bitmap。理解一下Bitmap,有一个内存指针mNativePtr和一些属性,可以简单视为一小块内存。从[前篇](https://www.jianshu.com/p/a7596afb1aa1)知道,绘制其实是写一块共享内存,而Bitmap可视为共享内存的一小块。 15 | 16 | 这个例子中构造了两个Canvas和一个Bitmap,分别调用其draw方法,先是mCanvas往Bitmap里绘制一个方块,再在onDraw方法内调用canvas.drawBitmap绘制这个方块。 17 | 18 | ![CustomView.draw](./CustomView.draw.png) 19 | 20 | 思考一个问题,为什么mCanvas需要设置Bitmap? 21 | 22 | 很简单,因为它没有。来看一下draw的起点ViewRootImpl(软件绘制,不开启硬件加速下)。![ViewRootImpl.drawSoftware](./ViewRootImpl.drawSoftware.png) 23 | 24 | 这个通过mSurface.lockCanvas返回的Canvas是View.draw的canvas变量,所以当1,2情况时,Canvas都持有一个Bitmap,指向共享内存里的某一小块,当调用Canvas.draw方法时就能绘制出东西。但对于自定义Canvas来说并不是,即使设置一个Bitmap和绘制了Bitmap,但不往共享内存上写,屏幕上是不会显示的,SurfaceView同理,通过Surface.lockCanvas获取持有共享内存的Canvas,绘制完毕后调用Surface.unlockCanvasAndPost把绘制内容显示到surface上并release掉Canvas。![SurfaceView.internalLockCanvas](./SurfaceView.internalLockCanvas.png) 25 | 26 | 顺带一提Canvas.save和Canvas.restore方法,如下Demo 27 | 28 | ![MyView.onDraw](./MyView.onDraw.png) 29 | 30 | 效果图如 31 | 32 | ![效果图](./效果图.png) 33 | 34 | 画的是三个颜色和旋转角度都不同的小方形。 35 | 36 | 步骤1把默认坐标系旋转20°,画出第一个蓝色的方形,步骤2保存当前的matrix(旋转了20°),继续旋转20°,此时坐标系已经旋转了40°,画出第二个黄色的方块,步骤3,恢复上一步保存的matrix(旋转了20°),此时坐标系还是旋转了20°,步骤4,再旋转40°,此时坐标系旋转了60°,画出第三个黑色方块。 37 | 38 | Canvas.save用于保存当前matrix和clip,Canvas.restore用于恢复上次保存的matrix和clip。 39 | 40 | ### Drawable 41 | 42 | Drawable是一个能画出来的物体的抽象,使用前需要调用setBounds确定位置和大小,通过getIntrinsicHeight和getIntrinsicWidth取到实际大小。Drawable可以有几种形式存在:Bitmap、Nine Patch、Vector、Shape、Layers等。 43 | 44 | ![getdrawable](./getdrawable.png) 45 | 46 | 从Resource.getDrawable会判断是否.xml结尾,不是的话走6,7步,如果从xml中读取,需要getResource.getDrawable -> ResourceImpl.loadDrawableForCookie -> drawable.createFromXml -> DrawableInflater.inflateFromXmlForDensity -> drawable.inflateFromTag 47 | 48 | ![DrawableInflater.inflateFromTag](./DrawableInflater.inflateFromTag.png) 49 | 50 | 看一下Shape实现类GradientDrawable的inflate实现,读取各项属性并赋值,到draw方法。![GradientDrawable.draw](./GradientDrawable.draw.png) 51 | 52 | 调用canvas.drawRect把mRect画出来,而mRect的赋值在ensureValidRect。![GradientDrawable.ensureValidRect](./GradientDrawable.ensureValidRect.png) 53 | 54 | bounds在哪里设置的?答案是ImageView.updateDrawable内,会调用Drawable.getIntrinsicHeight赋值(从xml中size属性读取),再调用configureBounds -> setBounds,如果使用的不是ImageView,一定要在draw之前**调用setBounds**,否则size就会出错。 55 | 56 | ![ImageView.updateDrawable](./ImageView.updateDrawable.png) 57 | 58 | 回到loadDrawableForCookie,再看一下6,7步加载图片的过程,通过AssetManager读取图片流数据,通过Drawable.createFromResourceStream这个我们经常使用的方法获取到Drawable。 59 | 60 | ![Drawable.createFromResourcesStream](./Drawable.createFromResourcesStream.png) 61 | 62 | 取到屏幕密度之后调用BitmapFactory.decodeResourcesStream,计算密度后调用native创建Bitmap,感兴趣的同学可以看下更具体的分析文章(如[理解Bitmap](https://zhuanlan.zhihu.com/p/31450987))。 63 | 64 | ### 总结 65 | 66 | 本文探究了两点 67 | 68 | - View.Canvas与new Canvas的区别。 69 | - 创建Drawable的过程 70 | 71 | ### 参考资料 72 | 73 | Android 7.1.1 源码 74 | 75 | Android 官方文档,[Canvas and Drawable](https://developer.android.com/guide/topics/graphics/2d-graphics.html), [Drawable](https://developer.android.com/reference/android/graphics/drawable/package-summary.html)等 76 | 77 | -------------------------------------------------------------------------------- /绘图基础--Canvas与Drawable/CustomView.draw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/绘图基础--Canvas与Drawable/CustomView.draw.png -------------------------------------------------------------------------------- /绘图基础--Canvas与Drawable/Drawable.createFromResourcesStream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/绘图基础--Canvas与Drawable/Drawable.createFromResourcesStream.png -------------------------------------------------------------------------------- /绘图基础--Canvas与Drawable/DrawableInflater.inflateFromTag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/绘图基础--Canvas与Drawable/DrawableInflater.inflateFromTag.png -------------------------------------------------------------------------------- /绘图基础--Canvas与Drawable/GradientDrawable.draw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/绘图基础--Canvas与Drawable/GradientDrawable.draw.png -------------------------------------------------------------------------------- /绘图基础--Canvas与Drawable/GradientDrawable.ensureValidRect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/绘图基础--Canvas与Drawable/GradientDrawable.ensureValidRect.png -------------------------------------------------------------------------------- /绘图基础--Canvas与Drawable/ImageView.updateDrawable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/绘图基础--Canvas与Drawable/ImageView.updateDrawable.png -------------------------------------------------------------------------------- /绘图基础--Canvas与Drawable/MyView.onDraw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/绘图基础--Canvas与Drawable/MyView.onDraw.png -------------------------------------------------------------------------------- /绘图基础--Canvas与Drawable/ResourcesImpl.loadDrawableForCookie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/绘图基础--Canvas与Drawable/ResourcesImpl.loadDrawableForCookie.png -------------------------------------------------------------------------------- /绘图基础--Canvas与Drawable/SurfaceView.internalLockCanvas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/绘图基础--Canvas与Drawable/SurfaceView.internalLockCanvas.png -------------------------------------------------------------------------------- /绘图基础--Canvas与Drawable/ViewRootImpl.drawSoftware.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/绘图基础--Canvas与Drawable/ViewRootImpl.drawSoftware.png -------------------------------------------------------------------------------- /绘图基础--Canvas与Drawable/getdrawable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/绘图基础--Canvas与Drawable/getdrawable.png -------------------------------------------------------------------------------- /绘图基础--Canvas与Drawable/效果图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenSiLiang/android-toy/28301529530d600178ae5c25be7181a5ecf184c7/绘图基础--Canvas与Drawable/效果图.png --------------------------------------------------------------------------------