├── .nojekyll ├── .gitignore ├── docs ├── MultiThread │ ├── 线程池.md │ ├── Java中多线程理解系列.md │ ├── 使用单一线程维持对象状态的一致性.md │ ├── 线程的生命周期详解.md │ ├── ReentrantLock.md │ ├── 从AtomicInteger来看CAS机制.md │ └── ThreadLocal.md ├── JVM │ ├── 5_Java线程安全与锁优化.md │ ├── 8_JVM类文件结构.md │ ├── 9_JVM字节码执行引擎.md │ ├── 6_JVM程序编译与代码优化.md │ ├── 深入理解JVM虚拟机读书笔记.md │ ├── 4_Java内存模型与线程.md │ ├── 1_Java的内存区域.md │ ├── 3_JVM类加载机制.md │ └── 2_JVM的GC和内存分配.md ├── Kotlin │ └── Kotlin中的泛型.md ├── Android │ ├── UI │ │ ├── RecyclerView和DiffUtils使用问题.md │ │ ├── RecyclerView中出现item重复的问题分析.md │ │ └── 仿京东、淘宝首页,通过两层嵌套的RecyclerView实现tab的吸顶效果.md │ ├── 其他 │ │ ├── BroadcastReceiver和LocalBroadcastReceiver的区别.md │ │ ├── 热修复.md │ │ ├── Android7.0上的混合编译.md │ │ ├── APK打包及安装过程.md │ │ ├── 通过事务支持加快sqlite数据库批量操作的效率.md │ │ ├── 详解APT.md │ │ ├── Android中的事件传递.md │ │ ├── 组件化.md │ │ ├── IntentFilter匹配规则.md │ │ └── 插件化.md │ ├── 图片相关 │ │ ├── Bitmap相关文章.md │ │ ├── 图像显示原理.md │ │ ├── DiskLruCache.md │ │ ├── Best_Practices_for_Using_Alpha.md │ │ └── 从assets目录和drawable加载的Bitmap的区别.md │ ├── SharedPreference和ContentProvider.md │ ├── 面试题收集 │ │ ├── Android面试题系列.md │ │ ├── Interview_1.md │ │ ├── Interview_2.md │ │ └── InterView_Backup.md │ ├── Android系统工作机制分析.md │ ├── 开源库 │ │ ├── RxJava.md │ │ ├── Retrofit.md │ │ ├── BlockCanary.md │ │ ├── tinker.md │ │ ├── OKHTTP.md │ │ ├── RxJava相关文章.md │ │ └── ActivityRouter.md │ ├── App启动流程.md │ ├── Activity │ │ ├── Activity的生命周期和启动模式.md │ │ └── Activity泄露发现与诊断.md │ ├── IPC │ │ ├── Android中的IPC机制.md │ │ └── 如何判断自己的进程是被Android_low_memory_killer杀死的.md │ ├── Handler.md │ └── 为什么RxJava的observeOn不能随便用.md ├── img │ ├── java.jpeg │ ├── logo.png │ ├── 二叉树.png │ ├── 完全二叉树.png │ ├── 满二叉树.png │ ├── Buckets.jpg │ ├── adt_use.png │ ├── Binder工作机制.png │ ├── adt_use_2.png │ ├── adt_use_3.png │ ├── cpu_cache.png │ ├── draw_arc.png │ ├── gc_copy.webp │ ├── java_map.jpg │ ├── view_event.jpg │ ├── Activity_gc.png │ ├── Android显示框架.jpg │ ├── ThreadLocal.webp │ ├── atlas组件打包过程.png │ ├── class_loader.png │ ├── cpu_hardware.png │ ├── hashmap_hash.jpg │ ├── jvm_runtime.png │ ├── linklist_add.jpg │ ├── Activity启动流程图.jpeg │ ├── PorterDuff_Mode.png │ ├── atomic_intger.png │ ├── class_loader.webp │ ├── dumpsys_meminfo.png │ ├── gc_collector.webp │ ├── gc_mark_clean.webp │ ├── hashmap_resize.jpg │ ├── hashmap_resize2.jpg │ ├── java_collection.jpg │ ├── jvm_class_load.webp │ ├── AllocationTracker.jpg │ ├── Android_pack_apk.png │ ├── IBookManager_java.png │ ├── binary_tree_node.png │ ├── cache_coherence.webp │ ├── concurrenthashmap.png │ ├── gc_mark_clear_up.webp │ ├── java_memory_model.png │ ├── jstack_print_info.png │ ├── linkedlist_remove.jpg │ ├── thread_lifecycle.jpg │ ├── Android_install_apk.png │ ├── activity_lifecycle.jpg │ ├── cpu_cache_framework.png │ ├── gc_collector_table.webp │ ├── concurrenthashmap_put.webp │ ├── java_object_access_1.webp │ ├── java_object_access_2.webp │ ├── java_object_lifecycle.jpeg │ ├── view_left_right_top_bottom.jpg │ ├── qrcode_for_gh_ac160b0b30bd_258.jpg │ └── viewpager_offsetScreenPageLimit.png ├── Network │ ├── TCP.md │ ├── Https.md │ └── HTTP与HTTPS有什么区别.md ├── Java │ ├── 深入解析Sting_intern.md │ ├── Java中一些比较难理解的知识点.md │ ├── Java系列集合源码分析.md │ ├── Java中的注解.md │ └── 从源码角度分析ArrayList和Vector的区别.md ├── PerformanceOptimization │ ├── 图片优化.md │ ├── 启动优化.md │ ├── ANR分析.md │ ├── 安装包优化.md │ ├── 耗电优化.md │ ├── 内存优化.md │ ├── App的性能优化系列文章.md │ ├── 一种Fragment懒加载的优化策略.md │ ├── 如何优化Activity启动速度.md │ ├── Feed流上的优化实践.md │ ├── 一种Activity预加载的方法.md │ ├── 绘制优化.md │ ├── 性能分析相关命令和工具介绍.md │ ├── 在provider初始化的时候如何处理耗时操作.md │ └── 稳定性优化.md ├── DesignPattern │ ├── 设计模式中6大设计原则.md │ └── 尽量不要用实现、继承,请用组合、聚合.md ├── index.md └── Algorithm │ └── 为什么说二分查找的时间复杂度是O(logn).md ├── _coverpage.md ├── README.md ├── index.html └── _sidebar.md /.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | site -------------------------------------------------------------------------------- /docs/MultiThread/线程池.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/JVM/5_Java线程安全与锁优化.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/Kotlin/Kotlin中的泛型.md: -------------------------------------------------------------------------------- 1 | # Kotlin中的泛型 -------------------------------------------------------------------------------- /docs/Android/UI/RecyclerView和DiffUtils使用问题.md: -------------------------------------------------------------------------------- 1 | TODO 2 | -------------------------------------------------------------------------------- /docs/JVM/8_JVM类文件结构.md: -------------------------------------------------------------------------------- 1 | https://www.jianshu.com/p/43a93c3216b8 -------------------------------------------------------------------------------- /docs/JVM/9_JVM字节码执行引擎.md: -------------------------------------------------------------------------------- 1 | https://www.jianshu.com/p/8be235393021 -------------------------------------------------------------------------------- /docs/Android/其他/BroadcastReceiver和LocalBroadcastReceiver的区别.md: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /docs/JVM/6_JVM程序编译与代码优化.md: -------------------------------------------------------------------------------- 1 | https://www.jianshu.com/p/904c17f0d09e -------------------------------------------------------------------------------- /docs/img/java.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/java.jpeg -------------------------------------------------------------------------------- /docs/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/logo.png -------------------------------------------------------------------------------- /docs/img/二叉树.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/二叉树.png -------------------------------------------------------------------------------- /docs/img/完全二叉树.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/完全二叉树.png -------------------------------------------------------------------------------- /docs/img/满二叉树.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/满二叉树.png -------------------------------------------------------------------------------- /docs/Network/TCP.md: -------------------------------------------------------------------------------- 1 | [TCP 为什么三次握手而不是两次握手](https://blog.csdn.net/lengxiao1993/article/details/82771768) -------------------------------------------------------------------------------- /docs/img/Buckets.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/Buckets.jpg -------------------------------------------------------------------------------- /docs/img/adt_use.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/adt_use.png -------------------------------------------------------------------------------- /docs/img/Binder工作机制.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/Binder工作机制.png -------------------------------------------------------------------------------- /docs/img/adt_use_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/adt_use_2.png -------------------------------------------------------------------------------- /docs/img/adt_use_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/adt_use_3.png -------------------------------------------------------------------------------- /docs/img/cpu_cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/cpu_cache.png -------------------------------------------------------------------------------- /docs/img/draw_arc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/draw_arc.png -------------------------------------------------------------------------------- /docs/img/gc_copy.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/gc_copy.webp -------------------------------------------------------------------------------- /docs/img/java_map.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/java_map.jpg -------------------------------------------------------------------------------- /docs/img/view_event.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/view_event.jpg -------------------------------------------------------------------------------- /docs/img/Activity_gc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/Activity_gc.png -------------------------------------------------------------------------------- /docs/img/Android显示框架.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/Android显示框架.jpg -------------------------------------------------------------------------------- /docs/img/ThreadLocal.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/ThreadLocal.webp -------------------------------------------------------------------------------- /docs/img/atlas组件打包过程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/atlas组件打包过程.png -------------------------------------------------------------------------------- /docs/img/class_loader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/class_loader.png -------------------------------------------------------------------------------- /docs/img/cpu_hardware.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/cpu_hardware.png -------------------------------------------------------------------------------- /docs/img/hashmap_hash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/hashmap_hash.jpg -------------------------------------------------------------------------------- /docs/img/jvm_runtime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/jvm_runtime.png -------------------------------------------------------------------------------- /docs/img/linklist_add.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/linklist_add.jpg -------------------------------------------------------------------------------- /docs/img/Activity启动流程图.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/Activity启动流程图.jpeg -------------------------------------------------------------------------------- /docs/img/PorterDuff_Mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/PorterDuff_Mode.png -------------------------------------------------------------------------------- /docs/img/atomic_intger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/atomic_intger.png -------------------------------------------------------------------------------- /docs/img/class_loader.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/class_loader.webp -------------------------------------------------------------------------------- /docs/img/dumpsys_meminfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/dumpsys_meminfo.png -------------------------------------------------------------------------------- /docs/img/gc_collector.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/gc_collector.webp -------------------------------------------------------------------------------- /docs/img/gc_mark_clean.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/gc_mark_clean.webp -------------------------------------------------------------------------------- /docs/img/hashmap_resize.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/hashmap_resize.jpg -------------------------------------------------------------------------------- /docs/img/hashmap_resize2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/hashmap_resize2.jpg -------------------------------------------------------------------------------- /docs/img/java_collection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/java_collection.jpg -------------------------------------------------------------------------------- /docs/img/jvm_class_load.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/jvm_class_load.webp -------------------------------------------------------------------------------- /docs/Android/图片相关/Bitmap相关文章.md: -------------------------------------------------------------------------------- 1 | > [从assets目录和drawable加载的Bitmap的区别](/docs/Android/图片相关/从assets目录和drawable加载的Bitmap的区别.md) -------------------------------------------------------------------------------- /docs/img/AllocationTracker.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/AllocationTracker.jpg -------------------------------------------------------------------------------- /docs/img/Android_pack_apk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/Android_pack_apk.png -------------------------------------------------------------------------------- /docs/img/IBookManager_java.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/IBookManager_java.png -------------------------------------------------------------------------------- /docs/img/binary_tree_node.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/binary_tree_node.png -------------------------------------------------------------------------------- /docs/img/cache_coherence.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/cache_coherence.webp -------------------------------------------------------------------------------- /docs/img/concurrenthashmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/concurrenthashmap.png -------------------------------------------------------------------------------- /docs/img/gc_mark_clear_up.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/gc_mark_clear_up.webp -------------------------------------------------------------------------------- /docs/img/java_memory_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/java_memory_model.png -------------------------------------------------------------------------------- /docs/img/jstack_print_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/jstack_print_info.png -------------------------------------------------------------------------------- /docs/img/linkedlist_remove.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/linkedlist_remove.jpg -------------------------------------------------------------------------------- /docs/img/thread_lifecycle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/thread_lifecycle.jpg -------------------------------------------------------------------------------- /docs/Java/深入解析Sting_intern.md: -------------------------------------------------------------------------------- 1 | [深入解析Sting#intern](https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html) -------------------------------------------------------------------------------- /docs/img/Android_install_apk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/Android_install_apk.png -------------------------------------------------------------------------------- /docs/img/activity_lifecycle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/activity_lifecycle.jpg -------------------------------------------------------------------------------- /docs/img/cpu_cache_framework.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/cpu_cache_framework.png -------------------------------------------------------------------------------- /docs/img/gc_collector_table.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/gc_collector_table.webp -------------------------------------------------------------------------------- /docs/img/concurrenthashmap_put.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/concurrenthashmap_put.webp -------------------------------------------------------------------------------- /docs/img/java_object_access_1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/java_object_access_1.webp -------------------------------------------------------------------------------- /docs/img/java_object_access_2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/java_object_access_2.webp -------------------------------------------------------------------------------- /docs/img/java_object_lifecycle.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/java_object_lifecycle.jpeg -------------------------------------------------------------------------------- /docs/Android/SharedPreference和ContentProvider.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [你最了解的SharedPreference和ContentProvider知多少](https://xiaozhuanlan.com/topic/7349825601) -------------------------------------------------------------------------------- /docs/img/view_left_right_top_bottom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/view_left_right_top_bottom.jpg -------------------------------------------------------------------------------- /docs/Android/图片相关/图像显示原理.md: -------------------------------------------------------------------------------- 1 | # 图像显示原理 2 | 3 | blog: [Android图形显示系统——一张图片的显示流程](http://blog.csdn.net/jxt1234and2010/article/details/50524213) 4 | -------------------------------------------------------------------------------- /docs/img/qrcode_for_gh_ac160b0b30bd_258.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/qrcode_for_gh_ac160b0b30bd_258.jpg -------------------------------------------------------------------------------- /docs/img/viewpager_offsetScreenPageLimit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGaoH/KnowledgeSummary/HEAD/docs/img/viewpager_offsetScreenPageLimit.png -------------------------------------------------------------------------------- /_coverpage.md: -------------------------------------------------------------------------------- 1 | # KnowledgeSummary 1.0.0 2 | 3 | Github 4 | Get Started 5 | -------------------------------------------------------------------------------- /docs/PerformanceOptimization/图片优化.md: -------------------------------------------------------------------------------- 1 | * [LruCache](../Android/图片相关/LruCache.md) 2 | * [DiskLruCache](../Android/图片相关/DiskLruCache.md) 3 | * [图像显示原理](../Android/图片相关/图像显示原理.md) 4 | -------------------------------------------------------------------------------- /docs/Android/面试题收集/Android面试题系列.md: -------------------------------------------------------------------------------- 1 | - [Android中面试题总结(附答案)](./Android中面试题总结.md) 2 | - [面试题集1](./Interview_1.md) 3 | - [面试题集2](./Interview_2.md) 4 | - [小专栏Android面试整理](./InterView_Enhance.md) -------------------------------------------------------------------------------- /docs/PerformanceOptimization/启动优化.md: -------------------------------------------------------------------------------- 1 | ### 启动优化 2 | [你知道android的MessageQueue.IdleHandler吗?](https://mp.weixin.qq.com/s/KpeBqIEYeOzt_frANoGuSg?) 3 | 4 | - 白屏 prewindow cleartask 5 | - ‌多进程初始化sdk,每个进程都会初始化一次,听云sdk 6 | - 异步初始化,线程池串行,idle handler,推送,web资源延迟初始化 -------------------------------------------------------------------------------- /docs/Android/其他/热修复.md: -------------------------------------------------------------------------------- 1 | # 热修复 2 | 3 | 参考: 4 | 5 | [干货满满,Android热修复方案介绍](https://yq.aliyun.com/articles/231111?utm_content=m_34179) 6 | 7 | [阿里推出业界首个非侵入式热修复方案Sophix,颠覆移动端传统发版更新流程!](https://mp.weixin.qq.com/s/5KjSPvUflbg0pVRIjtLiRA?spm=5176.100239.blogcont102404.14.ARzI6c) -------------------------------------------------------------------------------- /docs/Java/Java中一些比较难理解的知识点.md: -------------------------------------------------------------------------------- 1 | ### Java中一些比较难理解的知识点(泛型,注解,反射) 2 | 3 | 对于 Java 中的泛型,注解,反射等这些知识点,因为我们平时只会使用到它们的基础功能,所以我们在很多时候只是知道个大概,但是对于这些知识的理解往往是你与一般开发者拉开差距的地方。 4 | 5 | 下面的两篇文章主要从网络博客中摘要下来的文章,文末有链接,暂时这一块我这边没有进行特殊整理。 6 | 7 | > [Java中的注解](/docs/Java/Java中的注解.md) 8 | 9 | > [Java中的泛型](/docs/Java/Java中的泛型.md) 10 | 11 | ``待补充`` 12 | > [Java中的反射] -------------------------------------------------------------------------------- /docs/PerformanceOptimization/ANR分析.md: -------------------------------------------------------------------------------- 1 | ANR(Application Not responding)。Android中,主线程(UI线程)如果在规定时内没有处理完相应工作,就会出现ANR。具体来说,ANR会在以下几种情况中出现: 2 | 1. 输入事件(按键和触摸事件)5s内没被处理 3 | 2. BroadcastReceiver的事件(onRecieve方法)在规定时间内没处理完(前台广播为10s,后台广播为60s) 4 | 3. service 前台20s后台200s未完成启动 5 | 4. ContentProvider的publish在10s内没进行完 6 | 7 | [AndroidANR问题总结](https://www.jianshu.com/p/fa962a5fd939) -------------------------------------------------------------------------------- /docs/JVM/深入理解JVM虚拟机读书笔记.md: -------------------------------------------------------------------------------- 1 | ## 整理完成 2 | 3 | - [Java的内存区域](/docs/JVM//1_Java的内存区域.md) 4 | - [JVM的GC和内存分配](/docs/JVM/2_JVM的GC和内存分配.md) 5 | - [JVM类加载机制](/docs/JVM/3_JVM类加载机制.md) 6 | - [JVM类加载器](/docs/JVM/7_JVM类加载器.md) 7 | 8 | ## 待整理 9 | - [Java线程安全与锁优化](/docs/JVM/5_Java线程安全与锁优化.md) 10 | - [JVM程序编译与代码优化](/docs/JVM/6_JVM程序编译与代码优化.md) 11 | - [JVM类文件结构](/docs/JVM/8_JVM类文件结构.md) 12 | - [JVM字节码执行引擎](/docs/JVM/9_JVM字节码执行引擎) -------------------------------------------------------------------------------- /docs/DesignPattern/设计模式中6大设计原则.md: -------------------------------------------------------------------------------- 1 | #### 里式替换原则 2 | 3 | 所有引用基类的地方必须能透明地使用其子类的对象。 4 | 5 | #### 依赖倒置原则 6 | 7 | 高层不应该依赖底层模块,两者都应该依赖其抽象 8 | 抽象不应该依赖细节 9 | 细节应该依赖抽象 10 | 11 | 本质是:通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合。 12 | 13 | #### 接口隔离原则 14 | 15 | 建立单一接口,不要建立臃肿庞大的接口。通俗一点来说,接口尽量细化,同时接口中的方法尽量少。 16 | 17 | #### 迪米特法则 18 | 19 | 最少知识原则 20 | 21 | 一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道的最少。 22 | 23 | #### 开闭原则 24 | 25 | 一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。 -------------------------------------------------------------------------------- /docs/Android/Android系统工作机制分析.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | > [深入分析AsyncTask](/docs/Android/深入分析AsyncTask.md) 4 | 5 | > [从源码角度剖析Handler机制](/docs/Android/从源码角度剖析Handler机制.md) 6 | 7 | > [Android启动流程分析](/docs/Android/android启动流程分析.md) 8 | 9 | 10 | 11 | > [Activity的生命周期和启动模式](/docs/Android/Activity/Activity的生命周期和启动模式.md) 12 | 13 | > [View事件传递](/docs/Android/其他/Android中的事件传递.md) 14 | 15 | > [详解注解处理器APT技术](/docs/Android/其他/详解APT.md) 16 | 17 | > [APK打包及安装过程](/docs/Android/其他/APK打包及安装过程.md) 18 | 19 | > [Android中的IPC机制](/docs/Android/IPC/Android中的IPC机制.md) -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Welcome to MkDocs 2 | 3 | For full documentation visit [mkdocs.org](https://www.mkdocs.org). 4 | 5 | ## Commands 6 | 7 | * `mkdocs new [dir-name]` - Create a new project. 8 | * `mkdocs serve` - Start the live-reloading docs server. 9 | * `mkdocs build` - Build the documentation site. 10 | * `mkdocs -h` - Print help message and exit. 11 | 12 | ## Project layout 13 | 14 | mkdocs.yml # The configuration file. 15 | docs/ 16 | index.md # The documentation homepage. 17 | ... # Other markdown pages, images and other files. 18 | -------------------------------------------------------------------------------- /docs/PerformanceOptimization/安装包优化.md: -------------------------------------------------------------------------------- 1 | # 性能优化5/5 -- 安装包优化 2 | 3 | * 打包时,对代码和资源进行混淆、压缩 4 | * 使用Lint删除冗余资源 5 | * 采用合适的图片编码格式,没必要都是高清 6 | * 使用WebP图片格式 7 | 8 | ## 真实项目实际操作 9 | * 剔除无用so文件,整理出so文件维护列表,减少约2M 10 | * 剔除无用资源文件,通过gradle自动检测,和代码优化对齐(但是会存在循环引用, 需要人为参与) 11 | * 引入AndResGuard , 混淆资源文件路径和文件名称, 大幅减少了resources.arsc 索引文件的体积, 减少2M 12 | * 仅支持armeabi-v7a一种CPU ABI指令集(兼容运行 x86, x86_64, arm64-v8a), 以后不在支持 armeabi(市场占有率几乎为0), 减少约2.5M 13 | * 图片相似度检测算法(采用感知哈希算法 dHash), 找出图片目录中所有相同的图片(海明距离 = 0 , 此处的海明距离可以理解为相似程度, 值越小越相似), 减少约1.5M 14 | * 压缩图片, 采用ImageOptim, 有损压缩70% 15 | * 优化代码混淆 , 将之前未混淆和混淆力度不够的组件, 尽量混淆 16 | * RN资源预加载 , 节约约1M, 移除无用滤镜文件。 -------------------------------------------------------------------------------- /docs/MultiThread/Java中多线程理解系列.md: -------------------------------------------------------------------------------- 1 | ## Java中多线程理解系列 2 | 3 | > [线程的生命周期详解](/docs/MultiThread/线程的生命周期详解.md) 4 | 5 | > [synchronized关键字的原理](/docs/MultiThread/synchronized关键字的原理.md) 6 | 7 | > [深入解析volatile关键字](/docs/MultiThread/深入解析volatile关键字.md) 8 | 9 | > [一次搞懂sleep、wait、yield、join和interrupted线程相关方法](/docs/MultiThread/一次搞懂sleep、wait、yield、join和interrupted线程相关方法.md) 10 | 11 | > [从AtomicInteger来看CAS机制](/docs/MultiThread/从AtomicInteger来看CAS机制.md) 12 | 13 | > [多线程间通信](/docs/MultiThread/多线程间通信.md) 14 | 15 | > [ThreadLocal](/docs/MultiThread/ThreadLocal.md) 16 | 17 | > [ReentrantLock](/docs/MultiThread/ReentrantLock.md) 18 | 19 | > [使用单一线程维持对象状态的一致性](/docs/MultiThread/使用单一线程维持对象状态的一致性.md) 20 | 21 | -------------------------------------------------------------------------------- /docs/Java/Java系列集合源码分析.md: -------------------------------------------------------------------------------- 1 | ## Java 系列集合源码分析 2 | 3 | 工欲善其事,必先利其器。平时我们用的最多的API就是Java中各式各样的集合类了,要想运用得当,必须要对集合中一些实现细节有所了解。 4 | 5 | [ArrayList和Vector的区别](./从源码角度分析ArrayList和Vector的区别.md) 主要介绍了ArrayList和Vector的差异,Vector因为其自身性能的问题,我们现在几乎不用这个集合类了,这里可以简单了解下。 6 | 7 | [ArrayList和LinkedList的区别](./从源码角度解析ArrayList和LinkedList的区别.md) 主要介绍了ArrayList和LinkedList的差异,这两个集合可以说是我们开发过程中遇到最多的集合类,我们需要学习两者在实现上的差异性来灵活地根据不同的场景调整集合的使用策略。 8 | 9 | [关于HashMap你需要知道的一些细节](./关于HashMap你需要知道的一些细节.md) 这篇文章主要介绍了HashMap中一些实现细节,如何解决hash碰撞,如何扩容等问题。 10 | 11 | [ConcurrentHashMap是如何保证线程安全的](./ConcurrentHashMap是如何保证线程安全的.md),ConcurrentHashMap在Android 的开发中用的很少,但是在面试过程中面试官经常会结合多线程的相关知识来考验你对于它的理解。 12 | 13 | [Java中的红黑树解析](./我画了近百张图来理解红黑树.md)这篇文章最早是由我在公司内部的一个分享转换过来的,之前发在掘金平台,仅这一篇文章就收获了四百多的点赞,为了将红黑树讲清楚,说了很多张图,理解红黑树,这篇文章就够了。 -------------------------------------------------------------------------------- /docs/PerformanceOptimization/耗电优化.md: -------------------------------------------------------------------------------- 1 | # 性能优化4/5 -- 耗电优化 2 | 3 | * 耗电检查工具 4 | * 耗电三大块 5 | * 常用优化方案 6 | 7 | 8 | ### 耗电检查工具 9 | BatteryHistorian 10 | 11 | 可直观展示手机的耗电过程。 12 | 13 | 14 | ### 耗电三大块 15 | 手机耗电三大块: 16 | 17 | * 屏显 18 | * CPU 19 | * 语音通话和用户流量 20 | 21 | ##### 屏显 22 | 应用内调节亮度不合适,一般由系统控制。 23 | 24 | ##### CPU 25 | 26 | Linux内核提供5中变频模式,不同模式下,CPU运行频率不同,功耗也不同。但是需要在Root权限下才能改变频模式。 27 | 28 | ##### 语音通话和蜂窝流量 29 | 30 | WiFi下数据通信比蜂窝流量功耗低,所以尽量在WiFi下进行数据通信。 31 | 32 | 蜂窝下,尽量降低网络请求频率。 33 | 34 | ### 常用优化方案 35 | 36 | 1.浮点运算比整数运算更消耗CPU时间片,耗电也会增加。 37 | 38 | 避开浮点运算的方法: 39 | * 除法变乘法 40 | * 充分利用移位 41 | * 利用arm neon指令集做并行运算,需要arm v7及以上架构CPU才支持 42 | 43 | 2.避免WakeLock使用不当 44 | 45 | 使用完WakeLock,要及时释放。 46 | 47 | 3.使用JobScheduler 48 | 49 | 使用JobScheduler在满足一些场景下才执行任务,区分任务的重要性和紧急性。比如WIFI下才进行Log的发送、充电时才进行数据的备份等等。 -------------------------------------------------------------------------------- /docs/Android/开源库/RxJava.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [RxJava 是如何实现线程切换的(上)](https://www.jianshu.com/p/3dd582bb10cc) 4 | 5 | [RxJava 是如何实现线程切换的(下)](https://www.jianshu.com/p/88aa273d37be) 6 | 7 | ### RxJava变换操作符map,flatMap,concatMap,buffer 8 | 9 | - map:【数据类型转换】将被观察者发送的事件转换为另一种类型的事件 10 | - flatMap:【化解循环嵌套和接口嵌套】将被观察者发送的事件序列进行拆分 & 转换 后合并成一个新的事件序列,最后再进行发送 11 | - concatMap:【有序】与 flatMap 的 区别在于,拆分 & 重新合并生成的事件序列 的顺序与被观察者旧序列生产的顺序一致 12 | - flatMapIterable:相当于对 flatMap 的数据进行了二次扁平化 13 | - buffer:定期从被观察者发送的事件中获取一定数量的事件并放到缓存区中,然后把这些数据集合打包发射 14 | - flatMapMaybe 15 | 16 | > The map operator creates a new Observable that emits items that have been converted from items emitted by the source Observable. The map operator would allow us, for example, to turn an Observable that emits Storys into an Observable that emits the titles of those Storys. 17 | 18 | [link](https://www.jianshu.com/p/c820afafd94b) -------------------------------------------------------------------------------- /docs/PerformanceOptimization/内存优化.md: -------------------------------------------------------------------------------- 1 | # 性能优化2/5 -- 内存优化 2 | 3 | ### 内存占用分析工具 4 | 5 | 1. Memory Monitor:交互过程中,方便查看内存波动情况 6 | 2. Heap Viewer:方便查看不同类型的对象,内存大致分配状况 7 | 3. Allocation Tracker:更细致,可以跟踪应用的内存分配,并列出调用堆栈 8 | 9 | 可借助上面3个工具查看内存的分配情况,比如分析大对象、内存频繁GC等。如果遇到大对象,比如Bitmap,可以使用BitmapFactory.Options相关技术(inSampleSize)压缩图像再加载。 10 | 11 | Allocation Tracker 界面: 12 | 13 | 14 | 15 | 16 | ### 内存泄漏分析工具 17 | 1. LeakCanary (开源库一节已经介绍) 18 | 2. MAT 工具 19 | 20 | ##### MAT 21 | Android Studio导出的hprof文件是Dalvik格式的,需要转换成J2SE hprof才能被MAT使用。较高版本Studio在Capture一栏也可以导出标准的hprof。 22 | 23 | 使用Android SDK自带的转换工具hprof-conv, 转换命令: 24 | 25 | ``` 26 | ./hprof-conv path/file.hprof exitPath/heap-converted.hprof 27 | ``` 28 | 29 | 工具所在目录: 30 | 31 | ``` 32 | ../Android/sdk/platform-tools/hprof-conv 33 | ``` 34 | 35 | -------------------------------------------------------------------------------- /docs/PerformanceOptimization/App的性能优化系列文章.md: -------------------------------------------------------------------------------- 1 | ## 实践 2 | 3 | [Feed流上的优化实践](/docs/PerformanceOptimization/Feed流上的优化实践.md) 4 | 5 | [性能分析相关命令和工具介绍](/docs/PerformanceOptimization/性能分析相关命令和工具介绍.md) 6 | 7 | [如何优化Activity启动速度](/docs/PerformanceOptimization/如何优化Activity启动速度.md) 8 | 9 | [一种Activity预加载的方法](/docs/PerformanceOptimization/一种Activity预加载的方法.md) 10 | 11 | [在provider初始化的时候如何处理耗时操作](/docs/PerformanceOptimization/在provider初始化的时候如何处理耗时操作.md) 12 | 13 | ## 专题(待完善) 14 | 15 | [绘制优化](/docs/PerformanceOptimization/绘制优化.md) 16 | 17 | [启动优化](/docs/PerformanceOptimization/启动优化.md) 18 | 19 | [内存优化](/docs/PerformanceOptimization/内存优化.md) 20 | 21 | [稳定性优化](/docs/PerformanceOptimization/稳定性优化.md) 22 | 23 | [ANR分析](/docs/PerformanceOptimization/ANR分析.md) 24 | 25 | [耗电优化](/docs/PerformanceOptimization/耗电优化.md) 26 | 27 | [安装包优化](/docs/PerformanceOptimization/安装包优化.md) 28 | 29 | [图片优化](/docs/PerformanceOptimization/图片优化.md) 30 | -------------------------------------------------------------------------------- /docs/Android/其他/Android7.0上的混合编译.md: -------------------------------------------------------------------------------- 1 | Android N引入了一种包含编译、解释和JIT(Just In Time)的混合运行时,以便在安装时间、内存占用、电池消耗和性能之间获得最好的折衷。 2 | 3 | ART是在Android KitKat(译者注:Android 4.0)引入并在Lollipop(译者注:Android 5.0)中设为默认解决方案的主要特性之一,是当时的一种新的运行时。ART取代了Dalvik,但是前者与后者仍然保持了字节码级的兼容,因为前者仍在运行DEX文件。ART的主要特征之一就是安装时对应用的AOT编译。这种方式的主要优点就是优化产生的本地代码性能更好,执行起来需要更少的电量。劣势在于安装文件所需的空间和时间。在Lollipop和Marshmallow(译者注:Android 6.0)中,大的应用需要数分钟才能安装完。 4 | 5 | Android N包含了一个混合模式的运行时。应用在安装时不做编译,而是解释字节码,所以可以快速启动。ART中有一种新的、更快的解释器,通过一种新的JIT完成,但是这种JIT的信息不是持久化的。取而代之的是,代码在执行期间被分析,分析结果保存起来。然后,当设备空转和充电的时候,ART会执行针对“热代码”进行的基于分析的编译,其他代码不做编译。为了得到更优的代码,ART采用了几种技巧包括深度内联。 6 | 7 | 对同一个应用可以编译数次,或者找到变“热”的代码路径或者对已经编译的代码进行新的优化,这取决于分析器在随后的执行中的分析数据。这个步骤仍被简称为AOT,可以理解为“全时段的编译”(All-Of-the-Time compilation)。 8 | 9 | 这种混合使用AOT、解释、JIT的策略的全部优点如下。 10 | 11 | - 即使是大应用,安装时间也能缩短到几秒 12 | - 系统升级能更快地安装,因为不再需要优化这一步 13 | - 应用的内存占用更小,有些情况下可以降低50% 14 | - 改善了性能 15 | - 更低的电池消耗 16 | 17 | [原文](https://www.jianshu.com/p/8d3701e3ee94) -------------------------------------------------------------------------------- /docs/Network/Https.md: -------------------------------------------------------------------------------- 1 | # HTTPS 2 | 3 | 问题: 4 | 5 | 1. HTTPS环境下,能否使用ip通信? 6 | 2. HTTPS环境下,修改系统时间,能够正常通信? 7 | 8 | 因为HTTPS通信,会在客户端安装一个数字证书,IO时,会校验通信信息是否符合证书里的内容。 9 | 问题1中,因为证书会校验域名,但是ip没有域名,所以直接使用ip是无法通信的。 10 | 问题2中,因为证书会校验有效期,客户端修改系统时间可能使得改后的时间超过证书有效期,就会无法通过校验,故而无法通信。 11 | 12 | https为什么要用到对称加密和非对称加密,只有一种不可以吗? 13 | 14 | 这实际上考察3个点,一个是什么是对称加密,一个是非对称加密。还有一个是https的原理。 15 | 16 | 对称加密效率高,算法公开、计算量小、加密速度快、加密效率高。缺点是秘钥的管理和分发非常困难,不够安全。在数据传送前,发送方和接收方必须商定好秘钥,然后双方都必须要保存好秘钥,如果一方的秘钥被泄露,那么加密信息也就不安全了。 17 | 18 | 非对称加密算法需要两个密钥来进行加密和解密,这两个秘钥是公开密钥(简称公钥)和私有密钥(简称私钥),即常说的“公钥加密,私钥解密”或“私钥加密,公钥解密”。私钥只能由一方安全保管,不能外泄,而公钥则可以发给任何请求它的人。 19 | 非对称加密使用这对密钥中的一个进行加密,而解密则需要另一个密钥。安全性更高,公钥是公开的,秘钥是自己保存的,不需要将私钥给别人。非对称加密需要通过证书来验证公钥的合法性。 20 | 21 | https的秘钥是对称加密,但是对称加密解决不了秘钥的传输问题,所以引入非对称加密需要通过证书来验证公钥的合法性 22 | 23 | https服务端发送到客户端的证书,如果不去检查有效性可以吗,直接通过认证可以吗,为什么? 24 | 25 | 【参考】:不可以,证书可以伪造,还有证书是否过期,证书包含以下信息:申请者公钥、申请者的组织信息和个人信息、签发机构 CA 的信息、有效时间、证书序列号等信息的明文,同时包含一个签名。不去校验,容易被中间人攻击。 -------------------------------------------------------------------------------- /docs/Android/其他/APK打包及安装过程.md: -------------------------------------------------------------------------------- 1 | ### APK的打包流程 2 | 3 | Android的包文件APK分为两个部分:代码和资源,所以打包也分为资源打包和代码打包两部分。 4 | 5 | APK整体打包流程如下图所示: 6 | ![](../../img/Android_pack_apk.png) 7 | 8 | - 1、通过AAPT工具进行资源文件(包括AndroidManifest.xml、布局文件、各种xml资源等)的打包,生成R.java 9 | - 2、通过AIDL工具处理AIDL文件,生成相应的Java文件 10 | - 3、通过Javac工具编译项目源码,生成class文件 11 | - 4、通过DX工具将所有的Class文件转换成DEX文件,该过程主要是完成Java字节码装换成Dalvik字节码,压缩常量池以及清除信息等工作 12 | - 5、 通过ApkBuilder将资源文件、DEX文件打包生成APK文件 13 | - 6、 利用KeyStore对生成的APK文件进行签名 14 | - 7、 如果是正式版的APK,还会利用ZIPAlign工具进行对齐处理,对齐的过程就是将文件中所有的资源文件起始距离都偏移4字节的整数倍,这样通过内存映射访问APK文件的速度回更快 15 | 16 | 17 | ### APK安装过程 18 | 19 | ![](../../img/Android_install_apk.png) 20 | 21 | - 1、 复制APK到/data/app目录下,解压并扫描安装包。 22 | - 2、 资源管理器解析APK文件 23 | - 3、 解析AndroidManifest.xml文件,并在data/data目录下创建对应的应用数据目录 24 | - 4、 然后对dex文件进行优化,并保存在dalivk-cache目录下 25 | - 5、 将AndroidManifest.xml中解析出的四大组件信息注册到PackageMangerService中 26 | - 6、 安装完成后,发送广播 27 | 28 | ### APK文件结构 29 | 30 | 参考内容: 31 | 32 | [APK文件结构和安装过程](https://mp.weixin.qq.com/s?__biz=MzI3MDE0NzYwNA==&mid=2651433396&idx=1&sn=180d2285d5c1c61aeeed4462ae7d75a9&scene=23&srcid=0525qQiTtYdC1rllR0q0maOm#rd) -------------------------------------------------------------------------------- /docs/Android/开源库/Retrofit.md: -------------------------------------------------------------------------------- 1 | # Retrofit 2 | 3 | 通过动态代理,将@Get/@Post等IO相关的注解方法,代理成OkHttpCall,内部实现网络请求。 4 | 5 | Retrofit CreateApi实现原理 6 | 7 | Retrofit 如何实现文件(或图片上传)接口是如何定义的 8 | >大意要回答multipart/form-data 文件上传表单中 9 | 它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件。当上传的字段是文件时,会有Content-Type来表名文件类型;content-disposition,用来说明字段的一些信息; 10 | 由于有boundary隔离,所以multipart/form-data既可以上传文件,也可以上传键值对,它采用了键值对的方式,所以可以上传多个文件。 11 | 12 | 13 | Retrofit+Okhttp+Rxjava在华为的好多手机会OOM是由线程数溢出引起如何解决 14 | 15 | 在Android7.0及以上的华为手机(EmotionUI_5.0及以上)的手机产生OOM,这些手机的线程数限制都很小(应该是华为rom特意修改的limits),每个进程只允许最大同时开500个线程。解决方法尽量不要重复创建retrofit 和Okhttp的实例,手动设置okhttp 最大空闲连接数量,并减少keep-alive时间 16 | 如 17 | ConnectionPool pool = new ConnectionPool(5, 10000, TimeUnit.MILLISECONDS); 18 | 19 | OkHttpClient client = new OkHttpClient.Builder() 20 | .connectionPool(pool) 21 | .build(); 22 | 23 | RxJava+Retrofit进行网络请求; Header里如何添加通用参数, Response里如何解析,只返回业务层result,不需要每个Bean都有code ? 24 | 25 | 问题一: 26 | Header里如何添加通用参数,如果是自己封装的代码,可以在封装Request中可以解决,也可以增加拦截器,通过拦截器去做。 27 | 28 | 问题二: 29 | Retrofit中通过addConverterFactory来自定义一个Convert类,在convert方法里面做转换。 30 | 31 | 32 | [拆轮子系列:拆 Retrofit](https://blog.piasy.com/2016/06/25/Understand-Retrofit/index.html) -------------------------------------------------------------------------------- /docs/Android/App启动流程.md: -------------------------------------------------------------------------------- 1 | 2 | #### 应用启动流程 3 | 冷启动和热启动相比,冷启动多了一个创建APP 进程的过程,热启动由于进程已经存在,没有这个创建的过程。 4 | 5 | 一般来说,冷启动包括了以下内容: 6 | 7 | 8 | 1、启动进程 9 | 点击图标发生在Launcher应用的进程,startActivity()函数最终是由Instrumentation通过Android的Binder跨进程通信机制 发送消息给 system_server 进程; 10 | 在 **system_server** 中,启动进程的操作在ActivityManagerService。AMS发现ProcessRecord不存在时,就会执行Process.start(),最终是通过 socket 通信告知 Zygote 进程 fork 子进程(app进程) 11 | 12 | 13 | 2、开启主线程 14 | app进程创建后,首先是反射调用android.app.ActivityThread类的main方法,main()函数里会初始化app的运行时环境:创建 ApplicationThread,Looper,Handler 对象,并开启主线程消息循环Looper.loop()。 15 | 16 | 17 | 3、创建并初始化 Application和Activity 18 | ActivityThread的main()调用 ActivityThread#attach(false) 方法进行 Binder 通信,通知system_server进程执行 ActivityManagerService#attachApplication(mAppThread) 方法,用于初始化Application和Activity。 19 | 在system_server进程中,ActivityManagerService#attachApplication(mAppThread)里依次初始化了Application和Activity,分别有2个关键函数: 20 | - thread#bindApplication() 方法通知主线程Handler 创建 Application 对象、绑定 Context 、执行 Application#onCreate() 生命周期 21 | - mStackSupervisor#attachApplicationLocked() 方法中调用 ActivityThread#ApplicationThread#scheduleLaunchActivity() 方法,进而通过主线程Handler消息通知创建 Activity 对象,然后再调用 mInstrumentation#callActivityOnCreate() 执行 Activity#onCreate() 生命周期 22 | 23 | 24 | 4、布局&绘制 25 | -------------------------------------------------------------------------------- /docs/Android/Activity/Activity的生命周期和启动模式.md: -------------------------------------------------------------------------------- 1 | ### Activity的生命周期 2 | 3 | ![](../../img/activity_lifecycle.jpg) 4 | 5 | #### 问题:使用Application Context启动Activity为什么会crash 6 | 7 | 示例: 8 | 9 | ``` 10 | val intent = Intent(this@MainActivity, SecondActivity::class.java) 11 | applicationContext.startActivity(intent) 12 | 13 | ``` 14 | 15 | Crash Log: 16 | 17 | ``` 18 | Caused by: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want? 19 | ``` 20 | 21 | 原因是,被启动Activity默认放在启动它的Activity所在的栈。Application Context没有Activity栈,所以会异常。 22 | 23 | 解决方案是,使用``flag:FLAG_ACTIVITY_NEW_TASK``,让被启动Activity处在新建的栈。 24 | 25 | ``` 26 | val intent = Intent(this@MainActivity, SecondActivity::class.java) 27 | intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK 28 | applicationContext.startActivity(intent) 29 | ``` 30 | 31 | #### 问题:类似微博分享功能适合的launchMode,为什么不是singleInstance 32 | > 分享页面有跳转其他页面按钮,点击跳转其他页面,如果使用singleInstance,点击回退,就回不到分享页面了。这里需要注意的是,singleInstance是独占一个栈的。 33 | 34 | 无论 Dialog 弹出覆盖页面,对 Activity 生命周期没有影响,只有再启动另外一个 Activity 的时候才会进入 onPause 状态,而不是想象中的被覆盖或者不可见. 35 | 36 | 同时通过 AlertDialog 源码或者 Toast 源码我们都可以发现它们实现的原理都是 windowmanager.addView();来添加的, 它们都是一个个 view ,因此不会对 activity 的生命周期有任何影响。 37 | 38 | https://blog.csdn.net/cloud_castle/article/details/56011562 -------------------------------------------------------------------------------- /docs/Android/IPC/Android中的IPC机制.md: -------------------------------------------------------------------------------- 1 | ### 什么是IPC 2 | > IPC是Inter-Process Communication的缩小,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。 3 | 4 | ### 进程和线程 5 | > 进程:正在进行中的程序。其实进程就是一个应用程序运行时的内存分配空间。 线程:其实就是进程中一个程序执行控制单元,一条执行路径。 进程负责的是应用程序的空间的标示。线程负责的是应用程序的执行顺序。 6 | 7 | ### Androrid中的多进程模式 8 | 9 | Android的四大组件(Activity/Service/BroadcastReceiver/ContentProvider)注册时,如果使用android:proces修饰,说明该组件创建在新的进程中。 10 | 11 | android:process:作用是设置进程名称。 12 | 13 | android:process 两种声明方式: 14 | 15 | * android:process=“:remote" 16 | 以:打头的进程属于当前应用的私有进程,其他应用程序的组件不可以和它跑在同一进程中。 17 | 18 | * android:process=“com.utils.myapplication.user" 19 | 以包名形式声明的进程,属于全局进程,其他应用可以通过shareUserId方式和该进程跑在同一进程中,从而分享应用间的私有数据。但是这两个应用的签名还必须相同,才能共享私有数据,比如data目录、组件信息等。 20 | 21 | ### 使用多进程的问题 22 | 23 | 1. 静态成员和单例模式失效; 24 | 2. 线程同步机制完全失效; 25 | 3. SharePreference可靠性下降; 26 | 4. Application会多次创建。 27 | 28 | ### Android中的IPC方式 29 | 30 | #### 使用Bundle 31 | Android中四大组件中的三大组件都是支持在Intent中传递Bundle数据的,由于Bundle事先了Parcelable接口,所以它可以方便地在不同的进程间传输。 32 | 33 | GitHub上有个关于使用Bundle的讨论:[Android里面为什么要设计出Bundle而不是直接用Map结构](https://github.com/android-cn/android-discuss/issues/142) 34 | 35 | #### 使用文件共享 36 | 37 | #### 使用Messenger 38 | 39 | #### 使用AIDL 40 | 41 | #### 使用ContentProvider 42 | 43 | #### 使用Socket 44 | 45 | #### Add 46 | 补充:之前遇到过的问题,在应用Application中写入SharedPreference时,在多进程情况下,每个进程都会走Application的创建,这样会导致进程间会有数据的同步问题,考虑在写入Sp的时候判断当前的进程。 -------------------------------------------------------------------------------- /docs/Android/图片相关/DiskLruCache.md: -------------------------------------------------------------------------------- 1 | # DiskLruCache 2 | 3 | 基本原理: 4 | 5 | 将图片保存在磁盘中,维护一个journal文件用来记录用户对文件的4种操作记录。初始化和任何一种行为都会刷新该journal文件,并且根据该journal文件,将缓存文件记录到LinkedHashMap。和LruCache一样,使用了LinkedHashMap,作为LRU算法的数据基础。 6 | 7 | 操作行为有4种: 8 | 9 | * DIRTY: 含义如其名,意味着一条脏数据。当调用edit()时,就会向journal文件写入一条DIRTY记录,表示我们正准备写入一条缓存数据,但不知结果如何。 10 | 11 | * CLEAN: 表示数据是干净的,可以使用了。当调用commit(),会向journal文件写入一条CLEAN记录,表示缓冲数据可用,可以READ。 12 | 13 | * READ: 当调用get(),会向journal文件写入一条READ记录,表示读取一条缓存数据。 14 | 15 | * REMOVE: 当调用remove(),会向journal文件写入一条REMOVE记录,表示删除一条缓存数据。 16 | 17 | journal文件: 18 | 19 | ``` 20 | libcore.io.DiskLruCache // DiskLruCache的标记,固定常量 21 | 1 // DiskLruCache版本号 22 | 100 // 应用程序版本号 23 | 2 // open()方法中传入的,通常情况下为1 24 | 25 | CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 26 | DIRTY 335c4c6028171cfddfbaae1a9c313c52 27 | CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342 28 | REMOVE 335c4c6028171cfddfbaae1a9c313c52 29 | DIRTY 1ab96a171faeeee38496d8b330771a7a 30 | CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234 31 | READ 335c4c6028171cfddfbaae1a9c313c52 32 | READ 3400330d1dfc7f3f7f4b8d4d803dfcf6 33 | 34 | ``` 35 | 36 | 37 | 38 | 问: 39 | 40 | 1. 即便get()时,从LinkedHashMap中获得了缓存文件的名字(即hash后的key),从文件列表中拿到文件不还是要逐个遍历么,定位名字是否匹配的文件么? 41 | 42 | A: 读文件时,根据key,从LinkedHashMap中获取Entry,判断Entry是否存在,且readable。满足条件后,根据DiskLruCache内部保存的存放缓存文件的directory,结合key,对应缓存文件的绝对路径就有了,即directory + key。所以获取对应缓存文件也就简单了,即 43 | 44 | ``` 45 | new File(directory, key) 46 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## KnowledgeSummary 2 | 3 | > `KnowledgeSummary`:中高级Android开发知识总结系列. 4 | 5 | # Category 6 | 7 | Icon | Title | Detail 8 | ---|--- |--- 9 | ☕️ | Java集合源码分析,主要包含Vector,ArrayList 和LinkedList的差异比较,HashMap的实现细节,ConcurrentHashMap是如何保证线程安全的 和 红黑树的实现原理。 | [详情](./Docs/Java/Java系列集合源码分析.md) 10 | 🚍 | 如何理解JVM虚拟机,这个部分大多是【深入理解JVM虚拟机】一书的总结笔记。 | [详情](./Docs/JVM/深入理解JVM虚拟机读书笔记.md) 11 | 🤖 | 主要分析Android中的一些工作机制,如Handler,AsyncTask,系统启动流程,IPC机制等等 | [详情](./Docs/Android/Android系统工作机制分析.md) 12 | 📚 | Android面试题收集,除了自己平时收集的一些面试题以外,还有一些是`小专栏`【Android面试】里总结。 | [详情](./Docs/Android/面试题收集/Android面试题系列.md) 13 | 📷 | 性能优化是开发中永远逃避不了的问题,这里主要介绍了一些自己平时做的各种优化,还有就是关于各种优化应该怎么做的相关知识的整理。 | [详情](./Docs/PerformanceOptimization/App的性能优化系列文章.md) 14 | 👍 | 关于一些开源库源码的分析,这个部分量比较大,目前还在完善中。 | [详情](./Docs/Android/开源库/开源库源码分析系列文章.md) 15 | 🔐 | 教你如何理解多线程,主要是从源码角度分析了synchronized,volatile和CAS等的工作原理。 | [详情](./Docs/MultiThread/Java中多线程理解系列.md) 16 | 17 | # Something trivial 18 | 19 | [浅析CoordinatorLayout工作机制](./docs/Android/UI/解决CoordinatorLayout的动画抖动以及回弹问题.md) 20 | 21 | [仿京东、淘宝首页,通过两层嵌套的RecyclerView实现tab的吸顶效果](./docs/Android/UI/仿京东、淘宝首页,通过两层嵌套的RecyclerView实现tab的吸顶效果.md) 22 | 23 | [怎么做牛逼的动画](./docs/Android/其他/怎么做牛逼的动画.md) 24 | 25 | [为什么RxJava的observeOn不能随便用](./docs/Android/为什么RxJava的observeOn不能随便用.md) 26 | 27 | [一种Fragment懒加载的优化策略](./docs/PerformanceOptimization/一种Fragment懒加载的优化策略.md) 28 | 29 | ..... to be continue 30 | 31 | 32 | ## 欢迎关注我的公众号,查看更多干货。 33 | ![wx.jpg](https://i.loli.net/2020/08/07/EzP8QZNwdOgWRni.jpg) 34 | -------------------------------------------------------------------------------- /docs/PerformanceOptimization/一种Fragment懒加载的优化策略.md: -------------------------------------------------------------------------------- 1 | 之前在做重构的时候,需要梳理首页的代码逻辑,在这个过程中我发现我们的 App 每次启动的时候都会默认加载所有的页面框架,除了首页之外,还包括【商城页】、【消息页】和【个人页】。 2 | 3 | 因为首页默认采用ViewPager作为容易来承载app的四个页面切换,当首页ViewPager的offsetScreenPageLimit为3时,会默认加载当前页面的后三个页面。 4 | 5 | ![image](../img/viewpager_offsetScreenPageLimit.png) 6 | 7 | 针对这种情况,考虑做延迟加载,于是把offsetScreenPageLimit设置为1。 8 | 9 | 当将offsetScreenPageLimit设置为1的时候,超过这个阈值的页面会被销毁。 10 | 11 | 于是重写了ViewPager中Adapter的页面的创建和销毁逻辑,利用Fragment的show/hide 来做特殊处理,保证已经创建过的页面不会重复创建。 12 | 13 | ```java 14 | class IndexPagerAdapter @Inject constructor(private val fm: FragmentManager, 15 | @Named("IndexAdapterFragments") val fragments: ArrayList 16 | ) : FragmentPagerAdapter(fm) { 17 | 18 | override fun getCount(): Int = fragments.size 19 | override fun getItem(position: Int): Fragment = fragments[position] 20 | 21 | override fun instantiateItem(container: ViewGroup, position: Int): Any { 22 | val fragment = super.instantiateItem(container, position) as Fragment 23 | fm.beginTransaction().show(fragment).commitAllowingStateLoss() 24 | return fragment 25 | } 26 | 27 | override fun destroyItem(container: ViewGroup, position: Int, obj: Any) { 28 | fm.beginTransaction().hide(obj as Fragment).commitAllowingStateLoss() 29 | } 30 | 31 | override fun getItemPosition(item: Any): Int { 32 | return POSITION_UNCHANGED 33 | } 34 | } 35 | 36 | ``` 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /docs/DesignPattern/尽量不要用实现、继承,请用组合、聚合.md: -------------------------------------------------------------------------------- 1 | 继承 Extends(实现 Implements)应该仅当 base is a super 才能使用,也就是说,你希望 base 表现出所 有 super 的特性的时候才使用继承。否则应该使用组合。 2 | 3 | 先给个简单的列子: 4 | 5 | ```java 6 | public class MyView extends View implements View.OnClickListener { 7 | public MyView() { 8 | ... 9 | setOnClickListener(this); 10 | } 11 | @Override 12 | public void onClick(View v) { 13 | ... 14 | } 15 | } 16 | ``` 17 | 18 | 发现什么问题了? 19 | 20 | MyView myView = new MyView(); 21 | myView.onClick(myView); 22 | myView.onClick(null); 23 | 24 | 上述的使用真的合法吗? 25 | 26 | 你需要让所有使用 MyView 的人都知道 MyView 实际上是一个 View.OnClickListener 吗? 27 | 你的onClick(View)需要被别人调用吗? 28 | 你希望别人把你的 MyView 当成 View.OnClickListener 来使用吗? 29 | 30 | 如果答案是否定的,那么请不要使用继承(实现)。 31 | 32 | 所以,请写成这样: 33 | 34 | ```java 35 | 36 | public class MyView extends View { 37 | public MyView() { 38 | ... 39 | setOnClickListener(new View.OnClickListener() { 40 | @Override 41 | public void onClick(View v) { 42 | ... 43 | } 44 | }); 45 | } 46 | } 47 | ``` 48 | 49 | 甚至可以写成这样: 50 | 51 | ```java 52 | 53 | public class MyView extends View { 54 | private static final View.OnClickListener CLICK_LISTENER = new View.OnClickListene(){ 55 | @Override 56 | public void onClick(View v) { 57 | ... 58 | } 59 | }); 60 | 61 | public MyView() { 62 | ... 63 | setOnClickListener(CLICK_LISTENER); 64 | } 65 | 66 | } 67 | ``` -------------------------------------------------------------------------------- /docs/Algorithm/为什么说二分查找的时间复杂度是O(logn).md: -------------------------------------------------------------------------------- 1 | 为什么说二分查找的时间复杂度是O(log n) 2 | 3 | 这个问题本质上一个数学题。 4 | 5 | 先举个例子。 6 | ![](https://raw.githubusercontent.com/JasonGaoH/Images/master/binary_search.png) 7 | 8 | 给定一个长度为16的有序数组,如上图所示。 9 | 10 | 现在我们要查找在这个数组上查找13这个数。 11 | 12 | ![](https://raw.githubusercontent.com/JasonGaoH/Images/master/binary_search_1.jpeg) 13 | 14 | 首先我们将(length / 2 )拿到中间的元素,这个作为一个基准 15 | 16 | ![](https://raw.githubusercontent.com/JasonGaoH/Images/master/binary_search_2.jpeg) 17 | 18 | 我们发现13是小于16的,所以需要对左边的数组再一半对比。 19 | 20 | ![](https://raw.githubusercontent.com/JasonGaoH/Images/master/binary_search_3.jpeg) 21 | 22 | 重复再尝试去查找 23 | 24 | ![](https://raw.githubusercontent.com/JasonGaoH/Images/master/binary_search_4.jpeg) 25 | 26 | 最后拿到13这个数。 27 | 28 | ![](https://raw.githubusercontent.com/JasonGaoH/Images/master/binary_search_5.jpeg) 29 | 30 | 这里从长度为16的数组里面查找元素,最多需要查找4次才能查找到。 31 | 32 | 简单可以得到下面的公式。 33 | 34 | ![](https://raw.githubusercontent.com/JasonGaoH/Images/master/binary_search_6.png) 35 | 36 | 37 | 假设我们有n个有序元素,对n个元素进行查找的时候,时间复杂度为k,这样就可以有下面的公式。 38 | 39 | ![](https://raw.githubusercontent.com/JasonGaoH/Images/master/binary_search_7.png) 40 | 41 | ![](https://raw.githubusercontent.com/JasonGaoH/Images/master/binary_search_8.png) 42 | 43 | ![](https://raw.githubusercontent.com/JasonGaoH/Images/master/binary_search_9.png) 44 | 45 | ![](https://raw.githubusercontent.com/JasonGaoH/Images/master/binary_search_10.png) 46 | 47 | ![](https://raw.githubusercontent.com/JasonGaoH/Images/master/binary_search_11.png) 48 | 49 | 50 | [What does the time complexity O(log n) actually mean?](https://hackernoon.com/what-does-the-time-complexity-o-log-n-actually-mean-45f94bb5bfbf) -------------------------------------------------------------------------------- /docs/Android/其他/通过事务支持加快sqlite数据库批量操作的效率.md: -------------------------------------------------------------------------------- 1 | 查看 ContentProvider 的源代码,我们可以看到在 Provider 基类中已经支持了数据库的批量操作 2 | 3 | ```java 4 | // Override this to handle requests to perform a batch of operations, or the default implementation will iterate over the operations and call 5 | 6 | public @NonNull ContentProviderResult[] applyBatch( 7 | @NonNull ArrayList operations) 8 | throws OperationApplicationException { 9 | final int numOperations = operations.size(); 10 | final ContentProviderResult[] results = new ContentProviderResult[numOperations]; 11 | for (int i = 0; i < numOperations; i++) { 12 | results[i] = operations.get(i).apply(this, results, i); 13 | } 14 | return results; 15 | } 16 | ``` 17 | 18 | 我们可以通过调用 provider 的方法批量对数据库做数据修改,通过这个函数的注释可以看到,基类的默认 实现只是会把所有的 operation 拿出来逐个执行,对数据库的写入性能并没有太大的提升。 19 | 20 | 数据库的事务:数据库事务是由一组数据库操作序列组成,事务作为一个整体被执行。 21 | 22 | 事务的原子性:包含在其中的对数据库的操作序列最终要么全部执行,要么全部不执行。当全部执行时, 事务对数据库的修改将生效;当全部不执行时,数据库维持原有的状态,不会被修改。 23 | 24 | 我们可以通过覆写 ContentProvider 的 applyBatch(Stringauthority, ArrayList operations)方法来实现对事务处理的支持 25 | 26 | 27 | ```java 28 | 29 | public @NonNull ContentProviderResult[] applyBatch( 30 | @NonNull ArrayList operations) 31 | throws OperationApplicationException { 32 | SQLiteDatabase db = mDbHelper.getWritableDatabase(); 33 | db.beginTransaction(); 34 | try { 35 | ContentProviderResult[] results = super.applyBatch(operations); 36 | db.setTransactionSuccessful(); 37 | return results; 38 | } finally { 39 | db.endTransaction(); 40 | } 41 | } 42 | ``` 43 | 44 | 在同一个事务中执行批量对数据库的修改,可以大大提高写入数据库的效率。 -------------------------------------------------------------------------------- /docs/MultiThread/使用单一线程维持对象状态的一致性.md: -------------------------------------------------------------------------------- 1 | Android 明确规定,程序不能在主线程进行耗时的操作,否则会导致 ANR。有些 Android 基础的程序员都 会视此规定为金科玉律,绝不敢越雷池半步。遇到耗时的操作,程序员通常都会将其放在后台线程,以 MediaPlayer 为例,其 setDataSource()和 stop()方法有可能会阻塞,简单的处理方法是: 2 | 3 | ```java 4 | 5 | public void play(final MediaPlayer player, final String path) { new Thread() { 6 | @Override 7 | public void run() { 8 | player.setDataSource(path); 9 | player.start(); 10 | } 11 | }.start(); 12 | } 13 | 14 | public void stop(final MediaPlayer player) { 15 | new Thread() { 16 | @Override 17 | public void run() { 18 | player.stop(); 19 | } 20 | }.start(); 21 | } 22 | ``` 23 | 24 | 这个方案有几个问题显而易见: 25 | 1. 随意启动线程,开销是个问题 26 | 2. 如下代码无法保证执行结果的正确性 27 | 28 | ```java 29 | public void test() { 30 | MediaPlayer player = new MediaPlayer(); 31 | play(player, "http://xxx"); 32 | stop(player); 33 | } 34 | ``` 35 | 36 | 3. 最严重的是,如果 MediaPlayer 不是线程安全的,多个线程同时对其操作,因为没有同步,后果不堪设想。 37 | 38 | 一个简单实用的解决方案是:将所有对 MediaPlayer 的操作,都改在同一个线程内执行。这样可以减少随意启动线程导致的开销,也可以保证任务的执行顺序严格遵守任务的触发顺序,同时因为只有一个线程对对象操作,即使没有同步,也不用担心出现问题。 39 | 可以写一个包含无限循环的 Thread,可以使用 Android 的 HandlerThread,也直接使用 Java 并发线程库 提供的 SingleThreadExecutor,改进后的方案如下: 40 | 41 | ```java 42 | 43 | Executor mExecutor = Executors.newSingleThreadExecutor(); 44 | public void play(final MediaPlayer player, final String path) { 45 | mExecutor.execute(new Runnable() { 46 | @Override 47 | public void run() { 48 | player.setDataSource(path); 49 | } 50 | }); 51 | } 52 | 53 | public void stop(final MediaPlayer player) { 54 | mExecutor.execute(new Runnable() { 55 | @Override 56 | public void run() { 57 | player.stop(); } 58 | }); 59 | } 60 | 61 | ``` -------------------------------------------------------------------------------- /docs/MultiThread/线程的生命周期详解.md: -------------------------------------------------------------------------------- 1 | 每一个线程都有自己的生命周期。 2 | 3 | 在解释线程的生命周期之前,请大家思考一个问题,线程在执行start方法后就代表线程已经开始执行了吗? 4 | 5 | ![](../img/thread_lifecycle.jpg) 6 | 7 | 由上图所知,线程的生命周期大体可以分为5个主要的阶段: 8 | - NEW 9 | - RUNNABLE 10 | - RUNNING 11 | - BLOCKED 12 | - TERMINATED 13 | 14 | #### 线程的NEW状态 15 | 当我们使用关键字new创建一个Thread对象时,此时它并不处于执行状态,因为没有调用start方法启动该线程,那么线程的状态未NEW状态,准确地说,它只是Thread对象的状态,因为在没有start之前,该线程根本不存在,与你使用关键字new 创建一个普通的Java对象没有什么区别。 16 | 17 | NEW状态通过start方法进入RUNNABLE状态。 18 | 19 | #### 线程的RUNNABLE状态 20 | 线程对象进入RUNNABLE状态必须调用start方法,那么此时才是真正地在JVM进程中创建了一个线程,线程一经启动就可以立即得到执行嘛?答案是否定的,线程的运行与否和进程一样要听命于CPU的调度,那么我们把这个中间状态称为可执行状态(RUNNABLE),也就是说它具备执行的资格,但是并没有真正地执行气啦而是等待CPU的调度。 21 | 22 | 由于存在RUNNING状态,所依不会直接进入BLOCKED状态和TERMINATED状态,即使是在线程的执行逻辑中调用wait,sleep或者其他block的IO操作等,也必须先获得CPU的调度执行权才可以,严格来讲,RUNNABLE的线程只能意外终止或者进入RUNNING状态。 23 | #### 线程的RUNNING状态 24 | 一旦CPU通过轮询或者其他方式从任务可执行队列中选中了线程,那么此时它才能真正地执行自己的逻辑代码,需要说的一点是一个正在RUNNING状态的线程试试上也是RUNNABLE的,但是反过来则不成立。 25 | 26 | 在该状态中,线程的专题可以发生如下的状态转换。 27 | - 直接进入TERMINATED状态,比如调用JDK已经不推荐使用的stop方法或者判断某个逻辑标识 28 | - 进入BLOCKED状态,比如调用了sleep,或者wait方法而进入了waitSet中。 29 | - 进行某个阻塞的IO操作,比如因网络数据镀锌而进入了BLOCKED状态。 30 | - 获取某个锁资源,从而加入到该锁的阻塞队列中而进入了BLOCKED状态。 31 | - 由于CPU的调用器轮询使该线程放弃执行,进入RUNNALE状态。 32 | - 线程主动调用yield方法,放弃CPU执行权,进入RUNNABLE状态。 33 | 34 | #### 线程的BLOCKED状态 35 | 线程在BLOCKED状态中可以切换至如下几个状态。 36 | - 直接进入TERMINATED状态,比如调用JDK已经不推荐使用的stop方法或者意外死亡(JVM crash)。 37 | - 线程阻塞的操作结束,比如读取了想要的数组字节进入到RUNNABLE状态。 38 | - 线程完成了指定时间的休眠,进入到了RUNNABLE状态。 39 | - Wait中的线程被其他线程notify/notifyall唤醒,进入RUNNABLE状态。 40 | - 线程获取到来了某个锁资源,进入RUNNABLE状态。 41 | - 现在在阻塞过程中被打断,比如其他线程调用了interrupt方法,进入RUNNABLE状态。 42 | 43 | #### 线程的TERMINATED状态 44 | TERMINATED是一个线程的最终状态,在该状态中线程不会切换到其他任何状态,线程进入TERMINATED状态,意味着线程将不会切换到其他任何状态,线程进入TERMINATED状态,意味着该线程的生命周期都结束了,下列这些情况将会使线程进入TERMINATED状态。 45 | 46 | - 线程运行正常结束,结束生命周期。 47 | - 线程运行出错意外结束。 48 | - JVM Crash,导致所有的线程都结束。 49 | -------------------------------------------------------------------------------- /docs/Android/Activity/Activity泄露发现与诊断.md: -------------------------------------------------------------------------------- 1 | [ADT插件](http://developer.android.com/sdk/installing/installing-adt.html) 2 | 3 | 发现 Activity 泄露的三种种方法: 4 | 5 | 1. 通过StrictMode模式来发现,如果有Activity泄露直接会闪退,不过这种方法有时过于严格了,会把一 些延迟释放 Activity 的场景也算在内; 6 | 开启 StrictMode 需要在 BaseActicity.onCreate() 加上:StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().penaltyDeath().build()); 7 | 2. 使用LeakCanary 8 | 9 | 3. 通过 adb shell dumpsys meminfo 和 ddms 来确定泄露源,这种方法更精确,一般用于第一种方法发现 具体的可疑 Activity 泄露,然后再通过这种方法再精确定位问题。 10 | 11 | 举个例子说一下方法 3 的具体流程: 12 | 13 | 例子的场景:由于小米系统首次启动联系人(PeopleActivity)的速度特别慢,所以我们采用了预加载的技 术,在 Application.onCreate 启动了 PreloadPeopleActivity,之后用户在进入 PeopleActivity 时, PreloadPeopleActivity 会自动 finish。 14 | 15 | 1. 点击联系人 PeopleActivity,然后打开 ddms , 找到对应的进程 com.miui.miuilite:csp,进入进程对应的 heap 视图,然后点击右边的"Cause GC"来手动触发进程的 gc 。 16 | 17 | ![image](../../img/Activity_gc.png) 18 | 2. adb shell dumpsys meminfo com.miui.miuilite:csp 19 | 20 | ![image](../../img/dumpsys_meminfo.png) 21 | 22 | 可以看到 Activities: 3 23 | 24 | 说明当前 csp 进程有 3 个 Activity 实例,而我们只启动了 PeopleActivity,之前的 PreloadActivity 也已经 25 | finish,所以这里肯定有 Activity 泄露! 26 | 27 | 3. 回到 ddms, Dump HPROF file,然后点击 OOL,输入 select * from instanceof android.app.Activity,点 击右上角红色的执行按钮得到结果: 28 | 29 | ![image](../../img/adt_use.png) 30 | 31 | 可以看到 PreloadActivity 没有释放,PeopleActivity 也多了一份实例,先来看看 PreloadPeopleActivity 为什 么泄露。 32 | 33 | 4. 右键点击 PreloadPeopleActivity --> Merge Shortest Path to GC Roots --> exclude weak/soft references 34 | 35 | ![image](../../img/adt_use_2.png) 36 | 37 | 泄露的根源:ProviderStatusWatcher 的 mContext 对象抓住了 PreloadPeopleActivity 38 | 39 | 代码修改: 40 | 41 | ```java 42 | 43 | if(mIsPreload) { 44 | // 预加载时 45 | mProviderStatusWatcher.setContext(getApplicationContext()); 46 | } 47 | ``` 48 | 49 | 5. 验证结果,修改后重新进入联系人后 50 | ![image](../../img/adt_use_3.png) 51 | 52 | 可以看到这次 Activities 显示为 1 个,说明已经没有泄露了。 -------------------------------------------------------------------------------- /docs/Android/IPC/如何判断自己的进程是被Android_low_memory_killer杀死的.md: -------------------------------------------------------------------------------- 1 | 在 Android 系统中,Process 的生命周期是有系统控制的,当用户通过 back 键关掉 程序时,进程依然存在,其依然占用一定的内存。随着系统运行时间越长,用户打开的程序越多,整个系统占用的内存越多,Android 会定时执行一次检查,杀死一些进程,进而释放一些可用的内存空间,android 系统是通过 low memory killer 机制来实现的。 2 | 3 | Android 的 low memory killer 是基于 linux 的 oom 规则改进而来的,OMS 通过一些比较复杂的评分机制,对每个进程进行打分,然后根据分数高低和系统配置的一些参数来决定 kill 哪些进程。 4 | 5 | 几个主要概念: 6 | 7 | 1. Process Importance:ActivityManager.RunningAppPrcessInfo 中定义了不同种类 App 的 Importance 8 | 2. oom_adj:ProcessList 中定义了不同类型进程的 adj。Low memory killer 主要是通过进程的 oom_adj 来判定进程的重要程度。oom_adj 的大小和进程的类型以及进程被调度的次序有关。 9 | 3. 系统目录/sys/module/lowmemorykiller/parameters/adj 中定义了当前系统配置的 oom_adj,例如:0,1,2,7,14,15。这些值最初是在 init.rc 中配置的 10 | 4. 系统目录/sys/module/lowmemorykiller/parameters/minfree 中定义了当前系统配置 的 minfreee(单位为 4k,page 的大小),例如:1536,2048,4096,5120,5632,6144 11 | 5. 以上两组 int 数组是一一对应,即当系统剩余内存小于某个 minfree 时, lowmemorykiller 会杀死对应 oom_adj 的进程 12 | 6. 每个进程当前的 oom_adj 是实时变化的,即进程处于不同的状态,其 oom_adj 会随时发生变化。其值存在系统目录/proc//oom_adj 中 13 | 7. 系统初始进程 init 进程的 oom_adj 为-16,是最小的,根据 oom_adj 和 minfree 的 配置,该进程永远不会被 kill。其值是在 init.rc 中设定的:write /proc/1/oom_adj -16 8. 查看系统剩余的内存:cat /proc/meminfo 14 | 9. Low memory killer 的具体实现可参看:kernel/drivers/misc/lowmemorykiller.c 后台程序被 kill 有多种原因,目前发现的可以解释的有两种: 15 | 1. 由 restartPackage 传入 packageName 导致该包里所有的进程被 kill。 16 | 2. 由 lowmemorykiller 当在系统内存紧张时根据优先级选择一些进程 kill 17 | 第一类目前还没有很好的办法来避免,目前只能做到自己的 taskkiller 不要通过 restartPackage 来 kill 自己包里的任何进程。 18 | 更多的主要是由第二类造成的,那么如何判断是否是由第二类造成的呢?可以通过 如下方法来判断: 19 | 1. adb shell 20 | 2. cat /sys/module/lowmemorykiller/parameters/adj,该命令会打印出该系统配置的进 21 | 程优先级值。例如:0,1,2,7,14,15 22 | 1. cat /sys/module/lowmemorykiller/parameters/minfree,该命令会打印系统配置的针对不同 adj 的最小内存阈值。例如:1536,2048,4096,5120,5632,6144 23 | 2. 根据 ps 命令找到目标进程对应的 pid,比如 com.android.contacts 进程 24 | 3. cat /proc/桌面进程 pid/oom_adj,会得到步骤 2 中输出的某个值比如 2,根据第二步中该值出现的顺序,找到第三步中输出的相同数据的那个值 B,比如 2048 25 | 4. cat /proc/meminfo,找到 MemFree: 这一行,后面会有一个值 A,例如 79932 kB 26 | 5. 比较 B*4 和 A 的大小,如果 Ax if it is currently 30 | * holding expected. 31 | * @return true if successful 32 | */ 33 | public final native boolean compareAndSwapInt(Object o, long offset, 34 | int expected, 35 | int x); 36 | ``` 37 | 38 | UnSafe.java对应的cpp是UnSafe.cpp 39 | 40 | ```c++ 41 | UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) 42 | UnsafeWrapper("Unsafe_CompareAndSwapInt"); 43 | oop p = JNIHandles::resolve(obj); 44 | jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); 45 | return (jint)(Atomic::cmpxchg(x, addr, e)) == e; 46 | UNSAFE_END 47 | ``` 48 | 49 | 可以看到UnSafe.cpp中最后调用的是cmpxchg这个CPU指令,这个是一个原子操作。由于是CPU级别的指令,其开销比需要操作系统参与的锁的开销小。 50 | 51 | ABA问题是什么 52 | 53 | ABA 问题,正常情况下不需要关注,如果只关心结果的情况下,但是某些情况下,比如交易,资金流动这种情况下,需要有资金流转的记录。 54 | 55 | 如何解决ABA问题。 -------------------------------------------------------------------------------- /docs/Android/其他/详解APT.md: -------------------------------------------------------------------------------- 1 | 2 | ### 注解基础 3 | 元注解 @Retention: 4 | 表示注解存在于什么阶段,有以下三种, 5 | RetentionPolicy.SOURCE: 只存在于源码,编译期就会被擦除。类似于@override,只是给开发人员看的。 6 | RetentionPolicy.CLASS: 存在于源码和编译期,不在运行时。是默认的policy。 7 | RetentionPolicy.RUNTIME: 存在于源码、编译期、运行时。 8 | 9 | https://www.race604.com/annotation-processing/ 10 | 11 | 12 | ### 调试注解处理器代码 13 | http://www.jianshu.com/p/80a14bc35000 14 | 15 | 1)在自定义AbstractProcessor#process()添加断点 16 | 17 | 2)project/ gradle.properties添加如下配置,并sync。如果端口被占用,可以换个端口 18 | org.gradle.daemon=true 19 | org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5006 20 | 21 | 3)命令行运行 ./gradlew --daemon,启动守护线程 22 | 23 | 4)在AS中新建Remote Debugger (如果有,只需要在Select Configuration中选中AnnotationProcessor对应的Remote Debugger),然后点击Debug按钮,启动调试。注意端口要和gradle.properties配置的端口一致。 24 | 25 | 26 | 27 | 28 | 5)命令行运行 ./gradlew clean connectedCheck 29 | 注意,如果执行改命令仍然不能调试,那就./gradlew --stop关闭所以daemon,再./gradlew --deamon重启,即3,4,5步重新走一遍。 30 | 31 | 常见问题: 32 | 1)gradle.properties添加daemon配置同步后,AS中出现如下 33 | 34 | A: daemon默认占用5005端口,上面错误说明该端口已经被占用,可以修改成其他端口。 35 | 36 | 常见命令: 37 | ./gradlew --daemon 启动守护线程 38 | ./gradlew --daemon --stop 关闭所以守护线程 39 | ./gradlew --daemon --status 查看守护线程状态 40 | 41 | ### 注解处理器精髓 42 | 工作大体内容: 43 | 1) 获取被注解的Element; 44 | 2) 代码校验,比如被注解的类要能实例化,所以要是public,不能是abstract,方法必须public等; 45 | 3) 根据Element获取要生成代码的信息,结合JavaPoet,生成代码; 46 | 47 | JavaPoet:https://github.com/square/javapoet 48 | 49 | ======================== Element ========================= 50 | Element: 51 | 52 | 53 | TypeElement: 54 | 代表的是源代码中的类型元素,例如类。然而,TypeElement并不包含类本身的信息。你可以从TypeElement中获取类的名字,但是你获取不到类的信息,例如它的父类。这种信息需要通过TypeMirror获取。你可以通过调用elements.asType()获取元素的TypeMirror。 55 | 56 | 57 | ======================== 创建Compiler Module ========================= 58 | 创建一个名为compiler的Java Library,这个类将会写代码生成的相关代码。核心就是在这里。 59 | 60 | 配置build.gradle 61 | 62 | apply plugin: 'java' 63 | sourceCompatibility = 1.7 64 | targetCompatibility = 1.7 65 | dependencies { 66 | compile fileTree(dir: 'libs', include: ['*.jar']) 67 | compile 'com.google.auto.service:auto-service:1.0-rc2' 68 | compile 'com.squareup:javapoet:1.7.0' 69 | compile project(':annotation') 70 | } 71 | 72 | 定义编译的jdk版本为1.7,这个很重要,不写会报错。 73 | AutoService 主要的作用是注解 processor 类,并对其生成 META-INF 的配置信息。 74 | JavaPoet 这个库的主要作用就是帮助我们通过类调用的形式来生成代码。 75 | 依赖上面创建的annotation Module。 76 | -------------------------------------------------------------------------------- /docs/Android/面试题收集/Interview_1.md: -------------------------------------------------------------------------------- 1 | 基础 2 | 3 | 1. 讲一下Android应用程序启动过程 4 | 2. Android中进程间的通信有哪些方法 5 | 3. view的工作原理及measure、layout、draw流程,哪一个可以放在子线程中执行 6 | 4. 讲一下Android View的事件分发机制 7 | 5. 如果控件内部卡顿你如何去解决并优化 8 | 6. listview和recycleview缓存机制 9 | 7. handler机制是什么 10 | 8. 讲一下IntentService 11 | 9. 讲一下Bitmap加载,三级缓存机制,如何实现一个LRUCache 12 | 10. 项目中做过哪些android性能优化,怎么做的? 13 | 11. Java的内存模型,每个部分的特点和作用 14 | 12. 讲一下你理解的GC 15 | 13. 讲一下对内存泄露的理解,常见的内存泄漏有哪些,怎么解决的 16 | 14. 为什么要使用多线程?多线程需要注意那些问题 17 | 15. 导致线程不安全的原因有哪些,怎么解决 18 | 16. lock和synchronize有什么区别 19 | 17. 讲一下乐观锁、悲观锁及使用场景 20 | 18. 讲一下hashmap的实现,hashtable和hashmap的区别是什么,hashtable为什么会被弃用,ConcurrentHashmap的实现,segment的概念、concurrenthashmap高效的原因是什么 21 | 19. 使用过那些三方库,有没有阅读过一些源码,找一个你认为的技术亮点介绍一下 22 | 20. Broadcast的分类?有序,无序?粘性,非粘性?本地广播? 23 | >广播可以分为有序广播、无序广播、本地广播、粘性广播。其中无序广播通过sendBroadcast(intent)发送,有序广播通过sendOrderedBroadcast(intent)发送。 24 | 25 | >有序广播 26 | (1) 有序广播可以用priority来调整优先级 取值范围-1000~+1000,默认为0,数值越大优先级越高,优先级越高越优先获得广播响应。 27 | (2) abortBroadcast()可来终止该广播的传播,对更低优先级的屏蔽,注意只对有序广播生效。 28 | (3) 有序广播在传播数据中会发生比如setResultData(),getResultData(),在传播过程中,可以从新设置数据 29 | 30 | >关于本地广播,可以查看这篇文章。总的来说,本地广播是通过LocalBroadcastManager内置的Handler来实现的,只是利用了IntentFilter的match功能,至于BroadcastReceiver 换成其他接口也无所谓,顺便利用了现成的类和概念而已。在register()的时候保存BroadcastReceiver以及对应的IntentFilter,在sendBroadcast()的时候找到和Intent对应的BroadcastReceiver,然后通过Handler发送消息,触发executePendingBroadcasts()函数,再在后者中调用对应BroadcastReceiver的onReceive()方法。 31 | 32 | >粘性消息:粘性消息在发送后就一直存在于系统的消息容器里面,等待对应的处理器去处理,如果暂时没有处理器处理这个消息则一直在消息容器里面处于等待状态,粘性广播的Receiver如果被销毁,那么下次重建时会自动接收到消息数据。(在 android 5.0/api 21中deprecated,不再推荐使用,相应的还有粘性有序广播,同样已经deprecated) 33 | 34 | 进阶 35 | 36 | 1. 讲一下classloader有哪些,如何自定义一个classloader 37 | 2. 讲一下双亲委派机制 38 | 3. Android中热修复如何实现 39 | 4. Android中插件化如何实现 40 | 5. Bunder的机制和原理是什么 41 | 6. JVM内存分配策略,垃圾回收标记算法,垃圾回收策略 42 | 7. Minor GC、Major GC和Full GC之间的区别 43 | 8. JsBridge的实现 44 | 9. 讲一下MVC,MVP,MVVM的理解 45 | 46 | 扩展 47 | 48 | 1. 是否使用过kotlin,说说你的感受 49 | 2. RN和weex有没有接触过,实现原理是什么 50 | 3. 前端有没有做过,做过什么项目,使用的什么框架 51 | 4. 服务端有没有了解过,使用过什么框架 52 | 5. 其他语言是否接触过,有没有过一些项目开发 53 | 54 | 项目 55 | 56 | 1. 介绍一下目前项目的整体架构,你认为现在的架构中是否存在问题,什么问题,如何解决 57 | 2. 项目中使用的一些三方库,有没有深入看过源码,挑一个你认为设计的比较好的讲一下,为什么你认为这个地方设计的比较好 58 | 3. 从业务和技术实现方案两个方面,讲一个你主导过或者是作为核心人员参与过的一个项目,这个项目中的亮点是什么,针对项目进行一些提问 59 | 4. 简历中挑选几个感兴趣点或项目,询问一下项目的技术实现 60 | 5. 讲一下工作中遇到过的困难,怎么解决的,这件事对你的成长是什么 61 | 6. 为什么离职,未来1-2年的职业规划是什么,你期待新的岗位能给你提供什么 -------------------------------------------------------------------------------- /docs/Android/其他/Android中的事件传递.md: -------------------------------------------------------------------------------- 1 | 当我们的手指触碰到屏幕,事件是按照Activity->ViewGroup->View这样的流程到达最终响应触摸事件的View的。 2 | 3 | 而在事件分发过程中,涉及到三个最重要的方法:dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent。 4 | 5 | 我们的手指触摸到屏幕的时候,会触一个Action_Down类型的事件,当前页面的Activity会首先做出相应,也就是说会走到Activity的dispatchTouchEvent()方法内。 6 | 7 | 在这个方法内部有下面两个逻辑: 8 | 9 | 调用getWindow.superDispatchTouchEvent()。 10 | 如果上一步返回true,则直接返回true;否则return自己的onTouchEvent()。 11 | 显然,当getWindow.superDispatchTouchEvent()返回true,表示当前事件已经被消费掉,无需调用onTouchEvent;否则代表事件并没有被处理,因此需要调用Activity的onTouchEvent进行处理。 12 | 13 | 我们都知道,getWindow()返回的是PhoneWindow,因此这句代码本质上调用了PhoneWindow中的superDispatchTouchEvent()。而后者实际上调用了mDecor.superDispatchTouchEvent(event)。 14 | 这个mDecor也就是DecorView,它是FrameLayout的一个子类。 15 | 在DecorView中的superDispatchTouchEvent(event)中调用的是super.dispatchTouchEvent()。 16 | 因此,本质上调用的是ViewGroup的dispatchTouchEvent()。 17 | 18 | 到这里,事件已经从Activity传递到ViewGroup了。接下来我们分析ViewGroup。 19 | 20 | 在ViewGroup的dispatchTouchEvent()中逻辑大致如下: 21 | 22 | 通过onInterceptTouchEvent()判断当前ViewGroup是否拦截,默认的ViewGroup都是不拦截的; 23 | 24 | 如果拦截,则return自己的onTouchEvent();如果不拦截,则根据child.dispatchTouchEvent()的返回值判断。如果返回true,则return true;否则return自身的onTouchEvent(),在这里实现了未处理事件的向上传递。 25 | 26 | 通常情况下,ViewGroup的onInterceptTouchEvent()都返回false,表示不拦截。这里需要注意的是事件序列,比如Down事件、Move事件…Up事件,从Down到Up是一个完整的事件序列,对应着手指从按下到抬起这一系列事件,如果ViewGroup拦截了Down事件,那么后续事件都会交给这个ViewGroup的onTouchEvent。如果ViewGroup拦截的不是Down事件,那么会给之前处理这个Down事件的View发送一个Action_Cancel类型的事件,通知子View这个后续的事件序列已经被ViewGroup接管了,子View恢复之前的状态即可。 27 | 28 | 这里举一个常见的例子:在一个 Recyclerview 中有很多的 Button,我们首先按下了一个 button,然后滑动一段距离再松开,这时候 Recyclerview 会跟着滑动,并不会触发这个 button 的点击事件。这个例子中,当我们按下 button 时,这个 button 接收到了 Action_Down 事件,正常情况下后续的事件序列应该由这button处理。但我们滑动了一段距离,这时 Recyclerview 察觉到这是一个滑动操作,拦截了这个事件序列,走了自身的 onTouchEvent()方法,反映在屏幕上就是列表的滑动。 29 | 30 | 而这时 button 仍然处于按下的状态,所以在拦截的时候需要发送一个 Action_Cancel 来通知 button 恢复之前状态。事件分发最终会走到View的dispatchTouchEvent()中。在View的dispatchTouchEvent()中没onInterceptTouchEvent(),这里很容易理解,View没有child,也就不存在拦截。View的dispatchTouchEvent()直接return了自己的onTouchEvent()。如果onTouchEvent()返回true代表事件被消费,否则未消费的事件会向上传递,直到有View处理了事件或一直没有消费,最终回到Activity的onTouchEvent()终止。有时候会有人混淆onTouchEvent和onTouch。首先,这两个方法都在View的dispatchTouchEvent()中: 31 | 如果touchListener不为null,并且这个View是enable的,而且onTouch返回true,都满足时直接return true,走不到onTouchEvent()方法。否则,就会触发onTouchEvent()。因此onTouch优先于onTouchEvent获得事件处理权。 32 | 33 | ![image](../../img/view_event.jpg) -------------------------------------------------------------------------------- /docs/PerformanceOptimization/如何优化Activity启动速度.md: -------------------------------------------------------------------------------- 1 | 在一些低配手机上,我们经常会遇到白屏或者页面切换卡顿等现象,这些都是 Activity 启动速度慢的表现,本文将分享一些小米系统开发过程中优化 Activity 启动速度的经验。 2 | 3 | 我们把优化分为两类,分别叫做硬优化和软优化,先来说说软优化。 4 | 5 | > 软优化: 6 | 是指通过一些特殊的方式来达到优化的效果,这种方法一般会尽可能地少修改原代码的结构和逻辑。 7 | 8 | 一般主要用以下几种软优化的方法: 9 | 10 | 1. 预加载 11 | 2. 异步加载:费时的非 UI 操作放到另一个 Thread 12 | 3. 延迟加载:可以延迟展示的 UI 操作放到 IdleHandler 中 13 | 14 | ## 延迟加载 15 | 16 | ```java 17 | 18 | public class BaseActivity extends Activity { 19 | private boolean mDelayFinished = false; 20 | 21 | @Override 22 | protected void onCreate(final Bundle savedInstanceState) { 23 | super.onCreate (savedInstanceState); 24 | mDelayFinished = false; 25 | 26 | Looper.myQueue().addIdleHandler(new IdleHandler() { 27 | @Override 28 | public boolean queueIdle() { 29 | onCreateDelay(savedInstanceState); 30 | onStartDelay() ; 31 | onResumeDelay(); 32 | mDelayFinished = true; 33 | return false; 34 | } 35 | }); 36 | } 37 | 38 | protected void onCreateDelay(Bundle bundle) { 39 | // TODO : 可以延迟展示的 UI 操作 40 | } 41 | protected void onStartDelay() { } 42 | protected void onResumeDelay() { } 43 | protected void onPauseDelay() { } 44 | protected void onStopDelay() { } 45 | protected void onDestroyDelay() { } 46 | 47 | @Override 48 | protected void onResume() { 49 | super.onResume(); 50 | if (mDelayFinished) { 51 | onResumeDelay(); 52 | } 53 | } 54 | @Override 55 | protected void onPause() { 56 | super.onPause(); 57 | if (mDelayFinished) { 58 | onPauseDelay(); 59 | } 60 | } 61 | @Override 62 | protected void onStop() { 63 | super.onStop () ; 64 | if (mDelayFinished) { 65 | onStopDelay() ; 66 | } 67 | } 68 | @Override 69 | protected void onDestroy() { 70 | super.onDestroy(); 71 | if (mDelayFinished) { 72 | onDestroyDelay(); 73 | } 74 | } 75 | } 76 | ``` 77 | 78 | 79 | Looper.myQueue().addIdleHandler 能够保证操作在 Activity 启动完成之后再执 80 | 行,子类只要把可以延迟加载的 UI 操作放到对应的*Delay()即可。 81 | 82 | > 硬优化 83 | 84 | 深究代码的细节,通过查看 trace 文件,一步步地优化到极致。 85 | 86 | 1. 去除耗时的图片,数据库等操作 87 | 2. 精简布局,减少层次,尽可能多地使用 ViewStub 88 | 3. 一些费时的初始化操作提前到进程初始化执行 89 | 4. 优化主题加载资源的速度 90 | 5. 优化 Activity.installDecor 速度 91 | 6. 一些方法的优化,例如 Long.valueof,spareArray,具体有更好的性能 92 | 7. 调整切换动画效果 93 | 94 | 硬优化需要有一定耐心,一步步找到问题的根源,这种方法也是我们更主张的优化 95 | 方式,这种优化就需要对原代码的结构和逻辑做比较大改动。 -------------------------------------------------------------------------------- /docs/Android/其他/组件化.md: -------------------------------------------------------------------------------- 1 | 随着APP的发展,功能也会越来越丰富,代码急剧膨胀、编译速度慢,代码耦合、牵一发而动全身,这些问题都严重影响开发效率和项目的稳定性。这时候组件化和插件化就会派上用场。本篇文章是根据我们团队组件化的经历,介绍一些组件化的知识和相关问题的解决方案。 2 | 3 | 目录: 4 | 5 | 1.插件化和组件化的区别 6 | 2.为什么用组件化 7 | 3.组件化结构 8 | 3.1 项目层级结构 9 | 3.2 依赖管理 10 | 3.3 工程建设 11 | 4. 组件化的问题及相关解决方案 12 |     1)组件形式 13 |     2)资源冲突 14 |     3)组件通信  15 |     4)页面跳转 16 |     5)组件版本管理 17 |     6)分支管理 18 | 19 | 20 | ### 1.组件化和插件化的区别 21 | 插件化强调结果,允许功能模块以插件的形式,不需要预先打包到apk里,而是运行时在合适的时机动态加载进来。 22 | 组件化体现过程,更像是一种开发模式,不同功能模块的代码做物理隔离,比如分到不同的module或project中。组件化不具备插件化运行时动态加载的特性。 23 | 24 | ### 2. 为什么要用组件化 25 | 1)高效:因为每个组件都是一个独立的app,可以独立运行,所以开发人员只需要在自己的组件中开发测试即可,开发效率更高,编译速度更快 26 | 2)稳定:每个组件都是高内聚的模块,可以进行单元测试;一个组件代码发生变化,基本上也不会影响其他组件; 27 | 28 | ### 3. 组件化结构 29 | 30 | ##### 3.1 项目层级结构 31 | 32 | 每个团队对自己项目的层级划分都不太一样,粗略的说,可以分成下面三层: 33 | 基础业务层 34 | 业务支撑层 35 | 公共层 36 | 37 | 其中, 38 | 基础业务层:每个app的业务的部分,比如feed流、个人中心、登录注册等等 39 | 业务支撑层:比如用户Session、AB框架、打点、配置中心 40 | 公共层:比如图片、网络、UI基础组件、资源 41 | 42 | 43 | ##### 3.2 依赖管理 44 | 45 | 为了防止组件间循环依赖,组件依赖时要遵循一个原则:同一层的组件之间不能产生依赖,上层可以对下层组件产生依赖。 46 | 47 | 依赖方式参考: 48 | 49 | gralde 3.0以上 | gradle 2.x | 说明 50 | ------- | ------- | ------- 51 | implementation | compile | 编译期间只对直接依赖的组件可见,在运行期间所有组件可见 52 | api | compile | 编译期,运行期对所有组件可见 53 | compileOnly | provided | 只参与编译不打包到apk 54 | runtimeOnly | apk | 编译期间不可见,会打包到apk 55 | 56 | 对于implemention,假如模块A依赖模块B,模块B依赖模块C,即A->B->C,编译期间A不能访问C,当C修改时,gradle只会重新编译B,不会编译A。 57 | 58 | ##### 3.3 工程建设 59 | 项目大了,开发人员也会变多,一些基础设施和自动化的工具也有搭建起来。这里列举一些常用的: 60 | git、分支管理、持续集成(CI)、静态编译、lint、快速打包、灰度发布、bugly、协议文档等等。当然,实现插件化的,还有插件管理平台。 61 | 62 | 63 | ### 4. 组件化的问题及相关解决方案 64 | 组件化之后,很多代码不在一个项目里,没办法直接通信、引用代码,相关问题随之而来。 65 | 66 | ##### 4.1 组件形式 67 | 68 | 组件可以做是一个独立的project,也可以放到一个项目中,以module形式存在。 69 | 如果把组件做成一个project,可以给project建app和library两个module,其中app模块是为了组件独立运行,library才是组件的真身,给其他组件提供依赖。 70 | 我们都知道,一个module是Android Library还是Application,取决于build.gradle中apply plugin的方式。 71 | 如果把组件做成主项目中的一个module,如果要组件独立运行,需要apply plugin为com.andorid.application,如果要以组件提供对外依赖,则要修改为com.android.library。 72 | 可以用条件变量对apply的形式做管理,业界也有更优雅的修改方式,但基本上绕不开这个套路。 73 | ``` 74 | if (isModule) { 75 | apply plugin: 'com.android.library' 76 | } else { 77 | apply plugin: 'com.andorid.application' 78 | } 79 | ``` 80 | 每次切换,都需要重新gradle async,效率会打折扣。并且每次让开发人员手动操作,总避免不了哪天手抖犯错误。所以,还是建议采用第一种方法。 81 | 82 | ##### 4.2 资源冲突 83 | 不同组件有同名资源时,当组件打包到一起,就会产生资源冲突问题。 84 | 解决方法是,在build.gradle中添加资源前缀: 85 | ``` 86 | resourcePrefix 'module-a' 87 | ``` 88 | 这种做法IDE并不会对资源重命名,而是做了lint检查。开发人员需要手动为资源添加前缀。 89 | 90 | ##### 4.3 组件间通信 91 | 92 | 93 | ##### 4.4 页面跳转 94 | 95 | ##### 4.5 组件版本管理 96 | 97 | 98 | ##### 4.6 分支管理 99 | 参考git flow流程。 100 | 101 | -------------------------------------------------------------------------------- /docs/Android/其他/IntentFilter匹配规则.md: -------------------------------------------------------------------------------- 1 | # IntentFilter匹配规则 2 | 3 | Activity隐式调用时,需要Intent能够匹配目标组件的IntentFilter设置的过滤信息。 4 | 5 | IntentFilter设置的过滤信息有action、category、data。 6 | 7 | ### action匹配规则 8 | 9 | 1. Intent中必须有且一个action,没有action会匹配异常 10 | 2. Intent中的action,必须和目标组件IntentFilter中多个action中一个严格匹配(区分大小写),才能算action匹配成功。 11 | 12 | 注意:Intent中只能设置一个action,Intent.setAction() 13 | 14 | 目标组件: 15 | 16 | ``` 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ``` 32 | 33 | 所以, 34 | 35 | ``` 36 | Intent.setAction("com.shannon.Activity1") //匹配通过 37 | 38 | Intent.setAction("com.shannon.Activity2") //匹配通过 39 | 40 | Intent.setAction("com.shannon.Activity3") //匹配失败,目标组件没有Activity3这个action 41 | ``` 42 | 43 | ### category匹配规则 44 | 45 | Intent中可以添加多个category。要求这些category中的每一个,都必须被包含在目标组件IntentFilter中的category里。 46 | 47 | 48 | ``` 49 | Intent.addCategory("android.intent.category.DEFAULT") 50 | Intent.addCategory("android.intent.category.BROWSABLE") 51 | 匹配通过 52 | 53 | Intent.addCategory("android.intent.category.DEFAULT") //目标组件能匹配这个 54 | Intent.addCategory("android.intent.category.OTHER") //目标组件能无法匹配这个 55 | 不能全部匹配成功,所以结果是:匹配失败 56 | ``` 57 | 58 | ### data匹配规则 59 | 60 | data匹配规则和action类似,Intent的data必须和目标组件IntentFilter多个data中的一个严格匹配,才算匹配成功。 61 | 62 | data 结构:scheme://host:port/path... 63 | 64 | 结构解释: 65 | 66 | * Scheme:比如file、content、http等,如果URI中没指定scheme,那整个URI其他参数无效,也意味着URI是无效的。 67 | * Host:如果host未指定,整个URI其他参数无效,意味着URI也是无效的。 68 | 69 | 70 | 如果没指定scheme,系统会默认加上content和file。如下所示: 71 | 72 | ``` 73 | 74 | 75 | 76 | 77 | 使用Intent.setDataAndType(Uri.parse("file://abc"), "image/png"),能匹配 78 | 使用Intent.setDataAndType(Uri.parse("http://abc"), "image/png"),无法匹配 79 | 80 | ``` 81 | 82 | 83 | ``` 84 | 85 | 86 | 89 | 90 | 91 | 以下Intent可以匹配上述目标组件: 92 | Intent intent = new Intent("com.shannon.demo.jump"); 93 | intent.setData(Uri.parse("shannon://router")); 94 | ``` 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /docs/Android/图片相关/Best_Practices_for_Using_Alpha.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Alpha 是图形界面开发中常用的特效,通常我们会使用以下代码来实现 Alpha 特效: 4 | ```java 5 | view.setAlpha(0.5f); 6 | View.ALPHA.set(view, 0.5f); 7 | ObjectAnimator.ofFloat(view, "alpha", 0.5f).start(); 8 | view.animate().alpha(0.5f).start(); 9 | view.setAnimation(new AlphaAnimation(1.0f, 0.5f)); 10 | ``` 11 | 12 | 其效果都等同于: 13 | 14 | canvas.saveLayer(l, r, t, b, 127, Canvas.CLIP_TO_LAYER_SAVE_FLAG); 15 | 16 | 所以常见的 alpha 特效是通过将图像绘制到 offscreen buffer 中然后显示出来,这样的操作是非常消耗资源 的,甚至可能导致性能问题,在开发过程中我们可以通过其他方式避免创建 offsreen buffer。 17 | 18 | ## TextView 19 | 20 | 对于 TextView 我们通常需要文字透明效果,而不是 View 本身透明,所以,直接设置带有 alpha 值的 TextColor 是比较高效的方式。 21 | 22 | ```java 23 | // Not this 24 | textView.setAlpha(alpha); 25 | // 以下方式可以避免创建 offscreen buffer 26 | int newTextColor = (int) (0xFF * alpha) << 24 | baseTextColor & 0xFFFFFF; textView.setTextColor(newTextColor); 27 | 28 | ``` 29 | 30 | ## ImageView 31 | 同样的对于只具有 src image 的 ImageView,直接调用 setImageAlpha()方法更为合理。 32 | 33 | ```java 34 | // Not this, setAlpha 方法由 View 继承而来,性能不佳 35 | imageView.setAlpha(0.5f); 36 | // 使用以下方式时,ImageView 会在绘制图片时单独为图片指定 Alpha // 可以避免创建 offScreenBuffer 37 | imageView.setImageAlpha((int) alpha * 255); 38 | ``` 39 | 40 | ## CustomView 41 | 42 | 类似的,自定义控件时,应该直接去设置 paint 的 alpha。 43 | 44 | ```java 45 | 46 | // Not this 47 | customView.setAlpha(alpha); 48 | // But this 49 | paint.setAlpha((int) alpha * 255); canvas.draw*(..., paint); 50 | ``` 51 | 52 | 同时 Android 提供了 hasOverlappingRendering()接口,通过重写该接口可以告知系统当前 View 是否存在内容重叠的情况,帮助系统优化绘制流程,原理是这样的:对于有重叠内容的 View,系统简单粗暴的使用 offscreen buffer 来协助处理。当告知系统该 View 无重叠内容时,系统会分别使用合适的 alpha 值绘制每一层。 53 | 54 | ```java 55 | 56 | /** 57 | * Returns whether this View has content which overlaps. This function, intended to be 58 | * overridden by specific View types, is an optimization when alpha is set on a view. If 59 | * rendering overlaps in a view with alpha < 1, that view is drawn to an offscreen buffer 60 | * and then composited it into place, which can be expensive. If the view has no overlapping * rendering, the view can draw each primitive with the appropriate alpha value directly. 61 | * An example of overlapping rendering is a TextView with a background image, such as a 62 | * Button. An example of non-overlapping rendering is a TextView with no background, or 63 | * an ImageView with only the foreground image. The default implementation returns true; 64 | * subclasses should override if they have cases which can be optimized. * 65 | * @return true if the content in this view might overlap, false otherwise. */ 66 | public boolean hasOverlappingRendering() { 67 | return true; 68 | } 69 | ``` 70 | 71 | 最后引用 Chet Haase 的一句话作为总结 72 | “You know what your view is doing, so do the right thing for your situation.” 73 | 74 | 75 | > 参考 76 | 77 | [给 App 提速:Android 性能优化总结](https://juejin.im/entry/5aa24187518825557207f8e2) -------------------------------------------------------------------------------- /docs/JVM/4_Java内存模型与线程.md: -------------------------------------------------------------------------------- 1 | ### 概述 2 | > 多任务处理在现代计算机操作系统的必要性。 3 | 首先要充分利用计算机处理器的运算能力,由于计算机的运算速度与它的存储和通信子系统速度差距太大,不能让大量的时间被浪费在磁盘I/O、网络通信或者数据库访问上。 4 | 另外,并发能够让一个服务端同时对多个客户端提供服务。衡量一个服务性能的高低好坏,每秒事务处理数(Transactions Per Second,TPS)是最重要的指标之一,它代表着一秒内服务端平均能响应的请求总数。 5 | 6 | ### 硬件效率和一致性 7 | 在理解Java虚拟机并发之前,我们需要了解物理计算机中的并发问题。 8 | 9 | 由于计算机的存储设备与处理器的运算速度有几个数量级的差距,所有现代计算机系统都不得不加入一层读写速度尽可能解决处理器运算速度的高速缓存(Cache)来作为内存与处理器之间的缓冲:将运算需要使用到的数据复制到缓存中,让运算能快速运行,当运算结束后再从缓存同步回内存内存之中,这样处理器就无须等待缓慢的内存读写了。 10 | 11 | 基于高速缓存的存储交互很好地解决了处理器与内存的速度矛盾,但是也为计算机系统带来更高的复杂度,它引入了一个新的问题:缓存一致性。当多个处理器的运算任务都涉及到同一主内存区域是,将可能导致各自的缓存数据不一致。 12 | 13 | 为了解决缓存一致性,需要各个处理器访问缓存时都遵循一些协议,在读写时根据协议来操作。 14 | 15 | ![](../img/cache_coherence.webp) 16 | 17 | 本章中说到的“内存模型”,可以理解为在特定的操作协议下,对特定的内存或高速缓存进行读写访问的过程抽象。 18 | 19 | ### Java内存模型 20 | 21 | Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。 22 | 23 | > 目标 24 | Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的的底层细节。 25 | 26 | >注意:这里的变量与Java编程中所说的变量有所区别,它包括了实例字段、静态字段和构成数组对象的元素,但不包括局部变量与方法参数,因为后者是线程私有的,不会被独享,自然就不会存在竞争问题。 27 | 28 | Java内存模型规定了所有的变量都存储在内存中(此处的主内存与介绍物理硬件时的主内存名字一样,两者也可以相互类比,但此处仅仅是虚拟机内存的一部分)。每条线程还有自己的工作内存,可与处理器高速缓存类似,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量,线程间变量值得传递均需要通过主内存来完成。 29 | 30 | ![](../img/cpu_cache_framework.png) 31 | 32 | JMM中所说的主内存、工作内存与Java内存区域中的Java堆、栈、方法区等并不是同一个层次的划分,这两者基本上是没有关系的,如果两者一定要勉强对应起来,那从变量、主内存、工作内存的定义来看,主内存主要对应于Java堆中的对象实例数据部分,而工作内存则对应于虚拟机栈中的部分区域。从更低层次上说,主内存就直接对应于物理硬件的内存,而为了获取更好的运行速度,虚拟机可能会让工作内存优先存储于寄存器和高速缓存中,因为程序运行时主要访问的是工作内存。 33 | 34 | ### 内存间交互操作 35 | 关于主内存与工作内存之间具体的交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存之类的实现细节,Java内存模型中定义了以下8种操作来完成。 36 | 37 | 虚拟机必须保证这8种操作每一种操作都是原子的,不可再分的。 38 | 39 | * lock(锁定): 作用域主内存中的变量,它把一个变量标识为一条线程独占的装填。 40 | * unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。 41 | * read(读取):作用于主内存中的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。 42 | * load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。 43 | * use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值得字节码指令时将会执行这个操作。 44 | * assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。 45 | * store(存储):作用于工作内存的变量,它把工作内存中的一个变量的值传送到主内存中,以便随后的write操作使用。 46 | * write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。 47 | 48 | > 如果要把一个变量从主内存复制到工作内存,那就要顺序地执行read和load操作,如果要把变量从工作内存同步到主内存,就要顺序地执行store和write操作。注意,Java内存模型要求上述两个操作必须按顺序执行,而没有保证是连续执行。 49 | 50 | Java内存还规定了在执行上述8种操作时必须满足如下规则: 51 | * 不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存读取了但工作内存不接收,或者从工作内存发起回写了但主内存不接受的情况出现。 52 | * 不允许一个线程丢弃它的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。 53 | * 不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步会主内存中。 54 | * 一个新的变量只能在主内存中”诞生“,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说,就是对一个变量实施use、store之前,必须先执行过了assign和load操作。 55 | * 一个变量在同一个时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock,变量才会被解锁。 56 | * 吐过对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。 57 | * 对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)。 58 | 59 | 60 | https://www.jianshu.com/p/90a036212cb4 -------------------------------------------------------------------------------- /docs/Android/UI/RecyclerView中出现item重复的问题分析.md: -------------------------------------------------------------------------------- 1 | ### 问题背景 2 | 前不久我们项目中由用户反馈说遇到笔记重复的问题,而且不只一次遇到类似的反馈。 3 | 4 | 这种重复笔记总是出现的feed流的中间位置,如下示意图所示: 5 | 6 | ![](https://raw.githubusercontent.com/JasonGaoH/Images/master/0QP04e.jpg) 7 | 8 | 这个图画的有点丑,凑合看,意思大概就是这样的。 9 | 10 | 接下来,我就得追踪下这个问题了,开始时我几乎就一口咬定是接口返回的有问题,由于前几次后端没有日志,好像之前的反馈就那么过去了,直到后面又出现一次重复笔记的问题,这次是公司内部员工出现的,于是后端也通过这个抓到了相应的日志,发现返回的笔记的确没有重复的,这下跑不掉了,就是前端的问题。 11 | 12 | ### 问题排查 13 | 于是,我又重新梳理了下代码流程,发现有一处比较有嫌疑: 14 | 15 | ```java 16 | ... 17 | if (...) { 18 | mItems[0] = noteItem 19 | } else { 20 | mItems.add(0, noteItem) 21 | } 22 | mAdapter.items = mItems 23 | mAdapter.notifyItemChanged(0) 24 | ... 25 | ``` 26 | 27 | 鉴于是公司项目,我就省略掉业务逻辑了,这里的代码按照开发者的意图是当RecyclerView第一个item如果已经是noteItem这种类型的时候,我们就将这个位置item替换成最新的,如果这个位置的item不是noteItem这个数据的话,我们需要手动把它添加到第一个位置去,到这里实际上都没有什么问题。 28 | 29 | 但是,当我看到``mAdapter.notifyItemChanged(0)``这个方法,直觉告诉我这里好像有点问题,当上面的逻辑走到else这里的时候,会往list里add一个新的item,但是这时候调用的刷新方法却是notifyItemChanged(0)。 30 | 31 | 这个notifyItemChanged明显是刷新某个item的方法,即当这个item里的数据有变化时,调用这个方法去刷新这个item区域的UI,但是如果我们在adapter中add了一个新的item,再调用这个方法明显是不行的,这里是导致重复的原因嘛,我其实也不太确定。 32 | 33 | ### 问题复现 34 | 于是我写了一个Demo试了下。 35 | 36 | ```java 37 | class RecyclerViewActivity : AppCompatActivity() { 38 | 39 | private val mDataList = ArrayList() 40 | 41 | override fun onCreate(savedInstanceState: Bundle?) { 42 | super.onCreate(savedInstanceState) 43 | setContentView(R.layout.activity_recycler_view) 44 | 45 | val layoutManager = LinearLayoutManager(this) 46 | layoutManager.orientation = RecyclerView.VERTICAL 47 | recyclerView.layoutManager = layoutManager 48 | 49 | val customAdapter = CustomAdapter(mDataList) 50 | 51 | recyclerView.adapter = customAdapter 52 | 53 | for (i in 0..5) { 54 | mDataList.add("text: $i") 55 | } 56 | 57 | recyclerView.adapter?.notifyDataSetChanged() 58 | } 59 | 60 | //刷新方法 61 | fun refresh(view: View) { 62 | mDataList.add(0, "add item") 63 | recyclerView.adapter?.notifyItemChanged(0) 64 | } 65 | 66 | } 67 | ``` 68 | 构造一个普通的feed列表,每次点击刷新按钮,就会调用刷新方法,调用刷新方法的时候往index为0的位置再add一个item,然后再调用notifyItemChanged(0)方法,上下滑动后,发现数据是重复了。 69 | 70 | 下面放个gif图展示下效果。 71 | 72 | ![](https://raw.githubusercontent.com/JasonGaoH/Images/master/gifhome_480x1040_4s.gif) 73 | 74 | 从gif图中可以看到,点击刷新按钮,添加了”add item“,往下滑动后,出现了两个”text:5“的item,这个就是重复的item。 75 | 76 | ### 问题原因 77 | 我们看到notifyItemChanged的文档说明: 78 | ![](https://raw.githubusercontent.com/JasonGaoH/Images/master/NotifyItemChanged.jpg) 79 | 80 | > notifyItemChanged(int position) 81 | This is an item change event, not a structural change event. 82 | 83 | RecyclerView中有两种不同数据改变事件,一种叫(item changes)项目改变,另一种叫(structual changes)结构改变。项目改变指的是某个单个item的数据发生变化,这个时候没有位置的改变。而structual changes则是有位置的变化发生,主要是数据源的变化会导致RecyclerView item位置发生变化。 84 | 85 | 我们这个问题是我们往数据源前面加了一个item,这个时候应该需要调用具有structual changes 的方法来刷新,而不是采用notifyItemChanged来刷新,因为notifyItemChanged是一个item changes 的方法。 -------------------------------------------------------------------------------- /docs/Android/其他/插件化.md: -------------------------------------------------------------------------------- 1 | 2 | 插件化主要技术点: 3 | 4 | 1. 如何打插件包么 5 | 2. 插件没加载时,也没法引用插件,此时能调用插件接口么 6 | 3. 插件生命周期 7 | 4. 插件资源 8 | 5. 插件so 9 | 6. 插件回滚 10 | 7. 插件升级 11 | 12 | 13 | 14 | ## 基础知识 15 | 16 | 1. C/S通信中介--Binder 17 | 2. APP打包流程 18 | 3. APP安装流程 19 | 4. APP启动流程 20 | 5. 资源加载机制 21 | 6. 使用Gradle处理插件依赖 22 | 23 | ##### Binder 24 | 25 | 首先,做Android系统原代码的人应该非常熟悉Binder,如果没有它真的寸步难行。Binder涉及两层技术。你可以认为它是一个中介者模式,在客户端和服务器端之间,Binder就起到中介的作用,这是我这段时间对Binder的思考。要实现四大组件的插件化,就需要在Binder上做修改。Binder服务端的内容没办法修改,只能改客户端的代码。四大组件每个组件的客户端都不太一样,这个需要大家自己去发现,时间关系,这里就不多说了。 26 | 27 | 学习Binder的最好方式就是AIDL。你可以读到很多关于AIDL的资料,通过制订一个aidl文件自动生成一个Java类,研究一下这个Java类的每个方法和变量,然后再反过来看四大组件,其实都是跟AIDL差不多的方式。 28 | 29 | ##### APP打包流程 30 | 31 | 其次,是App打包的流程。代码写完了,执行一次打包操作,中途经历了资源打包、dex生成、签名等过程。其中最重要的就是资源的打包,即AAPT这一步,如果宿主和插件的资源id冲突,一种解决办法就是在这里做修改。 32 | 33 | ##### APP安装流程 34 | 35 | 第三,App在手机上的安装流程也很重要。熟悉安装流程不仅对插件化有帮助,在遇到安装bug的时候也非常重要。手机安装App的时候,经常会有下载异常,提示资源包不能解析,这时需要知道安装App的这段代码在什么地方,这只是第一步。第二步需要知道,App下载到本地后,具体要做哪些事情。手机有些目录不能访问,App下载到本地之后,放到哪个目录下,然后会生成哪些文件。插件化有个增量更新的概念,如何下载一个增量包,从本地具体哪个位置取出一个包,这个包的具体命名规则是什么,等等。这些细节都必须要清楚明白。 36 | 37 | ##### APP启动流程 38 | 39 | 第四,是App的启动流程。Activity启动有几种方式?一种是写一个startActivity,第二种是点击手机App,通过手机系统里的Launcher机制,启动App里默认的Activity。通常,App开发人员喜闻乐见的方式是第二种。那么第一种方式的启动原理是什么呢?另外,启动的时候,main函数在哪里?这个main函数的位置很重要,我们可以对它所在的类做修改,从而实现插件化。 40 | 41 | ##### 资源加载机制 42 | 43 | 第五点更重要,做Android插件化需要控制两个地方。首先是插件Dex的加载,如何把插件Dex中的类加载到内存?另外是资源加载的问题。插件可能是apk也可能是so格式,不管哪一种,都不会生成R.id,从而没办法使用。这个问题有好几种解决方案。一种是是重写Context的getAsset、getResource之类的方法,偷换概念,让插件读取插件里的资源,但缺点就是宿主和插件的资源id会冲突,需要重写AAPT。另一种是重写AMS中保存的插件列表,从而让宿主和插件分别去加载各自的资源而不会冲突。第三种方法,就是打包后,执行一个脚本,修改生成包中资源id。 44 | 45 | ##### 使用Gradle处理插件依赖 46 | 47 | 第六点,在实施插件化后,如何解决不同插件的开发人员的工作区问题。比如,插件1和插件2,需要分别下载哪些代码,如何独立运行?就像机票和火车票,如何只运行自己的插件,而不运行别人的插件?这是协同工作的问题。火车票和机票,这两个Android团队的各自工作区是不一样的,这时候就要用到Gradle脚本了,每个项目分别有各自的仓库,有各自不同的打包脚本,只需要把自己的插件跟宿主项目一起打包运行起来,而不用引入其他插件,还有更厉害的是,也可以把自己的插件当作一个App来打包并运行。 48 | 49 | ## 插件化技术流派: 50 | 51 | 动态替换(即Hook)、静态代理、Dex合并 52 | 53 | ##### 动态替换 54 | 55 | 第一种是动态替换,也就是Hook。可以在不同层次进行Hook,从而动态替换也细分为若干小流派。可以直接在Activity里做Hook,重写getAsset的几个方法,从而使用自己的ResourceManager和AssetPath;也可以在更抽象的层面,也就是在startActivity方法的位置做Hook,涉及的类包括ActivityThread、Instrumentation等;最高层次则是在AMS上做修改,也就是张勇的解决方案,这里需要修改的类非常多,AMS、PMS等都需要改动。总之,在越抽象的层次上做Hook,需要做的改动就越大,但好处就是更加灵活了。没有哪一个方法更好,一切看你自己的选择。 56 | 57 | ##### 静态代理 58 | 59 | 第二种是静态代理,这是任玉刚的框架采取的思路。写一个PluginActivity继承自Activity基类,把Activity基类里面涉及生命周期的方法全都重写一遍,插件中的Activity是没有生命周期的,所以要让插件中的Activity都继承自PluginActivity,这样就有生命周期了。 60 | 61 | ##### Dex合并 62 | 63 | 第三种是Dex合并,Dex合并就是Android热修复的思想。刚才说到了两个项目——AndFix和Nuwa,它们的思想是相同的。原生Apk自带的Dex是通过PathClassLoader来加载的,而插件Dex则是通过DexClassLoader来加载的。但有一个顺序问题,是由Davlik的机制决定的,如果宿主Dex和插件Dex都有一个相同命名空间的类的方法,那么先加载哪个Dex,哪个Dex中的这个类的方法将会占山为王,后面其他同名方法都替换了。所以,AndFix热修复就是优先加载插件包中的Dex,从而实现热修复。由于热修复的插件包通常只包括一个类的方法,体量很小,和正常的插件不是一个数量级的,所以只称为热修复补丁包,而不是插件。 64 | 65 | 66 | ## 热修复三大流派 67 | Method、Class、Dex 68 | 69 | ##### 1. Method 70 | 71 | 技术实现:在Native层修改方法寻址,即操作方法指针,将指针指向新的方法。 72 | 73 | 代表框架:AndFix、Sophix 74 | 75 | ##### 2. Class 76 | 77 | 技术实现:修改DexElements替换类。 78 | 79 | 代表框架:QZone超级补丁、Nuwa、Qfix 80 | 81 | ##### 3. Dex 82 | 83 | 技术实现:修改PathClassLoader的parent。 84 | 85 | 代表框架:Tinker、SigmaPatch 86 | 87 | 88 | 参考: 89 | [Android 插件化原理解析——插件加载机制](http://weishu.me/2016/04/05/understand-plugin-framework-classloader/) 90 | 91 | [《InfoQ:Android插件化--从入门到放弃》](http://www.infoq.com/cn/articles/android-plug-ins-from-entry-to-give-up) 92 | 93 | [《简书:Android 插件化和热修复知识梳理》](https://www.jianshu.com/p/704cac3eb13d) 94 | 95 | [VirtualAPK](https://github.com/didi/VirtualAPK/wiki) 96 | 97 | [AtLas](https://github.com/alibaba/atlas/tree/master/atlas-docs) -------------------------------------------------------------------------------- /docs/Android/开源库/BlockCanary.md: -------------------------------------------------------------------------------- 1 | ``` java 2 | public static void loop() { 3 | final Looper me = myLooper(); 4 | if (me == null) { 5 | throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); 6 | } 7 | final MessageQueue queue = me.mQueue; 8 | 9 | // Make sure the identity of this thread is that of the local process, 10 | // and keep track of what that identity token actually is. 11 | Binder.clearCallingIdentity(); 12 | final long ident = Binder.clearCallingIdentity(); 13 | 14 | for (;;) { 15 | Message msg = queue.next(); // might block 16 | if (msg == null) { 17 | // No message indicates that the message queue is quitting. 18 | return; 19 | } 20 | 21 | // This must be in a local variable, in case a UI event sets the logger 22 | final Printer logging = me.mLogging; 23 | if (logging != null) { 24 | logging.println(">>>>> Dispatching to " + msg.target + " " + 25 | msg.callback + ": " + msg.what); 26 | } 27 | 28 | final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs; 29 | 30 | final long traceTag = me.mTraceTag; 31 | if (traceTag != 0 && Trace.isTagEnabled(traceTag)) { 32 | Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); 33 | } 34 | final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); 35 | final long end; 36 | try { 37 | msg.target.dispatchMessage(msg); 38 | end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); 39 | } finally { 40 | if (traceTag != 0) { 41 | Trace.traceEnd(traceTag); 42 | } 43 | } 44 | if (slowDispatchThresholdMs > 0) { 45 | final long time = end - start; 46 | if (time > slowDispatchThresholdMs) { 47 | Slog.w(TAG, "Dispatch took " + time + "ms on " 48 | + Thread.currentThread().getName() + ", h=" + 49 | msg.target + " cb=" + msg.callback + " msg=" + msg.what); 50 | } 51 | } 52 | 53 | if (logging != null) { 54 | logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); 55 | } 56 | 57 | // Make sure that during the course of dispatching the 58 | // identity of the thread wasn't corrupted. 59 | final long newIdent = Binder.clearCallingIdentity(); 60 | if (ident != newIdent) { 61 | Log.wtf(TAG, "Thread identity changed from 0x" 62 | + Long.toHexString(ident) + " to 0x" 63 | + Long.toHexString(newIdent) + " while dispatching to " 64 | + msg.target.getClass().getName() + " " 65 | + msg.callback + " what=" + msg.what); 66 | } 67 | 68 | msg.recycleUnchecked(); 69 | } 70 | } 71 | 72 | ``` 73 | 74 | 原理:我们只需要计算打印这两天 log 的时间差,就能得到 dispatchMessage 的耗时,android 提供了 Looper.getMainLooper().setMessageLogging(Printer printer)来设置这个 logging 对 象,所以只要自定义一个 Printer,然后重写 println(String x)方法即可实现耗时统计了。 75 | 通过在 DispatchMessage 的方法的执行时间,来判断卡顿,管是哪种回调方式,回调一定发 生在 UI 线程。因此如果应用发生卡顿,一定是在 dispatchMessage 中执行了耗时操作。我 们通过给主线程的 Looper 设置一个 Printer,打点统计 dispatchMessage 方法执行的时间, 如果超出阀值,表示发生卡顿,则 dump 出各种信息,提供开发者分析性能瓶颈。 -------------------------------------------------------------------------------- /docs/PerformanceOptimization/Feed流上的优化实践.md: -------------------------------------------------------------------------------- 1 | 之前一直负责小红书的关注Feed的迭代工作,因为一直是在完成新功能的迭代工作,对于Feed的性能和消费体验就没有特别关注,加上对于这块业务的一些监控也没有落地,所以长期对于这块的性能基本上就是一个忽视的状态。随着业务越来越复杂,功能越来越多,收到好多反馈都是说关注页面的滑动体验很不好,于是决定对关注Feed做一个性能优化。 2 | 3 | 这里先从优化说起,之前在网上看到一个关于优化的分类,觉得很有道理。 4 | 5 | 请看下面这张图: 6 | ![](https://raw.githubusercontent.com/JasonGaoH/Images/master/uPic/optimization_category.png) 7 | 8 | ### 关于优化 9 | 优化整体可以分为两大类,软优化和硬优化。那么什么是软优化呢?软优化并不会改变某段代码的执行效率,而是通过调整这段代码的执行时机,来优化整体代码的运行性能。软优化里由分为三大类,分别为预加载,异步加载和懒加载。 10 | 11 | #### 软优化 12 | - 预加载 13 | 这几种加载方式都很好理解,改变的都是执行时机,预加载就是提前加载,把一些重要的数据提前加载好,等到合适的时机,就可以直接展示给用户,而不是在这个合适的时机才去加载数据,这种方式给用户地感觉是加载变快了。 14 | - 异步加载 15 | 而异步加载在Android中则很常见了,移动应用中有主线程的概念,主线程需要实时地对用户的操作予以反馈,这种反馈要非常及时,否则就会让用户觉得卡顿。因此,主线程的工作不宜有耗时操作,而且对于一些执行比较慢的代码,而且不需要同步执行的代码,我们可以考虑将它放到异步线程去执行。 16 | - 懒加载 17 | 最后就是懒加载了,懒加载就是把一些不是需要立即展示的东西往后放,因为我们的手机性能,内存是有限的,有些功能用户不是需要马上看到,则可以考虑在后续的某个适当的时机执行加载。 18 | 19 | #### 硬优化 20 | 接下来说说硬优化,硬优化这个东西就比较偏细节了。一段逻辑,我们用两层循环实现,功能肯定是OK的,但是如果在实现功能的情况下,我们用一层循环就可以解决了,这样不就提高了这段代码的运行效率了。这种方式的优化就是硬优化了,关于硬优化往往没有具体的方法,很多都是各种见仁见智,通过调整代码的逻辑来提高运行效率都可以称为硬优化。 21 | 22 | #### Feed流上的问题发现和解决 23 | 很多人会说,上面说了这么多软优化和硬优化,有什么用呢?别急,我们慢慢来说。上面说的算是一种对于优化的指导思想吧,下次我们遇到类似的问题可以从这几个维度来进行分析。 24 | 25 | 我们要做优化,首先肯定是要发现问题,也就是对症下药。 26 | 27 | 我之前主要处理的是Feed流的卡顿优化,所以我基本上都是通过Android Studio的Profiler工具来查看问题,现在Profiler的体验相对之前功能算是更加强大了,我每次都是使用Profiler来尝试抓一下Feed流滑动时的代码执行情况。 28 | 然后一个一个查看滑动过程中我们的耗时是在哪些地方。 29 | 30 | ##### 通过Profiler发现的问题 31 | 通过Profiler发现了以下几点问题: 32 | 1. 发现有一些比较耗时的方法,比如图文笔记上的滤镜的回收方法比较耗时,针对这样的可以考虑做这个release操作放到异步里面去; 33 | 2. 另外也发现一些业务的打点会在OnBindViewHolder中调用的很频繁,虽然点位数据的发送是在异步线程里,但是在发送数据之前会有数据的拼装操作,多少会有些性能损耗,而且打点这一类数据完全可以放到异步线程中去,因为这种打点并不需要很高的实时性; 34 | 3. 对于一些跨进程的操作,比如从Context中拿Service获取WiFi状态,这种在onBind中回调是非常不好的,所以针对这个我们只需要构造一个广播来监听WIFI状态变化即可,然后每次只需要拿那个记录好的网络状态变量即可 35 | 4. 还有关于视频笔记的处理,我们需要根据RecyclerView滑动过程中当前item中的视频start,stop和prepare做相应的调整,正常情况下,当RecyclerView处于滑动状态是不应该去prepare和start的,而且也不应该创建播放器实例和Texture View的; 36 | 5. 最后,通过Profiler我们还发现TextView的setText是很耗时的,当我们复杂多行文本的情况时,TextView去setText的时候需要进行复杂的测量,在测量完之后还需要绘制,所以针对这个场景可以考虑使用StaticLayout对Feed流中的文本作预渲染,关于StaticLayout的教程网上也有不少,后面有时间我也会抽空写下关于这块的实践。 37 | 38 | 上面都是通过Profiler来发现的一些问题。 39 | 40 | ##### View层级 41 | 另外还有一些算是基础的优化吧。因为View的层级随着Feed流的业务变得越来越深,我们需要进可能地减少View的层级,同时也尽量减少View的个数,可以考虑使用ConstraintLayout,但是ConstraintLayout这个东西要慎用,对于一些迭代比较快的业务场景,使用ConstraintLayout会增加我们的维护成本,而且会有一些比较坑的问题。 42 | 43 | 关于View层级的缩减基本没啥可讲的,尽量用merge和ViewStub,这个就是根据实际情况去消除一些不必要的层级。 44 | 45 | ##### RecyclerView缓存的利用 46 | 最后,我们也尝试了从RecyclerView缓存的角度来解决这个问题,RecyclerView默认会有四级缓存的概念,正常情况下,当我们的RecyclerView的卡片撑满屏幕的时候,我们进入该页面的时候,我们的RecyclerView会先创建ViewHolder,然后再bind这个ViewHolder,然后当我们向下滑动的时候,会再次创建一个ViewHolder,接着再执行Bind操作,只有当我们的RecycledView Pool的池子满了之后才不会继续创建ViewHolder,往后的滑动RecyclerViwe就只会执行bind操作了,这个时候我们发现这个OnCreateViewHolder需要从xml里去解析view,并且创建这个ViewHolder,这个创建对于我们的滑动是会有性能损耗,其实还是因为我们的这个卡片太复杂了。 47 | 48 | 所以,我们可以考虑提前创建一些ViewHolder,放到ViewHolder的池子里去,这样我们在滑动的时候,就不需要先Create再Bind了。 49 | 50 | 提前创建ViewHolder,在主线程Idle的状态下提前创建ViewHolder放到RecycledViewPool中,这个pool的size默认是5。 51 | ```java 52 | recyclerView.setRecycledViewPool(RecyclerView.RecycledViewPool().apply { 53 | LightExecutor.postIdle(Runnable { 54 | repeat(5) { 55 | //RecycledViewPool 默认size为5个,RecyclerView内部会有判断,如果满了不会create 56 | //注意这里取得是typePool的倒数第一个和第二个位置,需要保证注册的时候视频卡片和笔记在最后两个位置 57 | putRecycledView(mAdapter.createViewHolder(followFeedRecyclerView, mAdapter.typePool.size -1)) 58 | putRecycledView(mAdapter.createViewHolder(followFeedRecyclerView, mAdapter.typePool.size -2)) 59 | } 60 | }) 61 | }) 62 | ``` 63 | 64 | #### 结果 65 | 在做完这些优化之后,在Feed流上的卡顿就有很大改善了,另外,对于业务侧的指标也有一定的数据提升。 66 | 67 | #### 后续 68 | 对于这块业务的性能监控可以实施起来,这样有利于我们尽早发现卡顿问题,另外,对于一些优化的点其实还可以继续深挖,比如前创建ViewHolder是否可以使用AsyncLayoutInflator进行inflate,还有针对文本的预渲染可以考虑抽成组件形式,方便其他业务使用。 69 | 70 | 感谢您的阅读,如果觉得我的文章对你有帮助,请帮忙点赞,有问题可以评论留言。 71 | -------------------------------------------------------------------------------- /docs/Android/开源库/tinker.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### 方案对比 4 | 5 | 当前比较流行的热补丁方案有微信Tinker、阿里AndFix、美团Robust、QZone超级补丁。每个平台都有自己的局限,大家在技术选型时可以根据业务需要做选择。 6 | 7 | | 特性 | Tinker | QZone | AndFix | Robust | 8 | | ------|:-------|:------:| -----:| -----:| 9 | | 类替换 | yes | yes | no | no | 10 | | So替换 | yes | no | no| no| 11 | | 资源替换| yes| yes| no| no| 12 | | 全平台支持| yes| yes| yes| yes| 13 | | 即时生效| no| no| yes| yes| 14 | | 性能损耗| 较小| 较大| 较小| 较小| 15 | | 补丁包大小| 较小| 较大| 一般| 一般| 16 | | 开发透明| yes| yes| no| no| 17 | | 复杂度| 较低| 较低| 复杂| 复杂| 18 | | gradle支持| yes| no| no| no| 19 | | Rom体积| 较大| 较小| 较小| 较小| 20 | | 成功率| 较高| 较高| 一般| 最高| 21 | 22 | 23 | 总的来说: 24 | 25 | 1. AndFix作为native解决方案,首先面临的是稳定性与兼容性问题,更重要的是它无法实现类替换,它是需要大量额外的开发成本的; 26 | 2. Robust兼容性与成功率较高,但是它与AndFix一样,无法新增变量与类只能用做的bugFix方案; 27 | 3. Qzone方案可以做到发布产品功能,但是它主要问题是插桩带来Dalvik的性能问题,以及为了解决Art下内存地址问题而导致补丁包急速增大的。 28 | 29 | 30 | ### Tinker已知问题 31 | 32 | 由于原理与系统限制,Tinker有以下已知问题: 33 | 34 | 1. Tinker不支持修改AndroidManifest.xml,Tinker不支持新增四大组件(1.9.0支持新增非export的Activity); 35 | 2. 由于Google Play的开发者条款限制,不建议在GP渠道动态更新代码; 36 | 3. 在Android N上,补丁对应用启动时间有轻微的影响; 37 | 不支持部分三星android-21机型,加载补丁时会主动抛出"TinkerRuntimeException:checkDexInstall failed"; 38 | 对于资源替换,不支持修改remoteView。例如transition动画,notification icon以及桌面图标。 39 | 40 | ### 技术解析 41 | 42 | ##### 4. 如何打差分包,即补丁文件 43 | 44 | 使用DexDiff算法,[参考](https://www.zybuluo.com/dodola/note/554061) 45 | 46 | 47 | ##### 5. 不同API level,哪些环节需要分别处理 48 | 49 | 加载dex文件会区分API level。
50 | 51 | Level 24之前:
52 | 采用类似于MultiDex加载dex文件的原理,将补丁dex在dexElements中前置。因为谁在前面用谁,所以系统会使用补丁代码,从而达到热修复功能。 53 | 54 | Level 24及其之后:
55 | 56 | 57 | ``` 58 | 补充知识: 59 | API Level 26 : Android Oreo, 8.0 60 | API Level 24 : Android Nougat, 7.0 61 | API Level 23 : Android Marshmallow, 6.0 62 | API Level 21 : Android Lollipop, 5.0 63 | API Level 19 : Android Kitkat, 4.4 64 | ``` 65 | 66 | ##### 6. MultiDex原理?Tinker参考了MultiDex哪些技术? 67 | 68 | ##### 7. InstantRun原理?Tinker参考了InstantRun哪些技术? 69 | 70 | 更新资源文件时,借鉴了InstantRun的技术。 71 | 72 | 73 | 74 | 75 | #### 重要点 76 | 1. Tinker的补丁方案,Tinker采用的是下发差分包,然后在手机端合成全量的dex文件进行加载。 77 | 78 | ##### 加载补丁dex文件 79 | 使用PathClassLoader加载补丁dex。将补丁dex和原dex合并成fix_class.dex,用这个新的dex替换原有pathDexList中的内容。 80 | 81 | ##### 加载补丁so文件 82 | 83 | ##### 加载补丁资源文件 84 | 采用InstantRun的加载补丁资源方式,全量替换资源。实现方式是hook AssetManager.addAssetPath(),将补丁的资源目录传进去,以此达到替换老资源的方式。 85 | 86 | InstantRun的资源更新方式最简便而且兼容性也最好,市面上大多数的热补丁框架都采用这套方案。Tinker的这套方案虽然也采用全量的替换,但是在下发patch中依然采用差量资源的方式获取差分包,下发到手机后再合成全量的资源文件,有效的控制了补丁文件的大小。 87 | 88 | 89 | ### 类加载原理 90 | 91 | Android中类的加载也是通过ClassLoader来完成,具体来说就是PathClassLoader和DexClassLoader 这两个Android专用的类加载器,这两个类的区别如下: 92 | 93 | * PathClassLoader:只能加载已经安装到Android系统中的apk文件(/data/app目录),是Android默认使用的类加载器。 94 | * DexClassLoader:可以加载任意目录下的dex/jar/apk/zip文件,也就是我们一开始提到的补丁。 95 | 这两个类都是继承自BaseDexClassLoader,我们可以看一下BaseDexClassLoader的构造函数。 96 | 97 | ``` 98 | public BaseDexClassLoader(String dexPath, File optimizedDirectory, 99 | String libraryPath, ClassLoader parent) { 100 | super(parent); 101 | this.pathList = new DexPathList(this, dexPath, libraryPath,optimizedDirectory); 102 | } 103 | ``` 104 | 105 | 这个构造函数只做了一件事,就是通过传递进来的相关参数,初始化了一个DexPathList对象。DexPathList的构造函数,就是将参数中传递进来的程序文件(就是补丁文件)封装成Element对象,并将这些对象添加到一个Element的数组集合**dexElements**中去。 106 | 107 | 假设我们现在要去查找一个名为name的class,那么DexClassLoader将通过以下步骤实现: 108 | 109 | * 在DexClassLoader的findClass 方法中通过一个DexPathList对象findClass()方法来获取class 110 | * 在DexPathList的findClass 方法中,对之前构造好dexElements数组集合进行遍历,一旦找到类名与name相同的类时,就直接返回这个class,找不到则返回null。 111 | 112 | 总的来说,通过DexClassLoader查找一个类,最终就是就是在一个数组中查找特定值的操作。 113 | 114 | 115 | 参考文章: 116 | 117 | [Tinker](https://github.com/Tencent/tinker/wiki) 118 | [Android热补丁之Tinker原理解析](http://w4lle.com/2016/12/16/tinker/) 119 | 120 | -------------------------------------------------------------------------------- /docs/Android/开源库/OKHTTP.md: -------------------------------------------------------------------------------- 1 | # OkHttp 2 | 3 | ### 为什么要使用OkHttp 4 | * OkHttp 提供了对最新的 HTTP 协议版本 HTTP/2 和 SPDY 的支持,这 使得对同一个主机发出的所有请求都可以共享相同的套接字连接。 5 | * 如果 HTTP/2 和 SPDY 不可用,OkHttp 会使用连接池来复用连接以提高效率。 6 | * OkHttp 提供了对 GZIP 的默认支持来降低传输内容的大小。 7 | * OkHttp 也提供了对 HTTP 响应的缓存机制,可以避免不必要的网络请 8 | 求。 9 | * 当网络出现问题时,OkHttp 会自动重试一个主机的多个 IP 地址。 10 | 11 | ### OkHttp有哪些用法 12 | 13 | GET、POST 请求、上传文件、上传表单等等。 14 | 15 | ### OkHttp核心实现原理是什么 16 | 17 | OkHttp 内部的请求流程:使用 OkHttp 会在请求的时候初始化一个 Call 的实例, 然后执行它的 execute()方法或 enqueue()方法,内部最后都会执行到 getResponseWithInterceptorChain()方法,这个方法里面通过拦截器组成的责任链,依次经过用户自定义普通拦截器、重试拦截器、桥接拦截器、缓存拦截器、 18 | 连接拦截器和用户自定义网络拦截器以及访问服务器拦截器等拦截处理过程,来 获取到一个响应并交给用户。其中,除了 OKHttp 的内部请求流程这点之外,缓存和连接这两部分内容也是两个很重要的点。 19 | 20 | * Interceptors:用户自定义拦截器 21 | * retryAndFollowUpInterceptor:负责失败重试以及重定向 22 | * BridgeInterceptor:请求时,对必要的 Header 进行一些添加,接收响应 时,移除必要的 Header 23 | * CacheInterceptor: 负责读取缓存直接返回(根据请求的信息和缓存的响 应的信息来判断是否存在缓存可用)、更新缓存 24 | * ConnectInterceptor:负责和服务器建立连接 25 | * NetworkInterceptors:用户定义网络拦截器 26 | * CallServerInterceptor:负责向服务器发送请求数据、从服务器读取响应数据 27 | 28 | ### ConnectionPool 29 | 1. 判断连接是否可用,不可用则从 ConnectionPool 获取连接,ConnectionPool 30 | 无连接,创建新连接,握手,放入 ConnectionPool。 31 | 2. 它是一个 Deque,add 添加 Connection,使用线程池负责定时清理缓存。 32 | 3. 使用连接复用省去了进行 TCP 和 TLS 握手的一个过程 33 | 34 | ### 从这个库中学到什么有价值的或者说可借鉴的设计思想 35 | 36 | 使用责任链模式实现拦截器的分层设计,每一个拦截器对应一个功能,充分实现 了功能解耦,易维护。 37 | ### 手写拦截器 38 | 39 | ### 如何理解OkHttp 40 | 1. 利用 okhttp 实现基本的网络访问功能,包括基本的数据请求,表单提交,文件上传,文件断点下载,https的设置等等。 41 | 42 | 2. 深入研究 okhttp 源码,熟悉 okhttp 中的调用过程,拦截器原理,缓存原理以及其中涉及的设计模式,并可以自定义拦截器实现特殊的功能,如日志打印等等。 43 | 44 | 3. 在研究 okhttp 缓存原理之前,得首先熟悉 http 缓存的相关字段以及在设置 https 时,也要全面复习 https 的相关原理。 45 | 46 | 4. 怎么实现多路复用的(这个主要从https实现多路复用的原理上谈,用了二进制分帧,那OKHttp其实就是按照分帧来读取的,具体可以看相关源码。) 47 | 48 | ### 问题 49 | 1. 线程池如何优化的 50 | 2. 缓存怎么做的 51 | 52 | * Interceptors 53 | * 责任链设计模式 54 | 55 | 56 | ``` 57 | RealCall.java 58 | 59 | 使用责任链模式,处理一系列拦截器 60 | 61 | Response getResponseWithInterceptorChain() throws IOException { 62 | // Build a full stack of interceptors. 63 | List interceptors = new ArrayList<>(); 64 | interceptors.addAll(client.interceptors()); 65 | interceptors.add(retryAndFollowUpInterceptor); 66 | interceptors.add(new BridgeInterceptor(client.cookieJar())); 67 | interceptors.add(new CacheInterceptor(client.internalCache())); 68 | interceptors.add(new ConnectInterceptor(client)); 69 | if (!forWebSocket) { 70 | interceptors.addAll(client.networkInterceptors()); 71 | } 72 | interceptors.add(new CallServerInterceptor(forWebSocket)); 73 | 74 | Interceptor.Chain chain = new RealInterceptorChain( 75 | interceptors, null, null, null, 0, originalRequest); 76 | return chain.proceed(originalRequest); 77 | } 78 | 79 | 80 | 81 | RealInterceptorChain.java 82 | 83 | public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, 84 | Connection connection) throws IOException { 85 | if (index >= interceptors.size()) throw new AssertionError(); 86 | 87 | calls++; 88 | 89 | ... //安全检查 90 | 91 | // Call the next interceptor in the chain. 92 | RealInterceptorChain next = new RealInterceptorChain( 93 | interceptors, streamAllocation, httpCodec, connection, index + 1, request); 94 | Interceptor interceptor = interceptors.get(index); 95 | Response response = interceptor.intercept(next); 96 | 97 | ... //安全检查 98 | 99 | return response; 100 | } 101 | ``` 102 | [你猜一个TCP连接上面能发多少个HTTP请求](https://zhuanlan.zhihu.com/p/61423830) 103 | [OkHttp相关](https://juejin.im/post/5d450c3e6fb9a06af92b863e?utm_source=gold_browser_extension) 104 | 105 | [拆OkHttp](https://blog.piasy.com/2016/07/11/Understand-OkHttp/index.html) 106 | 107 | [OkHttp里的设计模式](https://www.jianshu.com/p/5cd6775cbb51​) 108 | [OkHttp的源码过程](https://juejin.im/post/5a704ed05188255a8817f4c9) 109 | [OkHttp的系列文章](https://www.jianshu.com/p/82f74db14a18​) -------------------------------------------------------------------------------- /docs/Network/HTTP与HTTPS有什么区别.md: -------------------------------------------------------------------------------- 1 | ### HTTP与HTTPS有什么区别.md 2 | 3 | HTTPS 是一种通过计算机网络进行安全通信的传输协议。HTTPS 经由 HTTP 进行通信,但利用 SSL/TLS 来加密数据包。HTTPS 开发的主要目的,是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。 4 | 5 | HTPPS 和 HTTP 的概念: 6 | 7 | HTTPS(全称:Hypertext Transfer Protocol over Secure Socket Layer),是以安全为目标的 HTTP 通道,简单讲是 HTTP 的安全版。即 HTTP 下加入 SSL 层,HTTPS 的安全基础是 SSL, 因此加密的详细内容就需要 SSL。它是一个 URI scheme(抽象标识符体系),句法类同 http: 体系。用于安全的 HTTP 数据传输。https:URL 表明它使用了 HTTP,但 HTTPS 存在不同于 HTTP 的默认端口及一个加密/身份验证层(在 HTTP 与 TCP 之间)。这个系统的最初研发由网景公司进行,提供了身份验证与加密通讯方法,现在它被广泛用于万维网上安全敏感的通讯,例如交易支付方面。 8 | 9 | 超文本传输协议 (HTTP-Hypertext transfer protocol) 是一种详细规定了浏览器和万维网服 务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议。 10 | 11 | HTTPS 协议需要到 CA 申请证书,一般免费证书很少,需要交费。HTTP 是超文本传输协议, HTTPS 则是具有安全性的 SSL 加密传输协议 HTTP 和 HTPPS 使用的是完全不 同的连接方式用的端口也不一样,前者是 80,后者是 443。HTTP 的连接很简单,是无状态的 HTTPS 协议是由 SSL + HTTP 协议构建的可进行加密传输、身份认证的网络协议要比 HTTP 协议安全 12 | 13 | HTTPS 解决的问题: 14 | 1. 信任主机的问题. 采用 HTTPS 的 Server 必须从 CA 申请一个用于证明服务器用途类型的证书. 改证书只有用于对应的 Server 的时候,客户度才信任次主机; 15 | 2. 防止通讯过程中的数据的泄密和被窜改 16 | 17 | HTTP 与 HTTPS 在写法上的区别也是前缀的不同,客户端处理的方式也不同,具体说来: 18 | 如果 URL 的协议是 HTTP,则客户端会打开一条到服务端端口 80(默认)的连接,并向其发送老的 HTTP 请求。 如果 URL 的协议是 HTTPS,则客户端会打开一条到服务端端口 443 (默认)的连接,然后与服务器握手,以二进制格式与服务器交换一些 SSL 的安全参数,附上加密的 HTTP 请求。 19 | 20 | 所以你可以看到,HTTPS 比 HTTP 多了一层与 SSL 的连接,这也就是客户端与服务端 SSL 握手的过程,整个过程主要完成以下工作:交换协议版本号 选择一个两端都了解的密码 对两端的身份进行认证 生成临时的会话密钥,以便加密信道。 21 | 22 | SSL 握手是一个相对比较复杂的过程,更多关于 SSL 握手的过程细节 可以参考 TLS/SSL 握手过程。 23 | SSL/TSL 的常见开源实现是 OpenSSL,OpenSSL 是一个开放源代码的软件库包,应用程序可以使用这个包来进行安全通信,避免窃听,同时确认另一端连接者的身份。这个包广泛被 应用在互联网的网页服务器上。 更多源于 OpenSSL 的技术细节可以参考 OpenSSL。 24 | 25 | 26 | ### HTTP1.0和HTTP1.1的一些区别 27 | 28 | HTTP1.0 最早在网页中使用是在 1996 年,那个时候只是使用一些较为简单的网页上和网络 请求上,而 HTTP1.1 则在 1999 年才开始广泛应用于现在的各大浏览器网络请求中,同时 HTTP1.1 也是当前使用最为广泛的 HTTP 协议。 29 | 30 | 主要区别主要体现在: 31 | 32 | 1. 缓存处理,在 HTTP1.0 中主要使用 header 里的 If-Modified-Since,Expires 来做 为缓存判断的标准,HTTP1.1 则引入了更多的缓存控制策略例如 Entity tag, If-Unmodified-Since, If-Match, If-None-Match 等更多可供选择的缓存头来控制缓 存策略。 33 | 2. 带宽优化及网络连接的使用,HTTP1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分, 即返回码是 206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。 34 | 3. 错误通知的管理,在 HTTP1.1 中新增了 24 个错误状态响应码,如 409(Conflict) 表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资 源被永久性的删除。 35 | 4. Host 头处理,在 HTTP1.0 中认为每台服务器都绑定一个唯一的 IP 地址,因此,请求消息中的 URL 并没有传递主机名(hostname)。但随着虚拟主机技术的发展, 在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它 36 | 们共享一个 IP 地址。HTTP1.1 的请求消息和响应消息都应支持 Host 头域,且请求消息中如果没有 Host 头域会报告一个错误(400 Bad Request)。 37 | 5、长连接,HTTP 1.1 支持长连接(PersistentConnection)和请求的流水线 (Pipelining)处理,在一个 TCP 连接上可以传送多个 HTTP 请求和响应,减少了建立和关闭连接的消耗和延迟,在 HTTP1.1 中默认开启 Connection: keep-alive,一定程度上弥补了 HTTP1.0 每次请求都要创建连接的缺点。 38 | 39 | ### SPDY 40 | 41 | 在讲 Http1.1 和 Http2.0 的区别之前,还需要说下 SPDY,它是 HTTP1.x 的优化方案: 2012 年 google 如一声惊雷提出了 SPDY 的方案,优化了 HTTP1.X 的请求延迟,解决了 42 | HTTP1.X 的安全性,具体如下: 43 | 44 | 1. 降低延迟,针对 HTTP 高延迟的问题,SPDY 优雅的采取了多路复用(multiplexing)。 多路复用通过多个请求 stream 共享一个 tcp 连接的方式,解决了 HOL blocking 的 问题,降低了延迟同时提高了带宽的利用率。 45 | 2. 请求优先级(request prioritization)。多路复用带来一个新的问题是,在连接 共享的基础之上有可能会导致关键请求被阻塞。SPDY 允许给每个 request 设置优先 级,这样重要的请求就会优先得到响应。比如浏览器加载首页,首页的 html 内容应 该优先展示,之后才是各种静态资源文件,脚本文件等加载,这样可以保证用户能 第一时间看到网页内容。 46 | 3. header 压缩。前面提到 HTTP1.x 的 header 很多时候都是重复多余的。选择合适 的压缩算法可以减小包的大小和数量。 47 | 4. 基于 HTTPS 的加密协议传输,大大提高了传输数据的可靠性。 48 | 5. 服务端推送(server push),采用了 SPDY 的网页,例如我的网页有一个 sytle.css 的请求,在客户端收到 sytle.css 数据的同时,服务端会将 sytle.js 的文件推送给客户 端,当客户端再次尝试获取 sytle.js 时就可以直接从缓存中获取到,不用再发请求了。 49 | 50 | SPDY 位于 HTTP 之下,TCP 和 SSL 之上,这样可以轻松兼容老版本的 HTTP 协议(将 HTTP1.x 的内容封装成一种新的 frame 格式),同时可以使用已有的 SSL 功能。 51 | 52 | ### HTTP2.0和HTTP1.X相比的新特性 53 | 54 | 1. 新的二进制格式(Binary Format),HTTP1.x 的解析是基于文本。基于文本协议的 格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然 很多,二进制则不同,只认 0 和 1 的组合。基于这种考虑 HTTP2.0 的协议解析决定 采用二进制格式,实现方便且健壮。 55 | 2. 多路复用(MultiPlexing),即连接共享,即每一个 request 都是是用作连接共享机 制的。一个 request 对应一个 id,这样一个连接上可以有多个 request,每个连接的 request 可以随机的混杂在一起,接收方可以根据 request 的 id 将 request 再归属 到各自不同的服务端请求里面。 56 | 3. header 压缩,如上文中所言,对前面提到过 HTTP1.x 的 header 带有大量信息,而 且每次都要重复发送,HTTP2.0 使用 encoder 来减少需要传输的 header 大小,通讯 双方各自 cache 一份 header fields 表,既避免了重复 header 的传输,又减小了需 要传输的大小。 57 | 4. 服务端推送(server push),同 SPDY 一样,HTTP2.0 也具有 server push 功能。 58 | 59 | 问题: 60 | 61 | 1. HTTPS环境下,能否使用ip通信? 62 | 2. HTTPS环境下,修改系统时间,能够正常通信? 63 | 64 | 因为HTTPS通信,会在客户端安装一个数字证书,IO时,会校验通信信息是否符合证书里的内容。 65 | 问题1中,因为证书会校验域名,但是ip没有域名,所以直接使用ip是无法通信的。 66 | 问题2中,因为证书会校验有效期,客户端修改系统时间可能使得改后的时间超过证书有效期,就会无法通过校验,故而无法通信。 -------------------------------------------------------------------------------- /docs/PerformanceOptimization/一种Activity预加载的方法.md: -------------------------------------------------------------------------------- 1 | 一般某个 Activity 界面打开过程包括如下阶段: 2 | 3 | 1. 从发起打开命令“startActivity”到出现该界面所在窗口动画前 — 如果该阶段时间较长,给用户的感觉就是 点击后有一个等待时间,会觉得机器反应慢。 4 | 2. 窗口动画过程 — 这个过程可以自己定义,在 startActivity 时指定旧窗口和新窗口的动画,好的动画也会给用户很快的感觉。 5 | 3. 窗口动画完成后到数据加载完成和现实数据的界面展现出来 — 这个主要包括窗口中所有 view 的 inflate 时间,使用的所有资源的加载时间和具体数据的 load 时间等。 6 | 7 | 为了提升新界面打开速度,我们一般通过修改自己的代码来实现,比如减少 view 层次,延迟加载,缓存机制,多线程,预加载等方法。以上方法都是在自己的进程启动后来做的,但对如下这种情况不太适用:即用户打开某个 activity,但该 activity 所在的进程还没有创建和启动时,这时可能需要更长的等待时间。 8 | 9 | 这里介绍一种 activity 的预加载方法,并尝试避免以上问题,先贴出代码,后面再详细介绍: 10 | 11 | ```java 12 | public class PreloadService extends IntentService { 13 | private final static String TAG = "PreloadService"; 14 | public final static String EXTRA_PRELOAD = "preload"; 15 | private static boolean sHasPreload = false; 16 | 17 | private static class LocalActivityRecord extends Binder { 18 | LocalActivityRecord(String _id, Intent _intent) { 19 | id = _id; 20 | intent = _intent; 21 | } 22 | final String id; 23 | Intent intent; 24 | } 25 | 26 | public PreloadService() { 27 | super("PreloadService"); 28 | } 29 | 30 | @Override 31 | protected void onHandleIntent(Intent intent) { 32 | Log.i(TAG, "PreloadService onHandleIntent..."); 33 | if (sHasPreload) { 34 | return; 35 | } 36 | ActivityThread activityThread = ActivityThread.currentActivityThread(); 37 | if (activityThread == null) { //4.0 ~ 4.2 上 ActivityThread 的对象是 ThreadLocal 的,当前线程中 无法获取,所以只能在 mainThread 中获取 38 | final Looper mainLooper = Looper.getMainLooper(); 39 | Handler mainHandler = new Handler(mainLooper); 40 | mainHandler.post(new Runnable() { 41 | 42 | @Override 43 | public void run() { 44 | activityThread = ActivityThread.currentActivityThread(); 45 | final ActivityThread finalActivityThread = activityThread; 46 | new Thread(new Runnable() { 47 | @Override 48 | public void run() { 49 | doPreload(finalActivityThread); } 50 | }).start(); 51 | } 52 | }); 53 | } else { 54 | doPreload(activityThread); 55 | } 56 | sHasPreload = true; 57 | } 58 | 59 | private void doPreload(ActivityThread activityThread) { 60 | Intent activityIntent = null; 61 | Activity activity = null; 62 | if (XXXActivity.sShouldPreload) { //如果真正的启动正在运行,预加载就不需 63 | activityIntent = new Intent(getApplicationContext(), XXXActivity.class); 64 | activityIntent.putExtra(EXTRA_PRELOAD, true); 65 | String id = XXXActivity.class.getName(); 66 | LocalActivityRecord activityRecord = new LocalActivityRecord(id, activityIntent); 67 | activity = activityThread.startActivityNow(null, activityRecord.id, activityRecord.intent, 68 | activityThread.resolveActivityInfo(activityRecord.intent), activityRecord, null, null); 69 | if (activity != null) { 70 | activityThread.performResumeActivity(activityRecord, true); 71 | activityThread.performDestroyActivity(activityRecord, true); 72 | } 73 | } 74 | } 75 | } 76 | ``` 77 | 78 | 启动预加载 79 | 80 | ```java 81 | 82 | Intent intent = new Intent(getApplicationContext(), PreloadService.class); 83 | context.startService(intent); 84 | ``` 85 | 86 | 基本原理 87 | 88 | 当 activity 所在进程还没有启动时,用户如果打开某个 activity,首先需要创建这个进程,要将 activity 使用 的 class,xml,resource 等从 apk 文件加载到内存,这个过程中有大量的 io,是比较耗时的。文中的预加 载方式可以很好的解决这个问题。 89 | 90 | 1. 首先将 PreloadService 放在与预加载 activity 相同的进程内 91 | 2. 在 PreloadService 中获取当前进程中唯一用来创建 activity 的底层的管理类:ActivityThread 的对象 92 | 3. ActivityThread 可以根据 intent 创建 intent 中制定的 activity 对象,Activity activity = activityThread.startActivityNow(...intent...) 93 | 4. 这样系统会创建该 activity,并且调用了 activity 的 onCreate,这样就会把 activity 的 contentView 加载进 来,如果在 onCreate 中调用了 fragmentManager.addFragment,这时 fragment 的 create 生命周期函数也 会调用 94 | 5. 因为能够使得 activity 加载的更彻底,再调用 activityThread.performResumeActivity(activityRecord, true),这样会调用 activity 的 onResume,同样的 fragment 的也会调用 95 | 6. 为了能够使得预加载的 activity 可以被回收,需要调用 activityThread.performDestroyActivity(activityRecord, true),该方法会将 activity 从 activityThread 中移除 96 | 7. 通过以上方法,在用户点击之前可以做预加载 -------------------------------------------------------------------------------- /docs/JVM/1_Java的内存区域.md: -------------------------------------------------------------------------------- 1 | ### 概述 2 | 3 | > Java与C++之间有一堵由``内存动态分配``和``垃圾回收机制``所围成的高墙,墙外面的人想进去,墙里面的人出不来。 4 | 5 | ### JVM的内存划分 6 | 7 | > JVM执行Java程序的过程:Java源代码文件(.java)会被Java编译器编译为字节码文件(.class),然后由JVM中的类加载器加载各个类的字节码文件,加载完毕之后,交由JVM执行引擎执行。 8 | 9 | 在上述过程中,JVM会用一段空间来存储执行程序期间需要用到的数据和相关信息,这段空间就是``运行时数据区(Runtime Data Area)``,也就是常说的JVM内存。JVM会将它所管理的内存划分为若干个不同的数据区域,划分结果如图: 10 | 11 | ![image](../img/jvm_runtime.png) 12 | 13 | 运行时数据区被分为``线程私有数据区``和``线程共享数据区``两大类: 14 | 15 | - 线程私有数据区包含:程序计数器、虚拟机栈、本地方法栈 16 | - 线程共享数据区包含:Java堆、方法区(内部包含常量池) 17 | 18 | #### 1.程序计数器(Program Counter Register) 19 | * 是``当前线程``所执行的字节码的行号计数器。 20 | * 如果线程正在执行的是一个Java方法,那么计数器记录的是正在执行的虚拟机字节码指令的地址。 21 | * 如果线程正在执行的是一个Native方法,那么计数器的值则为空。 22 | 23 | > 字节码解释器工作时,就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。 24 | 25 | * 为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,因此它是线程私有的内存. 26 | * 在Java虚拟机规范中,是唯一一个没有规定任何OutOfMemoryError情况的区域。 27 | 28 | #### 2.Java虚拟机栈(Java Virtual Machine Stacks) 29 | 30 | * 是``Java方法``执行的内存模型。 31 | * 每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。 32 | * 每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈道出栈的过程。 33 | 34 | > 局部变量表存放了编译期可知的各种基本数据类型、对象引用类型和returnAddress类型,它所需的内存空间在编译期间完成分配。 35 | * 是线程私有的内存,与线程生命周期相同。 36 | * 一般把Java内存区分为堆内存(Heap)和栈内存(Stack),其中『栈』指的是虚拟机栈,『堆』指的是Java堆。 37 | * 在Java虚拟机规范中,对这个区域规定了两种异常状况: 38 | * 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常; 39 | * 如果虚拟机栈可动态扩展且扩展时无法申请到足够的内存,将抛出OutOfMemoryError异常。 40 | 41 | #### 3.本地方法栈(Native Method Stack) 42 | * 是虚拟机使用到的Native方法服务。 43 | * 在虚拟机规范中,对这个区域无强制规定,由具体的虚拟机自由实现。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。 44 | 45 | #### 4.Java堆(Java Heap) 46 | * 用于存放几乎所有的对象实例和数组。 47 | * 被所有线程共享的一块内存区域,在虚拟机启动时创建。 48 | 49 | > 在Java堆中,可能划分出多个``线程私有``的分配缓冲区(Thread Local Allocation Buffer,TLAB),但无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快地分配内存。 50 | 51 | * 是垃圾收集器管理的重要区域,也被称作“GC堆”。 52 | * 是Java虚拟机所管理内存中最大的一块。 53 | * 可处于物理上不连续的内存空间中,只要逻辑上连续即可。 54 | * 在Java虚拟机规范中,如果在堆中没有完成内存分配,且堆也无法再扩展时,将会抛出OutOfMemoryError异常。 55 | 56 | #### 5.方法区(Method Area) 57 | * 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 58 | * 与Java堆一样,是各个线程共享的内存区域。 59 | * 人们更愿意把这个区域称为“永久代”(Permanent Generation),在发布的JDK1.7的HotSpot中,已经把原本放在永久代的字符串常量池移出。它还有个别名叫做Non-Heap(非堆)。 60 | * 和Java堆一样不需要连续的内存和可以选择固定大小或可扩展外,还可选择不实现GC。 61 | * 在Java虚拟机规范中,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。 62 | 63 | #### 6.运行时常量池(Runtime Constant Pool) 64 | 65 | > Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。 66 | 67 | * 相对于Class文件常量池的一个重要特征是具备动态性,体现在并非只有预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中。 68 | * 是方法区的一部分,会受到方法区内存的限制。 69 | * 在Java虚拟机规范中,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。 70 | 71 | ### HotSpot虚拟机对象探秘 72 | 73 | > 在大致知道了虚拟机内存的概况后,要想进一步了解这些虚拟机内存中的数据的其他细节,我们针对常用的虚拟机HotSpot和常用的内存区域Java堆为例,探讨HotSpot在Java堆中对象的分配、布局和访问的全过程。 74 | 75 | #### 1.对象的创建 76 | 77 | * 类加载检查 78 | 虚拟机遇到一条new指令时,首先将去检查new指令的参数是否能在常量池中定位到一个类的符号引用并且该符号引用代表的类是否被加载、解析和初始化过,若没有,则需要执行相应的类加载过程。 79 | * 分配内存:由Java堆中的内存是否规整决定如何给新生对象分配内存。 80 | * 若规整,采用“指针碰撞”分配方式: 81 | * 过程:将用过的内存放在一边,空闲的内存放在另一边,中间放着指针作为分界点的指示器,分配内存时把指针向空闲空间挪过一段与对象大小相等的距离。 82 | * 应用:使用Serial,ParNew等带Compact过程的收集器是,会采用指针碰撞来分配。 83 | * 若不规整,采用“空闲列表”分配方式: 84 | * 过程:维护一个记录可用内存的列表。当分配内存时,找到一块足够大的空间划分给对象实例并更新表记录。 85 | * 应用:使用CMS这种基于Mark-Sweep算法的收集器,会采用空闲列表。 86 | 87 | > 保证内存分配是线程安全的解决方案: 88 | * 对分配内存空间的动作进行同步处理(方法是采用CAS和失败重试来保证更新操作的原子性) 89 | * 把内存分配的动作按照线程划分在不同的空间之中进行(每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),当TLAB用完需要重新分配新的TLAB再同步锁定)。 90 | 91 | * 设置对象头:将对象的所属类、找到类的元数据信息的方式、对象的哈希码、对象的GC分代年龄等信息存放在对象的对象头中。 92 | > 经过上述步骤,一个对象就产生了,但此时所有的字段都还为零,还需要执行````方法进行初始化,才能成为真正可用的对象。 93 | 94 | #### 2.对象的内存布局 95 | 分为3块区域:对象头(Header),实例数据(Instance Data)和对齐填充(Padding). 96 | * 对象头分为两部分信息 97 | * Mark Word:用于存储对象自身的运行时数据,如哈希吗,GC分代年龄、所状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。 98 | * 类型指针:用于确定这个对象是那个类的实例。 99 | 100 | * 实例数据:存储真正的有效信息,是程序代码中定义的各种类型的字段内容。存储顺序会受虚拟机分配策略参数和字段在Java源码中定义顺序这两个因素影响。 101 | * 对齐填充:占位符,帮助补全未对齐的对象实例数据部分(保证是8字节的倍数),非必需。 102 | 103 | #### 3.对象的访问定位:主流有使用句柄和直接指针两种方式 104 | * 使用句柄::在Java堆中划分出一块内存来作为句柄池,reference存储的是对象的句柄地址,在句柄中包含了对象实例数据与类型数据各自的具体地址信息。好处:reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要修改。 105 | 106 | ![image](../img/java_object_access_1.webp) 107 | * 通过直接指针访问对象:在Java堆对象的布局中考虑如何放置访问类型数据的相关信息,reference存储的直接就是对象地址。好处:速度更快,节省了一次指针定位的时间开销。 108 | 109 | ![image](../img/java_object_access_2.webp) -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | KnowledgeSummary for Android Developer 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 |
loading...
20 | 21 | 22 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /docs/Android/Handler.md: -------------------------------------------------------------------------------- 1 | Handler 为什么会泄漏 2 | 3 | Handler如何在handleMessage方法拦截之前发出的message 4 | 5 | > 通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。 6 | 7 | ``` java 8 | 9 | /** 10 | * Handle system messages here. 11 | */ 12 | public void dispatchMessage(Message msg) { 13 | //首先检查msg是否设置了回调 14 | if (msg.callback != null) { 15 | handleCallback(msg); 16 | } else { 17 | if (mCallback != null) { 18 | //重点在这里,callback的handleMessage()方法的返回值决定了是否拦截消息 19 | //重点在这里,callback的handleMessage()方法的返回值决定了是否拦截消息 20 | //重点在这里,callback的handleMessage()方法的返回值决定了是否拦截消息 21 | if (mCallback.handleMessage(msg)) { 22 | return; 23 | } 24 | } 25 | handleMessage(msg); 26 | } 27 | } 28 | 29 | ``` 30 | 31 | * Handler 32 | * Handler如何在handleMessage方法拦截之前发出的message 33 | * HandlerThread的原理 34 | * IntentService的实现原理 35 | * Handler实现机制(很多细节需要关注:如线程如何建立和退出消息循环等等 36 | * Handler发消息给子线程,looper怎么启动 37 | * Handler postDelay这个延迟是怎么实现的 38 | * MessageQueue.IdleHandler 你了解嘛 39 | * Handler 造成内存泄漏,是什么东西造成的泄漏 40 | 41 | [Android 消息处理机制(Looper、Handler、MessageQueue,Message)](https://www.jianshu.com/p/02962454adf7) 42 | 43 | Android中为什么主线程不会因为Looper.loop()里的死循环卡死? 44 | * [主线程的工作原理](https://haldir65.github.io/2016/10/12/2016-10-12-How-the-mainThread-work/) 45 | > 这篇文章笔记有新意的思考点是:在2.2版本以前,这套机制是用我们熟悉的线程的wait和notify 来实现的,之前的Android版本用的是Java的线程wait和notify。 46 | 47 | >Android应用程序的主线程在进入消息循环过程前,会在内部创建一个Linux管道(Pipe),这个管道的作用是使得Android应用程序主线程在消息队列为空时可以进入空闲等待状态,并且使得当应用程序的消息队列有消息需要处理时唤醒应用程序的主线程。 48 | 49 | 这个主线程的休眠以及唤醒操作是基于Linux的epoll机制来实现的。 50 | 51 | 在MessageQueue.java的next方法中: 52 | 53 | ```java 54 | Message next() 55 | 56 | final long ptr = mPtr; 57 | if (ptr == 0) { 58 | return null; 59 | } 60 | int pendingIdleHandlerCount = -1; // -1 only during first iteration 61 | 62 | //nextPollTimeoutMillis 表示nativePollOnce方法需要等待nextPollTimeoutMillis 63 | //才会返回 64 | int nextPollTimeoutMillis = 0; 65 | for (;;) { 66 | if (nextPollTimeoutMillis != 0) { 67 | Binder.flushPendingCommands(); 68 | } 69 | //读取消息,队里里没有消息有可能会堵塞,两种情况该方法才会返回(代码才能往下执行) 70 | //一种是等到有消息产生就会返回, 71 | //另一种是当等了nextPollTimeoutMillis时长后,nativePollOnce也会返回 72 | nativePollOnce(ptr, nextPollTimeoutMillis); 73 | //nativePollOnce 返回之后才能往下执行 74 | synchronized (this) { 75 | // Try to retrieve the next message. Return if found. 76 | final long now = SystemClock.uptimeMillis(); 77 | Message prevMsg = null; 78 | Message msg = mMessages; 79 | if (msg != null && msg.target == null) { 80 | // 循环找到一条不是异步而且msg.target不为空的message 81 | do { 82 | prevMsg = msg; 83 | msg = msg.next; 84 | } while (msg != null && !msg.isAsynchronous()); 85 | } 86 | if (msg != null) { 87 | if (now < msg.when) { 88 | // 虽然有消息,但是还没有到运行的时候,像我们经常用的postDelay, 89 | //计算出离执行时间还有多久赋值给nextPollTimeoutMillis, 90 | //表示nativePollOnce方法要等待nextPollTimeoutMillis时长后返回 91 | nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); 92 | } else { 93 | // 获取到消息 94 | mBlocked = false; 95 | //链表一些操作,获取msg并且删除该节点 96 | if (prevMsg != null) 97 | prevMsg.next = msg.next; 98 | } else { 99 | mMessages = msg.next; 100 | } 101 | msg.next = null; 102 | msg.markInUse(); 103 | //返回拿到的消息 104 | return msg; 105 | } 106 | } else { 107 | //没有消息,nextPollTimeoutMillis复位 108 | nextPollTimeoutMillis = -1; 109 | } 110 | ..... 111 | ..... 112 | 113 | } 114 | ``` 115 | nativePollOnce()很重要,是一个native的函数,``nativePollOnce``最终调用到的是: 116 | 117 | > /frameworks/base/core/jni/android_os_MessageQueue.cpp中的``pollOnce``方法,pollOnce内部又是调用``pollInner(int timeoutMillis)``方法。 118 | 119 | pollInner中就是调用epoll_wait,epoll_wait就是使用的是Linux中epoll机制,关于epoll的机制可以看下这篇关于epoll的文章分析。[epoll的原理和实现](https://tqr.ink/2017/10/05/implementation-of-epoll/)。 120 | 121 | nativePollOnce这里会读取消息,队里里没有消息有可能会堵塞,这个时候会让出处理器,当有消息来了或者当设置的timeout时间到了就可以往下执行。 -------------------------------------------------------------------------------- /docs/Java/Java中的注解.md: -------------------------------------------------------------------------------- 1 | ### 注解相当于是加在代码上的标签。 2 | 3 | #### 注解的定义 4 | 5 | ``` 6 | public @interface Test { 7 | } 8 | ``` 9 | 创建形式跟接口类似,在interface前需要加"@"符号 10 | 11 | #### 元注解 12 | 为什么需要元注解?元注解是用来干嘛的? 13 | 14 | 首先元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。 15 | 16 | 因为注解定义出来,这个注解的作用域是什么,也就是说这个注解将应用于什么地方(例如是一个方法或者一个字段上)。另外,注解定义出来,需要明确注解是哪一个级别可用,在源代码中(SOURCE)、类文件中(CLASS)或者运行时(RUNTIME)。 17 | 18 | 这也就是为什么需要元注解,元注解就是帮助定义一下注解的是怎么使用的。 19 | 20 | 元注解有@Retention、@Documented、@Target、@Inherited、@Repeatable 5 种。 21 | 22 | #### @Retention 23 | Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。 24 | 25 | 它的取值如下: 26 | 27 | RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。 28 | RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。 29 | RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。 30 | 31 | ``` 32 | @Retention(RetentionPolicy.RUNTIME) 33 | public @interface Test { 34 | } 35 | ``` 36 | 上面的代码中,我们指定Test注解可以在程序运行周期被获取到,因此它的生命周期非常的长。 37 | 38 | 这里对于注解的@Retention怎么使用还是有点疑问,什么时候该用Source,什么时候该用CLASS以及什么时候该用RUNTIME呢? 39 | 40 | 根据主键的存活时间来划分如下 41 | 首先要明确存活的时间长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。 42 | 43 | 举例,一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解,比如Android中使用@Deprecated标记方法,字段,类等是否废弃, 44 | 还有很多场景也会使用Runtime,例如dagger2中的Component注解,dagger2中会在运行时会通过这个注解有些特殊的行为交互。 45 | 46 | 如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解。 47 | 48 | 例如AndroidX包下面的DrawableRes。 49 | ``` 50 | @Documented 51 | @Retention(RetentionPolicy.CLASS) 52 | @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.LOCAL_VARIABLE}) 53 | public @interface DrawableRes { 54 | } 55 | ``` 56 | DrawableRes表示期望整数参数、字段或方法返回值是一个drawable资源引用。 57 | 58 | 如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,使用SOURCE 注解。 59 | 60 | #### @Documented 61 | 顾名思义,这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去。 62 | 63 | #### @Target 64 | Target 是目标的意思,@Target 指定了注解运用的地方。 65 | 66 | 你可以这样理解,当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。 67 | 68 | @Target 有下面的取值 69 | 70 | - ElementType.ANNOTATION_TYPE 可以给一个注解进行注解 71 | - ElementType.CONSTRUCTOR 可以给构造方法进行注解 72 | - ElementType.FIELD 可以给属性进行注解 73 | - ElementType.LOCAL_VARIABLE 可以给局部变量进行注解 74 | - ElementType.METHOD 可以给方法进行注解 75 | - ElementType.PACKAGE 可以给一个包进行注解 76 | - ElementType.PARAMETER 可以给一个方法内的参数进行注解 77 | - ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举 78 | 79 | 80 | #### @Inherited 81 | Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。 82 | 83 | ``` 84 | @Inherited 85 | @Retention(RetentionPolicy.RUNTIME) 86 | @interface Test {} 87 | 88 | @Test 89 | public class A {} 90 | 91 | public class B extends A {} 92 | 93 | ``` 94 | 注解 Test 被 @Inherited 修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也拥有 Test 这个注解。 95 | 96 | @Repeatable 97 | Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。 98 | 99 | 什么样的注解会多次应用呢?通常是注解的值可以同时取多个。 100 | 101 | 举个例子,一个人他既是程序员又是产品经理,同时他还是个画家。 102 | 103 | ``` 104 | @interface Persons { 105 | Person[] value(); 106 | } 107 | 108 | @Repeatable(Persons.class) 109 | @interface Person{ 110 | String role default ""; 111 | } 112 | 113 | @Person(role="artist") 114 | @Person(role="coder") 115 | @Person(role="PM") 116 | public class SuperMan{ 117 | 118 | } 119 | ``` 120 | 121 | #### 注解的属性 122 | 注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。 123 | 124 | ``` 125 | @Target(ElementType.TYPE) 126 | @Retention(RetentionPolicy.RUNTIME) 127 | public @interface Test { 128 | int id(); 129 | String msg(); 130 | } 131 | 132 | ``` 133 | 上面代码定义了 Test 这个注解中拥有 id 和 msg 两个属性。在使用的时候,我们应该给它们进行赋值。 134 | 135 | 赋值的方式是在注解的括号内以 value="" 形式,多个属性之前用 ,隔开。 136 | 137 | ``` 138 | @Test(id=3,msg="hello annotation") 139 | public class Test { 140 | 141 | } 142 | 143 | ``` 144 | 145 | 需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。 146 | 147 | 注解中属性可以有默认值,默认值需要用 default 关键值指定。比如: 148 | 149 | ``` 150 | @Target(ElementType.TYPE) 151 | @Retention(RetentionPolicy.RUNTIME) 152 | public @interface TestAnnotation { 153 | public int id() default -1; 154 | public String msg() default "Hi"; 155 | } 156 | 157 | ``` 158 | 159 | #### 注解的提取 160 | 161 | 要想正确检阅注解,离不开一个手段,那就是反射。 162 | 163 | #### 注解与反射。 164 | 注解通过反射获取。首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解。 165 | ``` 166 | public boolean isAnnotationPresent(Class annotationClass) {} 167 | 168 | ``` 169 | 170 | 然后通过 getAnnotation() 方法来获取 Annotation 对象。 171 | ``` 172 | public A getAnnotation(Class annotationClass) {} 173 | 174 | ``` 175 | 或者是 getAnnotations() 方法。 176 | ``` 177 | public Annotation[] getAnnotations() {} 178 | ``` 179 | 180 | ``` 181 | @TestAnnotation() 182 | public class Test { 183 | public static void main(String[] args) { 184 | boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class); 185 | if ( hasAnnotation ) { 186 | TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class); 187 | System.out.println("id:"+testAnnotation.id()); 188 | System.out.println("msg:"+testAnnotation.msg()); 189 | } 190 | } 191 | } 192 | ``` 193 | 194 | 程序的运行结果是: 195 | ``` 196 | id:-1 197 | msg: 198 | ``` 199 | 200 | #### apt技术 201 | 202 | [link](https://blog.csdn.net/briblue/article/details/73824058) -------------------------------------------------------------------------------- /docs/PerformanceOptimization/绘制优化.md: -------------------------------------------------------------------------------- 1 | # 性能优化1/5 -- 绘制优化 2 | 3 | * Android系统显示原理 4 | * 布局优化 5 | * 避免过度绘制 6 | * 启动优化 7 | * 合理刷新机制 8 | * 提升动画性能 9 | * 滑动卡顿 10 | * 工具篇 11 | 12 | [文字预渲染](https://www.jianshu.com/p/9f7f9213bff8) 13 | 14 | 15 | ### Android系统显示原理 16 | 简单来说,就是应用层通过Measure、Layout、Draw,产生Surface缓存数据。然后通过进程间通信(匿名共享内存 SharedClient),把缓存数据交给系统层,利用SurfaceFlinger把数据渲染到屏幕。 17 | 18 | Android的图形显示系统采用Client/Server架构。其中每个应用程序都是Client,系统是Server。 19 | 20 | 每个应用和SurfaceFlinger之间,都会创建一个SharedClient,即一个SharedClient对应一个应用程序。而每个SharedClient中,最多可创建31个SharedBufferStack,即SharedClient包含SharedBufferStack的集合。其中,每个Surface都对应一个SharedBufferStack,也就是一个Window。这就意味着,一个应用程序同时做多包含31个窗口。 21 | 22 | ![Android显示框架](https://raw.githubusercontent.com/hningoba/KnowledgeSummary/master/img/Android显示框架.jpg) 23 | 24 | 25 | 26 | 要提高显示页面显示效率,一般有两个手段: 27 | 28 | 1. 减少布局层级,即布局优化 29 | 2. 避免过度绘制 30 | 31 | ### 布局优化 32 | 33 | ##### 检测工具 34 | 35 | 1.Hierarchy Viewer 36 | 37 | Tools -> Android -> Android Device Monitor中,打开Hierarchy Viewer。 38 | 可监测对程序当前页面布局情况。可查看布局的层级和每个节点的耗时情况。 39 | 40 | 2.Android Lint 41 | 42 | 扫描规则和缺陷级别可在 File->Settings->Inspections->Android Lint 中配置。 43 | 44 | 配置项: 45 | 46 | * TooDeepLayout: 表示布局太深,默认超过10层提示; 47 | * TooManyViews: 控件太多,默认超过80个控件会提示该问题; 48 | 49 | 启动lint后,Studio会给出扫描结果。根据结果优化对应布局文件即可。 50 | 51 | 52 | ##### 优化方法 53 | 可从3个方面优化布局。 54 | 55 | 1.减少布局层级 56 | 57 | 可以使用 merge 标签、RelativeLayout或ConstraintLayout等。很简单,不用细说。 58 | 59 | 2.提高显示速度 60 | 61 | 可以使用ViewStub,初始状态不会加载显示ViewStub指定的布局。只有当ViewStub显示时,或调用ViewStub.infalte()时,其指定的布局才会被加载和实例化。常用于网络异常提示等情况。 62 | 63 | 3.布局的复用 64 | 65 | 通用的布局,可以使用 include 标签。 66 | 67 | 68 | ### 避免过度绘制 69 | 70 | 过度绘制:Overdraw,指屏幕上某个像素在同一帧的时间内被绘制多次。 71 | 72 | 过度绘制的主要原因: 73 | 74 | * XML布局:控件有重叠且都设置背景 75 | * View自绘:View.onDraw()里同一区域被绘制多次 76 | 77 | 如果控件重叠多层,绘制每一层时,都会导致该区域被绘制。这种叠加就很容易导致过度绘制。 78 | 79 | 检测方法: 80 | 81 | * Hierarchy Viewer:查看布局层级,降低层级; 82 | * 手机设置中“GPU过度重绘”:通过颜色粗略判断Overdraw情况。 83 | 84 | 无色:没有过渡绘制,每个像素绘制1次; 85 | 蓝色:每个像素多绘制1次; 86 | 绿色:每个像素多绘制2次; 87 | 淡红色:每个像素多绘制3次; 88 | 深红色:每个像素多绘制4次或更多; 89 | 90 | 91 | ### 启动优化 92 | 93 | 启动分两种类型: 94 | 95 | * 冷启动:系统创建一个新的进程,先创建Application,再创建MainActivity 96 | * 热启动:从已有的进程中启动,所以不会再创建Application,而是直接创建MainActivity 97 | 98 | 启动耗时监测方法: 99 | 100 | 1. adb命令:adb shell am start -W [packageName]/[packageName.AppStartActivity] 101 | 2. 代码打点:打出启动过程中相关方法耗时情况,更具体 102 | 103 | ``` 104 | 写了个简单的demo,adb shell查看启动耗时,结果如下: 105 | 106 | Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=your_package/.MainActivity } 107 | Status: ok 108 | Activity: your_package/.MainActivity 109 | ThisTime: 680 110 | TotalTime: 680 111 | WaitTime: 696 112 | Complete 113 | ``` 114 | * TotalTime:应用的启动时间,包括1)创建进程 + 2)Application初始化 + 3)Activity初始化到界面显示 115 | 116 | add 启动优化之前的经验 117 | 118 | 119 | ### 合理刷新机制 120 | 1. 控制刷新频率:比如进度条等View,数据变化不到1%,没必要刷新View 121 | 2. 避免没必要的刷新:比如数据没变化、View不可见,就没必要刷新,但是View从不可见到可见,必须要刷新一次 122 | 3. 避免后台线程影响:后台线程虽然不会直接影响UI线程,但是如果后台线程开销过大,占用CPU过高,导致系统GC频繁、CPU时间片紧张,也会导致界面卡顿。典型案例:滑动RecyclerView,如果滑动过程中下载很多图片,可能导致RecyclerView卡顿。建议是监听RecyclerView的OnScrollListener,滑动或Fling时暂停图片下载线程工作,等滑动结束再开始。 123 | 4. 缩小刷新区域:1)自定义View可以更新局部区域,View.invalidate(Rect dirty);2)容器中某个Item发生变化,只需更新当前Item,比如notifyItemChanged(int position) 124 | 125 | 126 | ### 提升动画性能 127 | 尽量使用属性动画。 128 | 129 | Android的3种动画: 130 | 131 | 1)帧动画: 132 | 133 | 将一帧帧图片按顺序显示出来。可用AnimationDrawable定义帧动画。因为动画过程涉及图片多,是效果最差的一种动画。 134 | 135 | 2)补间动画: 136 | 137 | 对View进行一系列操作来改变显示效果。补间动画不需要定义动画过程中的每一帧,只需要定义开始和结束关键帧的内容,之间的效果自动生成。 138 | 139 | 补间动画支持4种动画模式:AlphaAnimation、ScaleAnimation、TranslationAnimation、RotateAnimation。 140 | 141 | ``` 142 | Animation rotateAnim = new RotateAnimation(0, 360, 143 | Animation.RELATIVE_TO_SELF, 0.5f, 144 | Animation.RELATIVE_TO_SELF, 0.5f); 145 | Animation scaleAnim = new ScaleAnimation(0f, 1f, 0f, 1f, 146 | Animation.RELATIVE_TO_SELF, 0.5f, 147 | Animation.RELATIVE_TO_SELF, 0.5f); 148 | 149 | AnimationSet animationSet = new AnimationSet(true); 150 | animationSet.addAnimation(rotateAnim); 151 | animationSet.addAnimation(scaleAnim); 152 | animationSet.setDuration(2000); 153 | imageView.setAnimation(animationSet); 154 | ``` 155 | 156 | 如果使用TraceView检测动画过程方法[Calls + Recur Calls]情况就能发现,把数据送到显示器更新DisplayList的方法(View.updateDisplayListIfDirty()) 调用频繁,这就导致补间动画时View重绘非常频繁。 157 | 158 | 3)属性动画: 159 | 160 | 由Android3.0(API 11)开始支持。通过修改动画的实际属性来实现动画效果。属性动画是一个非常全面的框架,几乎支持把任何对象变成动画。 161 | 162 | 如果使用TraceView检测动画过程方法[Calls + Recur Calls]能发现,相比补间动画,属性动画的刷新方法调用次数明显减少。 163 | 164 | ``` 165 | ObjectAnimator scaleX = ObjectAnimator.ofFloat(imageView, "scaleX", 0, 1); 166 | ObjectAnimator rotation = ObjectAnimator.ofFloat(imageView, "rotation", 0f, 360f); 167 | 168 | AnimatorSet animatorSet = new AnimatorSet(); 169 | animatorSet.setDuration(2000); 170 | animatorSet.play(scaleX).with(rotation); 171 | animatorSet.start(); 172 | ``` 173 | 174 | 175 | ### 滑动卡顿 176 | 177 | 从两个方面定位: 178 | 179 | 1. 滑动过程中,bind相关方法是不是做了大量数据操作,影响了UI线程;可以通过TraceView查看方法耗时情况 180 | 181 | 2. 滑动过程中,是否有大量GC情况;可以通过Memory Monitor、Heap Viewer等工具查看是否存在内存抖动; 182 | 183 | 184 | ### 工具篇--TraceView 185 | 186 | 可查看方法调用次数、方法执行耗时。 187 | 188 | 调用次数可看[Calls + Recur Calls]列。补间动画一节中已经提到过。 189 | -------------------------------------------------------------------------------- /docs/Android/图片相关/从assets目录和drawable加载的Bitmap的区别.md: -------------------------------------------------------------------------------- 1 | # 从assets目录和drawable加载的Bitmap的区别 2 | 3 | 一张图片从assert目录下加载到内存和从drawable目录下加载到内存有什么区别? 4 | 5 | 在assert目录下放一张图片,```image_asserts.png``` 6 | 7 | 然后尝试使用BitmapFactory解码这个图片得到一个Bitmap,通过Bitmap中byteCount方法来获取它所占的字节数,这样我们就可以比较这些Bitmap所占用的内存大小了。 8 | 9 | ``` 10 | val bitmapAssert = BitmapFactory.decodeStream(assets.open("image_assets.png")) 11 | Log.d("gaohui","bitmapAssert bitmap size: " + bitmapAssert.byteCount) 12 | ``` 13 | 14 | 输出结果是: 15 | ``` 16 | gaohui: bitmapAssert bitmap size: 186624 17 | ``` 18 | 19 | 接着我们来看下把同一张图片放在drawable-xxh目录下会是什么效果? 20 | ``` 21 | val bitmapXxh = BitmapFactory.decodeResource(resources, R.drawable.image_xxh, options) 22 | 23 | Log.d("gaohui","bitmapXxh size: " + bitmapXxh.byteCount) 24 | ``` 25 | 26 | 运行后,打印结果是: 27 | ``` 28 | gaohui: bitmapXxh size: 156816 29 | ``` 30 | 从上面的打印结果看发现相比于Assert目录下加载的Bitmap,在drawable-xxxh目录下解码出来的Bitmap字节数要小。 31 | 32 | 我们再把这张图片放到drawable-xxxh目录下,看看是不是还会有改变? 33 | 34 | ``` 35 | val bitmapXxxh = BitmapFactory.decodeResource(resources, R.drawable.image_xxxh, options) 36 | 37 | Log.d("gaohui","bitmapXxxh size: " + bitmapXxxh.byteCount) 38 | ``` 39 | 40 | 打印结果: 41 | ``` 42 | gaohui: bitmapXxxh size: 156816 43 | ``` 44 | 45 | 打印后发现竟然和drawable-xxh目录下的一样。 46 | 47 | 这里就感觉比较奇怪,按照Android官网的说法,不同drawable目录下的加载出来的Bitmap应该是不一样的。 48 | 49 | 这里我们尝试修改成下面这样: 50 | ``` 51 | //val bitmapXxxh = BitmapFactory.decodeResource(resources, R.drawable.image_xxxh, options) 52 | val bitmapXxxh = BitmapFactory.decodeResource(resources, R.drawable.image_xxxh, BitmapFactory.Options()) 53 | Log.d("gaohui","bitmapXxxh size: " + bitmapXxxh.byteCount) 54 | ``` 55 | 这样处理的打印结果就是正确的了。 56 | ``` 57 | gaohui: bitmapXxxh size: 88804 58 | ``` 59 | 上面drawable-xxxh目录下和drawable-xxh目录下同一张图片decode出来的Bitmap占用内存一样的原因主要因为BitmapFactory.Options()复用导致的。 60 | 后面的修改重新new了一个BitmapFactory.Options(),发现Bitmap的字节数变小了,实际上这才是正确的占用内存大小。 61 | 62 | 那么接下来我们要解决的问题是为什么不同目录下加载的同一张图片但是Bitmap占用的大小却不一样呢? 63 | 64 | 关于Bitmap的内存占用这里有个计算公式: 65 | 66 | > 图片内存大小 = (图片width * scale + 0.5) * (图片height * scale + 0.5) * 每个像素字节 67 | 68 | 而这个scale是这样计算的: 69 | > scale = 设备的屏幕密度 / drawable目录设定的屏幕密度 70 | 71 | 设备的屏幕密度跟手机有关,不同的手机可能表现不一样。 72 | 73 | 使用这个就可以获得设备的屏幕密度。 74 | ``` 75 | getResources().getDisplayMetrics().densityDpi 76 | ``` 77 | 78 | 不同的drawable目录下屏幕密度: 79 | 80 | 目录 | 屏幕密度 81 | ----|--- 82 | drawable-ldpi | 120dpi 83 | drawable-mdpi | 160dpi 84 | drawable-hdpi | 240dpi 85 | drawable-xhdpi | 320dpi 86 | drawable-xxhdpi | 480dpi 87 | drawable-xxxhdpi | 640dpi 88 | 89 | 当我们使用decodeResource方法读取drawable目录下面的图片时,会根据手机的屏幕密度,到对应的文件夹中查找该图片。若该图片存在于其他目录下,系统先对该图片进行缩放处理,再显示。 90 | 91 | 最后我们来看下每个像素占用的字节。 92 | 93 | BitmapFactory.Options中的inPreferredConfig表示的是图片解码格式,即图片的每个像素占用的字节数。 94 | 95 | 常见的解码格式有以下几种: 96 | * Bitmap.Config.ARGB_8888: ARGB 4个通道,每个像素4个字节。部分图片显示时不需要Alpha,所以可使用565格式。 97 | * Bitmap.Config.ARGB_4444: 官方标注Deprecated。ARGB 4个通道,每个像素2个字节。 98 | * Bitmap.Config.RGB_565:RGB 3个通道,每个像素2个字节。 99 | * Bitmap.Config.ALPHA_8:每个像素1个字节。主要用于Alpha通道模板,相当做一个染色。图像渲染两次,虽然节省内存,但增加了绘制开销 100 | 101 | 102 | 介绍了这么多我们来自己尝试计算下图片的内存大小是否和打印出来的结果一样。 103 | 104 | ``` 105 | val scaleXxh = densityDpi/480f 106 | Log.d("gaohui","scaleXxh: $scaleXxh") 107 | //图片高度和宽度都是216 108 | val byteCountXxh = (scaleXxh * 216 + 0.5)* (scaleXxh * 216 + 0.5) * 4 109 | Log.d("gaohui", "Calculator bitmapXxh size: $byteCountXxh") 110 | 111 | val scaleXxxh = densityDpi/640f 112 | //图片高度和宽度都是216 113 | val byteCountXxxh = (scaleXxxh * 216 + 0.5f)* (scaleXxxh * 216 + 0.5) * 4 114 | Log.d("gaohui", "Calculator bitmapXxXh size: $byteCountXxxh") 115 | 116 | ``` 117 | 118 | 打印出来结果如下所示: 119 | ``` 120 | gaohui: Calculator bitmapXxh size: 157609.0 121 | gaohui: Calculator bitmapXxXh size: 88804.0 122 | ``` 123 | 这里有人又会问问题了,你这个不对,drawable-xxh下通过getByteCount拿到的大小是156816,而你这里是157619,明显不对啊。 124 | 125 | 其实这里主要是精度问题导致的差距。 126 | 127 | 具体Bitmap的decode流程可以看下这篇文章:[Android中Bitmap占用内存计算](https://www.jianshu.com/p/578357ab6838) 128 | 129 | 这里我说下引起上述精度问题的差距主要在BitmapFactory.cpp中doDecode方法中,BitmapFactory.cpp中的doDecode方法是BitmapFactory.java上层调用过来的。 130 | 131 | 在BitmapFactory.cpp中的doDecode方法中: 132 | ``` 133 | static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, 134 | jobject options, bool allowPurgeable, bool forcePurgeable = false, 135 | bool applyScale = false, float scale = 1.0f) { 136 | 137 | ... 138 | int scaledWidth = decoded->width(); 139 | int scaledHeight = decoded->height(); 140 | if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) { 141 | scaledWidth = int(scaledWidth * scale + 0.5f); 142 | scaledHeight = int(scaledHeight * scale + 0.5f); 143 | 144 | //... 145 | } 146 | } 147 | 148 | ``` 149 | 这里计算Bitmap宽度的地方会先加个0.5f然后再转成int,而这里的scaledWidth和scaledHeight会被用到内存计算那里使用。 150 | 151 | 我们来改下这个部分的代码: 152 | 153 | ``` 154 | val scaleXxh = densityDpi/480f 155 | //图片高度和宽度都是216 156 | val w = (scaleXxh * 216 + 0.5).toInt() 157 | val h = (scaleXxh * 216 + 0.5).toInt() 158 | val byteCountXxh = w * h * 4 159 | Log.d("gaohui", "Calculator bitmapXxh size: $byteCountXxh") 160 | ``` 161 | 162 | 输出结果,这样的话就跟最开始通过Bitmap的getByteCount方法获取是一致的了。 163 | ``` 164 | gaohui: Calculator bitmapXxh size: 156816 165 | ``` 166 | 167 | Options.inSampleSize 图片解码尺寸 168 | 169 | 使用inSampleSize可以使解码后的图片尺寸(宽高)缩小,从而节省内存。 170 | 比如,inSampleSize=4,解码后图片宽高分别是原图的1/4。 171 | 原理是,解码器会根据这个值,每几个像素读入一次。 172 | 173 | 3.Options.inBitmap 重用内存 174 | 175 | Android3.0引入了此字段。使用了此字段,decode方法会尝试重用一个已经存在的位图。这就意味着位图内存被重用了,从而改善性能,并且没有内存分配和释放的过程。 176 | -------------------------------------------------------------------------------- /docs/JVM/3_JVM类加载机制.md: -------------------------------------------------------------------------------- 1 | ### 类的加载过程 2 | 3 | ``JVM类加载机制``:虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接运营的Java类型,这就是虚拟机的类加载机制。 4 | 5 | ``动态扩展的特性``:在Java语言里面,类型的加载、链接和初始化过程都是在程序运行期间完成的,这种策略虽然会零类加载时稍微增加一些性能开销,但是回味Java应用程序提供高度的灵活性,Java里天生可以动态扩展的语言特性就是依赖运行期动态加载和动态链接这个特点完成的。 6 | 7 | #### 类加载的时机 8 | 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期如下: 9 | * 加载(Loading) 10 | * 验证(Verification) 11 | * 准备(Preparation) 12 | * 解析(Resolution) 13 | * 初始化(Initialization) 14 | * 使用(Using) 15 | * 卸载(Unloading) 16 | 17 | 其中验证、准备、解析这3个部分称为连接(Linking)。 18 | 19 | ![image](../img/jvm_class_load.webp) 20 | 21 | > 注意: 22 | > 加载、验证、准备、初始化和卸载这5个阶段的顺序是固定的,累得加载过程必须按照找各种顺序来执行,而解析阶段则不一定; 23 | > 为了支持Java语言的运行时绑定,解析在某些情况下可以在初始化阶段之后再开始。 24 | 25 | #### 加载 26 | “加载”是”类加载“(Class Loading)过程的一个阶段。 27 | 1. 通过一个累得全限定名来获取定义此类的``二进制字节流``。 28 | 2. 将这个字节流所代表的``静态存储结构``转化为方法区的``运行时数据结构``。 29 | 3. 在内存中生成一个代表这个类的``java.lang.Class``对象,作为方法区这个类的各种的数据访问接口。 30 | 31 | #### 验证 32 | * 验证是连接阶段的第一步,且工作量在虚拟机的类加载子系统中战略相当大的一部分。 33 | * 目的:为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。 34 | 35 | > 由此可见,它能直接决定JVM能否承受恶意代码的攻击,因此验证阶段很有必要,但由于它对程序运行期没有影响,并不一定必要,可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。 36 | 37 | 验证过程大致上可以分为4个检验动作: 38 | * 文件格式验证 39 | * 内容:验证字节流是否符合Class文件格式的规范、以及是否能被当前版本的虚拟机处理。 40 | * 目的:保证输入的字节流能正确地解析并存储于方法区之内,且格式上符合描述一个Java类型信息的要求。只有保证二进制字节流通过了该验证后,它才会进入内存的方法区中进行存储,所以后续3个验证阶段全部是基于方法区而不是字节流了。 41 | 42 | * 元数据验证 43 | * 内容:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。 44 | * 目的:对类的元数据信息进行语义校验,保证不存在不符合Java语言规范的元数据信息。 45 | 46 | * 字节码验证:是验证过程中最复杂的一个阶段。 47 | * 内容:对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的事件。 48 | * 目的:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。 49 | 50 | * 符号引用验证: 51 | * 内容:对类自身以外(如常量池中的各种符号引用)的信息进行匹配性校验。 52 | * 目的是确保解析动作能正常执行,如果无法通过符号引用验证,那么将会抛出一个java.lang.IncompatibleClassChangeError异常的子类。 53 | * 注意:该验证发生在虚拟机将符号引用转化为直接引用的时候,即『解析』阶段。 54 | 55 | #### 准备 56 | * 为类变量``分配内存``:因为这里的变量是由方法区分配内存的,所以仅包括类变量而不包括实例变量,后者将在对象实例化时随着对象一起在堆上分配。 57 | * 设置类变量``初始值``:通常情况下零值,但是如果这个类变量被final修饰,编译时Javac将会为该变量生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将该变量赋值为等于号后面的值。 58 | 59 | #### 解析 60 | 解析阶段是虚拟机将``常量池``内的符号引用替换为直接引用的过程。 61 | * 符号引用:以一组符号来描述所引用的目标。 62 | * 可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。 63 | * 与虚拟机的内存布局无关,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中,所有即使各种虚拟机的实现的内存布局不同,但是能接受符号引用都是一致的。 64 | * 直接引用: 65 | * 可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。 66 | * 与虚拟机实现的内存布局有关,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不同。 67 | 68 | * ``发生时间``:JVM会根据需要来判断,是在类被加载器``加载时``就对常量池中的符号引用进行解析,还是等到一个符号引用将要被``使用前``才去解析。 69 | 70 | * 解析动作:有7类符号及其对应在常量池的7种常量类型 71 | * 类或接口(CONSTANT_Class_info) 72 | * 字段(CONSTANT_Fieldref_info) 73 | * 类方法(CONSTANT_Methodref_info) 74 | * 接口方法(CONSTANT_InterfaceMethodref_info) 75 | * 方法类型(CONSTANT_MethodType_info) 76 | * 方法句柄(CONSTANT_MethodHandle_info) 77 | * 调用点限定符(CONSTANT_InvokeDynamic_info) 78 | 79 | > 举个例子,设当前代码所处的为类D,把一个从未解析过的符号引用N解析为一个类或接口C的直接引用,解析过程分三步: 80 | 81 | * 若C不是数组类型:JVM将会把代表N的全限定名传递给D类加载器去加载这个类C。在加载过程中,由于元数据验证、字节码验证的需要,又可能触发其他相关类的加载动作。一旦这个加载过程出现了任何异常,解析过程就宣告失败。 82 | * 若C是数组类型且数组元素类型为对象:JVM也会按照上述规则加载数组元素类型。 83 | * 若上述步骤无任何异常:此时C在JVM中已成为一个有效的类或接口,但在解析完成前还需进行符号引用验证,来确认D是否具备对C的访问权限。如果发现不具备访问权限,将抛出java.lang.IllegalAccessError异常。 84 | 85 | #### 初始化 86 | * 类初始化是类加载过程的最后一步,会真正开始执行类中定义的Java程序代码(或者说是字节码)。而之前的类加载过程中,除了在『加载』阶段用户应用程序可通过自定义类加载器参与之外,其余阶段均由虚拟机主导和控制。 87 | * 与准备阶段的区分: 88 | * 准备阶段:变量赋初始零值。 89 | * 初始化阶段:根据Java程序的设定去初始化类变量和其他资源,或者说是执行``()``过程。 90 | 91 | ``()``:由编译器自动收集类中的所有类变量的赋值动作和静态语句块static{}中的语句合并产生。 92 | * 是线程安全的,在多线程环境中会被正确地加锁、同步。 93 | * 对于类和接口是非必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成 ``()``。 94 | * 接口与类不同的是,执行接口的`` ()``不需要先执行父接口的 ``()``,只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的``()``。 95 | 96 | 在虚拟机规范中,规定了有且只有5中情况必须立即对类进行初始化: 97 | * 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时; 98 | * 使用java.lang.reflect包的方法对类进行反射调用的时候; 99 | * 当初始化一个类的时候,若发现其父类还未进行初始化,需先触发其父类的初始化; 100 | * 在虚拟机启动时,需指定一个要执行的主类,虚拟机会先初始化它; 101 | * 当使用JDK1.7的动态语言支持时,若一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,且这个方法句柄所对应的类未进行初始化,需先触发其初始化。 102 | 103 | ### 类加载器和双亲委托模型 104 | > 每个类加载器都拥有一个独立的类名称空间,它不仅用于加载类,还和这个类本身一起作为在JVM中的唯一标识。所以比较两个类是否相等,只要看它们是否由同一个类加载器加载,即使它们来源于同一个Class文件且被同一个JVM加载,只要加载它们的类加载器不同,这两个类就必定不相等。 105 | 106 | 从JVM的角度来讲,只有两种不同的类加载器: 107 | * 一种是启动类加载器(Bootstrap ClassLoader) 108 | * 由C++语言编写,是虚拟机自身的一部分。 109 | * 负责加载\lib目录下,或者被-Xbootclasspath参数指定的路径中的且可被虚拟机识别的类库。 110 | * 无法被Java程序直接引用,如果自定义类加载器想要把加载请求委派给引导类加载器的话,可直接用null代替。 111 | * 另一种是其他类加载器:由Java语言实现,独立于虚拟机外部,并且全部都继承自抽象类java.lang.ClassLoader,可被Java程序直接引用。 112 | * 扩展类加载器(Extension ClassLoader) 113 | * 由sun.misc.Launcher$ExtClassLoader实现。 114 | * 负责加载<JAVA_HOME>\lib\ext目录中的、或者被java.ext.dirs系统变量所指定的路径中的所有类库。 115 | * 应用程序类加载器(Application ClassLoader) 116 | * 是默认的类加载器,是ClassLoader#getSystemClassLoader()的返回值,故又称为系统类加载器。 117 | * 由sun.misc.Launcher$App-ClassLoader实现。 118 | * 负责加载用户类路径上所指定的类库。 119 | * 自定义类加载器(User ClassLoader):如果以上类加载起不能满足需求,可自定义。 120 | 121 | 关系图如下: 122 | 123 | ![image](../img/class_loader.webp) 124 | 125 | > 虽然数组类不通过类加载器创建而是由JVM直接创建的,但仍与类加载器有密切关系,因为数组类的元素类型最终还要靠类加载器去创建。 126 | 127 | #### 双亲委派模型(Parents Delegation Model) 128 | * 表示类加载器之间的层次关系。 129 | * 前提:除了顶层启动类加载器外,其余类加载器都应当有自己的父类加载器,且它们之间关系一般不会以继承(Inheritance)关系来实现,而是通过组合(Composition)关系来复用父加载器的代码。 130 | * 工作过程:若一个类加载器收到了类加载的请求,它先会把这个请求委派给父类加载器,并向上传递,最终请求都传送到顶层的启动类加载器中。只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。 131 | * 注意:不是一个强制性的约束模型,而是Java设计者推荐给开发者的一种类加载器实现方式。 132 | * 优点:类会随着它的类加载器一起具备带有优先级的层次关系,可保证Java程序的稳定运作;实现简单,所有实现代码都集中在java.lang.ClassLoader的loadClass()中。 133 | 134 | > 比如,某些类加载器要加载java.lang.Object类,最终都会委派给最顶端的启动类加载器去加载,这样Object类在程序的各种类加载器环境中都是同一个类。相反,系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。 135 | -------------------------------------------------------------------------------- /docs/Android/面试题收集/Interview_2.md: -------------------------------------------------------------------------------- 1 | RxJava+Retrofit进行网络请求; 2 | Header里如何添加通用参数, 3 | Response里如何解析,只返回业务层result,不需要每个Bean都有code 4 | 5 | 设计模式:工厂 建造者 装饰者 6 | 7 | mvp模式的了解和介绍 8 | 9 | 对RecyclerView的了解,踩过哪些坑? 10 | 11 | margin 和padding的区别 12 | MeasureSpeac.EXACTLY/UN.../ATMOLST -> wrap_content/match_parent 13 | 绘制流程?控件的margin,是在measure还是layout,还是draw过程中处理? 14 | 15 | 方法数超过65535怎么解决? 16 | 17 | 子线程中进行UI操作方法有哪些? Handler的post()方法;View的post()方法;Activity的runOnUiThread()方法 18 | 19 | 事件分发 20 | String, StringBuffer, StringBuilder的区别; 21 | String: 定长,字符串操作会产生新的String对象; 22 | StringBuffer:线程安全,有synchronized修饰,不定长,默认容量是16个字节+传入字符串长度,可扩展,操作不会产生新的对象; 23 | StringBuilder:线程不安全,其他同StringBuffer; 24 | 25 | final 修饰符的作用 26 | >final 可以修饰类、变量和方法。修饰类代表这个类不可被继承。修饰变量代表此变量不可被改变。修饰方法表示此方法不可被重写 (override)。 27 | 28 | 29 | Java: 30 | * Object有哪些方法,每个方法的用处?wait notify hashCode finalize getClass toString clone equals()延伸:wait和sleep区别 31 | * Java内存模型,每块内存存放哪些东西? String的常量池存放在哪里? 32 | * Java垃圾回收的几个算法?有哪些垃圾收集器,优缺点是什么?比如G1收集器相对于CMS有哪些优缺点? 33 | * 检测内存泄漏用什么工具?内存泄露是怎么监测的?LeakCanery的原理? 34 | * 内存泄漏的场景? 35 | * 内部类持有外部类的引用原理? 36 | * 四种引用方式; 37 | * 关键字volatile使用场景?(不会volatile,可问对Java并发的3个重要特性:原子性、可见性、有序性的理解?) 38 | 39 | http://www.cnblogs.com/dolphin0520/p/3920373.html 40 | 41 | 7.怎么解决死锁问题? 42 | >某个任务在等待另一个任务,而后者又等待别的任务,这样一直下去,直到这个链条上的任务又在等待第一个任务释放锁。这得到了一个任务之间互相等待的连续循环,没有哪个线程能继续。这被称之为死锁。当以下四个条件同时满足时,就会产生死锁: 43 | (1) 互斥条件。任务所使用的资源中至少有一个是不能共享的。 44 | (2) 任务必须持有一个资源,同时等待获取另一个被别的任务占有的资源。 45 | (3) 资源不能被强占。 46 | (4) 必须有循环等待。一个任务正在等待另一个任务所持有的资源,后者又在等待别的任务所持有的资源,这样一直下去,直到有一个任务在等待第一个任务所持有的资源,使得大家都被锁住。 47 | 要解决死锁问题,必须打破上面四个条件的其中之一。在程序中,最容易打破的往往是第四个条件。 48 | 49 | 6.类加载机制?双亲委派模型的优点? 50 | 6.说说泛型,泛型原理,为什么可以用T; 51 | 7.反射机制,及其优劣处; 52 | 8.Override和Overload的含义去区别?(进阶:Java静态分派和动态分派?这两个谁是单分派,谁是多分派?) 53 | 9.说说对注解的了解,如何设计Butterknife? 54 | 9.用到了哪些设计模式?知道装饰者、观察者、组合模式? 55 | 56 | Android 57 | 1.Activity的四种启动方式 58 | 2.绘制流程?控件的margin,是在measure还是layout,还是draw过程中处理? 59 | 3.事件分发机制?onTouch和onTouchEvent是什么区别?如果我重写了onTouch和onClick,它们的调用顺序是怎样的?什么时候会不调用onClick? 60 | 61 | 4.LocalBroadCast实现原理 62 | >本地广播是通过LocalBroadcastManager内置的Handler来实现的,只是利用了IntentFilter的match功能,至于BroadcastReceiver 换成其他接口也无所谓,顺便利用了现成的类和概念而已。在register()的时候保存BroadcastReceiver以及对应的IntentFilter,在sendBroadcast()的时候找到和Intent对应的BroadcastReceiver,然后通过Handler发送消息,触发executePendingBroadcasts()函数,再在后者中调用对应BroadcastReceiver的onReceive()方法。 63 | 64 | 6.SurfaceView用过么?双缓冲的原理是什么? 65 | 6.apk如何瘦身? 66 | 7.方法数超过65535怎么解决?multidex何时将包合并? 67 | 68 | 69 | 70 | 71 | ------------------------------------------------------------------- 72 | 73 | 1.不可变对象,如string 74 | 75 | 2.apk如何瘦身 76 | 77 | 3.一张图片内存大小 78 | 79 | 4.如何检查没有使用的资源 80 | 81 | 5.multidex何时将包合并 82 | 83 | 6.MVP相比MVC的优势 84 | 85 | 7.混合开发 86 | 87 | 8.UI层优化:如何解决过度绘制、滑动卡顿等 88 | 89 | 9.内存优化: 90 | 91 | 10.插件化相关 92 | 93 | 11.异步加载 94 | 95 | 96 | 12.动态加载 97 | 98 | 13.个性化UI时有哪些需要注意的,比较性能方面 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 面试题: 107 | 1、JNI: 108 | 1)NewStringUtf 、NewString、GetStringUtfChars、GetStringChars 区别? 109 | 从 返回值,释放方式方面解答。 110 | NewString 111 | jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize len); 112 | Constructs a new java.lang.String object from an array of Unicode characters. 113 | 114 | NewStringUTF 115 | jstring NewStringUTF(JNIEnv *env, const char *bytes); 116 | Constructs a new java.lang.String object from an array of characters in modified UTF-8 encoding. 117 | 118 | GetStringChars 119 | const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy); 120 | Returns a pointer to the array of Unicode characters of the string. This pointer is valid until ReleaseStringchars() is called. 121 | 122 | GetStringUTFChars 123 | const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy); 124 | Returns a pointer to an array of bytes representing the string in modified UTF-8 encoding. This array is valid until it is released by ReleaseStringUTFChars(). 125 | If isCopy is not NULL, then *isCopy is set to JNI_TRUE if a copy is made; or it is set to JNI_FALSE if no copy is made. 126 | 127 | 128 | 2)modified utf-8 encoding 是什么意思? 129 | 130 | 3)如果Java层传入的字符串是Unicode的扩展字符,比如emoji表情,一个字符占4个字节的,JNI层将这个jstring转换成char会遇到什么问题?怎么解决呢? 131 | 坑:不能用JNI的GetStringUTFChars,要用Java的反射,将jstring转换成char. 132 | 133 | 3)native的代码中,遇到过哪些异常代码会使程序崩溃? 134 | (比如CallObjectMethod() null的参数会崩) 135 | 136 | 4)ndk-build的参数了解哪些? 137 | 138 | 2、Android 139 | ##Activity的四种启动方式; 140 | standard, singleTop, singleTask, 141 | 142 | ##android使用xml文件的优缺点; 143 | 优点:在xml中写布局,实现UI和功能分开,方便机型适配、APP国际化; 144 | 缺点:解析xml文件耗时; 145 | 146 | ##Handler机制 147 | Handler依附于创建的线程;Looper、Message、Handler的关系; 148 | 149 | 3)子线程中进行UI操作方法有哪些? Handler的post()方法;View的post()方法;Activity的runOnUiThread()方法 150 | 151 | 4)图片压缩有哪些方法? 152 | 153 | 5)ANR:Service、广播造成的ANR如何解决; 154 | 5s UI线程没有响应;10s 没有接收到广播; 155 | 156 | 解决ANR方法: 157 | 1)不要在UI线程或影响UI渲染的地方做耗时操作; 158 | 2)可以在/data/anr/traces.txt中,查看ANR日志; 159 | 160 | 6)IntentService和Service区别 161 | 162 | 6)OOM:如何解决; 163 | http://www.cnblogs.com/xsmhero/p/3566890.html 164 | 165 | 7)四种引用方式; 166 | 167 | # Android内存结构 168 | #JVM垃圾回收机制 169 | #内存泄露是怎么监测的? 170 | 171 | #Android中的同步方式有哪些? 172 | 同步方法、同步代码块、原子操作 173 | 174 | 3、网络 175 | 176 | 4、Java 177 | HashMap和HashTable的区别; 178 | 1)线程安全:HashMap不安全,HashTable安全,因为HashTable有Synchronized修饰; 179 | 2)速度:HashMap快一点; 180 | 3)允许空键:HashMap允许空键; 181 | 182 | String, StringBuffer, StringBuilder的区别; 183 | String: 定长,字符串操作会产生新的String对象; 184 | StringBuffer:线程安全,有synchronized修饰,不定长,默认容量是16个字节+传入字符串长度,可扩展,操作不会产生新的对象; 185 | StringBuilder:线程不安全,其他同StringBuffer; 186 | 187 | 说说泛型,为什么可以用T; 188 | 反射机制,及其优劣处; 189 | 设计模式:代理模式; 190 | 了解到的加密算法有哪些?比如SHA1和MD5能解释下原理么? 191 | 对称加密和非对称加密? -------------------------------------------------------------------------------- /docs/PerformanceOptimization/性能分析相关命令和工具介绍.md: -------------------------------------------------------------------------------- 1 | # 性能有关的关键词:内存,ANR,卡顿,耗电,重新加载 2 | 3 | ## 相关命令 4 | 1. top -m 20,列出系统中 top 20 耗 cpu 的进程信息,会持续更新 5 | 2. adb shell dumpstate,dump 出系统每个进程的当前状态,主要分析 anr 相关的问题 6 | 3. adb shell dumpsys,dump 出系统中所包含的所有 android 相关的实例,比如有哪些 Provider, 7 | Activity 栈,注册的 receiver,进程,内存使用情况,App 信息等 8 | 4. adb shell dumpsys meminfo XXXX(包名),打印出某个 apk 当前的内存使用情况 9 | 5. MAT,用来分析内存方面的消耗 10 | 6. traceview,可以分析程序运行的一段过程中,每个函数所使用的时间情况。需要的打点函数: Debug.startMethodTracing(); Debug.stopMethodTracing() 11 | 7. ADT中的dump view hierarchy from UIAutomtor,该工具可以dump出当前界面的view层次结构,不过它不会显示自定义的 view 类名,而显示基础的几个 view 相关的类名,可以通过在 view 上设置contentDescription 属性来指定是哪个 view 12 | 8. adb logcat + grep -i "XXXX", 按需输出 log 信息 13 | 9. adb shell ps | grep XXX,察看进程信息,可以找到进程号 14 | 10. 可以自定义一些可以提高效率的命令,可以把这些命令放到~/.bashrc 文件中,这样可以在任意 15 | shell 下直接使用了。比如: 16 | 10.1 快速从当前目录下递归查找文件:例如:找到文件名中有 Manager 的文件: ff manager 17 | 18 | ```java 19 | function ff() { 20 | find . | grep -i --color "$@" 21 | } 22 | ``` 23 | 10.2 git 快速更新代码,例如:gpull 24 | ```java 25 | function gpull(){ 26 | git pull --rebase 27 | } 28 | ``` 29 | 10.3 察看数据库中存储 date 的 long 型对应的时间,比如数据库中存的是 1391334196940,可以用 dd @1391334196 (去除原值的后三位) 30 | > alias dd='date +"%F,%T" -d' 31 | 32 | 10.4 查看内存,比如:meminfo com.miui.miuilite 33 | 34 | ```java 35 | function meminfo(){ 36 | adb shell dumpsys meminfo $1 37 | } 38 | 39 | ``` 40 | 10.5 pull 出某个程序的数据库,比如:pulldb com.miui.miuilite ~/tmp/tmp/ 41 | 42 | ```java 43 | 44 | function pulldb(){ 45 | adb pull /data/data/"$1"/databases $2 46 | } 47 | ``` 48 | 49 | 11. 递归查找从当前目录开始,文件中包含某些字符串的所有文件及其所在行:大小写敏感:grep -r 50 | "XXXX" . 或者 大小写不敏感:grep -i -r "XXXX" . 51 | Logcat 中比较有用的几个参数: 52 | * adb logcat 打印进程和线程号:adb logcat -v thread 53 | * adb logcat 打印进程,线程和 Tag: adb logcat -v threadtime 54 | 55 | 这两个命令可以把进程号和线程号打印出来,再用上 grep,可以打印出某个进程输出的所有 log。这个对 于那些 log 信息很多的手机会很好用。 56 | 57 | ## ANR分析 58 | ANR 的分析一般需要两个文件:发生 anr 时的 log 文件,发生 anr 时生成的 trace 文件。trace 文件可以通 过 adb shell dumpstate 来获取,也可以直接到系统的/data/anr/目录获取 59 | 60 | 1. Log 文件:log 文件中搜索 anr,找到出现 anr 时 cpu 在每个进程中的使用情况,看看每个进程的百 分比 61 | 2. CPU usage from .. ago 和 CPU usage from ... later 之间的表示出现 anr 之前的 cpu 使用情况。 CPU usage from ... later 之后的表示出现 anr 之后的 cpu 使用情况 62 | 3. 看 anr 出现之前的 cpu 使用情况的"TOTAL:"部分,如果接近 100%,则表示是由于 cpu 使用过多, 饥饿所致。如果离 100%相差很大,则可能是 UI 线程出现死锁所致 63 | 4. Trace 文件:trace 文件生成方法:adb shell dumpsate > ~/temp/trace.txt 64 | 5. trace 文件中找到“Cmd line: 进程名”的部分,看看自己进程中每个线程的当前状态。 65 | 6. tid=1 的为 UI 线程,看看它的状态。如果是 blocked 或者 wait on,则表示被锁住了,看看锁在哪 66 | 里。如果是 Suspend,则表示被挂起了 67 | 7. 如果 UI 线程没被锁住,而是停在某个函数调用上,则说明该函数较耗时。可以设法将该函数放到 68 | 非UI线程。 69 | 8. 如果 UI 线程中的函数没被锁住,且不是耗时函数,而且每次 dump 时都在这段代码附近,且进程 70 | 总是占大量的 cpu,则可能是该函数附近出现了死循环。 71 | 72 | ## 内存分析 73 | 74 | 使用 MAT 的好处 75 | 1. MAT 可以列出当前某个进程中所有的对象,以及每个对象的详细信息。而且针对每个对象,MAT 可以找出对该对象的所有引用,回收路径等。 76 | 2. 可以找出每个类创建了多少个对象,每个对象占了多少内存。MAT 中可以通过类名或包名来过 滤,也可以按占用内存的大小来排序等。 77 | 3. 可以进行内存泄露的分析,比如如果确定在一个进程中某个类只能有一个对象,如果有多个了,则 可以判断出之前的那个没有被释放。比如切换语言或横竖屏切换时 activity 泄露 78 | 79 | 使用 MAT 来分析某个操作导致的内存变化。步骤: 80 | 1. 操作前,先 dump 出 hprof file,比如名称为 hprof1 81 | 2. 操作后,dump 出 hprof file,比如名称为 hprof2 82 | 3. 找到 hprof1 中所有的 bitmap(先搜索 Bitmap 对象,然后选择 list object -> with incoming references),产生文件 hprof1_bitmap_object 83 | 4. 找到 hprof2 中所有的 bitmap(先搜索 Bitmap 对象,然后选择 list object -> with incoming references),产生文件 hprof2_bitmap_object 84 | 5. 对 hprof1_bitmap_object 进行 Export to TXT,保存文件 before.txt 85 | 6. 对 hprof2_bitmap_object 进行 Export to TXT,保存文件 after.txt 86 | 7. 使用 linux 的 diff 命令:diff before.txt after.txt 87 | 8. diff 命令列出新增的 Bitmap 对象信息:比如: 88 | < android.graphics.Bitmap @ 0x42a29e90| 48 | 147,520 89 | 9. ... 90 | 10. > android.graphics.Bitmap @ 0x4177cf40| 48 | 100,304 91 | 11. 92 | 12. 根据列出的对象值(比如:0x42a29e90),对于新增的对象 0x42a29e90 到 hprof2 中通过Find Object by Address(放大镜按钮)搜索出该对象 A 93 | 13. 查看 A 的 gc 路径:Path to GC Roots -> exclude weak/soft references 就可以找到 Bitmap 对象 A 的引用情况 94 | 95 | ## 耗电分析 96 | 97 | * CPU方面 98 | 1. 可以通过 top 命令观察一段时间内哪些进程占用 cpu 比较多,在什么操作或环境下会增大,什么时 99 | 候下降。最好结合 log,效果会更好。 100 | 2. 确定进程后,再结合操作/环境变化/log 信息等,大体猜出 cpu 消耗主要产生在哪个模块或哪个功 101 | 能。 102 | 3. 缩小范围后,然后就手动加 log 了。log 主要用来打印出某个函数或代码段运行时消耗的时间。 103 | 4. 如果想知道这个函数从哪里调过来的,可以用 Log.(TAG, msg, new Exception())打印出调用栈。对 于放在一些线程中的执行过程,通过这个调用栈可能还无法找到调用的根结点,可以在该线程上一 层调用的地方再打印调用栈。 104 | 5. 在待机情况下,频繁的对 cpu 进行 wake up 会比较耗电:可以通过 adb shell dumpsys 命令找出当 前注册了哪些 alarm 以及 alarm 的类型,尽量不要使用系统睡眠状态下唤醒系统的类型 105 | 106 | * 屏幕方面 107 | 屏幕的耗电主要是界面刷新所致,可以在开发者模式下打开“显示面(surface)更新”开关后,再观 察所有界面下,在没有操作和动画时,是否有界面刷新,如果有,尽量关闭这些不必要的刷新。 108 | 109 | ## 重新加载分析方法 110 | 111 | 1. 尽量找出哪种情况下会出现重新加载,进行操作通过观察 log 的方式来找规律:如果是进程被 kill, 可以通过 adb logcat | grep ActivityManager 来观察进程的变化情况。如果是自己的 activity 被 destory,可以通过加 log 的方式来看。 112 | 2. 通过 adb shell dumpsys 把当前系统中的 activity 等信息 dump 出来后,分析自己的 activity 的一些 状态,尤其关注“Processes in Current Activity Manager State:”部分里的信息。这里面的信息对分 析由于 lowmemorykiller 导致的加载很有帮助。 113 | 114 | 进程被 kill 有如下原因: 115 | 1. restartPackage 导致该 apk 中的所有 process 都被 kill 116 | 2. lowmemorykiller机制 117 | 3. sd 卡 unmount 时,如果某个进程中有打开 sd 卡中某个文件而没有关闭时,该进程会被 kill 118 | 4. 当 A 进程被 kill 时,如果有 B 进程通过 ContentProvider 机制与 A 有连接,则 B 进程也会被 kill 119 | 5. 三星有些手机上,调用一键清理后,再打开清理的应用会出现异常,或者再回桌面时桌面重新加 载。解决方案:桌面进程中要有 service,不一定非得是 forground service 120 | 121 | ## 卡顿分析 122 | 1. 打开帧率,如果只观察自己程序的帧率变化,可以在自己的 view 上 draw 出来或者通过 log 的形式 打印出来 123 | 2. 如果要观察其他程序的帧率变化,必要的话自己可以编个系统,编译前把系统的帧率显示开关打 开。具体在:framework 下 ViewRootImpl.java 下的 DEBUG_FPS 开关打开即可。也可以通过这篇 文章介绍的方法(不过我还没有亲自试过): http://blog.csdn.net/jinzhuojun/article/details/10428435 124 | 3. 对于界面操作的卡顿,可以通过 adb logcat | grep "dalvikvm",观察在操作时有关 dalvikvm-heap 和 gc 相关的信息,如果存在说明有 bitmap 等相关的大块内存申请和释放,这种在界面操作时尽量 避免才是 125 | 4. 界面层数:层数越多,画这一屏时需要时间就越多,尽量减少层数。可以通过 hierarchyviewer 来 分析。同时需要注意 window 相关的 flag 和 windowWallpaper 等。 -------------------------------------------------------------------------------- /docs/PerformanceOptimization/在provider初始化的时候如何处理耗时操作.md: -------------------------------------------------------------------------------- 1 | 如果 Provider 在 onCreate()的过程中需要做一些耗时的操作,例如数据的初始化,清理数据库中的垃圾数 据,或者重建数据的索引等等,像这一类的操作可以放到一个后台线程顺序执行。 2 | 3 | 代码: 4 | 5 | ```java 6 | 7 | private HandlerThread mBackgroundThread; private Handler mBackgroundHandler; 8 | 9 | @Override 10 | public boolean onCreate() { 11 | super.onCreate(); 12 | mBackgroundThread = new HandlerThread("ProviderWorker", Process.THREAD_PRIORITY_BACKGROUND); 13 | mBackgroundThread.start(); 14 | 15 | mBackgroundHandler = new Handler(mBackgroundThread.getLooper()) { 16 | @Override 17 | public void handleMessage(Message msg) { 18 | performBackgroundTask(msg.what, msg.obj); } 19 | }; 20 | // 任务在后台线程顺序执行 21 | scheduleBackgroundTask(BACKGROUND_TASK_INITIALIZE); 22 | scheduleBackgroundTask(BACKGROUND_TASK_UPDATE_LOCALE); 23 | scheduleBackgroundTask(BACKGROUND_TASK_UPDATE_SEARCH_INDEX); 24 | return true; 25 | } 26 | 27 | protected void scheduleBackgroundTask(int task) { 28 | mBackgroundHandler.sendEmptyMessage(task); 29 | } 30 | 31 | // 在线程中处理耗时的操作 32 | protected void performBackgroundTask(int task, Object arg) { 33 | switch (task) { 34 | case BACKGROUND_TASK_INITIALIZE: { 35 | doInitialize(); 36 | break; 37 | } 38 | case BACKGROUND_TASK_UPDATE_LOCALE: { 39 | doIUpdateLocale(); 40 | break; 41 | } 42 | case BACKGROUND_TASK_UPDATE_SEARCH_INDEX: { 43 | doIUpdateSearchIndex(); 44 | break; 45 | } 46 | } 47 | } 48 | ``` 49 | 50 | 这样做的好处是把耗时操作放在后台线程处理,使得 provider 的 onCreate()不会执行很长时间,provider 初始化完成之后,开始处理 query/insert/update/delete 的操作。 51 | 52 | 假如 query/insert/update/delete 等数据库操作发生在后台的任务结束之后,这样做看起来是没有问题的, 但是很有可能 doInitialize()还没有结束,就会有进程来操作数据库。 53 | 54 | 接下来需要对我们的实现做一下改进, 来确保数据库初始化完成之前 query/insert/update/delete 等数据库 操作 不会被执行 55 | 56 | 假设在 doInitialize()之后,我们就准备好可以进行读操作,而在 doIUpdateSearchIndex() 之后我们才允许 写操作。 57 | 58 | 假如 query/insert/update/delete 等数据库操作发生在后台的任务结束之后,这样做看起来是没有问题的, 但是很有可能 doInitialize()还没有结束,就会有进程来操作数据库。 59 | 60 | 接下来需要对我们的实现做一下改进, 来确保数据库初始化完成之前 query/insert/update/delete 等数据库 操作 不会被执行。 61 | 62 | 假设在 doInitialize()之后,我们就准备好可以进行读操作,而在 doIUpdateSearchIndex() 之后我们才允许 写操作 63 | 64 | ```java 65 | 66 | private HandlerThread mBackgroundThread; 67 | private Handler mBackgroundHandler; 68 | 69 | private volatile CountDownLatch mReadAccessLatch; 70 | private volatile CountDownLatch mWriteAccessLatch; 71 | @Override 72 | public boolean onCreate() { 73 | super.onCreate(); 74 | // The provider is closed for business until fully initialized mReadAccessLatch = new CountDownLatch(1); mWriteAccessLatch = new CountDownLatch(1); 75 | mBackgroundThread = new HandlerThread("ProviderWorker", Process.THREAD_PRIORITY_BACKGROUND); 76 | mBackgroundThread.start(); 77 | mBackgroundHandler = new Handler(mBackgroundThread.getLooper()) { 78 | @Override 79 | public void handleMessage(Message msg) { 80 | performBackgroundTask(msg.what, msg.obj); 81 | } }; 82 | scheduleBackgroundTask(BACKGROUND_TASK_INITIALIZE); 83 | scheduleBackgroundTask(BACKGROUND_TASK_UPDATE_LOCALE); 84 | scheduleBackgroundTask(BACKGROUND_TASK_UPDATE_SEARCH_INDEX); 85 | scheduleBackgroundTask(BACKGROUND_TASK_OPEN_WRITE_ACCESS); 86 | } 87 | 88 | protected void scheduleBackgroundTask(int task) { 89 | mBackgroundHandler.sendEmptyMessage(task); 90 | } 91 | 92 | // 在线程中处理耗时的操作 93 | protected void performBackgroundTask(int task, Object arg) { 94 | switch (task) { 95 | case BACKGROUND_TASK_INITIALIZE: { 96 | doInitialize(); 97 | mReadAccessLatch.countDown(); 98 | mReadAccessLatch = null; 99 | break; 100 | } 101 | case BACKGROUND_TASK_UPDATE_LOCALE: { 102 | doIUpdateLocale(); 103 | break; 104 | } 105 | case BACKGROUND_TASK_UPDATE_SEARCH_INDEX: { 106 | doIUpdateSearchIndex(); 107 | break; 108 | } 109 | case BACKGROUND_TASK_OPEN_WRITE_ACCESS: { 110 | mWriteAccessLatch.countDown(); 111 | mWriteAccessLatch = null; 112 | break; 113 | } 114 | } 115 | ``` 116 | 117 | 我们新增加了两个计数器 mReadAccessLatch 和 mWriteAccessLatch,初始化值都为 1,doInitialize()结束 之后 mReadAccessLatch 赋值为 null,所有后台任务结束之后 mWriteAccessLatch 赋值为 null, 这样在 query/insert/update/delete 之前先判断一下计数器就可以知道是否可以执行该操作了。 118 | 119 | ```java 120 | private void waitForAccess(CountDownLatch latch) { 121 | if (latch == null) { 122 | return; 123 | } 124 | while (true) { 125 | try { 126 | latch.await(); 127 | return; 128 | } catch (InterruptedException e) { 129 | Thread.currentThread().interrupt(); } 130 | } 131 | } 132 | 133 | @Override 134 | public Uri insert(Uri uri, ContentValues values) { 135 | waitForAccess(mWriteAccessLatch); return doInsert(); 136 | } 137 | 138 | @Override 139 | public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 140 | waitForAccess(mWriteAccessLatch); 141 | return doUpdate(); 142 | } 143 | 144 | @Override 145 | public int delete(Uri uri, String selection, String[] selectionArgs) { 146 | waitForAccess(mWriteAccessLatch); 147 | return doDelete(); 148 | } 149 | 150 | @Override 151 | public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) { 152 | waitForAccess(mReadAccessLatch); 153 | return doQuery(); 154 | } 155 | ``` 156 | 157 | 在数据库操作之前增加 waitForAccess(),这样在数据库初始化的过程中,会阻塞其他进程对数据库的写操 作,当初始化完成之后,所有被阻塞的操作再被执行。 -------------------------------------------------------------------------------- /docs/PerformanceOptimization/稳定性优化.md: -------------------------------------------------------------------------------- 1 | # 性能优化3/5 -- 稳定性优化 2 | 3 | * Java层Crash监控 4 | * Native层Crash监控 5 | * ANR 分析 6 | 7 | 8 | ### Java层Crash监控 9 | 10 | 这个比较简单,注册一个UncaughtExceptionHandler,发生crash时,该Handler会捕获异常。/Users/hai/Documents/Document/my_blog/KnowledgeSummary/README.md 11 | 12 | 13 | 因为应用已经发生异常,所以捕获异常后可以先保存在本地,等下次应用启动且网络正常时,才发到分析平台。 14 | 15 | ### Native层Crash监控 16 | 17 | Native层发生crash时,会通过信号机制(一种重要的进程间通信机制)传递异常信息,即Linux内核会生成错误信号并通知当前进程。应用进程接收到错误信号后,可以捕获该信号并执行对应的信号处理函数。 18 | 19 | Linux有一类专门用于描述Crash的信号,其中,在Android系统上,Native层最常见的导致Crash的信号量如下: 20 | 21 | 信号 | 一般导致此错误的原因 22 | ------- | ------- 23 | SIGILL | 执行了非法指令、可执行文件出错、堆栈溢出等情况 24 | SIGABRT | 调用abort函数生成 25 | SIGBUS | 访问非法地址,包括内存地址对齐(alignment)出错 26 | SIGFPE | 算术运算错误,如溢出或除0等 27 | SIGSEGV | 访问不属于自己的存储空间或访问只读存储空间 28 | SIGPIPE | 管道异常。通常在进程间通信时产生 29 | 30 | 31 | ### ANR 分析 32 | 33 | ANR,即Application not responding,应用无响应。 34 | 35 | ##### 常见3种ANR类型: 36 | 37 | 1.KeyDispatch Timeout 38 | 39 | 最常见,应用对输入事件5秒内无响应,比如按键、触摸。 40 | 41 | 2.Broadcast Timeout 42 | 43 | 广播接收器在指定时间内(系统默认10秒)没有处理完,且没有结束执行onReceive()。 44 | 45 | 3.Service Timeout 46 | 47 | 出现概率较小。是指Service在指定时间内(系统默认20秒)没有处理完。 48 | 49 | 50 | ##### ANR log 51 | 52 | 关键词解释: 53 | 54 | * ANR in: 发生ANR的具体类 55 | * PID: 发生ANR的进程,系统此时会生成trace文件 56 | * Reason: 发生ANR的原因 57 | * CPU usage: CPU使用情况 58 | 59 | log 示例: 60 | 61 | ``` 62 | ANR in com.utils.myapplication (com.utils.myapplication/.MainActivity) 63 | 64 | PID: 30234 65 | 66 | Reason: Input dispatching timed out (com.utils.myapplication/com.utils.myapplication.MainActivity, Waiting to send non-key event because the touched window has not finished processing certain input events that were delivered to it over 500.0ms ago. Wait queue length: 4. Wait queue head age: 6125.4ms.) 67 | 68 | Load: 6.74 / 6.71 / 6.8 69 | 70 | CPU usage from 278670ms to 0ms ago (2018-03-26 20:26:47.043 to 2018-03-26 20:31:25.712): 71 | 4.8% 1526/system_server: 2.9% user + 1.8% kernel / faults: 25652 minor 3 major 72 | 2.2% 575/surfaceflinger: 0.9% user + 1.2% kernel / faults: 13 minor 73 | 2% 10531/logd: 2% user + 0% kernel / faults: 81 minor 74 | 0.9% 736/adbd: 0.1% user + 0.7% kernel / faults: 7917 minor 75 | 0.9% 2249/com.android.systemui: 0.6% user + 0.2% kernel / faults: 210 minor 76 | 0.8% 29537/kworker/u8:6: 0% user + 0.8% kernel 77 | 0.6% 18098/com.tencent.mobileqq: 0.3% user + 0.3% kernel / faults: 308 minor 78 | 0.6% 402/msm-core:sampli: 0% user + 0.6% kernel 79 | 0.6% 22978/kworker/u8:5: 0% user + 0.6% kernel 80 | 0.6% 25159/kworker/u8:3: 0% user + 0.6% kernel 81 | 0.5% 3/ksoftirqd/0: 0% user + 0.5% kernel 82 | 0.4% 12/ksoftirqd/1: 0% user + 0.4% kernel 83 | 0.4% 5160/mdss_fb0: 0% user + 0.4% kernel 84 | 0.4% 7/rcu_preempt: 0% user + 0.4% kernel 85 | 0.4% 2199/VosMCThread: 0% user + 0.4% kernel 86 | 0.4% 589/mm-pp-dpps: 0.1% user + 0.2% kernel 87 | 0.3% 1//init: 0.1% user + 0.1% kernel / faults: 2675 minor 88 | 0.3% 23831/kworker/0:4: 0% user + 0.3% kernel 89 | 0.3% 41/smem_native_rpm: 0% user + 0.3% kernel 90 | 0.2% 5279/perfd: 0.1% user + 0.1% kernel / faults: 2 minor 91 | 0.2% 22527/kworker/0:1: 0% user + 0.2% kernel 92 | 0.2% 2813/mcd: 0.1% user + 0% kernel 93 | 0.2% 323/cfinteractive: 0% user + 0.2% kernel 94 | 0.2% 574/servicemanager: 0% user + 0.1% kernel 95 | 0.2% 1699/com.tencent.mobileqq:MSF: 0.1% user + 0% kernel / faults: 132 minor 96 | 0.2% 747/thermal-engine: 0% user + 0.1% kernel 97 | 0.1% 18885/com.tencent.mobileqq:TMAssistantDownloadSDKService: 0.1% user + 0% kernel / faults: 260 minor 98 | 0.1% 2586/wpa_supplicant: 0% user + 0% kernel / faults: 5 minor 99 | 0.1% 29566/kworker/0:2: 0% user + 0.1% kernel 100 | 0.1% 25031/kworker/1:1: 0% user + 0.1% kernel 101 | 0.1% 804/qfp-daemon: 0% user + 0% kernel / faults: 23 minor 102 | 0.1% 32581/com.tencent.mm: 0% user + 0% kernel / faults: 323 minor 103 | 0% 2887/com.miui.home: 0% user + 0% kernel / faults: 1288 minor 104 | 0% 5249/logcat: 0% user + 0% kernel 105 | 0% 790/cnss-daemon: 0% user + 0% kernel 106 | 0% 29239/kworker/u8:0: 0% user + 0% kernel 107 | 0% 46/irq/260-cpr3: 0% user + 0% kernel 108 | 0% 2197/wlan_logging_th: 0% user + 0% kernel 109 | 0% 32534/com.tencent.mm:push: 0% user + 0% kernel / faults: 171 minor 110 | 0% 16/ksoftirqd/2: 0% user + 0% kernel 111 | 0% 25216/kworker/1:2: 0% user + 0% kernel 112 | 0% 781/netd: 0% user + 0% kernel / faults: 943 minor 113 | 0% 10780/com.miui.analytics: 0% user + 0% kernel / faults: 245 minor 114 | 0% 27/dsps_smd_trans_: 0% user + 0% kernel 115 | 0% 726/jbd2/dm-1-8: 0% user + 0% kernel 116 | 0% 468/perfd: 0% user + 0% kernel / faults: 10 minor 117 | 0% 3049/com.miui.daemon: 0% user + 0% kernel / faults: 198 minor 118 | 0% 3123/com.miui.whetstone: 0% user + 0% kernel / faults: 38 minor 1 major 119 | 0% 26520/kworker/3:4: 0% user + 0% kernel 120 | 0% 20/ksoftirqd/3: 0% user + 0% kernel 121 | 0% 2456/com.android.phone: 0% user + 0% kernel / faults: 25 minor 122 | 0% 3257/com.miui.securitycenter.remote: 0% user + 0% kernel / faults: 153 minor 1 major 123 | 0% 3545/com.miui.powerkeeper:service: 0% user + 0% kernel / faults: 24 minor 124 | 0% 656/sensors.qcom: 0% user + 0% kernel / faults: 56 minor 125 | 0% 967/rild: 0% user + 0% kernel / faults: 17 minor 126 | 0% 1115/com.android.defcontainer: 0% user + 0% kernel / faults: 607 minor 127 | 0% 2836/irq/22-408000.q: 0% user + 0% kernel 128 | 0% 4022/com.xiaomi.metoknlp: 0% user + 0% kernel / faults: 187 minor 129 | 0% 29821/com.xiaomi.market: 0% user + 0% kernel / faults: 359 minor 130 | 0% 807/time_daemon: 0% user + 0% kernel / faults: 20 minor 131 | 0% 2946/com.xiaomi.xmsf: 0% user + 0% kernel / faults: 12 minor 132 | 0% 3068/com.android.nfc: 0% user + 0% kernel 133 | 134 | ``` 135 | 136 | -------------------------------------------------------------------------------- /docs/JVM/2_JVM的GC和内存分配.md: -------------------------------------------------------------------------------- 1 | ### 哪些内存需要回收 2 | [Java的内存区域](./Java的内存区域.md)中介绍了Java内存运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭。 3 | 4 | 栈中的栈帧随着方法的进入和退出而有条不紊地执行值出栈和入栈的操作。每一个栈帧中分配多少内存基本上是在类结构确定下来就已知的,大体上认为这部分内存时编译器可知的。这部分内存在方法结束或者线程结束时就自动回收了。 5 | 6 | 而Java堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,只有在程序运行期间才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,Java中GC关注的就是这部分内存。 7 | 8 | ### 内存什么时候回收 9 | > 分为Java堆的内存回收和方法区的内存回收 10 | #### Java堆的内存回收 11 | Java堆中存放的几乎是所有对象的实例,GC回收这部分内存前,需要判断这些对象的存活状态。 12 | 13 | ##### 对象存活判定算法 14 | 15 | > 概念:引用的四种类型 16 | * 强引用(StrongReference) 17 | * 具有强引用的对象不会被GC; 18 | * 即便内存空间不足,JVM宁愿抛出OutOfMemoryError使程序异常终止,也不会随意回收具有强引用的对象。 19 | * 软引用(SoftReference) 20 | * 只具有软引用的对象,会在内存空间不足的时候被GC,如果回收之后内存仍不足,才会抛出OOM异常; 21 | * 软引用常用于描述有用但并非必需的对象,比如实现内存敏感的高速缓存。 22 | * 弱引用(WeakReference) 23 | * 只被弱引用关联的对象,无论当前内存是否足够都会被GC; 24 | * 强度比软引用更弱,常用于描述非必需对象。 25 | * 虚引用(PhantomReference) 26 | * 仅持有虚引用的对象,在任何时候都可能被GC 27 | * 常用于跟踪对象被GC回收的活动; 28 | * 必须和引用队列 (ReferenceQueue)联合使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。 29 | 30 | ##### a. 引用计数法 31 | 定义:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。 32 | 33 | > 然而在主流的Java虚拟机里未选用引用计数算法来管理内存,主要原因是它难以解决对象之间相互循环引用的问题,所以出现了另一种对象存活判定算法。 34 | 35 | ##### b.可达性分析法 36 | 定义:通过一系列被称为『GC Roots』的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。 37 | 38 | > 可作为GC Roots的对象 39 | > * 虚拟机栈中引用的对象,只要是指栈中的``本地变量`` 40 | > * 本地方法栈中的``Native方法``引用的对象 41 | > * 方法区中``类静态属性``引用的对象 42 | > * 方法区中``常量``引用的对象 43 | 44 | ![image](https://raw.githubusercontent.com/JasonGaoH/Images/master/gc_roots.png) 45 | 46 | 需要注意的是,在可达性分析算法中被判定不可达的对象还未真的判『死刑』,至少要经历两次标记过程:判断对象是否有必要执行finalize()方法;若被判定为有必要执行finalize()方法,之后还会对对象再进行一次筛选,如果对象能在finalize()中重新与引用链上的任何一个对象建立关联,将被移除出“即将回收”的集合。 47 | 48 | #### 方法区的回收 49 | 方法区在HotSpot虚拟机中被称为永久代。 50 | 永久代的垃圾收集主要回收两部分内存:废弃常量和无用的类。 51 | ##### a.废弃常量的回收 52 | 与回收Java堆中的对象的GC很类似,即在任何地方都未被引用的常量会被GC。 53 | 54 | ##### b.无用的类回收 55 | 无用的类需满足一下三个条件才会被GC: 56 | * 该类所有的实例都已被回收,即Java堆中不存在该类的任何实例; 57 | * 加载该类的ClassLoader已经被回收; 58 | * 该类对应的java.lang.Class对象没在任何地方被引用,即无法在任何地方通过反射访问该类的方法。 59 | 60 | ### 内存如何回收 61 | 如何回收也就是谈到垃圾回收算法: 62 | 63 | #### 垃圾收集算法 64 | 65 | ##### 分代收集算法 66 | * 根据对象存活周期的不同,将Java堆划分为新生代和老年代,并根据各个年代的特点采用最适当的收集算法。 67 | * 新生代:大批对象死去,只有少量存活。使用``复制算法``,只需要复制少量存活对象即可。 68 | * 老年代:对象存活率高。使用``标记-清除算法``和``标记-整理算法``,只需要标记较少的回收对象即可。 69 | 70 | * 是当前商业虚拟机都采用的一种算法。 71 | 72 | > 接下来依次介绍以上提及的三种算法。 73 | 74 | ##### 复制算法 75 | * 把可用内存容量划分为大小相等的两块,每次使用其中的一块。当这一块的内存用尽后,把还存活着的对象``复制``到另外一块上面,再将这一块内存空间一次清理掉。 76 | * 优点:每次都是对整个半区进行内存回收,无需考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。 77 | * 缺点:每次可使用的内存缩小为原来的一半,内存使用率低。 78 | 79 | > 有研究表明新生代中的对象98%是朝生夕死的,因此没必要按照1:1来划分内存空间,而是分为一块较大的Eden空间和两块较小的Survivor空间,在HotSpot虚拟机中默认比例为8:1:1。每次使用Eden和一块Survivor,回收时将这两块中存活着的对象一次性地复制到另外一块Survivor上,再做清理。可见只有10%的内存会被“浪费”,倘若Survivor空间不足还需要依赖其他内存(老年代)进行分配担保。 80 | 81 | ![image](../img/gc_copy.webp) 82 | 83 | ##### 标记-清除算法 84 | * 首先是``标记``出所有需要回收的对象,然后统一``清除``所有被标记的对象。 85 | * 是最基础的收集算法。 86 | * 缺点:``标记``和``清除``的效率不高;空间碎片太多,``标记-清除``之后产生大量不连续的内存碎片,可能会导致后续需要分配较大对象时,因无法找到足够的连续内存而提前触发另一次GC,影响系统性能. 87 | 88 | ![image](../img/gc_mark_clean.webp) 89 | 90 | ##### 标记-整理算法 91 | * 首先``标记``出所有需要回收的对象,然后进行``整理``,使得存活的对象都向一端移动,最后直接清理掉端边边界以外的内存。 92 | * 有点:即没有浪费50%的内存空间,又不存在空间碎片问题,性价比较高。 93 | * 一般情况下,老年代会选择标记-整理算法。 94 | 95 | 96 | ![image](../img/gc_mark_clear_up.webp) 97 | 98 | 99 | #### HotSpot算法实现&垃圾回收器 100 | > 接下来介绍如何在HotSpot虚拟机上实现对象存活判定算法和垃圾手机算法,并保证虚拟机高效执行。 101 | 102 | ##### 枚举根节点 103 | 主流虚拟机采用的都是``准确式GC``,在执行系统停顿之后无需执行上下文和全局的引用位置,而是通过一些办法直接获取到存放对象的地方,在HotSpot中是通过一组称为``OopMap``的数据结构来实现的,完成类加载后会计算出对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程中,也会在``特定的位置``记录下栈和寄存器中的哪些位置是引用。这样GC在扫描时就可直接得知这些信息,并快速准确地完成GC Roots的枚举。 104 | 105 | ##### 安全点 106 | 上述“特定的位置”被称为安全点,即程序执行时并非在所有地方都停顿执行GC,只在到达安全点时才暂停,降低GC的成本。 107 | 108 | * 安全点选取的标准:可让程序长期执行的地方,如方法调用,循环跳转、异常跳转等具有指令序列复用的特征。 109 | * 使所有线程在最近的安全点上再停顿的方案: 110 | * 抢先式中断:无需代码主动配合,在发生GC时把所有的线程全部中断,若线程中断处不在安全点上就恢复线程,让它“跑”到安全点上。现在几乎没有虚拟机采用抢先式中断来暂停线程从而响应GC事件。 111 | * 主动式中断:在GC要中断线程时不直接对线程操作,而是设置一个中断标志,让各个线程在执行时主动轮询它,当中断标志为真时就自己主动中断挂起。 112 | 113 | ##### 安全区域 114 | 安全点机制只能保证程序执行时,在不太长的时间内遇到可进入GC的安全点,但在程序不执行时(如线程处于Sleep或Blocked状态)线程无法响应JVM的中断请求,此时就需要安全区域来解决。 115 | 116 | * 安全区域:引用关系不会发生变化的一段代码片段,在安全区域中的任意地方开始GC都是安全的,可看做是扩展的安全点。 117 | * 执行过程:当线程执行到安全区域中的代码时就标识一下,如果这时JVM要发起GC就不用管被标识的线程;在线程要离开安全区域时检查系统是否已经完成了根节点枚举,若完成则线程可以继续执行,否则等待直到收到可以安全离开安全区域的信号为止。 118 | 119 | > 到此只是简单介绍了HotSpot如何发起内存回收,而具体的回收动作是由虚拟机所采用的GC收集器决定的,通常虚拟机中往往不止有一种GC收集器,下图展示的是HotSpot虚拟机中存在的七种作用于不同分代(新生代、老年代)的收集器,其中被连线的两个收集器表示可以搭配使用。 120 | 121 | ![image](../img/gc_collector.webp) 122 | 123 | 124 | ![image](../img/gc_collector_table.webp) 125 | 126 | > 并行(Parallel):多条垃圾收集线程并行工作,而用户线程仍处于等待状态。 127 | 128 | > 并发(Concurrent):垃圾收集线程与用户线程一段时间内同时工作,用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。 129 | 130 | #### 内存分配和回收策略 131 | 132 | > 对象的内存分配广义上是指在堆上分配,主要是在新生代的Eden区上,如果启动了TLAB,将按线程优先在TLAB上分配,少数情况下也可能会分配在老年代中。分配细节还是取决于所使用的GC收集器组合以及虚拟机中与内存相关的参数的设置。以下介绍几条普遍的内存分配规则。 133 | 134 | * ``对象有线在Eden分配``:大多数情况下对象在新生代Eden区中分配,当Eden区没有足够空间进行分配时虚拟机将发起一次Minor GC。 135 | 136 | > 新生代GC(Minor GC):发生在新生代的垃圾收集动作。较频繁、回收速度也较快。 137 | 138 | > 老年代GC(Major GC/Full GC):发生在老年代的垃圾收集动作。出现Major GC经常会伴随至少一次的Minor GC。速度一般比Minor GC慢10倍以上。 139 | 140 | * ``大对象直接进入老年代``:对于需要大量连续内存空间的Java对象(如很长的字符串以及数组),如果大于虚拟机设定的-XX:PretenureSizeThreshold参数值将直接在老年代分配。这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存复制。 141 | 142 | * ``长期存活的对象将进入老年代``:虚拟机会给每个对象定义一个年龄计数器,当对象在Eden出生并经过第一次Minor GC后仍存活且能被Survivor容纳的话,将被移动到Survivor空间中并将对象年龄设为1;当对象在Survivor区中每“熬过”一次Minor GC年龄就+1,直至增加到一定程度(默认为15岁,可通过-XX: MaxTenuringThreshold设置)就会被晋升到老年代中。 143 | 144 | * ``动态对象年龄判定``:为了能更好地适应不同程序的内存状况,虚拟机并不要求一定要达到-XX: MaxTenuringThreshold设置值才能晋升到老年代,当Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,那么年龄大于或等于该年龄的对象可以直接进入老年代。 145 | 146 | * ``空间分配担保``:在发生Minor GC之前虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,若是,说明可确保Minor GC是安全的,反之虚拟机会查看-XX:HandlePromotionFailure设置值是否允许担保失败;若允许,会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小;若大于,将尝试进行一次Minor GC,若小于或者不允许担保失败,将改为进行一次Full GC。 147 | 148 | > 解释:当大量对象在MinorGC后仍然存活的情况时,需要借助老年代进行分配担保,把Survivor无法容纳的对象直接进入老年代,但前提是老年代本身还有容纳这些对象的剩余空间,由于在完成内存回收之前无法预知实际存活对象,只好取之前每次回收晋升到老年代对象容量的平均大小值作为经验值,与老年代的剩余空间进行比较,从而决定是否进行Full GC来让老年代腾出更多空间。 -------------------------------------------------------------------------------- /docs/Android/面试题收集/InterView_Backup.md: -------------------------------------------------------------------------------- 1 | ## KnowledgeSummary目录: 2 | 3 | >此文件为备份文件,后续会从以下记录中问题以及知识点来分门别类的来总结。 4 | 5 | * [Java](#Java) 6 | * [Android](#Android) 7 | * [开源库](#开源库) 8 | * [性能优化](#性能优化) 9 | * [图片相关](#图片相关) 10 | * [Android其他](#Android其他) 11 | * [多线程相关](#多线程相关) 12 | * [网络](#网络) 13 | * [设计模式](#设计模式) 14 | * [算法](#算法) 15 | * [操作系统](#操作系统) 16 | 17 | ### Java 18 | * Java基础 19 | * Java基础知识总结 20 | * Java中的集合框架 21 | * 从源码角度分析ArrayList和Vector的区别 22 | * 从源码角度分析ArrayList和LinkedList的区别 23 | * 从源码角度分析HashMap的原理与实现 24 | * ConcurrentHashMap 25 | * LinkedHashMap 26 | * TreeMap 27 | 28 | * Java进阶 29 | * Java中的注解 30 | * Java中的ClassLoader机制 31 | * JVM 32 | * GC回收策略 33 | * GC算法 34 | * 类加载过程 35 | * 垃圾收集机制 对象创建,新生代与老年代 36 | * 可否用try-catch捕获OOM以避免其发生 37 | * 内存泄漏场景,如何定位及修复内存泄漏 38 | * 垃圾回收机制与调用System.gc()区别 39 | * NIO 40 | * Java四种引用 41 | * JavaPoet的使用指南 42 | 43 | 44 | 45 | 46 | ### Android 47 | * UI 48 | * SurfaceView VS TextureView 49 | * ConstraintLayout 50 | * 常用Drawable 51 | * Drawable VS Bitmap 52 | * CoordinateLayout的原理分析 53 | 54 | * Activity和Fragment 55 | * Activity的生命周期和启动模式 56 | * 简述Activity启动全部过程 57 | * launchMode 58 | * Activity调用方式 59 | * AlertDialog,Toast对Activity生命周期的影响 60 | * Dialog,PopupWindow,Activity区别 61 | * Activity与Fragment之间生命周期比较 62 | * 多层Fragment嵌套的时候setUserHint 63 | * 下拉状态栏是不是影响activity的生命周期,如果在onStop的时候做了网络请求,onResume的时候怎么恢复 64 | * Activity之间的通信方式 65 | * Android里面为什么要设计出Bundle而不是直接用Map结构 66 | * fragment 各种情况下的生命周期 67 | * ViewPager使用细节,如何设置成每次只初始化当前的Fragment,其他的不初始化 68 | * fragment之间传递数据的方式 69 | 70 | * Service 71 | * Service的生命周期 72 | * 怎么启动service,service和activity怎么进行数据交互 73 | * [如何在后台下载任务, 并在通知栏显示进度](https://juejin.im/post/586072c861ff4b005820901d) 74 | 75 | * BroadcastReceiver 76 | * 广播(动态注册和静态注册区别,有序广播和标准广播) 77 | * BroadcastReceiver,LocalBroadcastReceiver区别 78 | * 广播的使用场景 79 | 80 | * ContentProvider 81 | * Android系统为什么会设计ContentProvider,进程共享和线程安全问题 82 | 83 | * View 84 | * WebView优化(包括加载加速) 85 | * View事件传递 86 | * 封装view的时候怎么知道view的大小 87 | * 如何计算一个View的层级 88 | * RecycleView的使用,原理,RecycleView优化 89 | * ListView的优化 90 | * LinearLayout、RelativeLayout、FrameLayout的特性、使用场景 91 | * view渲染 92 | * ListView重用的是什么 93 | * 自定义View的属性引用attr,styleable里定义的名称可否与系统已经存在的name重复?当然是不可以的,编译器会预先检查系统已经存在或者之前已经定义重复的 94 | * Android为什么引入Parcelable 95 | * 有没有尝试简化Parcelable的使用 96 | * 序列化的作用,以及 Android 两种序列化的区别 97 | * [TextView性能瓶颈,渲染优化,以及StaticLayout的一些用处](https://www.jianshu.com/p/9f7f9213bff8) 98 | 99 | ### 开源库 100 | 101 | * [Fresco] 102 | * [Tinker] 103 | * [ActivityRouter] 104 | * [ARouter] 105 | * ButterKnife 106 | * [EventBus] 107 | * [RxJava] 108 | * RxJava 109 | * RxJava的功能与原理实现 110 | * RxJava简介及其源码解读? 111 | * RxJava的作用,优缺点 112 | * RxJava变换操作符map,flatMap,concatMap,buffer 113 | * Retrofit 114 | * [OKHTTP] 115 | * [LeakCanary] 116 | * [Atlas] 117 | * [BlockCanary] 118 | * Glide 119 | * glide 使用什么缓存 120 | * Glide 内存缓存如何控制大小 121 | * UETool 122 | 123 | ### 性能优化 124 | 125 | * [绘制优化] 126 | * [启动优化] 127 | * [内存优化] 128 | * [稳定性优化] 129 | * [ANR分析] 130 | * [耗电优化] 131 | * [安装包优化] 132 | 133 | (待整理) 134 | * OOM定位及解决方案 135 | * 安全、加固 136 | * 如何保持应用的稳定性 137 | * 性能优化如何分析systrace 138 | 139 | 140 | ### 图片相关 141 | 142 | * [LruCache] 143 | * [DiskLruCache] 144 | * 图片加载原理 145 | * 图片裁剪、压缩、旋转、滤镜 146 | * 如何做图片缓存 147 | * 如何防止加载大图OOM 148 | * [图像显示原理] 149 | * [Bitmap和Drawable ] 150 | * [图片加载优化] 151 | * Bitmap 使用时候注意什么 152 | * bitmap recycler 相关 153 | * 图片加载库相关,bitmap如何处理大图,如一张30M的大图,如何预防OOM 154 | 155 | ### Android其他 156 | 157 | * [Art和Dalvik区别] 158 | * [详解注解处理器APT技术] 159 | * gradle 160 | * [APK打包及安装过程] 161 | * ActicityThread相关 162 | * [IntentFilter匹配规则] 163 | * [插件化] 164 | * [组件化] 165 | * [热修复方案对比] 166 | * 埋点框架 167 | * 画出Android的大体架构图 168 | * 描述清点击AndroidStudio的build按钮后发生了什么 169 | * 动态权限适配方案,权限组的概念 170 | * 进程保活 171 | * Android进程分类 172 | * 是否熟悉Android jni开发,jni如何调用java层代码 173 | * 多线程断点续传原理 174 | * Appliction启动过程(App启动过程) 175 | * App启动流程,从点击桌面开始 [link](http://www.androidos.net.cn/doc/day/2018-02-18/15384.md) 176 | * 为什么不能在子线程更新UI 177 | * App启动崩溃异常捕捉 178 | * [数据库如何进行升级,SQLite增删改查的基础sql语句] 179 | * App中唤醒其他进程的实现方式 180 | * AndroidManifest的作用与理解 181 | * Android中开启摄像头的主要步骤 182 | * Application 和 Activity 的 context 对象的区别 183 | * 差值器&估值器 184 | * Android中进程内存的分配,能不能自己分配定额内存 185 | * 视频加密传输 186 | * 数据怎么压缩,数据的安全 187 | * 插桩(打点等会用到) 188 | * 模块化实现(好处,原因) 189 | * 统计启动时长,标准 190 | * 动态布局 191 | * App是如何沙箱化,为什么要这么做; 192 | * 权限管理系统(底层的权限是如何进行 grant 的) 193 | 194 | ### 多线程相关 195 | 196 | * 死锁 197 | * Android多进程 198 | * Android进程间通信--Binder 199 | * 多线程间通信 200 | * 线程安全 201 | * Java线程池 202 | * 多线程池的优化(OKHTTP里面有) 203 | * ThreadLocal 204 | * ReentrantLock 205 | * 同步相关知识 206 | * 线程间 操作 List(多线程) 207 | * synchronized与Lock的区别 208 | * volatile 209 | * JVM 内存区域 开线程影响哪块内存 210 | * AIDL 211 | * 进程与线程 212 | * 并发集合了解哪些 213 | * CAS介绍 214 | * 开启线程的三种方式,run()和start()方法区别 215 | * 多线程(关于AsyncTask缺陷引发的思考) 216 | * 手写生产者/消费者模式 217 | * 多进程场景遇见过么? 218 | * 如何保证多线程读写文件的安全? 219 | * volatile的原理 220 | * synchronize的原理 221 | * lock原理 222 | * 线程如何关闭,以及如何防止线程的内存泄漏 223 | * wait/notify 224 | * 多线程:怎么用、有什么问题要注意;Android线程有没有上限,然后提到线程池的上限 225 | * 线程间操作List 226 | 227 | ### 网络 228 | * TCP/UDP的区别 229 | * 数字证书包含的内容 230 | * HTTPS 231 | * Https请求慢的解决办法,DNS,携带数据,直接访问IP 232 | * 网络请求缓存处理,okhttp如何处理网络缓存的 233 | * https相关,如何验证证书的合法性,https中哪里用了对称加密,哪里用了非对称加密,对加密算法(如RSA)等是否有了解 234 | * TCP与UDP区别与应用(三次握手和四次挥手)涉及到部分细节(如client如何确定自己发送的消息被server收到) HTTP相关 235 | * 提到过Websocket 问了WebSocket相关以及与socket的区别 236 | * https握手过程,如何实现数据加密?客户端如何保证安全实现双重证书校验?请你设计一个登录功能,需要注意哪些安全问题 237 | 238 | 239 | ### 设计模式 240 | * 代理 241 | * 责任链(OKHTTP、Android事件分发) 242 | * 观察者(RxJava) 243 | * 装饰者(Java I/O Stream) 244 | * 生产者消费者 245 | * 单例 246 | * 适配器模式,装饰者模式,外观模式的异同? 247 | 248 | 249 | ### 算法 250 | * 排序,快速排序,堆排序的实现 251 | * 常用数据结构简介 252 | * 树:B+树的介绍,B+树 253 | * 图:有向无环图的解释 254 | * 阻塞队列BlockQueue 255 | * 二叉树 深度遍历与广度遍历 256 | * 二叉树,给出根节点和目标节点,找出从根节点到目标节点的路径 257 | * 判断环(猜测应该是链表环) 258 | * 链表反转 259 | * 两个栈组成一个队列 260 | * 一个无序,不重复数组,输出N个元素,使得N个元素的和相加为M,给出时间复杂度、空间复杂度。手写算法 261 | * 合并多个单有序链表(假设都是递增的) 262 | * 上一问扩展,海量数据,内存中放不下,怎么求出。 263 | * String to Integer 264 | 265 | 266 | ### 操作系统 267 | * 进程调度 268 | * 进程状态 269 | * 进程间通信的方式 270 | * 逻辑地址与物理地址,为什么使用逻辑地址 271 | * 为什么要有线程,而不是仅仅用进程 272 | -------------------------------------------------------------------------------- /docs/Android/UI/仿京东、淘宝首页,通过两层嵌套的RecyclerView实现tab的吸顶效果.md: -------------------------------------------------------------------------------- 1 | > 文章已同步发表于微信公众号JasonGaoH,[仿京东、淘宝首页,通过两层嵌套的RecyclerView实现tab的吸顶效果](https://mp.weixin.qq.com/s?__biz=MzUyNTE2OTAzMQ==&mid=2247483770&idx=1&sn=38b95c3cff51e216248c0eb860155437&chksm=fa237992cd54f084802be4b0be2b52e4f5052019d51562f65245ca8b0faf3e0d61d296fc0e7d&token=1938879438&lang=zh_CN#rd) 2 | 3 | ### 为什么会有这篇文章 4 | 5 | 之前写过一篇文章[使用CoordinatorLayout过程中遇到的两个问题以及浅析CoordinatorLayout工作机制](https://juejin.im/post/5d233cc86fb9a07ec42b7f57),这篇文章上主要讲了通过CoordinatorLayout实现tab吸顶的效果时遇到的问题,效果跟京东、淘宝首页类似,只不过实现方法不同而已,但是使用CoordinatorLayout来实现是会有不少细节问题是很难处理好的,下面会详细介绍。 6 | 7 | 首先我们可以简单看下京东首页的效果gif,来看看我们到底是要实现什么样的效果: 8 | 9 | ![image](https://raw.githubusercontent.com/JasonGaoH/Images/master/jingdong.gif) 10 | 11 | 京东首页的tab筛选区将feed分为两个部分,上面是各种不同item,tab的下半部分可以左右横滑,并且下拉可以加重更多,只要网络有数据的情况下理论上是可以无限下拉的。 12 | 13 | 其实用CoordinatorLayout来实现tab吸顶,如果能将一些细节问题处理好的话,其实大致可以实现类似京东首页的这个效果,具体细节问题可以参考文章开头说的之前的文章,文章里讲了下使用CoordinatorLayout来实现类似效果遇到的动画抖动问题以及页面回弹问题以及对应的解决方法。 14 | 15 | 那么为什么会不采用CoordinatorLayout来实现,转而采用嵌套RecyclerView的方式呢? 16 | 17 | 首先我们来看下CoordinatorLayout实现的大致布局: 18 | 19 | ![image](https://raw.githubusercontent.com/JasonGaoH/Images/master/CoordinatorLayout.png) 20 | 21 | 一个问题是从AppBarLayout滑动效果是不能传递到下面的ViewPager里去的,我尝试了各种方式都没能解决掉这个问题,可以简单看下Demo效果图: 22 | 23 | ![image](https://raw.githubusercontent.com/JasonGaoH/Images/master/coordinatorlayout_fix.gif) 24 | 25 | 从gif图大致可以看到AppBarLayout滑上去之后惯性消失了,tab下面的区域是不能接着滚动的。 26 | 27 | > 这个惯性消失的问题,我在网上找到了一个一篇解决惯性消失的文章如下[支付宝首页交互三部曲3实现支付宝首页交互](https://blog.kyleduo.com/2017/07/21/alipay-home-3-alipay-home/),实现方式大致是自己把CoordinatorLayout这套机制再实现了一遍,因为是自己实现的,里面的一些机制是比较方便改动的,它处理惯性这个问题的逻辑大致是将AppBarLayout中未消费的y轴偏移量拿出来再交由RecyclerView去滑动,代码如下: 28 | 29 | ```java 30 | mHeaderView.setOnHeaderFlingUnConsumedListener(new APHeaderView.OnHeaderFlingUnConsumedListener() { 31 | @Override 32 | public int onFlingUnConsumed(APHeaderView header, int targetOffset, int unconsumed) { 33 | APHeaderView.Behavior behavior = mHeaderView.getBehavior(); 34 | int dy = -unconsumed; 35 | if (behavior != null) { 36 | mRecyclerView.scrollBy(0, dy); 37 | } 38 | return dy; 39 | } 40 | }); 41 | ``` 42 | > 这里由于篇幅原因,就不展开详细介绍了,感兴趣地同学可以点开上面的链接去研究研究,文章中也贴出了GitHub地址。 43 | 44 | 我们重新回到我们一开始的问题,为什么想替换掉CoordinatorLayout,另外一个问题是CoordinatorLayout这种实现相对比较简单,但是会导致页面的嵌套层级很深,我们从上面贴出来的布局来看,view嵌套的层级特别深,而且如果我们要实现类似京东或者淘宝首页这样的效果,在TabLayout上面的区域,也就是下图箭头标注的地方必须要采用RecyclerView的来实现,因为tab上半部分的内容和个数都是不确定的,使用RecyclerView才比较方便,但是这样页面的层级就更深了,加载速度也变得更慢了。 45 | 46 | ![image](https://raw.githubusercontent.com/JasonGaoH/Images/master/CoordinatorLayout_fix.png) 47 | 48 | ### 如何实现 49 | 50 | 要抛弃CoordinatorLayout,那么如何实现呢? 51 | 52 | 我们用Android Studio中的Layout Inspector工具看了下京东首页的布局,发现的确是采用两层RecyclerView嵌套来实现的,展示的布局大致如下所示: 53 | 54 | ![image](https://raw.githubusercontent.com/JasonGaoH/Images/master/jingdong_layout.png) 55 | 56 | 那么接下来是怎么去实现这个效果了。其实一开始我以为要采用嵌套滚动这套机制来实现,后来发现不采用嵌套滚动机制也是可以实现的。 57 | 58 | 现在我们可以大致构造这样一个布局: 59 | 60 | ![image](https://raw.githubusercontent.com/JasonGaoH/Images/master/nested_recycler_view.png) 61 | 62 | 我们把ViewPager以及TabLayout这一块作为外部RecyclerView的一个item,ViewPager可能会有个多个内部RecyclerView,只要我们能让外部RecyclerView和内部RecyclerView的滑动事件正确分发基本就可以解决这个问题了。 63 | 64 | 如果只是构造出这个布局出来,我们发现内部的RecyclerView都不会显示出来,因为滑动完全由外部RecyclerView接管了。 65 | 66 | 那么重点来了,这种情况如何处理? 67 | 68 | 其实RecyclerView的LayoutManager中有这两个方法用于判断RecyclerView在水平方向上和竖直方向上是否可以滚动的。 69 | 70 | ``` 71 | public boolean canScrollHorizontally() { 72 | return false; 73 | } 74 | 75 | public boolean canScrollVertically() { 76 | return false; 77 | } 78 | ``` 79 | 然后LayoutManager有各种不同的实现LinearLayoutManager,StaggeredGridLayoutManager等 80 | 81 | 这个LayoutManager中的canScrollVertically和canScrollHorizontally在RecyclerView的onInterceptTouchEvent中是会拿来作判断,判断当前RecyclerView是否需要处理滑动事件的。 82 | 83 | > 还有一点需要注意:我们处理内外两层RecyclerView的滑动冲突问题,主要是想解决下面两种场景:一、手指上滑时,当外部RecyclerView滑动底部的时候,内部的RecyclerView能继续去响应用户的滑动,因为内部的RecyclerView理论上是可以无限滚动的;二、手指下滑时,当内部的RecyclerView滑动到顶部的时候,外部的RecyclerView能够继续响应用户的下滑事件。 84 | 85 | 其实上面已经说出了如何处理嵌套RecyclerView的最重要的点,其他的部分相当于都是一些细节的处理了。 86 | 87 | 在外部RecyclerView(下面成ParentRecyclerView)中的重写LayoutManager的canScrollVertically方法如下: 88 | 89 | ``` 90 | fun initLayoutManager() { 91 | val linearLayoutManager = object :LinearLayoutManager(context) { 92 | 93 | override fun canScrollVertically():Boolean { 94 | //找到当前的childRecyclerView 95 | val childRecyclerView = findNestedScrollingChildRecyclerView() 96 | //只有当前childRecyclerView滑动到顶部才认为ParentRecyclerView是可以竖直方向是可以滚动的 97 | return childRecyclerView == null || childRecyclerView.isScrollTop() 98 | } 99 | 100 | } 101 | linearLayoutManager.orientation = LinearLayoutManager.VERTICAL 102 | layoutManager = linearLayoutManager 103 | } 104 | ``` 105 | 在内部RecyclerView(以下称ChildRecyclerView)中定义了isScrollTop(),用于判断ChildRecyclerView是否滚动到顶部。 106 | ``` 107 | fun isScrollTop(): Boolean { 108 | //RecyclerView.canScrollVertically(-1)的值表示是否能向下滚动,false表示已经滚动到顶部 109 | return !canScrollVertically(-1) 110 | } 111 | ``` 112 | 113 | 另外,在ParentRecyclerView的onTouchEvent方法中: 114 | ``` 115 | override fun onTouchEvent(e: MotionEvent): Boolean { 116 | if(lastY == 0f) { 117 | lastY = e.y 118 | } 119 | if(isScrollEnd()) { 120 | //如果父RecyclerView已经滑动到底部,需要让子RecyclerView滑动剩余的距离 121 | val childRecyclerView = findNestedScrollingChildRecyclerView() 122 | childRecyclerView?.run { 123 | val deltaY = (lastY - e.y).toInt() 124 | if(deltaY != 0) { 125 | scrollBy(0,deltaY) 126 | } 127 | } 128 | } 129 | lastY = e.y 130 | return try { 131 | super.onTouchEvent(e) 132 | } catch (e: Exception) { 133 | e.printStackTrace() 134 | false 135 | } 136 | } 137 | ``` 138 | 139 | 关于滑动事件主要代码就是上面这些,具体可以可以看看项目代码[NestedRecyclerView](https://github.com/JasonGaoH/NestedRecyclerView)。 140 | 141 | 还有关于RecyclerView的fling部分,在RecyclerView的onScrollStateChanged回调中监听y轴的总的偏移量totalDy,然后在RecyclerView不滚动的时候交由内部或者外部RecyclerView去fling,这里就不赘述了,具体可以看项目的代码。 142 | 143 | 最后贴上项目的运行gif图展示: 144 | 145 | ![image](https://raw.githubusercontent.com/JasonGaoH/NestedRecyclerView/master/gif/nested_recyclerview_1.gif) 146 | 147 | ![image](https://raw.githubusercontent.com/JasonGaoH/NestedRecyclerView/master/gif/nested_recyclerview_2.gif) 148 | 149 | ### 最后 150 | 写作不易,欢迎大家点赞,如果有问题,欢迎提出一起讨论,您的点赞是我写作的最大动力,感谢! -------------------------------------------------------------------------------- /docs/MultiThread/ThreadLocal.md: -------------------------------------------------------------------------------- 1 | # ThreadLocal 2 | 3 | ThreadLocal为每一个使用该变量的线程都提供了独立的副本,可以做线程间的数据隔离,每一个线程都可以访问各自内部的副本变量。 4 | 5 | #### ThreadLocal的简单使用 6 | ``` 7 | import java.util.stream.IntStream; 8 | 9 | public class ThreadLocalExample { 10 | public static void main(String[] args) { 11 | //创建ThreadLocal实例 12 | ThreadLocal tlocal = new ThreadLocal(); 13 | //创建5个线程,使用tlocal 14 | IntStream.range(0, 5).forEach(i -> new Thread(() -> { 15 | //每个线程之间都是独立的,设置tlocal互相受影响 16 | tlocal.set(i); 17 | System.out.println(Thread.currentThread() + "set i " + tlocal.get()); 18 | try { 19 | Thread.sleep(1000); 20 | } catch (InterruptedException e) { 21 | e.printStackTrace(); 22 | } 23 | System.out.println(Thread.currentThread() + "get i " + tlocal.get()); 24 | }).start() 25 | ); 26 | } 27 | } 28 | ``` 29 | 上面的代码定义了一个全局唯一的ThreadLocal,然后启动了5个线程对ThreadLocal进行set和get操作,通过下面的输出会发现5个线程之间彼此不会相互影响,每一个线程存入threadLocal中的值是完全不同彼此独立的。 30 | ``` 31 | Thread[Thread-0,5,main]set i 0 32 | Thread[Thread-1,5,main]set i 1 33 | Thread[Thread-3,5,main]set i 3 34 | Thread[Thread-2,5,main]set i 2 35 | Thread[Thread-4,5,main]set i 4 36 | Thread[Thread-0,5,main]get i 0 37 | Thread[Thread-1,5,main]get i 1 38 | Thread[Thread-4,5,main]get i 4 39 | Thread[Thread-2,5,main]get i 2 40 | Thread[Thread-3,5,main]get i 3 41 | ``` 42 | ThreadLocal除了set和get方法,还有一个initialValue的方法。 43 | 44 | initialValue()方法为ThreadLocal要保存的数据类型指定了一个初始化值,在ThreadLocal中默认返回值为null,实例代码如下: 45 | ``` 46 | protected T initialValue() { 47 | return null; 48 | } 49 | ``` 50 | 我么可以重写initialValue方法来进行数据的初始化,即可以不通过set方法来给ThreadLocal赋值,如下面的代码: 51 | ``` 52 | ThreadLocal threadLocal = new ThreadLocal() { 53 | @Override 54 | protected Object initialValue() { 55 | return new Object(); 56 | } 57 | }; 58 | 59 | new Thread(() -> { 60 | System.out.println(threadLocal.get()); 61 | }).start(); 62 | System.out.println(threadLocal.get()); 63 | ``` 64 | 数据输出如下,每一个想通过get方法获取到的值都是不一样的。 65 | ``` 66 | java.lang.Object@4f90c612 67 | java.lang.Object@65ab7765 68 | ``` 69 | 70 | #### ThreadLocal源码分析 71 | 72 | ##### set(T t)方法 73 | set方法主要是为ThreadLocal指定将要被存储的数据,如果重写了initialValue方法,在不钓鱼set方法的情况下,数据的初始值是initialValue方法计算结果。 74 | ```java 75 | //ThreadLocal的set方法 76 | public void set(T value) { 77 | Thread t = Thread.currentThread(); 78 | ThreadLocalMap map = getMap(t); 79 | if (map != null) 80 | map.set(this, value); 81 | else 82 | createMap(t, value); 83 | } 84 | //ThreadLocal的createMap方法 85 | void createMap(Thread t, T firstValue) { 86 | t.threadLocals = new ThreadLocalMap(this, firstValue); 87 | } 88 | //ThreadLocalMap的set方法源码 89 | private void set(ThreadLocal key, Object value) { 90 | // We don't use a fast path as with get() because it is at 91 | // least as common to use set() to create new entries as 92 | // it is to replace existing ones, in which case, a fast 93 | // path would fail more often than not. 94 | 95 | Entry[] tab = table; 96 | int len = tab.length; 97 | int i = key.threadLocalHashCode & (len-1); 98 | 99 | for (Entry e = tab[i]; 100 | e != null; 101 | e = tab[i = nextIndex(i, len)]) { 102 | ThreadLocal k = e.get(); 103 | 104 | if (k == key) { 105 | e.value = value; 106 | return; 107 | } 108 | 109 | if (k == null) { 110 | replaceStaleEntry(key, value, i); 111 | return; 112 | } 113 | } 114 | 115 | tab[i] = new Entry(key, value); 116 | int sz = ++size; 117 | if (!cleanSomeSlots(i, sz) && sz >= threshold) 118 | rehash(); 119 | } 120 | ``` 121 | set方法的运行流程大致如下: 122 | 1. 使用Thread.currentThread()获取当前线程,根据当前线程获取与之关联的ThreadLocalMap的数据结构 123 | 2. 如果map为null,则创建一个ThreadLocalMap,用当前ThreadLocal实例作为key,将要存放的数据作为value,对象到ThreadLocal中则是创建了一个Entry 124 | 3. 如果map不为null,调用map的set方法,set方法中会遍历整个map的Entry,如果发现Entry的Key为null,则直接将其逐出兵器使用心得数据占用被逐出数据的位置,这个过程主要是为了防止内存泄漏 125 | 4. 接着创建心得Entry,使用ThreadLocal作为Key,将要存放的数据作为value 126 | 5. 最后再根据ThreadLocalMap的放弃数据元素的大小和阈值做比较,进行可以为null的数据项清理工作。 127 | 128 | ##### get(T t)方法 129 | ```java 130 | //ThreadLocal的get方法 131 | public T get() { 132 | Thread t = Thread.currentThread(); 133 | ThreadLocalMap map = getMap(t); 134 | if (map != null) { 135 | ThreadLocalMap.Entry e = map.getEntry(this); 136 | if (e != null) { 137 | @SuppressWarnings("unchecked") 138 | T result = (T)e.value; 139 | return result; 140 | } 141 | } 142 | return setInitialValue(); 143 | } 144 | 145 | private T setInitialValue() { 146 | T value = initialValue(); 147 | Thread t = Thread.currentThread(); 148 | ThreadLocalMap map = getMap(t); 149 | if (map != null) 150 | map.set(this, value); 151 | else 152 | createMap(t, value); 153 | return value; 154 | } 155 | ``` 156 | get方法的工作流程如下: 157 | 1. 使用Thread.currentThread()获取当前线程,再通过当前线程获取与之关联的ThreadLocalMap,如果获取的到map,则以当前ThreadLocal作为key值获取对于的Entry,如果Entry不为null,则直接返回Entry的值。 158 | 2. 如果通过当前线程获取到的map为null,则需要调用setInitialValue方法,setInitialValue中首先调用initialValue获取初始值,再拿到这个初始值后跟上面的set方面的逻辑基本没啥区别了。 159 | 160 | > 无论是get方法还是set方法,都不可避免地要与ThreadLocalMap和Entry打交道,ThreadLocalMap是一个完全类似于HashMap的数据结构,仅仅用于存放线程存放在ThreadLocal中的数据备份,ThreadLocalMap中的所有方法对外部都是不可见的。 161 | > 在ThreadLocalMap中用于存储数据的是Entry,它是一个WeakReference类型的子类,之所以这样设计是为了能够在JVM发生垃圾回收事件时,能够自动回收防止发现内存泄漏。 162 | 163 | ##### ThreadLocal内存示意图 164 | 上面对于方法流程的分析还是显得有点抽象,我们来看下面这张图,我是从网上下载下来的,好多博客都在引用这张图,对于ThreadLocal运行时内存表现的很形象。 165 | ![image](../img/ThreadLocal.webp) 166 | 167 | - 每个thread中都存在一个map(ThreadLocalMap), map的类型是ThreadLocal.ThreadLocalMap。 168 | - ThreadLocalMap中的key为一个threadlocal实例,value则是我们需要存储的值。 169 | - ThreadLocalMap中是用弱引用包含的hreadlocal实例,当threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收。 170 | - 正常情况下,我们的value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收。 171 | 172 | 之前看到网上有人问过这类问题,ThreadLocalMap.Entry使用弱引用,当堆内存吃紧时,会不会因为ThreadLocal被回收导致该线程获取不到对应的实例? 173 | > 其实因为当前Thread会持有这个ThreadLocalMap,Java里的Thread类每一个都会一个ThreadLocalMap,这个是强引用,虽然ThreadLocalMap中的key是弱引用的ThreadLocal,当内存吃紧时,这个map仍然会有一个强引用指向它,所以gc并不会回收这部分内存,只有当当前Thread退出后,强引用断开,这部分才有可能会被GC回收。 -------------------------------------------------------------------------------- /docs/Java/从源码角度分析ArrayList和Vector的区别.md: -------------------------------------------------------------------------------- 1 | ## ArrayList和Vector的区别 2 | 3 | > 文章已同步发表于微信公众号JasonGaoH, [ArrayList和Vector的区别](https://mp.weixin.qq.com/s?__biz=MzUyNTE2OTAzMQ==&mid=2247483726&idx=1&sn=0fa987066009e2e8c347a7310f5701fe&chksm=fa2379a6cd54f0b0464108f0d82ddea6de723cc181c0a3d873fd1078985c4ac9d007a7a0395d&token=1938879438&lang=zh_CN#rd) 4 | 5 | ArrayList和Vector这两个集合本质上并没有什么太大的不停,他们都实现了List接口,而且底层都是基于Java数组来存储集合元素。 6 | 7 | 在ArrayList集合类的源代码中也可以看到下面一行: 8 | ```java 9 | transient Object[] elementData; // non-private to simplify nested class access 10 | ``` 11 | 12 | 在Vector集合类的源代码中也可以看到类似的一行: 13 | ```java 14 | protected Object[] elementData; 15 | ``` 16 | 17 | 从上面的代码中可以看出,ArrayList使用transient修饰了elementData数组,这保证系统序列化ArrayList对象时不会直接序列化elementData数组,而是通过ArrayList提供的writeObject、readObject方法来实现定制序列化; 18 | 19 | 但对于Vector而言,它没有使用transient修饰elementData数据,而且Vector只提供了一个writeObject方法,并未完全实现订制序列化。 20 | 21 | 从序列化机制的角度来看,ArrayList的实现比Vector的实现更安全。 22 | 23 | 除此之外,Vector其实就是ArrayList的线程安全版本,ArrayList和Vector绝大部分方法都是相同的,只是Vector的方法增加了synchronized修饰。 24 | 25 | 下面先来看ArrayList中的add(int index,E element)方法的源代码。 26 | ```java 27 | public void add(int index, E element) { 28 | //检查是否下标越界 29 | rangeCheckForAdd(index); 30 | //保证ArrayList底层的数组可以保存所有集合元素 31 | ensureCapacityInternal(size + 1); // Increments modCount!! 32 | //将elementData数组中的index位置之后的所有元素向后移动一位 33 | //也就是将elementData数组的index位置的元素空出来 34 | System.arraycopy(elementData, index, elementData, index + 1, 35 | size - index); 36 | //将新元素将入elementData数组的index的位置 37 | elementData[index] = element; 38 | size++; 39 | } 40 | ``` 41 | 42 | 再来看Vector中的add(int index,E element)方法的源代码: 43 | ```java 44 | public void add(int index, E element) { 45 | insertElementAt(element, index); 46 | }; 47 | ``` 48 | 从上面可以看出,Vector的add(int index,E element)方法其实就是insertElementAt(int index,E element). 49 | 50 | 接着来看insertElementAt(int index,E element)的源码。 51 | ```java 52 | public synchronized void insertElementAt(E obj, int index) { 53 | //增加集合的修改次数 54 | modCount++; 55 | //如果添加位置大于集合长度,则抛出异常 56 | if (index > elementCount) { 57 | throw new ArrayIndexOutOfBoundsException(index 58 | + " > " + elementCount); 59 | } 60 | //保证Vector底层的数组可以保存所有集合元素 61 | ensureCapacityHelper(elementCount + 1); 62 | //将elementData数组中的index位置之后的所有元素向后移动一位 63 | //也就是将elementData数组的index位置的元素空出来 64 | System.arraycopy(elementData, index, elementData, index + 1, elementCount - index); 65 | //将新元素将入elementData数组的index的位置 66 | elementData[index] = obj; 67 | elementCount++; 68 | } 69 | 70 | ``` 71 | 72 | 将ArrayList中的add(int index,E element)方法和Vector的insertElementAt(int index,E element)方法进行对比,可以发现Vector的insertElementAt(int index,E element)方法只是多了个synchronized修饰,而且多了一行代码modCount++,这并不代表ArrayList中的add(int index,E element)方法没有这行代码,ArrayList只是将这行代码放在ensureCapacityInternal中完成。 73 | 74 | 接下来我们看ensureCapacityInternal(int minCapacity)方法的源码: 75 | ```java 76 | private void ensureCapacityInternal(int minCapacity) { 77 | //当elementData数组为空,如果传进来的minCapacity<=10时,minCapacity取10,否则取传进来的参数 78 | if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { 79 | minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); 80 | } 81 | 82 | ensureExplicitCapacity(minCapacity); 83 | } 84 | 85 | private void ensureExplicitCapacity(int minCapacity) { 86 | modCount++; 87 | 88 | // overflow-conscious code 89 | //如果minCapacity大于原数组的长度,则需要扩容 90 | if (minCapacity - elementData.length > 0) 91 | grow(minCapacity); 92 | } 93 | private void grow(int minCapacity) { 94 | // overflow-conscious code 95 | int oldCapacity = elementData.length; 96 | //将新容量扩充为原来的1.5倍 97 | int newCapacity = oldCapacity + (oldCapacity >> 1); 98 | //如果新的newCapacity依然小于minCapacity,直接将minCapacity赋值给newCapacity 99 | if (newCapacity - minCapacity < 0) 100 | newCapacity = minCapacity; 101 | //如果新的newCapacity超过最大的数组长度,则进行更大的扩容 102 | if (newCapacity - MAX_ARRAY_SIZE > 0) 103 | newCapacity = hugeCapacity(minCapacity); 104 | // minCapacity is usually close to size, so this is a win: 105 | //通过Arrays.copyOf扩充一个新数组,数组的长度为newCapacity 106 | elementData = Arrays.copyOf(elementData, newCapacity); 107 | } 108 | ``` 109 | 110 | ```java 111 | private void ensureCapacityHelper(int minCapacity) { 112 | // overflow-conscious code 113 | //如果minCapacity大于原数组的长度,则需要扩容 114 | if (minCapacity - elementData.length > 0) 115 | grow(minCapacity); 116 | } 117 | private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 118 | 119 | private void grow(int minCapacity) { 120 | // overflow-conscious code 121 | int oldCapacity = elementData.length; 122 | //如果capacityIncrement大于0,则新的newCapacity等于旧的oldCapacity加上capacityIncrement, 123 | //如果不是,则新的newCapacity等于旧的oldCapacity*2,表示扩容两倍 124 | int newCapacity = oldCapacity + ((capacityIncrement > 0) ? 125 | capacityIncrement : oldCapacity); 126 | //如果新的newCapacity依然小于minCapacity,直接将minCapacity赋值给newCapacity 127 | if (newCapacity - minCapacity < 0) 128 | newCapacity = minCapacity; 129 | //如果新的newCapacity超过最大的数组长度,则进行更大的扩容 130 | if (newCapacity - MAX_ARRAY_SIZE > 0) 131 | newCapacity = hugeCapacity(minCapacity); 132 | //通过Arrays.copyOf扩充一个新数组,数组的长度为newCapacity 133 | elementData = Arrays.copyOf(elementData, newCapacity); 134 | } 135 | ``` 136 | 137 | 将ArrayList中的ensureCapacityInternal和Vector中的ensureCapacityHelper方法进行对比,可以发现这两个方法几乎完全相同,只是在扩充底层数组的容量时略有区别而已。ArrayList总是将底层数组的容量扩充为原来的1.5倍,但Vector则多了一个选择。 138 | 139 | 当capacityIncrement大于0时,扩充后的容量等于原来的容量加上这个capacityIncrement的值,如果不是大于0,则扩充为原来容量的2倍。 140 | 141 | Vector的ensureCapacityHelper方法在扩充数组容量时多一个选择是因为,创建Vector可以传入一个capacityIncrement参数,如下构造方法: 142 | > Vector(int initialCapacity, int capacityIncrement):以initialCapacity作为底层数组的初始长度,以capacityIncrement作为扩充数组时的增大步长来创建Vector对象。 143 | 144 | 但是对于ArrayList而言,它的构造方法最多只能指定一个initialCapacity参数。 145 | 146 | 147 | ### 注意 148 | 即使需要在多线程环境下使用List集合,而且需要保证List集合的线程安全,依然可以避免使用Vector,而是考虑将ArrayList包装成线程安全的集合类。Java提供了一个Collections工具类,通过该工具synchronizedList方法可以将一个普瑞的ArrayList包装成线程安全的ArrayList。 149 | 150 | -------------------------------------------------------------------------------- /docs/Android/开源库/RxJava相关文章.md: -------------------------------------------------------------------------------- 1 | 之前在项目中通过监听 RecyclerView 的scrollEvents来判断是否需要去加载更多。 2 | 3 | 因为我们在RecyclerView滑动的时候来判断是否需要加载更多的,这个时候scrollEvents这边会一直会有回调,所以为了防止在弱网下会出现多次请求的情况,项目中特地加了一个isLoading的flag来防止这种情况的发生。 4 | 5 | ``` 6 | fun loadMore(remainCount: Int = 6,loadFinish: () -> Boolean):Observable { 7 | scrollEvents() 8 | .filter { loadFinish() } 9 | .map { 10 | var realRemainCount = 0 11 | if (layoutManager is StaggeredGridLayoutManager) { 12 | (layoutManager as StaggeredGridLayoutManager).let { 13 | val last = IntArray(it.spanCount) 14 | it.findLastVisibleItemPositions(last) 15 | realRemainCount = it.itemCount - last.last() 16 | } 17 | } 18 | if (layoutManager is LinearLayoutManager) { 19 | (layoutManager as LinearLayoutManager).let { 20 | realRemainCount = it.itemCount - it.findLastVisibleItemPosition() 21 | } 22 | } 23 | 24 | when { 25 | realRemainCount == 1 -> { 26 | PagingState.END 27 | } 28 | realRemainCount <= remainCount -> { 29 | PagingState.PAGING 30 | } 31 | else -> PagingState.OTHERS 32 | } 33 | } 34 | } 35 | 36 | loadMore(){ 37 | isLoading 38 | }.subcribe({ 39 | //这个时候是网络请求,在doOnSubscribe的时候会isLoading设置为true,在doOnTerminal的时候会再将isLoading设置为false。 40 | loadMoreAction() 41 | },{}) 42 | 43 | ``` 44 | 45 | 但是之前在解其他bug的时候我在scrollEvents的subscribe之前切了个线程,类似下面这样。 46 | 47 | ``` 48 | loadMore(){ 49 | isLoading 50 | }.observeOn(AndroidSchedulers.mainThread()) 51 | .subcribe({ 52 | loadMoreAction() 53 | },{}) 54 | 55 | ``` 56 | 57 | 但是这之后有用户反馈feed里出现了重复的笔记,通过log发现是用户在当时触发了两次一模一样的加载更多的请求。 58 | 59 | 所以又回头梳理这个部分的逻辑,于是写了下面这个demo试了一下。 60 | 61 | ```java 62 | 63 | private var isLoading = false 64 | var executor = Executors.newSingleThreadExecutor() 65 | 66 | fun testObserveOn() { 67 | Observable.fromArray(1,2) 68 | .filter { 69 | Log.d("gaohui", "filter") 70 | isLoading.not() 71 | } 72 | .observeOn(Schedulers.from(executor)) 73 | .subscribe({ 74 | isLoading = true 75 | Log.d("gaohui", "subscribe") 76 | Observable.just(it) 77 | .subscribeOn(Schedulers.io()) 78 | .observeOn(Schedulers.from(executor)) 79 | .subscribe({ 80 | Log.d("gaohui", "it $it") 81 | isLoading = false 82 | },{}) 83 | },{}) 84 | } 85 | ``` 86 | 87 | 如上代码,打印结果是这样的: 88 | ``` 89 | 2020-04-03 14:08:40.101 14083-14153/? D/gaohui: filter 90 | 2020-04-03 14:08:40.102 14083-14153/? D/gaohui: filter 91 | 2020-04-03 14:08:40.102 14083-14153/? D/gaohui: subscribe 92 | 2020-04-03 14:08:40.103 14083-14153/? D/gaohui: subscribe 93 | 2020-04-03 14:08:40.103 14083-14153/? D/gaohui: it 1 94 | 2020-04-03 14:08:40.105 14083-14153/? D/gaohui: it 2 95 | ``` 96 | 97 | 当我们把切换线程的observeOn代码注释掉之后 98 | ``` 99 | fun testObserveOn() { 100 | Observable.fromArray(1,2) 101 | .filter { 102 | Log.d("gaohui", "filter") 103 | isLoading.not() 104 | } 105 | //.observeOn(Schedulers.from(executor)) 106 | .subscribe({ 107 | isLoading = true 108 | Log.d("gaohui", "subscribe") 109 | Observable.just(it) 110 | .subscribeOn(Schedulers.io()) 111 | .observeOn(Schedulers.from(executor)) 112 | .subscribe({ 113 | Log.d("gaohui", "it $it") 114 | isLoading = false 115 | },{}) 116 | },{}) 117 | } 118 | 119 | ``` 120 | 121 | 这个时候打印结果是下面这样的。 122 | 123 | ``` 124 | 2020-04-03 14:10:51.057 14258-14258/com.gaohui.android.code.collection D/gaohui: filter 125 | 2020-04-03 14:10:51.057 14258-14258/com.gaohui.android.code.collection D/gaohui: subscribe 126 | 2020-04-03 14:10:51.057 14258-14258/com.gaohui.android.code.collection D/gaohui: filter 127 | 2020-04-03 14:10:51.058 14258-14340/com.gaohui.android.code.collection D/gaohui: it 1 128 | ``` 129 | 130 | 这里我发现把observeOn切到主线程还是切到当前线程,只要执行了切线程的操作,我们的isLoading这个flag的控制效果就失效了。 131 | 132 | 其实这里的observeOn 和 Android 里的 post 效果是一样的,observeOn 这个表示把这个之后的操作包装成一个Runnable 放到observeOn指定的这个Schedulers中去。 133 | 134 | observeOn的逻辑基本都在ObservableObserveOn.java 这个类里面。 135 | 136 | 在ObservableObserveOn.java中ObserveOnObserver这个内部类中的OnNext中,如下所示: 137 | 138 | ``` 139 | @Override 140 | public void onNext(T t) { 141 | if (done) { 142 | return; 143 | } 144 | 145 | if (sourceMode != QueueDisposable.ASYNC) { 146 | queue.offer(t); 147 | } 148 | schedule(); 149 | } 150 | 151 | ``` 152 | 153 | 这里会把这个 t 放到queue 这个队列中。这个t 是我们Observable上游发送过来的数据源,这个queue 是在ObserveOnObserver这个内存类中定义的一个队列。 154 | 155 | ``` 156 | static final class ObserveOnObserver extends BasicIntQueueDisposable 157 | implements Observer, Runnable { 158 | 159 | private static final long serialVersionUID = 6576896619930983584L; 160 | final Observer downstream; 161 | final Scheduler.Worker worker; 162 | final boolean delayError; 163 | final int bufferSize; 164 | 165 | SimpleQueue queue; 166 | ... 167 | } 168 | ``` 169 | 170 | 这个queue是用来存放当前线程需要执行的一些任务的。 171 | 172 | 接着在onNext后面调用了schedule方法。这个schedule就是准备执行这个Runnable。 173 | 174 | ``` 175 | void schedule() { 176 | if (getAndIncrement() == 0) { 177 | worker.schedule(this); 178 | } 179 | } 180 | 181 | ``` 182 | 183 | 这个Runnable里的run方法是下面这样的。 184 | 185 | ``` 186 | @Override 187 | public void run() { 188 | if (outputFused) { 189 | drainFused(); 190 | } else { 191 | drainNormal(); 192 | } 193 | } 194 | ``` 195 | 196 | 我们这里暂且不关注outputFused这个flag是啥意思,这里无论是drainFused方法还是drainNormal方法, 197 | 基本都是轮询queue里的消息,如果queue里有消息需要处理,就取出来执行。这里的逻辑和主线程的MessageQueue很类似,都是轮询,取消息进行处理。 198 | 199 | 看到这里我突然有点明白我们上面那里调用了observeOn之后为啥我们的flag isLoading的控制效果就没有生效了,observeOn之后的操作相当于是非阻塞的,我们切了一个线程,observeOn中的subscribe的代码时不会马上执行的,这样代码又回头去取新的数据流了,这个时候isLoading这个flag是没有改变的,所以导致触发了两次网络请求。 -------------------------------------------------------------------------------- /docs/Android/为什么RxJava的observeOn不能随便用.md: -------------------------------------------------------------------------------- 1 | 之前在项目中通过监听 RecyclerView 的scrollEvents来判断是否需要去加载更多。 2 | 3 | 因为我们在RecyclerView滑动的时候来判断是否需要加载更多的,这个时候scrollEvents这边会一直会有回调,所以为了防止在弱网下会出现多次请求的情况,项目中特地加了一个isLoading的flag来防止这种情况的发生。 4 | 5 | ``` 6 | fun loadMore(remainCount: Int = 6,loadFinish: () -> Boolean):Observable { 7 | scrollEvents() 8 | .filter { loadFinish() } 9 | .map { 10 | var realRemainCount = 0 11 | if (layoutManager is StaggeredGridLayoutManager) { 12 | (layoutManager as StaggeredGridLayoutManager).let { 13 | val last = IntArray(it.spanCount) 14 | it.findLastVisibleItemPositions(last) 15 | realRemainCount = it.itemCount - last.last() 16 | } 17 | } 18 | if (layoutManager is LinearLayoutManager) { 19 | (layoutManager as LinearLayoutManager).let { 20 | realRemainCount = it.itemCount - it.findLastVisibleItemPosition() 21 | } 22 | } 23 | 24 | when { 25 | realRemainCount == 1 -> { 26 | PagingState.END 27 | } 28 | realRemainCount <= remainCount -> { 29 | PagingState.PAGING 30 | } 31 | else -> PagingState.OTHERS 32 | } 33 | } 34 | } 35 | 36 | loadMore(){ 37 | isLoading 38 | }.subcribe({ 39 | //这个时候是网络请求,在doOnSubscribe的时候会isLoading设置为true,在doOnTerminal的时候会再将isLoading设置为false。 40 | loadMoreAction() 41 | },{}) 42 | 43 | ``` 44 | 45 | 但是之前在解其他bug的时候我在scrollEvents的subscribe之前切了个线程,类似下面这样。 46 | 47 | ``` 48 | loadMore(){ 49 | isLoading 50 | }.observeOn(AndroidSchedulers.mainThread()) 51 | .subcribe({ 52 | loadMoreAction() 53 | },{}) 54 | 55 | ``` 56 | 57 | 但是这之后有用户反馈feed里出现了重复的笔记,通过log发现是用户在当时触发了两次一模一样的加载更多的请求。 58 | 59 | 所以又回头梳理这个部分的逻辑,于是写了下面这个demo试了一下。 60 | 61 | ```java 62 | 63 | private var isLoading = false 64 | var executor = Executors.newSingleThreadExecutor() 65 | 66 | fun testObserveOn() { 67 | Observable.fromArray(1,2) 68 | .filter { 69 | Log.d("gaohui", "filter") 70 | isLoading.not() 71 | } 72 | .observeOn(Schedulers.from(executor)) 73 | .subscribe({ 74 | isLoading = true 75 | Log.d("gaohui", "subscribe") 76 | Observable.just(it) 77 | .subscribeOn(Schedulers.io()) 78 | .observeOn(Schedulers.from(executor)) 79 | .subscribe({ 80 | Log.d("gaohui", "it $it") 81 | isLoading = false 82 | },{}) 83 | },{}) 84 | } 85 | ``` 86 | 87 | 如上代码,打印结果是这样的: 88 | ``` 89 | 2020-04-03 14:08:40.101 14083-14153/? D/gaohui: filter 90 | 2020-04-03 14:08:40.102 14083-14153/? D/gaohui: filter 91 | 2020-04-03 14:08:40.102 14083-14153/? D/gaohui: subscribe 92 | 2020-04-03 14:08:40.103 14083-14153/? D/gaohui: subscribe 93 | 2020-04-03 14:08:40.103 14083-14153/? D/gaohui: it 1 94 | 2020-04-03 14:08:40.105 14083-14153/? D/gaohui: it 2 95 | ``` 96 | 97 | 当我们把切换线程的observeOn代码注释掉之后 98 | ``` 99 | fun testObserveOn() { 100 | Observable.fromArray(1,2) 101 | .filter { 102 | Log.d("gaohui", "filter") 103 | isLoading.not() 104 | } 105 | //.observeOn(Schedulers.from(executor)) 106 | .subscribe({ 107 | isLoading = true 108 | Log.d("gaohui", "subscribe") 109 | Observable.just(it) 110 | .subscribeOn(Schedulers.io()) 111 | .observeOn(Schedulers.from(executor)) 112 | .subscribe({ 113 | Log.d("gaohui", "it $it") 114 | isLoading = false 115 | },{}) 116 | },{}) 117 | } 118 | 119 | ``` 120 | 121 | 这个时候打印结果是下面这样的。 122 | 123 | ``` 124 | 2020-04-03 14:10:51.057 14258-14258/com.gaohui.android.code.collection D/gaohui: filter 125 | 2020-04-03 14:10:51.057 14258-14258/com.gaohui.android.code.collection D/gaohui: subscribe 126 | 2020-04-03 14:10:51.057 14258-14258/com.gaohui.android.code.collection D/gaohui: filter 127 | 2020-04-03 14:10:51.058 14258-14340/com.gaohui.android.code.collection D/gaohui: it 1 128 | ``` 129 | 130 | 这里我发现把observeOn切到主线程还是切到当前线程,只要执行了切线程的操作,我们的isLoading这个flag的控制效果就失效了。 131 | 132 | observeOn的逻辑基本都在ObservableObserveOn.java 这个类里面。 133 | 134 | 在ObservableObserveOn.java中ObserveOnObserver这个内部类中的OnNext中,如下所示: 135 | 136 | ``` 137 | @Override 138 | public void onNext(T t) { 139 | if (done) { 140 | return; 141 | } 142 | 143 | if (sourceMode != QueueDisposable.ASYNC) { 144 | queue.offer(t); 145 | } 146 | schedule(); 147 | } 148 | 149 | ``` 150 | 151 | 这里会把这个 t 放到queue 这个队列中。这个t 是我们Observable上游发送过来的数据源,这个queue 是在ObserveOnObserver这个内存类中定义的一个队列。 152 | 153 | ``` 154 | static final class ObserveOnObserver extends BasicIntQueueDisposable 155 | implements Observer, Runnable { 156 | 157 | private static final long serialVersionUID = 6576896619930983584L; 158 | final Observer downstream; 159 | final Scheduler.Worker worker; 160 | final boolean delayError; 161 | final int bufferSize; 162 | 163 | SimpleQueue queue; 164 | ... 165 | } 166 | ``` 167 | 168 | 这个queue是用来存放当前线程需要执行的一些任务的。 169 | 170 | 接着在onNext后面调用了schedule方法。这个schedule就是准备执行这个Runnable。 171 | 172 | ``` 173 | void schedule() { 174 | if (getAndIncrement() == 0) { 175 | worker.schedule(this); 176 | } 177 | } 178 | 179 | ``` 180 | 181 | 这个Runnable里的run方法是下面这样的。 182 | 183 | ``` 184 | @Override 185 | public void run() { 186 | if (outputFused) { 187 | drainFused(); 188 | } else { 189 | drainNormal(); 190 | } 191 | } 192 | ``` 193 | 194 | 我们这里暂且不关注outputFused这个flag是啥意思,这里无论是drainFused方法还是drainNormal方法, 195 | 基本都是轮询queue里的消息,如果queue里有消息需要处理,就取出来执行。这里的逻辑和主线程的MessageQueue很类似,都是轮询,取消息进行处理。 196 | 197 | 看到这里我突然有点明白我们上面那里调用了observeOn之后为啥我们的flag isLoading的控制效果就没有生效了,observeOn之后的操作相当于是非阻塞的,我们切了一个线程,observeOn中的subscribe的代码时不会马上执行的,这样代码又回头去取新的数据流了,这个时候isLoading这个flag是没有改变的,所以导致触发了两次网络请求。 198 | 199 | 这里的observeOn 和 Android 里的 post 效果是一样的,observeOn 这个表示把这个之后的操作包装成一个Runnable 放到observeOn指定的这个Schedulers中去。 -------------------------------------------------------------------------------- /_sidebar.md: -------------------------------------------------------------------------------- 1 | - 开始 2 | 3 | - [概述](/README.md) 4 | 5 | - Algorighm: 6 | - [为什么说二分查找的时间复杂度是O(logn)](/docs/Algorithm/为什么说二分查找的时间复杂度是O(logn).md) 7 | - Android: 8 | - [Android系统工作机制分析](/docs/Android/Android系统工作机制分析.md) 9 | - [从源码角度剖析Handler机制](/docs/Android/从源码角度剖析Handler机制.md) 10 | - [深入分析AsyncTask](/docs/Android/深入分析AsyncTask.md) 11 | - [Handler为什么会泄露](/docs/Android/Handler.md) 12 | - [SharedPreference和ContentProvider](/docs/Android/SharedPreference和ContentProvider.md) 13 | - [android启动流程分析](/docs/Android/android启动流程分析.md) 14 | - [为什么RxJava的observeOn不能随便用](/docs/Android/为什么RxJava的observeOn不能随便用.md) 15 | - Activity: 16 | - [Activity泄露发现与诊断](/docs/Android/Activity/Activity泄露发现与诊断.md) 17 | - [Activity的生命周期和启动模式](/docs/Android/Activity/Activity的生命周期和启动模式.md) 18 | - IPC: 19 | - [Android中的IPC机制](/docs/Android/IPC/Android中的IPC机制.md) 20 | - [使用进程完成后台任务](/docs/Android/IPC/使用进程完成后台任务.md) 21 | - [如何判断自己的进程是被Android_low_memory_killer杀死的](/docs/Android/IPC/如何判断自己的进程是被Android_low_memory_killer杀死的.md) 22 | - UI: 23 | - [RecyclerView中出现item重复的问题分析](/docs/Android/UI/RecyclerView中出现item重复的问题分析.md) 24 | - [RecyclerView和DiffUtils使用问题](/docs/Android/UI/RecyclerView和DiffUtils使用问题.md) 25 | - [RecyclerView缓存](/docs/Android/UI/RecyclerView缓存.md) 26 | - [仿京东、淘宝首页,通过两层嵌套的RecyclerView实现tab的吸顶效果](/docs/Android/UI/仿京东、淘宝首页,通过两层嵌套的RecyclerView实现tab的吸顶效果.md) 27 | - [解决CoordinatorLayout的动画抖动以及回弹问题](/docs/Android/UI/解决CoordinatorLayout的动画抖动以及回弹问题.md) 28 | - 其他: 29 | - [APK打包及安装过程](/docs/Android/其他/APK打包及安装过程.md) 30 | - [Android7.0上的混合编译](/docs/Android/其他/Android7.0上的混合编译.md) 31 | - [Android中的事件传递](/docs/Android/其他/Android中的事件传递.md) 32 | - [BroadcastReceiver和LocalBroadcastReceiver的区别](/docs/Android/其他/BroadcastReceiver和LocalBroadcastReceiver的区别.md) 33 | - [IntentFilter匹配规则](/docs/Android/其他/IntentFilter匹配规则.md) 34 | - [怎么做牛逼的动画](/docs/Android/其他/怎么做牛逼的动画.md) 35 | - [插件化-VirtualAPK](/docs/Android/其他/插件化-VirtualAPK.md) 36 | - [插件化](/docs/Android/其他/插件化.md) 37 | - [热修复](/docs/Android/其他/热修复.md) 38 | - [组件化](/docs/Android/其他/组件化.md) 39 | - [详解APT](/docs/Android/其他/详解APT.md) 40 | - [通过事务支持加快sqlite数据库批量操作的效率](/docs/Android/其他/通过事务支持加快sqlite数据库批量操作的效率.md) 41 | - 图片相关: 42 | - [Best_Practices_for_Using_Alpha](/docs/Android/图片相关/Best_Practices_for_Using_Alpha.md) 43 | - [Bitmap相关文章](/docs/Android/图片相关/Bitmap相关文章.md) 44 | - [DiskLruCache](/docs/Android/图片相关/DiskLruCache.md) 45 | - [LruCache](/docs/Android/图片相关/LruCache.md) 46 | - [从assets目录和drawable加载的Bitmap的区别](/docs/Android/图片相关/从assets目录和drawable加载的Bitmap的区别.md) 47 | - [图像显示原理](/docs/Android/图片相关/图像显示原理.md) 48 | - 开源库: 49 | - [ARouter](/docs/Android/开源库/ARouter.md) 50 | - [ActivityRouter](/docs/Android/开源库/ActivityRouter.md) 51 | - [ActivityRouter](/docs/Android/开源库/ActivityRouter.md) 52 | - [EventBus](/docs/Android/开源库/EventBus.md) 53 | - [Fresco](/docs/Android/开源库/Fresco.md) 54 | - [LeakCanary](/docs/Android/开源库/LeakCanary.md) 55 | - [OKHTTP](/docs/Android/开源库/OKHTTP.md) 56 | - [Retrofit](/docs/Android/开源库/Retrofit.md) 57 | - [RxJava](/docs/Android/开源库/RxJava.md) 58 | - [RxJava相关文章](/docs/Android/开源库/RxJava相关文章.md) 59 | - [tinker](/docs/Android/开源库/tinker.md) 60 | - Android/面试题收集: 61 | - [开源库源码分析系列文章](/docs/Android/开源库/开源库源码分析系列文章.md) 62 | - [Android中面试题总结](/docs/Android/面试题收集/Android中面试题总结.md) 63 | - [Android面试题系列](/docs/Android/面试题收集/Android面试题系列.md) 64 | - [InterView_Backup](/docs/Android/面试题收集/InterView_Backup.md) 65 | - [InterView_Enhance](/docs/Android/面试题收集/InterView_Enhance.md) 66 | - [Interview_1](/docs/Android/面试题收集/Interview_1.md) 67 | - [Interview_2](/docs/Android/面试题收集/Interview_2.md) 68 | - DesignPattern: 69 | - [尽量不要用实现、继承,请用组合、聚合](/docs/DesignPattern/尽量不要用实现、继承,请用组合、聚合.md) 70 | - [设计模式中6大设计原则](/docs/DesignPattern/设计模式中6大设计原则.md) 71 | - JVM: 72 | - [1_Java的内存区域](/docs/JVM/1_Java的内存区域.md) 73 | - [2_JVM的GC和内存分配](/docs/JVM/2_JVM的GC和内存分配.md) 74 | - [3_JVM类加载机制](/docs/JVM/3_JVM类加载机制.md) 75 | - [4_Java内存模型与线程](/docs/JVM/4_Java内存模型与线程.md) 76 | - [5_Java线程安全与锁优化](/docs/JVM/5_Java线程安全与锁优化.md) 77 | - [6_JVM程序编译与代码优化](/docs/JVM/6_JVM程序编译与代码优化.md) 78 | - [7_JVM类加载器](/docs/JVM/7_JVM类加载器.md) 79 | - [8_JVM类文件结构](/docs/JVM/8_JVM类文件结构.md) 80 | - [9_JVM字节码执行引擎](/docs/JVM/9_JVM字节码执行引擎.md) 81 | - [深入理解JVM虚拟机读书笔记](/docs/JVM/深入理解JVM虚拟机读书笔记.md) 82 | - Java: 83 | - [ConcurrentHashMap是如何保证线程安全的](/docs/Java/ConcurrentHashMap是如何保证线程安全的.md) 84 | - [Java中一些比较难理解的知识点](/docs/Java/Java中一些比较难理解的知识点.md) 85 | - [Java中的泛型](/docs/Java/Java中的泛型.md) 86 | - [Java中的注解](/docs/Java/Java中的注解.md) 87 | - [Java基础知识总结](/docs/Java/Java基础知识总结.md) 88 | - [Java系列集合源码分析](/docs/Java/Java系列集合源码分析.md) 89 | - [从源码角度分析ArrayList和Vector的区别](/docs/Java/从源码角度分析ArrayList和Vector的区别.md) 90 | - [从源码角度解析ArrayList和LinkedList的区别](/docs/Java/从源码角度解析ArrayList和LinkedList的区别.md) 91 | - [关于HashMap你需要知道的一些细节](/docs/Java/关于HashMap你需要知道的一些细节.md) 92 | - [我画了近百张图来理解红黑树](/docs/Java/我画了近百张图来理解红黑树.md) 93 | - [深入解析Sting#intern](/docs/Java/深入解析Sting_intern.md) 94 | - Kotlin: 95 | - [Kotlin中的泛型](/docs/Kotlin/Kotlin中的泛型.md) 96 | - MultiThread: 97 | - [Java中多线程理解系列](/docs/MultiThread/Java中多线程理解系列.md) 98 | - [Java中多线程理解系列](/docs/MultiThread/Java中多线程理解系列.md) 99 | - [Java中多线程理解系列](/docs/MultiThread/Java中多线程理解系列.md) 100 | - [一次搞懂sleep、wait、yield、join和interrupted线程相关方法](/docs/MultiThread/一次搞懂sleep、wait、yield、join和interrupted线程相关方法.md) 101 | - [从AtomicInteger来看CAS机制](/docs/MultiThread/从AtomicInteger来看CAS机制.md) 102 | - [使用单一线程维持对象状态的一致性](/docs/MultiThread/使用单一线程维持对象状态的一致性.md) 103 | - [多线程间通信](/docs/MultiThread/多线程间通信.md) 104 | - [深入解析volatile关键字](/docs/MultiThread/深入解析volatile关键字.md) 105 | - [线程池](/docs/MultiThread/线程池.md) 106 | - [线程的生命周期详解](/docs/MultiThread/线程的生命周期详解.md) 107 | - Network: 108 | - [HTTP与HTTPS有什么区别](/docs/Network/HTTP与HTTPS有什么区别.md) 109 | - [Https](/docs/Network/Https.md) 110 | - [TCP](/docs/Network/TCP.md) 111 | - PerformanceOptimization: 112 | - [ANR分析](/docs/PerformanceOptimization/ANR分析.md) 113 | - [App的性能优化系列文章](/docs/PerformanceOptimization/App的性能优化系列文章.md) 114 | - [Feed流上的优化实践](/docs/PerformanceOptimization/Feed流上的优化实践.md) 115 | - [一种Activity预加载的方法](/docs/PerformanceOptimization/一种Activity预加载的方法.md) 116 | - [一种Fragment懒加载的优化策略](/docs/PerformanceOptimization/一种Fragment懒加载的优化策略.md) 117 | - [内存优化](/docs/PerformanceOptimization/内存优化.md) 118 | - [启动优化](/docs/PerformanceOptimization/启动优化.md) 119 | - [图片优化](/docs/PerformanceOptimization/图片优化.md) 120 | - [在provider初始化的时候如何处理耗时操作](/docs/PerformanceOptimization/在provider初始化的时候如何处理耗时操作.md) 121 | - [如何优化Activity启动速度](/docs/PerformanceOptimization/如何优化Activity启动速度.md) 122 | - [稳定性优化](/docs/PerformanceOptimization/稳定性优化.md) 123 | - [绘制优化](/docs/PerformanceOptimization/绘制优化.md) 124 | - [耗电优化](/docs/PerformanceOptimization/耗电优化.md) 125 | -------------------------------------------------------------------------------- /docs/Android/开源库/ActivityRouter.md: -------------------------------------------------------------------------------- 1 | # ActivityRouter 2 | 3 | ##### 1. @Router的作用 4 | 5 | 编译时,注解处理器会扫描所有Java源文件。最终会执行自定义注解处理器`RouterProcessor`中的几个关键方法 6 | 7 | ``` 8 | @AutoService(Processor.class) 9 | public class RouterProcessor extends AbstractProcessor { 10 | 11 | @Override 12 | public synchronized void init(ProcessingEnvironment processingEnv) {} 13 | 14 | @Override 15 | public Set getSupportedAnnotationTypes() {} 16 | 17 | @Override 18 | public boolean process(Set annotations, RoundEnvironment roundEnv) {} 19 | 20 | } 21 | 22 | ``` 23 | 在process()方法中,通过Element、RoundEnvironment等获取到被注解的类的信息,包括类名、注解内容。根据这些信息,借助JavaPoet生成Java文件,此Java文件就是连接注解和使用方的纽带。 24 | 25 | PS: 编译时注解处理器不熟悉的,可以参考[Java注解处理器](https://www.race604.com/annotation-processing/) 26 | 27 | 28 | ##### 2. APT工作流程 29 | 编译时,系统会调用RouterProcessor#process(),在此方法中做下面相关处理: 30 | 31 | 1. 获取所有被Router注解的Element,Element是程序的元素,可以是包、类、方法 32 | 2. 创建map方法 33 | 3. 遍历Element,获取@Routers.value和被注解的Activity(或者是方法) 34 | 4. 将Routers注解中的value(代码中的format)和页面(className)绑定,放到数据类Mapping中 35 | 5. 程序运行就会初始化Router,执行RouterInit.init(),进而执行RouterMapping_app.map()(这一步是注解处理器添加的初始化代码),即处理器生成的代码文件。此刻,所有流程串了起来。 36 | 37 | ``` 38 | RouterProcessor.java 39 | 40 | private boolean handleRouter(String genClassName, RoundEnvironment roundEnv) { 41 | 42 | // 获取被Router注解的Element 43 | Set elements = roundEnv.getElementsAnnotatedWith(Router.class); 44 | 45 | // 创建map方法 46 | MethodSpec.Builder mapMethod = MethodSpec.methodBuilder("map") 47 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC) 48 | .addStatement("java.util.Map transfer = null") 49 | .addStatement("com.github.mzule.activityrouter.router.ExtraTypes extraTypes") 50 | .addCode("\n"); 51 | 52 | 53 | for (String format : router.value()) { 54 | 55 | // 获取被注解的类名或方法名 56 | ClassName className; 57 | Name methodName = null; 58 | if (element.getKind() == ElementKind.CLASS) { 59 | className = ClassName.get((TypeElement) element); 60 | } else if (element.getKind() == ElementKind.METHOD) { 61 | className = ClassName.get((TypeElement) element.getEnclosingElement()); 62 | methodName = element.getSimpleName(); 63 | } else { 64 | throw new IllegalArgumentException("unknow type"); 65 | } 66 | 67 | ... // 安全检查 68 | 69 | // 关键!!! 将@Routers注解中的value(即代码中的format)和页面(className)绑定 70 | if (element.getKind() == ElementKind.CLASS) { 71 | mapMethod.addStatement("com.github.mzule.activityrouter.router.Routers.map($S, $T.class, null, extraTypes)", format, className); 72 | } else { 73 | mapMethod.addStatement("com.github.mzule.activityrouter.router.Routers.map($S, null, " + 74 | "new MethodInvoker() {\n" + 75 | " public void invoke(android.content.Context context, android.os.Bundle bundle, int requestCode) {\n" + 76 | " $T.$N(context, bundle, requestCode);\n" + 77 | " }\n" + 78 | "}, " + 79 | "extraTypes)", format, className, methodName); 80 | } 81 | } 82 | 83 | } 84 | ``` 85 | 86 | 注解处理器生成的文件: 87 | 88 | ``` 89 | RouterMapping_app.java 90 | 91 | public final class RouterMapping_app { 92 | public static final void map() { 93 | java.util.Map transfer = null; 94 | com.github.mzule.activityrouter.router.ExtraTypes extraTypes; 95 | 96 | transfer = null; 97 | extraTypes = new com.github.mzule.activityrouter.router.ExtraTypes(); 98 | extraTypes.setTransfer(transfer); 99 | 100 | // 最重要!!!注解和页面的绑定 101 | com.github.mzule.activityrouter.router.Routers.map("home/:homeName", HomeActivity.class, null, extraTypes); 102 | 103 | } 104 | } 105 | ``` 106 | 107 | 其中,Routers.map()就是将@Routers注解的value、param和对应的Activity放到mappings中。当使用Routers.open(context, url)启动一个Activity时,会遍历mappings,找到和url对应的Mapping,拿到Mapping中事先存储的目标Activity,再使用Intent启动Activity。 108 | 109 | ``` 110 | Routers.java 111 | 112 | static void map(String format, Class activity, MethodInvoker method, ExtraTypes extraTypes) { 113 | mappings.add(new Mapping(format, activity, method, extraTypes)); 114 | } 115 | 116 | private static boolean doOpen(Context context, Uri uri, Bundle extras, int requestCode) { 117 | initIfNeed(); 118 | Path path = Path.create(uri); 119 | 120 | for (Mapping mapping : mappings) { 121 | 122 | //找到和目标url匹配的Mapping 123 | if (mapping.match(path)) { 124 | 125 | // 解析Url中的参数放到Bundle中 126 | Bundle bundle = mapping.parseExtras(uri); 127 | appendExtras(uri, extras, bundle); 128 | 129 | // 如果被注解的是方法,直接通过反射调用方法 130 | if (mapping.getActivity() == null) { 131 | mapping.getMethod().invoke(context, bundle, requestCode); 132 | return true; 133 | } 134 | 135 | // 如果被注解的是Activity,则通过Intent启动 136 | Intent intent = new Intent(context, mapping.getActivity()); 137 | intent.putExtras(bundle); 138 | if (!(context instanceof Activity)) { 139 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 140 | } 141 | 142 | ... 143 | 144 | context.startActivity(intent); 145 | 146 | return true; 147 | } 148 | } 149 | return false; 150 | } 151 | ``` 152 | 153 | ``` 154 | Mapping.java 155 | 156 | public class Mapping { 157 | 158 | private final String format; 159 | private final Class activity; 160 | private final MethodInvoker method; 161 | private final ExtraTypes extraTypes; 162 | private Path formatPath; 163 | 164 | public Mapping(String format, Class activity, MethodInvoker method, ExtraTypes extraTypes) { 165 | if (format == null) { 166 | throw new NullPointerException("format can not be null"); 167 | } 168 | this.format = format; 169 | this.activity = activity; 170 | this.method = method; 171 | this.extraTypes = extraTypes; 172 | 173 | if (format.toLowerCase().startsWith("http://") || format.toLowerCase().startsWith("https://")) { 174 | this.formatPath = Path.create(Uri.parse(format)); 175 | } else { 176 | this.formatPath = Path.create(Uri.parse("helper://".concat(format))); 177 | } 178 | } 179 | } 180 | ``` 181 | 182 | 183 | 应用示例: 184 | 185 | ``` 186 | Routers.open(LaunchActivity.this, "router://home/somebody?id=123") 187 | 188 | 可打开如下页面 189 | 190 | @Router(value = "home/:homeName", stringParams = "source") 191 | public class HomeActivity extends DumpExtrasActivity { 192 | 193 | } 194 | 195 | ``` 196 | 197 | ##### 问题 3. 有没有好办法可以取代Routers.open()使用字段串拼接Url的方式 198 | 199 | 200 | 201 | 202 | --------------------------------------------------------------------------------