├── .DS_Store ├── .gitignore ├── Chatting ├── Android面试的那些事儿.md ├── App性能优化之响应时间优化.md ├── DataBinding系列之基础使用.md ├── RecyclerView性能优化之异步预加载.md ├── RecyclerView的性能优化.md ├── XREADME.md ├── 一名Android程序员的自我修养.md ├── 你真的会使用github吗.md ├── 写给即将面试的你.md ├── 同样都是开发,为什么你不如别人.md ├── 应届生如何成为一名合格的Android开发工程师.md ├── 开源项目创作指南.md ├── 我的2020年年终总结.md ├── 我的三年Android开发总结.md ├── 我的两年博客经验总结.md ├── 我的五年Android开发总结.md ├── 技术人的宅家指南.md ├── 浅谈App的启动优化.md ├── 由一个进度条更新所引发的思考.md ├── 程序员的bug修复宝典.md └── 通用技术面试问题.md ├── LICENSE ├── README.md ├── SourceCodeAnalysis ├── AndroidSystemStartup.md ├── AppProcessStartup.md ├── AppStartup.md ├── BroadcastRegisterSendReceive.md ├── ServiceBind.md └── ServiceStartup.md ├── experience ├── Android应用商店上架技巧.md └── 手把手教你如何巧用Github的Action功能.md ├── framework ├── X-Library系列Android应用框架详解.md ├── XPage系列|这次终于是全自动化注册了!.md └── 整套的版本更新解决方案-XUpdate.md ├── img ├── .DS_Store ├── android_framework.png ├── appprocess.png ├── appprocess1.png ├── appstart.png ├── appstart1.png ├── appstart2.png ├── appstart3.png ├── appstart4.png ├── github_1.png ├── github_2.png ├── receive_broadcast.png ├── register_receiver.png ├── send_broadcast.png ├── servicebind.png ├── servicebind1.png ├── servicebind2.png ├── servicestartup.png ├── servicestartup1.png ├── systemstart.png ├── systemstart2.png └── x_library.png └── video ├── .DS_Store ├── flutter ├── flutter_xupdate_chapter_1.md └── flutter_xupdate_chapter_2.md ├── github ├── DNS_github.txt ├── github_chapter_1.md └── github_chapter_2.md ├── template ├── template_chapter_android.md ├── template_chapter_flutter_1.md └── template_chapter_flutter_2.md ├── xpage ├── xpage_chapter_1.md ├── xpage_chapter_2.md └── xpage_chapter_3.md ├── xui ├── xui_chapter_1.md ├── xui_chapter_2.md └── xui_chapter_3.md └── xupdate ├── xupdate_chapter_1.md ├── xupdate_chapter_2.md ├── xupdate_chapter_3.md └── xupdate_chapter_4.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea/ 38 | .idea/workspace.xml 39 | .idea/tasks.xml 40 | .idea/gradle.xml 41 | .idea/assetWizardSettings.xml 42 | .idea/dictionaries 43 | .idea/libraries 44 | .idea/caches 45 | 46 | # Keystore files 47 | # Uncomment the following line if you do not want to check your keystore files in. 48 | #*.jks 49 | 50 | # External native build folder generated in Android Studio 2.2 and later 51 | .externalNativeBuild 52 | 53 | # Google Services (e.g. APIs or Firebase) 54 | google-services.json 55 | 56 | # Freeline 57 | freeline.py 58 | freeline/ 59 | freeline_project_description.json 60 | 61 | # fastlane 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | fastlane/readme.md 67 | -------------------------------------------------------------------------------- /Chatting/Android面试的那些事儿.md: -------------------------------------------------------------------------------- 1 | 2 | # Android面试的那些事儿 3 | 4 | > 跳槽,这在 IT 互联网圈是非常普遍的,也是让自己升职加薪,走上人生巅峰的重要方式。那么作为一个普通的Android程序猿,我们如何才能斩获大厂offer 呢? 5 | 6 | 疫情向好、面试在即,还在迷茫踌躇中的后浪们,如何才能在面试中让自己脱颖而出,让面试官眼前一亮? 7 | 8 | 下面,我将结合我过往的面试经历来帮大家分析,在Android大厂面试过程中,我们需要注意的关键点以及我们需要做怎样的准备。 9 | 10 | 本文适合人群: 刚毕业的大学生、缺乏面试经验的求职者、致力于在大厂社招中寻找Android相关机会的开发者等。 11 | 12 | > 文章末尾我将分享几篇满满干货的面试文章以及资料给大家,记得一定要好好收藏哦!! 13 | 14 | ## 面试前的准备 15 | 16 | > 在这部分,我将详细讲解面试前我们需要做哪些方面的工作,以保证我们在面试过程中更加顺利。 17 | 18 | ### 准备一份漂亮的简历 19 | 20 | **一份漂亮的简历就是你进入大厂的敲门砖。** 21 | 22 | 网上有很多教程教大家如何写出一份漂亮的简历,这里我就不做重复劳动了,直接分享给大家一个简历模板:https://github.com/geekcompany/ResumeSample 23 | 24 | 今天我就要逆向思维讲解一下什么样的简历是糟糕的,这里大家一定要检查一下自己的简历有没有如下的毛病: 25 | 26 | * `薪资期望定得过高或者过低`。我们在简历上填写的期望薪资,建议和投递的岗位薪资范围较为接近,懒人可以直接填面议。因为定得过高,面试官看到之后可能会加大面试过程中提问问题的难度。(你想啊,你定的薪资都比面试官高那么多,人家心里什么滋味?还不把你往死里问,看看你到底值不值这个价?)但是如果你定得过低,面试官可能直接就选择忽视你的简历了,毕竟工资又不是面试官发的,人家只是想招进来一个有能力可以背锅的,你定得那么低,明显是对自己水平没信心,也就不会考虑你了。 27 | 28 | * `对自己盲目自信,自己搞不明白的也往简历里写,什么都写精通`。这也是非常常见的问题。简历最讲究的是真诚,会什么就写什么,不要为了凸显自己多厉害而胡乱往上写,否则害的还是你自己。你要知道的是,一般好一点的面试官都会简单结合你简历上填写的内容进行针对性的提问。因为技术可提问的点非常多,但是面试的时间是有限的,那么如何才能快速地考察一个人的技术水平呢?最简单的方式就是结合这个人的工作经历进行针对性的提问。**其实面试最基本的一项任务就是验证你简历内容的真实性**。 29 | 30 | * `简历内容过于丰富,技能内容与岗位描述匹配度不高`。我就经常在Android招聘岗位上收到很多奇葩的简历。这些人的技能树通常是:C,Android,后台,js等,也就是俗称的全干工程师。说真的,即使你真的全会,你写的这个简历也只是适合小厂的面试,因为大厂是不会去招一个什么都会,什么都不精的人的。你需要在简历中着重突出你区别于其他人的优势,最好的做法就是什么样的岗位投递什么样的简历,多做几套简历作为备选。 31 | 32 | * `工作经历过于丰富`。例如3年待过3家及以上数量的公司。工作经历丰富固然是好事,但你也不能全都写到简历里去,选择2~3家较为有名的公司介绍一下即可,否则别人会对你的团队协作能力以及忠诚度提出质疑。 33 | 34 | * `项目经验过于简单或者论文化`。无论你的项目经验是多还是少,列举3~4个即可。除此之外,项目经验切忌不要论文化,我经常看到很多人的简历上项目经验是大段大段的描述,加起来可能有2~3页纸...说真的,你写这么多,面试官反而不会看,因为想全部看完实在是太累了。这里你只需要简单介绍一下项目的内容、你负责的模块和担任的角色、涉及到的技术以及最后项目的成果等即可。 35 | 36 | * `技术博客或者github主页没有什么内容也写到简历里`。记住技术博客或者github主页这一类的,本来都属于加分项,可有可无的,但是如果你写了,面试官誓必会满怀期待地点进去看,如果这个时候呈现给他的却是空白页或者寥寥几行内容的话,这种一落千丈的感受会给面试官留下非常不好的印象。 37 | 38 | * `简历中填写很多对求职无关的内容`。与职位要求无关的内容就不需要写到简历里去了。因为你的简历是拿去找工作用的,任何一个与找工作无关的内容写到简历里只会浪费你简历的空间。例如你的一些兴趣爱好或者无关证件。 39 | 40 | ### 自我介绍要背得滚瓜烂熟 41 | 42 | 自我介绍可以说是面试的必要环节,无论你参加什么形式的面试,面试官一定会首先让你做一个简单的自我介绍,所以自我介绍这一关一定要准备充分,最好做到烂熟于心。 43 | 44 | **自我介绍不是简历的重复背诵。我们在做自我介绍的时候,一定要把握好重点,切忌过长或者过短。** 45 | 46 | 自我介绍的过程,也是一个自我推销的过程。你可以把面试官当作你的顾客,而把你自己当作推销的产品。你要做的就是使用最真诚的方式,把你个人的工作经验、优点、能力与面试公司的岗位需求紧密结合起来,让面试官相信招这个人进来确实可以分担工作的压力。 47 | 48 | 那么我们在做自我介绍的时候,需要介绍哪些内容呢?下面我简单列举一些内容供大家参考: 49 | 50 | * `个人基本信息`。个人信息的介绍要突出重点。我们需要把重点放在与**「公司需求」**匹配的信息上,如果该信息匹配或有关联,那么我们就说,如果完全没关联,那就一句话带过或者不说。 51 | * `工作经历`。如果你的工作经历非常丰富,那么简单挑1~2家和目前应聘公司类似的简单介绍一下即可,其实底层的逻辑就是过去经历是否与目前应聘岗位相匹配或有关联。 52 | * `项目经历`。项目经历不要讲太多,挑一个匹配的或者印象最深刻的重点讲一下即可,其他的可以一笔带过。项目经历可以简单从四个维度展开:项目的背景、项目的内容、你在项目中承担的角色和工作、项目的成果或者业绩。 53 | * `未来愿景`。说一些积极向上的内容,进一步说明自身与岗位相匹配,描绘未来愿景,从而更好地打动面试官。(要让面试官觉得招你进来是非常有价值的,小伙子不仅是冲着钱来的,还是有追求讲情怀的) 54 | 55 | 自我介绍不易过长,准备2~3分钟即可。与此同时,你平时还需要多加练习,根据不同的公司、不同的场合以及面试的不同岗位,进行不同内容的自我介绍。 56 | 57 | ### 面试前多刷刷面试题 58 | 59 | > 面试前多刷面试题,是对面试最起码的尊重。 60 | 61 | 虽然我在这里不提倡大家临时抱佛脚,但是适当地抱一抱佛脚也比那些什么都不准备,就直接裸面的人要好很多,至少你的态度是端正的。 62 | 63 | 临近年终,很多人开始蠢蠢欲动了,这段时间面试的时候,我就经常能够面到几个啥都不准备,直接甩两膀子就过来面试的。面试基本上是一问三不知,要么就是说之前看过忘了...更有甚者直接就说,我就是出来面个试感受一下面试气氛以及最新行情的... 64 | 65 | 拜托,能不能给予面试最起码的尊重?你来面试也是需要花费面试官时间的,简单准备一下不香嘛?万一你运气好,恰好这家公司职位扩充,降低面试要求了呢?你这么随便,岂不是把白花花的机会全都给浪费掉了嘛! 66 | 67 | ### 面试前先了解一下应聘的公司及职位 68 | 69 | 提前了解一下应聘的公司及职位内容,可以避免一些不必要的尴尬: 70 | 71 | ``` 72 | “你知道我们公司是做什么的吗?” 73 | 74 | “emm...” 75 | 76 | “你知道我们这个岗位的工作内容吗?” 77 | 78 | “emm...” 79 | 80 | (老哥!你真的是来面试的吗?!) 81 | ``` 82 | 83 | 如果你是面试官,你会用一个都不知道公司和岗位职责是什么的人吗?这至少说明了2个问题: 84 | 85 | * 1.他对这次面试是不重视的!(那他怎么会对他的工作重视呢?) 86 | * 2.录用他的风险很高!(他要是工作一段时间发现不适合怎么办?) 87 | 88 | 所以我们在面试前,一定要先在网上搜索一下应聘公司的详细资料以及职位信息:百度、脉脉、企查查、看准网,企业官网等都可以获取到。 89 | 90 | 那么提前了解这些信息对我们面试会有哪些方面的帮助呢? 91 | 92 | * 1.方便我们准备与之匹配的简历。如果你应聘的是一家做手机Rom定制开发的公司,那么你的简历就需要围绕着手机Rom定制以及Android Framework开发展开。 93 | * 2.方便我们准备自我介绍的重点。如果你应聘的是一个手机蓝牙开发的职位,那么你在自我介绍的时候就需要突出你在设备通讯以及bluetooth、ble方面的经验和能力。 94 | * 3.方便我们准备与面试相关的面试题。如果你应聘的部门是做技术中台的,那么你可能就需要多准备一些技术中台、设计模式、框架设计、算法方面的知识。 95 | * 4.方便我们准备面试时提问的问题。如果你对应聘企业的一些规章、福利或者工作内容有疑问的可以提前准备一些相关问题。 96 | 97 | --- 98 | 99 | ## 面试过程中需要注意的点 100 | 101 | ### 保持良好的心态 102 | 103 | 只要我们在面试之前做好充足的准备,那么我们就应该有足够的信心去面对接下来的各种提问,我们唯一需要做的就是保持良好的心态,下面我简单归纳几点: 104 | 105 | * `淡化成败意识`。我们要以一个正常的心态去面对面试,毕竟这不同于研究生面试或者公务员面试那种考试性质的面试,即使没面上你也不会损失什么,何况还能积累面试的经验,所以我们有什么可以担心的呢,就权当是和面试官聊聊天,讨论讨论技术罢了。 106 | 107 | * `保持自信`。很多面试官在面试的时候,喜欢采用**"你确定吗?"**这一类的反问句去反问应聘者的回答,以核实应聘者对知识的掌握是否牢靠。如果这个时候你就开始怀疑自己,出现举棋不定的情况的话,那么面试官可能就会认为你之前的回答只是道听途说,或者就是瞎猜的,根本就没有掌握这个知识点。 108 | 109 | * `不要紧张`。紧张的表现主要包括:说话结巴,语无伦次,逻辑混乱,神情慌张,下意识做很多小动作,目光斜视等。这些在面试官看来都是紧张的表现,如果遇到好的面试官可能还会提醒你一下,否则面试基本是凉凉了。 110 | 111 | * `冷静思考`。在考官提问问题之后一定要先冷静思考,理清思路,不要急于回答。当遇到问题不清楚或者疑惑的时候,可以主动询问面试官,表达自己的疑惑。这样一方面显得比较沉着冷静,稳重得体。另一方面也可以给自己留出时间理清思路清晰回答。 112 | 113 | ### 注意基本礼仪 114 | 115 | > 无论任何时候,我们在与别人交谈的过程中都应当遵守最基本的礼仪。 116 | 117 | * 面试一定要准时,遇事提前沟通,不可随意放别人鸽子。 118 | * 说话一定要注意语速,不可过快或过慢,口齿要清晰。 119 | * 面试过程保持一定的严肃性,不可过于散漫甚至笑场。 120 | * 不要随意打断面试官的话,这是非常不礼貌的行为, 121 | * 回答面试官提出的问题时一定要有条理,逐句回答。 122 | * 视频或者电话面试的时候,一定要选择在一个相对安静的环境下进行。 123 | 124 | ### 合理运用表达技巧 125 | 126 | > 一个人的语言表达艺术标志着你的成熟和素养。尤其是在大厂中,很多问题其实是可以通过沟通来解决的,所以一个人的语言表达能力往往也是面试官需要考核的内容。 127 | 128 | 我们应该学会在面试过程中合理运用表达技巧,去凸显自己的语言表达能力。 129 | 130 | 那么我们应该怎么做呢,这里我仅仅是简单列举一些供大家参考: 131 | 132 | * 吐字清晰、大方得体、语速适中。 133 | * 说话的语气要平和,不可忽高忽低,也不能过于情绪化。 134 | * 认真聆听面试官的发言,注意面试官一些细微的表情变化以及手势动作。 135 | * 必要时可以使用一些机智、幽默的话术。 136 | * 当语言并不能完全表达意思的时候,可以加上手势或者书面的形式加以描述。 137 | 138 | ### 多做一些积极的沟通 139 | 140 | 我们在面试过程中,难免会遇到一些我们平时从未接触过的内容,如果这个时候你只是简单地回复"我没做过"、"我不了解"、"之前工作没有这方面的要求"之类的消极回答,最终的面试总评可能会被标上不善于思考和分析问题,从而导致面试分数大打折扣,因为没有哪个面试官是喜欢不善于思考和分析的应聘者的,尤其你应聘的还是一个研发岗位。 141 | 142 | 这里我建议大家在面试过程中最好还是多做一些积极的回答,少做一些消极的回答,除非你对这个问题是彻底不了解。 143 | 144 | 这里我们可以先和面试官通个气,表明自己平时对这块接触得不多,不过可以简单分析一下。如果这个时候面试官没有明确你不需要继续进行下去的话,你就可以简单思考和分析一下,然后提出你的观点。如果遇到一些好一点的面试官,说不定还会提醒你一下,或者和你一起分析,这就非常棒了。 145 | 146 | ### 面试禁忌 147 | 148 | 我们在面试的过程中,一定要注意避雷,以下列举出来的禁忌一定不要去尝试触碰! 149 | 150 | * `切勿答非所问,偷换概念`。当面试官提出一个你并不是很了解的问题时候,即使冷场也不要答非所问,进行偷换概念。因为面试最讲究的就是**真诚**二字。你这样做只会加深面试官对你的厌恶。 151 | 152 | * `切勿侃侃而谈`。有很多人在面试时,为了能够在面试官面前一展"风采",常常是夸夸其谈,口若悬河,殊不知这其实是犯了大忌的。因为在面试官眼里,你这样啪啦啪啦没完没了地讲下去,面试官可能会有如下四种理解: 153 | 154 | * 没有快速答到要点,认为你并没有get到这个问题的本质,对这块掌握得不够。 155 | * 卖弄自己的知识,日后和这种人合作起来会不会很费力? 156 | * 废话连篇,浪费我的时间,这样的人工作起来效率是否会打折扣? 157 | * 知识掌握得还是比较详细的。(这可能是唯一一个为数不多的正面评价吧) 158 | 159 | * `切勿进行不当的反问`。在一个不恰当的时机进行一个不恰当的反问,势必会导致很多麻烦。我们在面试过程中,一定不要和面试官进行争论或者反问面试官(虚心求教还是可以的)。如果面试官的水平或者度量还好的话可能没什么关系,但是也不能排除哪些个水平一般或者度量较小的面试官,你和他争论是没有任何好处的。你来这儿面试是为了找工作的,不是去争个对与错的,得罪了面试官没有任何好处。 160 | 161 | * `切勿套近乎`。面试过程中,一定要注意保持与面试官的距离,不要上来就套近乎,整得好像你跟面试官很熟似的。即使问题答不上来也不要笑场,记住面试是一件非常严肃的事情,不要过于儿戏! 162 | 163 | * `切勿问与面试结果相关的问题`。这里我需要明确的一点是,一轮面试下来,如果你是合适人选的话,面试官一定会告诉你下面面试的流程。你那样急吼吼地想要知道面试结果,非但没有任何意义,反而会加深面试官对你的厌恶。 164 | 165 | --- 166 | 167 | ## Android技术面试一般涉及的要素 168 | 169 | > 上文主要讲解了一些面试通用的技巧,下面就让我来简单讲解一下Android技术面试中主要涉及的内容有哪些。 170 | 171 | ### Java基础 172 | 173 | > 面试Android岗位,Java基础那是必问的。如果项目中使用kotlin比较多的话,可能还会问一些kotlin相关的问题。 174 | 175 | Java, 作为一门基础语言,考核的是应聘者是否具备扎实的基本功。很多培训班或者非科班出身的人,经常会栽在这一环节。一般这个环节的问题答不上来的话,基本上是提前结束了。 176 | 177 | 那么常见面试的Java基础问题有哪些呢?下面我们简单列举一些供大家参考: 178 | 179 | * 1.Java集合类List,Map,Set相关的实现原理。 180 | * 2.Java线程池的实现原理和使用 181 | * 3.Java线程同步相关的知识点。 182 | * 4.Java锁机制,以及死锁产生的原因以及解决方案。 183 | * 5.Java反射、泛型、注解相关的知识点以及使用。 184 | * 6.Java类加载机制。 185 | * 7.Java虚拟机的资源回收机制以及算法。 186 | 187 | 以上基本上是面试Android岗位的常见考点,所以我们必须重视对Java语言的学习和理解,即便你在平时工作中使用kotlin较多,也不能忽视对Java基础知识的巩固和学习。 188 | 189 | ### 设计模式 190 | 191 | > 设计模式其本身其实也是属于Java基础范畴的,只不过部分大厂对设计模式的要求较高,所以会单独拧出来进行考察。 192 | 193 | 很多大厂都喜欢招那些对代码有洁癖,有高追求的人进来。在他们眼里,追求的并不是这个功能如何实现,而是这个功能如何更好地实现。只要性能不佳或者扩展性不够强的话,结局基本上就是推翻重构。 194 | 195 | 其实设计模式的考核因人而异,面试官并不会过于为难你,因为设计模式其本身就是个非常虚的东西,很多人即使是了解设计模式,在平时的工作中也不一定会使用它们。所以设计模式的考核更多的是加分项,并不是必要项,所以即使答不上来其实也是没什么关系的。 196 | 197 | 但是如果你在简历中写了**"熟悉/精通常用的设计模式"**的时候,那么你就要小心了,因为你可能将会面临一波直击灵魂深处的拷问。 198 | 199 | 那么设计模式一般会考察哪些内容呢?下面我们简单列举一些供大家参考: 200 | 201 | * 1.java设计模式的六大设计原则以及它们的关系。 202 | * 2.单例模式、适配器模式、装饰者模式、代理模式、外观模式、策略模式、观察者模式、责任链模式、命令模式、状态模式、中介者模式。这十一种常用的设计模式是考核的重点,你需要掌握它们的优缺点以及使用的场景。 203 | * 3.适配器模式,装饰者模式,外观模式它们之间的区别是什么。 204 | * 4.代理模式、策略模式、状态模式它们之间的区别是什么。 205 | * 5.外观模式、中介模式它们之间的区别是什么。 206 | * 6.静态代理和动态代理的区别,什么场景使用,实现动态代理的几种方式。 207 | * 7.简单列举几个Android源码中使用到设计模式的例子。 208 | 209 | 以上我只是简单列举了一些设计模式常见的考点,因为设计模式的考核相对灵活,因此还是以实际应用场景为主。 210 | 211 | 例如,面试官可能会问你:简单列举几个你常用的设计模式,谈谈它们的优缺点以及使用的场景。 212 | 213 | 这个时候,大多数做Android开发的人脑海里可能只剩下单例模式、观察者模式这些个常常被他们滥用的设计模式。 214 | 215 | 这里我并不推荐大家说这两种设计模式,为什么呢?因为这两种设计模式并不能很好地诠释设计模式的美。而且你要知道任何东西听多了,人的心理是受不了的,这很容易导致面试官心态崩溃。 216 | 217 | 就拿我来说,只要一有人无脑回答单例模式或者观察者模式的时候,我都会情不自禁地提高问题的难度。你要知道,就是一个简简单单的单例模式,我也能给你问到心态崩溃。 218 | 219 | 所以听我的劝,不要一提到设计模式,你脑海里就只剩下单例模式或者观察者模式,多了解了解其他设计模式,对你会有很大的帮助。 220 | 221 | **注意**:对设计模式不够了解的,可以参考我开源的 [architect-java](https://github.com/xuexiangjys/architect-java) 项目,里面有我自己整理的一些简单的算法和设计模式的讲解,可以说非常实用。 222 | 223 | ### Android基础 224 | 225 | > Android基础,不用说这是面试Android岗位必须要问的内容。要是连这个都答不上来,基本你的面试就提前结束了。 226 | 227 | Android基础是任何Android面试都需要考核的内容。不过这也是按级别而定,一般高级开发工程师的面试,Android基础只是一笔带过。 228 | 229 | 那么Android基础有哪些呢?下面我们简单列举一些供大家参考: 230 | 231 | * 1.Handler机制以及相关知识。 232 | * 2.Activity和Fragment生命周期。 233 | * 3.Android四大组件相关的知识。 234 | * 4.Android进程间通信的方式。 235 | * 5.Context相关的知识。 236 | * 6.Activity的启动模式。 237 | * 7.Android动画相关知识。 238 | * 8.Android自定义组件相关知识。 239 | * 9.Android事件分发机制以及触摸事件冲突的处理。 240 | * 10.ANR产生的原因以及避免ANR的方式。 241 | * 11.内存泄漏产生的原因以及定位解决的方式。 242 | * 12.OOM产生的原因以及解决的方式。 243 | * 13.Android页面渲染机制以及优化方式。 244 | * 14.LinearLayout、FrameLayout、RelativeLayout和ConstraintLayout的理解和性能对比。 245 | * 15.Android各版本的特性。 246 | * 16.Android屏幕适配的技巧。 247 | * 17.MVC,MVP,MVVM的理解与实践。 248 | * 18.Android的主题、样式、属性相关的内容。 249 | * 19.JNI相关的知识。 250 | 251 | 以上内容是作为一名合格Android开发工程师所必备的知识点,也是常见的考点,请务必每条都要清楚掌握,这样你在面试过程中才能游刃有余。 252 | 253 | ### Android源码分析 254 | 255 | > Android源码分析,算是要求较高的考核。不过这在大厂面试中非常普遍,因为很多大厂对Android源码的分析和理解都有相当高的要求。 256 | 257 | 那么常见的Android源码分析有哪些呢?下面我们简单列举一些供大家参考: 258 | 259 | * 1.Android系统的启动流程分析。 260 | * 2.APP启动的流程分析。 261 | * 3.Activity的启动流程分析。 262 | * 4.Zygote进程的创建和启动流程分析。 263 | * 5.Window窗口创建和加载的流程分析。 264 | * 6.Dalvik和ART的理解。 265 | * 7.RecyclerView的源码分析。 266 | 267 | 阅读源码是一件相对枯燥的事情,如果平时工作中涉及不到的话就很难坚持下去,因此需要非常强的毅力。不过话又说回来,如果你能够熟练地掌握Android源码的话,那么就非常有机会进入大厂了。 268 | 269 | ### Android进阶技能 270 | 271 | > 这部分一般是对Android高级开发工程师的考核,主要涉及的点主要是一些性能优化,技术细节方面的问题。 272 | 273 | 要想成为一名高级开发工程师,性能优化以及架构设计永远都是绕不开的话题。做技术如果只是广而不深的话,是不可能成为一名高级开发工程师的。只有不断提升自己的不可替代性,才能提高自己的价值。 274 | 275 | 那么常见的Android进阶技能有哪些呢?下面我们简单列举一些供大家参考: 276 | 277 | * 1.App稳定性优化。(crash、性能以及体验等) 278 | * 2.App启动速度优化。 279 | * 3.App内存优化。 280 | * 4.App界面绘制优化。 281 | * 5.App瘦身优化。 282 | * 6.App安全优化。 283 | * 7.网络请求优化。 284 | * 8.WebView使用优化。 285 | * 9.RecyclerView的缓存刷新优化。 286 | * 10.AOP技术的原理和实践。 287 | * 11.gradle脚本持续集成技术。 288 | * 12.App进程保活。 289 | 290 | 以上只是Android进阶技能的一小部分通用性技术,除此之外还有很多细分领域相关的进阶技能。总之,如果这项技术是你掌握而其他人普遍不了解的,那么它就属于进阶技能。 291 | 292 | ### 新技术研究 293 | 294 | > Android这些年的技术发展基本上已经趋向成熟,所以对于新技术的研究也并不是那么看中,可能面试官就是随口问一下,想要知道你的学习欲望强不强罢了。 295 | 296 | 那么有什么新的技术可以在业余时间进行研究呢?下面我们简单列举一些供大家参考: 297 | 298 | * 1.Android组件化。 299 | * 2.Android插件化。 300 | * 3.Android热更新技术。 301 | * 4.Android JetPack框架技术。 302 | * 5.Kotlin开发技术。 303 | * 6.Android Hook技术。 304 | * 7.AOP技术。 305 | * 8.依赖注入技术IoC。 306 | * 9.跨平台开发技术:ReactNative、Flutter等。 307 | 308 | 上面的内容,其实很多已经算不上新技术了,如果你现在还不了解的话,那么你最好花点时间了解一下,否则我只能说你是真的out了。 309 | 310 | ### 开源项目源码分析 311 | 312 | > 开源项目源码分析和Android源码分析一样,也是考核应聘者对原理的理解。如果仅仅只是会使用而不对其原理加以了解的话,那么你也只能算是达到初级水平,这样是无法进入到大厂的。 313 | 314 | 那么开源项目的源码分析我们应该怎么做呢?首先我们需要带着问题一点点阅读源码,搞清楚其内部的实现逻辑,然后梳理出其大致的设计架构,画出UML图,最后总结出其中运用到的设计模式和思想。 315 | 316 | 那么有哪些优质的开源项目值得我们去研究其源码呢?下面我们简单列举一些供大家参考: 317 | 318 | * OkHttp 319 | * Retrofit 320 | * Glide 321 | * LeakCanary 322 | * RxJava 323 | * ARouter 324 | * EventBus 325 | * ButterKnife 326 | * GreenDao 327 | * Dagger2 328 | 329 | 以上我列举的基本上都是我们平时开发过程中经常使用到的开源项目,认真研究和分析他们的设计思想和精髓,并积极运用到我们平时的编码当中去,可以让我们的技术得到质的飞跃! 330 | 331 | ### 算法 332 | 333 | > 部分大厂对Android开发工程师的算法也是有一定要求的,这部分没有什么好说的,打开LeetCode去多刷刷题就可以了。 334 | 335 | 虽说Android开发工程师也需要掌握一定的算法,不过和那些专业做算法的相比肯定是没那么高的要求的,我们并不需要把LeetCode的每道题都刷一遍,只要把一些简单和中等难度的题刷一遍就可以了。 336 | 337 | 那么常见的Android算法题有哪些呢?下面我们简单列举一些供大家参考: 338 | 339 | * 1.各类排序。(尤其二分法插入排序、归并排序需要着重掌握其思想) 340 | * 2.手写反转链表、链表复制、链表合并。 341 | * 3.手写队列或者链表等数据结构的实现。 342 | * 4.字符串匹配、去重问题。 343 | * 5.双指针算法问题。 344 | * 6.数组查重问题。 345 | * 7.二叉树的遍历和序列化。 346 | * 8.贪心算法相关问题。 347 | 348 | 一个好的算法可能极大地提升应用的性能,如果你平时有心的话就会发现在Android源码中也经常能看到算法的身影,感兴趣的可以阅读一下SparseArray的源码。 349 | 350 | ## Android面试资料分享 351 | 352 | > 资料不在于多,而在于精。下面我就简单分享一下曾经对我帮助比较大的几个面试资料。 353 | 354 | * [【建议收藏】2020年中高级Android大厂面试秘籍,为你保驾护航金三银四,直通大厂](https://juejin.cn/post/6844904079177547789) 355 | * 刘望舒的《Android进阶解密》。这本书可以帮你系统地了解Android底层的一些基本原理。这里我就不给购买链接了,大家自己去网上搜吧。 356 | * [设计模式详解](https://github.com/youlookwhat/DesignPattern) 357 | * [计算机基础知识大全](https://github.com/CyC2018/CS-Notes) ,这是一个在Github上star有121k之多的开源项目,里面的内容非常全且基础,是准备面试的必备宝典。 358 | * [Android算法实例和设计模式解读](https://github.com/xuexiangjys/architect-java), 这是我自己整理的内容,里面包含一些简单的算法和设计模式的讲解,可以说非常实用。 359 | 360 | ## 最后 361 | 362 | 今天的文章可谓是积蓄了我这几年来的应聘和面试经历总结出来的经验,干货满满呀!如果你能够一直坚持看到这儿,那么首先我还是十分佩服你的毅力的。不过光是看完而不去付出行动,或者直接进入你的收藏夹里吃灰,那么我写这篇文章就没多大意义了。所以看完之后,还是多多行动起来吧! 363 | 364 | 可以非常负责地说,如果你能够坚持把我上面列举的内容都一个不拉地看完并且全部消化为自己的知识的话,那么你就至少已经达到了Android中级开发工程师以上的水平,进入大厂技术这块是基本没有什么问题的了。 365 | 366 | 另外,如果你有任何Android面试方面的问题,欢迎微信搜索公众号:**【我的Android开源之旅】**,届时我将回答你的疑惑! 367 | 368 | > 我是xuexiangjys,一枚热爱学习,爱好编程,致力于Android架构研究以及开源项目经验分享的技术up主。获取更多资讯,欢迎微信搜索公众号:**【我的Android开源之旅】** 369 | -------------------------------------------------------------------------------- /Chatting/App性能优化之响应时间优化.md: -------------------------------------------------------------------------------- 1 | # App性能优化之响应时间优化 2 | 3 | 速度优化的核心:空间 -> 时间 4 | 5 | * 1.能读缓存读缓存(缓存优先) 6 | * 2.能复用绝不新建(减少新建) 7 | * 3.能不做的尽量不做(减少任务) 8 | * 4.具体问题具体分析(必须做的能提前做就提前做,不必须做的延后做) 9 | 10 | ## 任务执行 11 | 12 | * 业务/任务的整合/拆分,梳理流程 13 | * 串行 -> 并行, 同步 -> 异步 14 | * 执行顺序按优先级调整 15 | * 延迟执行 16 | * 空闲执行,如:`IdleHandler` 17 | 18 | ## 资源加载 19 | 20 | * 懒加载 21 | * 部分加载(分段加载) 22 | * 预加载(数据、布局页面等) 23 | 24 | ## 数据结构 25 | 26 | * 数据结构优化(空间大小、读取速度、复用性、扩展性),例如:ArrayList和LinkedList、HashMap和SparseArray。 27 | * 数据缓存(内存缓存、磁盘缓存、网络缓存),分段缓存。这里可以参考glide. 28 | * 锁优化(减少过度锁,避免死锁),悲观锁/乐观锁。 29 | * 内存优化,避免内存抖动,频繁GC(尤其关注bitmap) 30 | 31 | ## 线程/IO 32 | 33 | * 线程优化(统一、优先级调度、任务特性) 34 | * IO优化(网络IO和磁盘IO),核心是减少IO次数 35 | * 网络:请求合并,请求链路优化,请求体优化,系列化和反序列化优化,请求复用等。 36 | * 磁盘:文件随机读写、SharePreference读写等(例如对于读多写少的,可使用内存缓存) 37 | * log优化(循环中的log打印,不必要的log打印,log等级) 38 | 39 | ## 页面渲染 40 | 41 | * 降低布局层级、减少嵌套、避免过度渲染(背景)(merge,ConstraintLayout) 42 | * 页面复用(include) 43 | * 页面懒加载 44 | * 布局延迟加载(ViewStub) 45 | * inflate优化(布局预加载+异步加载,动态new控件/X2C) 46 | * 动画优化(注意动画的执行耗时和内存占用) 47 | * 自定义view优化(减少onDraw、onLayout、onMeasure的对象创建和执行耗时) 48 | * bitmap和canvas优化(bitmap大小、质量、压缩、复用;canvas复用,clipRect) 49 | * RecycleView优化(减少刷新次数,缓存复用) 50 | 51 | ## 推荐工具 52 | 53 | * systrace、[Perfetto](https://ui.perfetto.dev/#!/) 、Profile 54 | * [performance](https://github.com/xanderwang/performance) 55 | * [LeakCanary](https://github.com/square/leakcanary) 56 | * [DoKit](https://github.com/didi/DoKit) 57 | -------------------------------------------------------------------------------- /Chatting/RecyclerView性能优化之异步预加载.md: -------------------------------------------------------------------------------- 1 | # RecyclerView性能优化之异步预加载 2 | 3 | ## 前言 4 | 5 | 首先需要强调的是,这篇文章是对我之前写的[《浅谈RecyclerView的性能优化》](https://juejin.cn/post/7164032795310817294)文章的补充,建议大家先读完这篇文章后再来看这篇文章,味道更佳。 6 | 7 | 当时由于篇幅的原因,并没有深入展开讲解,于是有很多感兴趣的朋友纷纷留言表示:能不能结合相关的示例代码讲解一下到底如何实现?那么今天我就结合之前讲的如何优化`onCreateViewHolder`的加载时间,讲一讲如何实现`onCreateViewHolder`的异步预加载,文章末尾会给出示例代码的链接地址,希望能给你带来启发。 8 | 9 | ## 分析 10 | 11 | 之前我们讲过,在优化`onCreateViewHolder`方法的时候,可以降低item的布局层级,可以减少界面创建的渲染时间,其本质就是降低view的inflate时间。因为`onCreateViewHolder`最大的耗时部分,就是view的inflate。相信读过`LayoutInflater.inflate`源码的人都知道,这部分的代码是同步操作,并且涉及到大量的文件IO的操作以及锁操作,通常来说这部分的代码快的也需要几毫秒,慢的可能需要几十毫秒乃至上百毫秒也是很有可能的。 如果真到了每个ItemView的inflate需要花上上百毫秒的话,那么在大数据量的RecyclerView进行快速上下滑动的时候,就必然会导致界面的滑动卡顿、不流畅。 12 | 13 | 那么如果你的程序里真的有这样一个列表,它的每个ItemView都需要花上上百毫秒的时间去inflate的话,你该怎么做? 14 | 15 | * 首先就是对布局进行优化,降低item的布局层级。但这点的优化往往是微乎其微的。 16 | * 其次可能就是想办法让设计师重新设计,将布局中的某些内容删除或者折叠了,对暂不展示的内容使用ViewStub进行延迟加载。不过说实在话,你既然有能力让设计师重新设计的话,还干个球的开发啊,直接当项目经理不香吗? 17 | * 最后你可能会考虑不用xml写布局,改为使用代码自己一个一个new布局。话说回来了,一个使用xml加载的布局都要花上上百毫秒的布局,可能xml都快上千行下去了,你确定要自己一个一个new下去? 18 | 19 | 以上的方式,都是建立在列表布局可以修改的情况下,如果我们使用的列表布局是第三方已经提供好的呢?(例如广告SDK等) 20 | 21 | 那么有没有什么办法既可以不用修改当前的xml布局,又可以极大地缩短布局的加载时间呢?毫无疑问,布局异步加载将为你打开新的世界。 22 | 23 | ## 原理 24 | 25 | > Google官方很早就发现了XML布局加载的性能问题,于是在androidx中提供了异步加载工具AsyncLayoutInflater。其本质就是开了一个长期等待的异步线程,在子线程中inflate view,然后把加载好的view通过接口抛出去,完成view的加载。 26 | 27 | 一般来说,对于复杂的列表,往往都对应了复杂的数据,而这复杂的数据往往又是通过服务器获取而来。所以一般来说,一个列表在加载前,往往先需要访问服务器获取数据,然后再刷新列表显示,而这访问服务器的时间大约也在300ms~1000ms之间。很多开发人员对这段时间往往没有加以利用,只是加上一个loading动画了事。 28 | 29 | 其实对于这一段事务真空的时间窗口,我们可以提前进行列表的ItemView的加载,这样等数据请求下来刷新列表的时候,我们`onCreateViewHolder`的时候就可以直接到已经事先预加载好的View缓存池中直接获取View传到ViewHolder中使用,这样`onCreateViewHolder`的创建时间几乎耗时为0,从而极大地提升了列表的加载和渲染速度。详细的流程可以参见下图: 30 | 31 | ![](https://s1.ax1x.com/2023/06/24/pCtDDyR.png) 32 | 33 | ## 实现 34 | 35 | 上面我简单地讲解了一下原理,下一步就是考虑如何实现这样的效果了。 36 | 37 | ### 预加载缓存池 38 | 39 | 首先在预加载前,我们需要先创建一个缓存池来存储预加载的View对象。 40 | 41 | 这里我选择使用`SparseArray`进行存储,key是Int型,存放布局资源的layoutId,value是Object型,存放的是这类布局加载View的集合。 42 | 43 | 这里的集合类型我选择的是LinkedList,因为我们的缓存需要频繁的添加和删除操作,并且LinkedList实现了Deque接口,具备先入先出的能力。 44 | 45 | 这里View的引用我选择的是软引用SoftReference,之所以不采用WeakReference, 目的就是希望缓存能多存在一段时间,避免内存的频繁释放和回收造成内存的抖动。 46 | 47 | ```java 48 | private static class ViewCache { 49 | 50 | private final SparseArray>> mViewPools = new SparseArray<>(); 51 | 52 | @NonNull 53 | public LinkedList> getViewPool(int layoutId) { 54 | LinkedList> views = mViewPools.get(layoutId); 55 | if (views == null) { 56 | views = new LinkedList<>(); 57 | mViewPools.put(layoutId, views); 58 | } 59 | return views; 60 | } 61 | 62 | public int getViewPoolAvailableCount(int layoutId) { 63 | LinkedList> views = getViewPool(layoutId); 64 | Iterator> it = views.iterator(); 65 | int count = 0; 66 | while (it.hasNext()) { 67 | if (it.next().get() != null) { 68 | count++; 69 | } else { 70 | it.remove(); 71 | } 72 | } 73 | return count; 74 | } 75 | 76 | public void putView(int layoutId, View view) { 77 | if (view == null) { 78 | return; 79 | } 80 | getViewPool(layoutId).offer(new SoftReference<>(view)); 81 | } 82 | 83 | @Nullable 84 | public View getView(int layoutId) { 85 | return getViewFromPool(getViewPool(layoutId)); 86 | } 87 | 88 | private View getViewFromPool(@NonNull LinkedList> views) { 89 | if (views.isEmpty()) { 90 | return null; 91 | } 92 | View target = views.pop().get(); 93 | if (target == null) { 94 | return getViewFromPool(views); 95 | } 96 | return target; 97 | } 98 | } 99 | ``` 100 | 101 | 从`getViewFromPool`方法我们可以看出,这里对于ViewCache来说,每次取出一个缓存View使用的是`pop`方法,我们都会将它从Pool中移除。 102 | 103 | ### 布局加载者 104 | 105 | 因为view的加载方法,涉及到三个参数: 资源Id-resourceId, 父布局-root和是否添加到根布局-attachToRoot。 106 | 107 | ```java 108 | public View inflate(int resource, ViewGroup root, boolean attachToRoot) { 109 | 110 | } 111 | ``` 112 | 113 | 这里在`onCreateViewHolder`方法中attachToRoot恒为false,因此异步布局加载只需要前面两个参数以及一个回调接口即可,即如下的定义: 114 | 115 | ```java 116 | public interface ILayoutInflater { 117 | /** 118 | * 异步加载View 119 | * 120 | * @param parent 父布局 121 | * @param layoutId 布局资源id 122 | * @param callback 加载回调 123 | */ 124 | void asyncInflateView(@NonNull ViewGroup parent, int layoutId, InflateCallback callback); 125 | /** 126 | * 同步加载View 127 | * 128 | * @param parent 父布局 129 | * @param layoutId 布局资源id 130 | * @return 加载的View 131 | */ 132 | View inflateView(@NonNull ViewGroup parent, int layoutId); 133 | } 134 | 135 | public interface InflateCallback { 136 | 137 | void onInflateFinished(int layoutId, View view); 138 | } 139 | ``` 140 | 141 | 至于接口实现的话,就直接使用Google官方提供的异步加载工具AsyncLayoutInflater来实现。 142 | 143 | ```java 144 | public class DefaultLayoutInflater implements PreInflateHelper.ILayoutInflater { 145 | 146 | private AsyncLayoutInflater mInflater; 147 | 148 | private DefaultLayoutInflater() {} 149 | 150 | private static final class InstanceHolder { 151 | static final DefaultLayoutInflater sInstance = new DefaultLayoutInflater(); 152 | } 153 | 154 | public static DefaultLayoutInflater get() { 155 | return InstanceHolder.sInstance; 156 | } 157 | 158 | @Override 159 | public void asyncInflateView(@NonNull ViewGroup parent, int layoutId, PreInflateHelper.InflateCallback callback) { 160 | if (mInflater == null) { 161 | Context context = parent.getContext(); 162 | mInflater = new AsyncLayoutInflater(new ContextThemeWrapper(context.getApplicationContext(), context.getTheme())); 163 | } 164 | mInflater.inflate(layoutId, parent, (view, resId, parent1) -> { 165 | if (callback != null) { 166 | callback.onInflateFinished(resId, view); 167 | } 168 | }); 169 | } 170 | 171 | @Override 172 | public View inflateView(@NonNull ViewGroup parent, int layoutId) { 173 | return InflateUtils.getInflateView(parent, layoutId); 174 | } 175 | } 176 | ``` 177 | 178 | ### 预加载辅助类 179 | 180 | 有了预加载缓存池ViewCache和异步加载能力的提供者IAsyncInflater,下面就是来协调这两者进行合作,完成布局的预加载和View的读取。 181 | 182 | 首先需要定义的是根据ViewGroup和layoutId获取View的方法,提供给Adapter的`onCreateViewHolder`方法使用。 183 | 184 | * 首先我们需要去ViewCache中去取是否已有预加载好的view供我们使用。如果有则取出,并进行一次预加载补充给ViewCache。 185 | * 如果没有,就只能同步加载布局了。 186 | 187 | ```java 188 | public View getView(@NonNull ViewGroup parent, int layoutId, int maxCount) { 189 | View view = mViewCache.getView(layoutId); 190 | if (view != null) { 191 | UILog.dTag(TAG, "get view from cache!"); 192 | preloadOnce(parent, layoutId, maxCount); 193 | return view; 194 | } 195 | return mLayoutInflater.inflateView(parent, layoutId); 196 | } 197 | ``` 198 | 199 | 对于预加载布局,并加入缓存的方法实现。 200 | 201 | * 首先我们需要去ViewCache查询当前可用缓存的数量,如果可用缓存的数量大于等于最大数量,即不需要进行预加载。 202 | * 对于需要预加载的,需要计算预加载的数量,如果当前没有强制执行的次数,就直接按剩余最大数量进行加载,否则取强制执行次数和剩余最大数量的最小值进行加载。 203 | * 对于预加载完毕获取的View,直接加入到ViewCache中。 204 | 205 | ```java 206 | public void preload(@NonNull ViewGroup parent, int layoutId, int maxCount, int forcePreCount) { 207 | int viewsAvailableCount = mViewCache.getViewPoolAvailableCount(layoutId); 208 | if (viewsAvailableCount >= maxCount) { 209 | return; 210 | } 211 | int needPreloadCount = maxCount - viewsAvailableCount; 212 | if (forcePreCount > 0) { 213 | needPreloadCount = Math.min(forcePreCount, needPreloadCount); 214 | } 215 | for (int i = 0; i < needPreloadCount; i++) { 216 | // 异步加载View 217 | mLayoutInflater.asyncInflateView(parent, layoutId, new InflateCallback() { 218 | @Override 219 | public void onInflateFinished(int layoutId, View view) { 220 | mViewCache.putView(layoutId, view); 221 | } 222 | }); 223 | } 224 | } 225 | ``` 226 | 227 | ### Adapter中执行预加载 228 | 229 | 有了预加载辅助类PreInflateHelper,下面我们只需要直接调用它的`preload`方法和`getView`方法即可。这里需要注意的是,ViewHolder中ItemView的ViewGroup就是RecyclerView它本身,所以Adapter的构造方法需要传入RecyclerView供预加载辅助类进行预加载。 230 | 231 | ```java 232 | public class OptimizeListAdapter extends MockLongTimeLoadListAdapter { 233 | private static final class InstanceHolder { 234 | static final PreInflateHelper sInstance = new PreInflateHelper(); 235 | } 236 | 237 | public static PreInflateHelper getInflateHelper() { 238 | return OptimizeListAdapter.InstanceHolder.sInstance; 239 | } 240 | 241 | public OptimizeListAdapter(RecyclerView recyclerView) { 242 | getInflateHelper().preload(recyclerView, getItemLayoutId(0)); 243 | } 244 | 245 | @Override 246 | protected View inflateView(@NonNull ViewGroup parent, int layoutId) { 247 | return getInflateHelper().getView(parent, layoutId); 248 | } 249 | } 250 | ``` 251 | 252 | ## 对比实验 253 | 254 | ### 模拟耗时场景 255 | 256 | 为了能够模拟inflateView的极端情况,这里我简单给inflateView增加300ms的线程sleep来模拟耗时操作。 257 | 258 | ```java 259 | /** 260 | * 模拟耗时加载 261 | */ 262 | public static View mockLongTimeLoad(@NonNull ViewGroup parent, int layoutId) { 263 | try { 264 | // 模拟耗时 265 | Thread.sleep(300); 266 | } catch (InterruptedException e) { 267 | e.printStackTrace(); 268 | } 269 | return LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false); 270 | } 271 | ``` 272 | 273 | 对于模拟耗时加载的Adapter,我们调用上面的方法创建ViewHolder。 274 | 275 | ```java 276 | public class MockLongTimeLoadListAdapter extends BaseRecyclerAdapter { 277 | /** 278 | * 这里是加载view的地方, 使用mockLongTimeLoad进行mock 279 | */ 280 | @Override 281 | protected View inflateView(@NonNull ViewGroup parent, int layoutId) { 282 | return InflateUtils.mockLongTimeLoad(parent, layoutId); 283 | } 284 | } 285 | ``` 286 | 287 | 而对于异步加载的耗时模拟,我则是copy了`AsyncLayoutInflater`的源码,然后修改了它在InflateThread中的加载方法: 288 | 289 | ```java 290 | private static class InflateThread extends Thread { 291 | public void runInner() { 292 | // 部分代码省略.... 293 | // 模拟耗时加载 294 | request.view = InflateUtils.mockLongTimeLoad(request.inflater.mInflater, 295 | request.parent, request.resid); 296 | } 297 | } 298 | ``` 299 | 300 | ### 对比数据 301 | 302 | #### 优化前 303 | 304 | ![](https://pic.imgdb.cn/item/64985b221ddac507ccd0ef1b.gif) 305 | 306 | ![](https://s1.ax1x.com/2023/06/25/pCUSkYq.png) 307 | 308 | #### 优化后 309 | 310 | ![](https://pic.imgdb.cn/item/64985b1a1ddac507ccd0e2cd.gif) 311 | 312 | ![](https://s1.ax1x.com/2023/06/25/pCUSAf0.png) 313 | 314 | 从上面的动图和日志,我们不难看出在优化前,每个`onCreateViewHolder`的耗时都在之前设定的300ms以上,这就导致了列表滑动和刷新都会产生比较明显的卡顿。 315 | 316 | 而再看优化后的效果,不仅列表滑动和刷新效果非常丝滑,而且每个`onCreateViewHolder`的耗时都在0ms,极大地提升了列表的刷新和渲染性能。 317 | 318 | ## 总结 319 | 320 | 相信看完以上内容后,你会发现写了这么多,无非就是把`onCreateViewHolder`中加载布局的操作提前,并放到了子线程中去处理,其本质依然是空间换时间,并将列表数据网络请求到列表刷新这段事务真空的时间窗口有效利用起来。 321 | 322 | 本文的全部源码我都放在了github上, 感兴趣的小伙伴可以下下来研究和学习。 323 | 324 | [项目地址: https://github.com/xuexiangjys/XUI/tree/master/app/src/main/java/com/xuexiang/xuidemo/fragment/components/refresh/sample/preload](https://github.com/xuexiangjys/XUI/tree/master/app/src/main/java/com/xuexiang/xuidemo/fragment/components/refresh/sample/preload) 325 | 326 | > 我是xuexiangjys,一枚热爱学习,爱好编程,勤于思考,致力于Android架构研究以及开源项目经验分享的技术up主。获取更多资讯,欢迎微信搜索公众号:**【我的Android开源之旅】** 327 | 328 | -------------------------------------------------------------------------------- /Chatting/RecyclerView的性能优化.md: -------------------------------------------------------------------------------- 1 | 2 | # RecyclerView的性能优化 3 | 4 | > 在我们谈RecyclerView的性能优化之前,先让我们回顾一下RecyclerView的缓存机制。 5 | 6 | ## RecyclerView缓存机制 7 | 8 | 众所周知,RecyclerView拥有四级缓存,它们分别是: 9 | 10 | * Scrap缓存:包括mAttachedScrap和mChangedScrap,又称屏内缓存,不参与滑动时的回收复用,只是用作临时保存的变量。 11 | * mAttachedScrap:只保存重新布局时从RecyclerView分离的item的无效、未移除、未更新的holder。 12 | * mChangedScrap:只会负责保存重新布局时发生变化的item的无效、未移除的holder。 13 | * CacheView缓存:mCachedViews又称离屏缓存,用于保存最新被移除(remove)的ViewHolder,已经和RecyclerView分离的视图,这一级的缓存是有容量限制的,默认最大数量为2。 14 | * ViewCacheExtension:mViewCacheExtension又称拓展缓存,为开发者预留的缓存池,开发者可以自己拓展回收池,一般不会用到。 15 | * RecycledViewPool:终极的回收缓存池,真正存放着被标识废弃(其他池都不愿意回收)的ViewHolder的缓存池。这里的ViewHolder是已经被抹除数据的,没有任何绑定的痕迹,需要重新绑定数据。 16 | 17 | ### RecyclerView的回收原理 18 | 19 | (1)如果是RecyclerView不滚动情况下缓存(比如删除item)、重新布局时。 20 | 21 | * 把屏幕上的ViewHolder与屏幕分离下来,存放到Scrap中,即发生改变的ViewHolder缓存到mChangedScrap中,不发生改变的ViewHolder存放到mAttachedScrap中。 22 | * 剩下ViewHolder会按照`mCachedViews` > `RecycledViewPool`的优先级缓存到mCachedViews或者RecycledViewPool中。 23 | 24 | (2)如果是RecyclerView滚动情况下缓存(比如滑动列表),在滑动时填充布局。 25 | 26 | * 先移除滑出屏幕的item,第一级缓存mCachedViews优先缓存这些ViewHolder。 27 | * 由于mCachedViews最大容量为2,当mCachedViews满了以后,会利用先进先出原则,把旧的ViewHolder存放到RecycledViewPool中后移除掉,腾出空间,再将新的ViewHolder添加到mCachedViews中。 28 | * 最后剩下的ViewHolder都会缓存到终极回收池RecycledViewPool中,它是根据itemType来缓存不同类型的ArrayList,最大容量为5。 29 | 30 | ### RecyclerView的复用原理 31 | 32 | 当RecyclerView要拿一个复用的ViewHolder时: 33 | 34 | * 如果是预加载,则会先去mChangedScrap中精准查找(分别根据position和id)对应的ViewHolder。 35 | * 如果没有就再去mAttachedScrap和mCachedViews中精确查找(先position后id)是不是原来的ViewHolder。 36 | * 如果还没有,则最终去mRecyclerPool找,如果itemType类型匹配对应的ViewHolder,那么返回实例,让它`重新绑定数据`。 37 | * 如果mRecyclerPool也没有返回ViewHolder才会调用`createViewHolder()`重新去创建一个。 38 | 39 | 这里有几点需要注意: 40 | 41 | * 在mChangedScrap、mAttachedScrap、mCachedViews中拿到的ViewHolder都是精准匹配。 42 | * mAttachedScrap和mCachedViews没有发生变化,是直接使用的。 43 | * mChangedScrap由于发生了变化,mRecyclerPool由于数据已被抹去,所以都需要调用`onBindViewHolder()`重新绑定数据才能使用。 44 | 45 | ### 缓存机制总结 46 | 47 | * RecyclerView最多可以缓存 N(屏幕最多可显示的item数【Scrap缓存】) + 2 (屏幕外的缓存【CacheView缓存】) + 5*M (M代表M个ViewType,缓存池的缓存【RecycledViewPool】)。 48 | * RecyclerView实际只有两层缓存可供使用和优化。因为Scrap缓存池不参与滚动的回收复用,所以CacheView缓存池被称为一级缓存,又因为ViewCacheExtension缓存池是给开发者定义的缓存池,一般不用到,所以RecycledViewPool缓存池被称为二级缓存。 49 | 50 | 如果想深入了解RecyclerView缓存机制的同学,可以参考[《RecyclerView的回收复用缓存机制详解》](https://blog.csdn.net/m0_37796683/article/details/105141373) 这篇文章。 51 | 52 | ## 性能优化方案 53 | 54 | 根据上面我们对缓存机制的了解,我们可以简单得到以下几个大方向: 55 | 56 | * 1.提高ViewHolder的复用,减少ViewHolder的创建和数据绑定工作。【最重要】 57 | * 2.优化`onBindViewHolder`方法,减少ViewHolder绑定的时间。由于ViewHolder可能会进行多次绑定,所以在`onBindViewHolder()`尽量只做简单的工作。 58 | * 3.优化`onCreateViewHolder`方法,减少ViewHolder创建的时间。 59 | 60 | ### 提高ViewHolder的复用 61 | 62 | 1.多使用Scrap进行局部更新。 63 | 64 | * (1) 使用`notifyItemChange`、`notifyItemInserted`、`notifyItemMoved`和`notifyItemRemoved`等方法替代`notifyDataSetChanged`方法。 65 | * (2) 使用`notifyItemChanged(int position, @Nullable Object payload)`方法,传入需要刷新的内容进行局部增量刷新。这个方法一般很少有人知道,具体做法如下: 66 | * 首先在notify的时候,在payload中传入需要刷新的数据,一般使用Bundle作为数据的载体。 67 | * 然后重写`RecyclerView.Adapter`的`onBindViewHolder(@NonNull RecyclerViewHolder holder, int position, @NonNull List payloads)`方法 68 | ```java 69 | @Override 70 | public void onBindViewHolder(@NonNull RecyclerViewHolder holder, int position, @NonNull List payloads) { 71 | if (CollectionUtils.isEmpty(payloads)) { 72 | Logger.e("正在进行全量刷新:" + position); 73 | onBindViewHolder(holder, position); 74 | return; 75 | } 76 | // payloads为非空的情况,进行局部刷新 77 | //取出我们在getChangePayload()方法返回的bundle 78 | Bundle payload = WidgetUtils.getChangePayload(payloads); 79 | if (payload == null) { 80 | return; 81 | } 82 | Logger.e("正在进行增量刷新:" + position); 83 | for (String key : payload.keySet()) { 84 | if (KEY_SELECT_STATUS.equals(key)) { 85 | holder.checked(R.id.scb_select, payload.getBoolean(key)); 86 | } 87 | } 88 | } 89 | ``` 90 | 91 | 详细使用方法可参考[XUI中的RecyclerView局部增量刷新](https://github.com/xuexiangjys/XUI/blob/master/app/src/main/java/com/xuexiang/xuidemo/fragment/components/refresh/sample/edit/NewsListEditAdapter.java) 中的代码。 92 | 93 | * (3) 使用`DiffUtil`、`SortedList`进行局部增量刷新,提高刷新效率。和上面讲的传入`payload`原理一样,这两个是Android默认提供给我们使用的两个封装类。这里我以`DiffUtil`举例说明该如何使用。 94 | * 首先需要实现`DiffUtil.Callback`的5个抽象方法,具体可参考[DiffUtilCallback.java](https://github.com/xuexiangjys/XUI/blob/master/app/src/main/java/com/xuexiang/xuidemo/fragment/components/refresh/sample/diffutil/DiffUtilCallback.java) 95 | * 然后调用`DiffUtil.calculateDiff`方法返回比较的结果`DiffUtil.DiffResult`。 96 | * 最后调用`DiffUtil.DiffResult`的`dispatchUpdatesTo`方法,传入RecyclerView.Adapter进行数据刷新。 97 | 98 | 详细使用方法可参考[XUI中的DiffUtil局部刷新](https://github.com/xuexiangjys/XUI/tree/master/app/src/main/java/com/xuexiang/xuidemo/fragment/components/refresh/sample/diffutil) 和 [XUI中的SortedList自动数据排序刷新](https://github.com/xuexiangjys/XUI/tree/master/app/src/main/java/com/xuexiang/xuidemo/fragment/components/refresh/sample/sortedlist) 中的代码。 99 | 100 | 2.合理设置RecyclerViewPool的大小。如果一屏的item较多,那么RecyclerViewPool的大小就不能再使用默认的5,可适度增大Pool池的大小。如果存在RecyclerView中嵌套RecyclerView的情况,可以考虑复用RecyclerViewPool缓存池,减少开销。 101 | 102 | 3.为RecyclerView设置`setHasStableIds`为true,并同时重写RecyclerView.Adapter的`getItemId`方法来给每个Item一个唯一的ID,提高缓存的复用率。 103 | 104 | 4.视情况使用`setItemViewCacheSize(size)`来加大CacheView缓存数目,用空间换取时间提高流畅度。对于可能来回滑动的RecyclerView,把CacheViews的缓存数量设置大一些,可以省去ViewHolder绑定的时间,加快布局显示。 105 | 106 | 5.当两个数据源大部分相似时,使用`swapAdapter`代替`setAdapter`。这是因为`setAdapter`会直接清空RecyclerView上的所有缓存,但是`swapAdapter`会将RecyclerView上的ViewHolder保存到pool中,这样当数据源相似时,就可以提高缓存的复用率。 107 | 108 | ### 优化onBindViewHolder方法 109 | 110 | 1.在onBindViewHolder方法中,去除冗余的setOnItemClick等事件。因为直接在onBindViewHolder方法中创建匿名内部类的方式来实现setOnItemClick,会导致在RecyclerView快速滑动时创建很多对象。应当把事件的绑定在ViewHolder创建的时候和对应的rootView进行绑定。 111 | 112 | 2.数据处理与视图绑定分离,去除onBindViewHolder方法里面的耗时操作,只做纯粹的数据绑定操作。当程序走到onBindViewHolder方法时,数据应当是准备完备的,禁止在onBindViewHolder方法里面进行数据获取的操作。 113 | 114 | 3.有大量图片时,滚动时停止加载图片,停止后再去加载图片。 115 | 116 | 4.对于固定尺寸的item,可以使用`setHasFixedSize`避免`requestLayout`。 117 | 118 | ### 优化onCreateViewHolder方法 119 | 120 | 1.降低item的布局层级,可以减少界面创建的渲染时间。 121 | 122 | 2.Prefetch预取。如果你使用的是嵌套的RecyclerView,或者你自己写LayoutManager,则需要自己实现Prefetch,重写`collectAdjacentPrefetchPositions`方法。 123 | 124 | ### 其他 125 | 126 | 以上都是针对RecyclerView的缓存机制展开的优化方案,其实还有几种方案可供参考。 127 | 128 | 1.取消不需要的item动画。具体的做法是: 129 | ```java 130 | ((SimpleItemAnimator) recyclerView.getItemAnimator()).setSupportsChangeAnimations(false); 131 | ``` 132 | 2.使用`getExtraLayoutSpace`为LayoutManager设置更多的预留空间。当RecyclerView的元素比较高,一屏只能显示一个元素的时候,第一次滑动到第二个元素会卡顿,这个时候就需要预留的额外空间,让RecyclerView预加载可重用的缓存。 133 | 134 | 135 | ## 最后 136 | 137 | 以上就是RecyclerView性能优化的全部内容,俗话说:百闻不如一见,百见不如一干,大家还是赶紧动手尝试着开始进行优化吧! 138 | 139 | > 我是xuexiangjys,一枚热爱学习,爱好编程,勤于思考,致力于Android架构研究以及开源项目经验分享的技术up主。获取更多资讯,欢迎微信搜索公众号:**【我的Android开源之旅】** 140 | 141 | -------------------------------------------------------------------------------- /Chatting/XREADME.md: -------------------------------------------------------------------------------- 1 | # 项目名称 2 | 3 | [![](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg)](https://github.com/xuexiang) 4 | 5 | 本项目是.........【项目简介】 6 | 7 | ## 关于我 8 | 9 | [![github](https://img.shields.io/badge/GitHub-xuexiangjys-blue.svg)](https://github.com/xuexiangjys) [![csdn](https://img.shields.io/badge/CSDN-xuexiangjys-green.svg)](http://blog.csdn.net/xuexiangjys) [![简书](https://img.shields.io/badge/简书-xuexiangjys-red.svg)](https://www.jianshu.com/u/6bf605575337) [![掘金](https://img.shields.io/badge/掘金-xuexiangjys-brightgreen.svg)](https://juejin.im/user/598feef55188257d592e56ed) [![知乎](https://img.shields.io/badge/知乎-xuexiangjys-violet.svg)](https://www.zhihu.com/people/xuexiangjys) 10 | 11 | ## 特点 12 | 13 | * ... 14 | * ... 15 | * ... 16 | 17 | ## 设计思想 18 | 19 | ... 20 | 21 | 22 | ## 演示 23 | 24 | ... 25 | 26 | ## 集成/安装指南 27 | 28 | ... 29 | 30 | ## 使用文档 31 | 32 | ... 33 | 34 | ## 相关项目 35 | 36 | * 1... 37 | * 2... 38 | * 3... 39 | 40 | ## 特别感谢 41 | 42 | * 1... 43 | * 2... 44 | * 3... 45 | 46 | ## 如何贡献 47 | 48 | * issue提交规范 49 | * Pull Request需要遵循的规范 50 | 51 | ## 如果觉得项目还不错,可以考虑打赏一波 52 | 53 | > 你的打赏是我维护的动力,我将会列出所有打赏人员的清单在下方作为凭证,打赏前请留下打赏项目的备注! 54 | 55 | ![收款码](https://ss.im5i.com/2021/06/14/6twG6.png) 56 | 57 | 感谢下面小伙伴的打赏: 58 | 59 | ## 联系方式 60 | 61 | > 更多资讯内容,欢迎扫描关注我的个人微信公众号:【我的Android开源之旅】 62 | 63 | ![微信公众号](https://s1.ax1x.com/2022/04/27/LbGMJH.jpg) 64 | 65 | ## 许可声明 66 | 67 | 本项目禁止用于商业用途,仅供学习,违者后果自负。 68 | -------------------------------------------------------------------------------- /Chatting/一名Android程序员的自我修养.md: -------------------------------------------------------------------------------- 1 | 2 | # 致敬达叔|一名Android程序员的自我修养 3 | 4 | ## 前言 5 | 6 | > "没有小角色,只有小演员",这是出自周星驰电影《喜剧之王》中周星驰扮演的尹天仇随身携带的书--《演员的自我修养》。 7 | 8 | 最近达叔的离世,让我感慨万千。作为在中国土生土长的90后,也算是看着达叔的电影长大的,尤其是他和周星驰合作拍摄的电影,给我带来了很多的快乐。 9 | 10 | 这段时间我又重新温习了一遍《喜剧之王》,在了解了达叔的人生经历之后,回头再细细品味达叔的作品,让我不得不敬佩达叔的职业素养:即使常年只演配角,没有多少戏份,却依然不忘磨练自己的演技,把每场戏都当作主角来演。只凭借出演配角和小角色就能出名的,相信唯有达叔一人了。 11 | 12 | 想到这儿,我不禁想起了我之前读过的一篇文章,文章的内容大概是说:一个人的修养决定了一个人的发展上限。达叔只凭借演配角就能在群星璀璨的演艺圈留下浓墨重彩的一笔,可见他的演员修养是极高的。 13 | 14 | 这里我再举一个我们身边的例子:我们很多人从大学毕业出来找工作后,当时大家的差距还是非常小的。可是为什么随着时间的推移,大家之间的差距会变得越来越大呢?而那些最出众的,也不一定是那些最聪明或者在学校成绩优异的那帮人?这是因为学校里的那套评判标准,是无法去衡量一个人的价值乃至修养的,而真正能衡量这一切的是社会。 15 | 16 | 说了这么多,我们言归正传,作为一名Android程序员,我们如何才能提高我们的自我修养呢? 17 | 18 | ## 如何提高自我修养 19 | 20 | > 通过不断提高我们自身的修养,才能不断突破我们的职业天花板,让我们的未来充满无限可能。 21 | 22 | 如何才能提高我们的自身的自我修养呢?因为我是从事Android开发,这里我就以Android程序员的视角,来阐述我们提高自我修养的六大要素。 23 | 24 | ### 保持对技术的热情 25 | 26 | > 对技术保持持续性的热爱是提高自身修养的先决条件。 27 | 28 | 俗话说,兴趣是最好的老师。由于信息技术日新月异的变化,这就要求我们从业者必须具备极强的自学能力,而保持对技术的热情可以为我们自学提供无穷的能量。 29 | 30 | **对技术的热爱是程序员的灵魂,一个没有了灵魂的程序员注定会沦为一台没有感情的搬砖机器。** 很难想象一个已经对技术失去热情的人,还能够在技术的道路上走多远。 31 | 32 | 那么如何才能保持对技术的热情呢? 33 | 34 | * `保持对新技术的好奇心`。只要保持一颗好奇心,我们才会不断地学习,不断地尝试新技术。 35 | * `保持技术的成就感`。多做有挑战性的技术或者事情,这样才能获得技术上的成就。如果每天做的都是千篇一律的事情,获得不了技术上的成就,那么热情逐渐冷淡是必然的。 36 | * `保持旺盛的精力`。很难想象一个终日无精打采的人能对技术有什么热情。 37 | 38 | 其实,热情这东西是虚的。并不是我们有了热情就能做成每件事的,而是我们做成了每件事获得相应的成就后,才能产生热情的。然后热情又会推动我们更加努力地做去每件事,从而形成一个正向的反馈。 39 | 40 | ### 掌握扎实的基本功 41 | 42 | > 优秀的程序员,拒绝花里胡哨。 43 | 44 | #### 做技术切勿盲目追新追异 45 | 46 | 在这个信息爆发的时代,技术的更新迭代是日新月异的。可能你前脚刚捂热的新技术,后脚就凉了... 47 | 48 | 就拿我们移动端的混合开发技术来说吧,两年前我还在使用`react-native`来写跨平台应用,可两年后我就转用flutter来写了。谁能晓得N年后我在用什么技术来写这些应用,说不定N年后都已经没有移动端的概念了。 49 | 50 | 其实无论混合开发的技术变成啥样,只要你搭载的操作系统还是那个Android或者IOS,对我们来说就是换汤不换药,没有本质上的区别。只要你把原生开发技术掌握扎实,换什么技术不能做? 51 | 52 | 所以,盲目地去追捧或者是学习新技术其实是不太理智的。 53 | 54 | #### 何为基本功 55 | 56 | **所谓的基本功,也就是基础知识,是那些亘古不变的真理。** 任何技术都有其基础知识,计算机也不例外。掌握了它们我们就能够融会贯通,这也是科班出来的程序员比半路出家的程序员的优势所在。 57 | 58 | 这里我就以Android技术为例子,其基本功主要有:计算机组成原理、操作系统(Linux)、计算机网络、数据库、数字电路、Java语言基础和设计模式等。 59 | 60 | 千万不要认为你会写几个界面、几个App,能在Android设备上跑了,就是掌握Android这门技术了...其实这些在我看来,都是一些皮毛而已。不掌握这些基本功,你是永远无法提升自己的技术水平的。 61 | 62 | #### 为什么要掌握扎实的基本功 63 | 64 | 记得以前看过《倚天屠龙记》里面的一个片段:张三丰教张无忌太极拳的时候,张无忌只学习了一遍就学会了。为什么张无忌能够学得这么快?这是因为他学会了九阳神功,内功大增,基础够扎实,像太极拳这种招式当然就一学就会啦。这里内功就好比我说的基本功,太极拳这一类的招式就好比我说的某一项技术。 65 | 66 | 所以,掌握扎实的基本功,是为了我们能够更加快速地学习一门新的技术,这样我们就能在日新月异的技术迭代中立于不败之地。 67 | 68 | ### 追本溯源探寻本质 69 | 70 | > 学习一门技术, 掌握如何去使用只是你迈出去的第一步,后面更重要的是要追本溯源,深究其底层实现原理。说到底,很多技术原本就是相通的,你对技术掌握的深度,决定了你在这条道路上能够走多远。 71 | 72 | **我们在学习任何技术的时候,要做到知其然并且知其所以然,切忌只停留在使用的层面。** 73 | 74 | 试想一下,你作为一名Android开发,每天都在和Activity或者Fragment打交道,如果突然有一天有人问你Activity是如何启动的,而你却答不上来,岂不是很尴尬? 75 | 76 | 做了这么多年的Android,我总结出一条规律:在解决问题没有任何思路的情况下,看源码理解其实现原理,往往能够提供非常好的解决思路。 77 | 78 | 那么怎样才能帮助我们掌握一门技术的实现原理呢? 79 | 80 | * 1.**养成阅读源码的好习惯**。优秀的源码不仅可以帮助我们理解其实现原理,还可以帮助我们快速提升技术水平。 81 | * 2.**熟练掌握设计模式**。掌握了设计模式,我们就可以极大地提升我们阅读源码的速度,加速我们理解其实现原理。 82 | * 3.**多尝试一些复杂/核心/难度性较高的工作**。因为只有涉及到一些复杂的业务或者功能的时候,我们才会涉及到技术的部分实现原理。 83 | * 4.**多思考多总结**。毕竟我们不可能一下子就能掌握一门技术的实现原理,这就要求我们需要进行日积月累的学习和反思,并不断进行总结,这样才能真正掌握这门技术。 84 | 85 | ### 严于律己宽以待人 86 | 87 | > 成功的人往往也是最自律的那波人,这在做技术领域也同样适用。 88 | 89 | 那么在技术领域,我们要怎样才能做到**严于律己宽以待人**呢? 90 | 91 | * 1.**养成良好的编码规范**。你写的代码就可以直接代表你的水平,良好的编码风格可以为你赢得同事的信任。 92 | * 2.**培养阅读文档和编写文档的能力**。阅读文档是作为一名研发人员的基本功,而编写文档则更考验一个人对技术的理解,属于更高层次的能力。 93 | * 3.**严格遵从行业内标准和团队标准**。任何标准都是为了提高效率,降低(沟通)成本。违反标准,尤其是团队标准,是不可原谅的。 94 | * 4.**对自己要有明确的认识和职业规划**。只有真正认识自己,知道自己的优势和不足,才能制定适合自己的职业规划,才能不断提升自己的技术实力。 95 | * 5.**严格的时间观念和时间管理**。今日事今日毕,拒绝拖延,这将会为你赢得更多的机会。 96 | * 6.**不轻易依赖他人**。自己的事情要由自己掌控,对于那些实在不可控,需要依赖他人的,也要做好两手准备。 97 | * 7.**不轻易评价他人**。团队中的每个人都是独特的个体,不可以将自己的标准强加于他人。当然对于一些达成共识的标准(如团队准则),可以在适当的场合委婉地提出。 98 | 99 | 记住一点,**你有多自律,就有多自由**。 100 | 101 | ### 勤于思考善于总结 102 | 103 | > 勤于思考,善于总结,这几乎是每个成功人士必备的基本素质。 104 | 105 | #### 为什么要思考 106 | 107 | 人之所以为人,是因为我们拥有了思考的能力。现代的科学、科技,哪一样不是人类思考的结晶?思考帮助我们提高了效率,节省了时间,降低了成本。 108 | 109 | 如果一件事情,你做过一遍又一遍,但每次只是重复上一次的操作,不进行任何思考的话,那么这件事情即使你是干上一千遍,也不会有多大的进步。 110 | 111 | **初级程序员只会使用框架,中级程序员会修改框架,而高级程序员则会创造框架,这其中的区别就在于思考的频率以及深度**。 112 | 113 | 俗话说,**一个人的成长往往伴随着痛苦**。这是因为只有去穿越和反思痛苦,才能得到远高于常人的思想深度。一个做任何事情都一帆风顺的人,是很难有所成长的。 114 | 115 | #### 总结的好处 116 | 117 | 只会思考,而不善于总结的话,那么思考的成果将会大打折扣。这是因为一个人的经验和能力,需要经过**实践**->**思考**->**总结**这三个过程而来,而总结在其中则起到了至关重要的作用。 118 | 119 | 那么经常总结可以为我们带来哪些好处呢? 120 | 121 | * 1.同样的错误不会再犯第二次。善于总结,可以帮助我们规避很多重复的错误。 122 | * 2.提炼思考的成果,加深印象和理解。总结的核心在于将复杂的事情简单化,这样更易于理解和记忆。 123 | * 3.建立自己的认知体系。总结的最终目的就是将实践中产生的经验和知识,转化为自己的认知,从而建立起自己的认知体系。 124 | 125 | ### 勇于尝试乐于分享 126 | 127 | > 无论你是做前端还是后端,无论你是纯做技术还是硬怼业务,我们都要勇于尝试,勇于探索,尝试成功的话也要乐于分享其中的收获。 128 | 129 | #### 勇于尝试 130 | 131 | 做技术要讲情怀,每天重复去做某件事情或者某项工作,都会极大地消耗我们对技术的热情。这就好比你在打通关游戏,如果你每关遇到的boss都是同一个,技能和难度都是一样的,那打起来是多么无聊? 132 | 133 | 所以在条件允许的情况下,我们一定要勇于尝试,不怕失败。因为只要你做成了一件事情,其带来的成就感一定会远远大于你做普通工作所带来的成就感,而这些都将使你脱颖而出,成就不凡。 134 | 135 | 那么我们可以尝试那些内容呢? 136 | 137 | * 尝试承担更高难度的任务。 138 | * 尝试优化和重构现有代码或者业务。 139 | * 尝试学习并实践新技术。 140 | 141 | 当然勇于尝试不等于无所畏惧地冒风险。在尝试之前一定要进行风险评估,做有把握的尝试,这样即使尝试失败了,也不至于有什么大的损失。 142 | 143 | #### 乐于分享 144 | 145 | 这里可能会有人问道: 146 | 147 | * "我幸幸苦苦进行的尝试,获得的经验为什么要分享给别人?" 148 | * "如果我把经验和技术分享给别人了,那我岂不是没有存在的价值了?" 149 | 150 | 其实不然,分享可能为我们带来很多好处: 151 | 152 | * 最直接的,你不分享出来,谁能知道你尝试的成果了? 153 | * 分享的过程,也是加深理解的过程,可以帮助你掌握得更加牢靠。 154 | * 分享成果,树立自己在该领域的领先地位/权威。 155 | 156 | ## 最后 157 | 158 | 无论我们从事何种职业,我们首先都要热爱它,并把提升自我修养作为一项重要的事情去做,这样才能帮助我们在未来的道路上越走越远。 159 | 160 | > 我是xuexiangjys,一枚热爱学习,爱好编程,勤于思考,致力于Android架构研究以及开源项目经验分享的技术up主。获取更多资讯,欢迎微信搜索公众号:**【我的Android开源之旅】** 161 | -------------------------------------------------------------------------------- /Chatting/你真的会使用github吗.md: -------------------------------------------------------------------------------- 1 | # 你真的会使用github吗? 2 | 3 | > github作为全球最大的开源软件托管平台,自2008年上线以来,一直吸引了无数的程序开发者在上面开源分享自己的项目代码。尤其是在微软收购github之后,更是吸引了很多非程序开发者将自己的知识和经验通过平台分享出来,可以说github是一个蕴藏了无数价值和宝藏的大宝库。然而,对于这样一个极具价值的平台,你真的会使用吗? 4 | 5 | ## github的价值 6 | 7 | github极具价值,下面我简单列举几点: 8 | 9 | * 存放自己的项目代码和文件 10 | 11 | * 解决实际工作中遇到的问题 12 | 13 | * 借鉴别人的知识和经验,提升自我的能力 14 | 15 | * 提供学习交流的场所 16 | 17 | * 搭建自己的博客 18 | 19 | * 提升自身行业的影响力和地位 20 | 21 | --- 22 | 23 | ## 如何使用github 24 | 25 | > 在继续往下阅读之前,请确保你不是所谓的"伸手党",因为以下的内容,可能对这部分的人群毫无价值,读了也是浪费时间。 26 | 27 | ### 一、如何使用github进行项目管理 28 | 29 | 1.首先,你得明确需要上传github的项目内容。这里我要特别提醒,你可别提交与公司相关的内容,你可是签过保密协议的,提交的内容要慎重,尽量避开与公司相关的内容。即使你想提交,也务必自己重写一个,当然公司核心的内容你就别提交了,重写也不行,违规的事情咱不能做。 30 | 31 | 2.在提交项目前,你得先了解`git`指令,因为在github上的所有操作都是通过`git`指令完成的,这里我有一篇[Git 常用命令](https://blog.csdn.net/xuexiangjys/article/details/79875167)可供参考。 32 | 33 | 3.了解完`git`指令后,下面就是开始上传项目代码了,这里我有一篇[如何使用Git命令提交项目代码](https://blog.csdn.net/xuexiangjys/article/details/79874571)可供参考。 34 | 35 | 4.对于项目的管理操作,可以借鉴一下网上的[GitHub项目管理基本操作](https://blog.csdn.net/weixin_41424247/article/details/78998916).这里,你需要了解的内容包括: 36 | 37 | * 主干(master)和分支(branch) 38 | * 合并请求(Pull Request和Merge) 39 | * 打标签(tag)和发布(release) 40 | * 项目介绍(README.md) 41 | * 知识库(wiki) 42 | * 问题建议(issues) 43 | * 项目计划(projects). 44 | 45 | ### 二、如何通过github解决实际工作中遇到的问题 46 | 47 | 1.先确定问题涉及技术的关键词。 48 | 49 | 2.使用github的[搜索功能](https://github.com/search)进行搜索。对结果我们可以通过语言(Languages)和收藏数(Most stars)进行排序筛选,如下图所示: 50 | 51 | ![](../img/github_1.png) 52 | 53 | 3.选择目标参考的项目。选择开源项目一般有以下几点考量因素: 54 | 55 | * 项目的star量:越多越好。 56 | * 项目的活跃度:这里考量的因素包含issue的总体数量、open issue和closed issue的数量、issue回复和解决的速度、项目最后一次提交的时间。 57 | * 文档是否齐全:是否有wiki或者README.md 58 | * 项目代码的质量:设计是否合理,是否符合设计模式原则,考虑项目的可扩展性、便利性和稳定性。 59 | * 开源作者的水平:作者其他项目的star量和行业影响力。 60 | * 注意开源协议,以免不必要的麻烦。 61 | 62 | 如下图所示,标红的我们都需要关注: 63 | 64 | ![](../img/github_2.png) 65 | 66 | 4.确定了参考的项目后,下面就是如何使用别人的开源项目。 67 | 68 | (1)先仔细阅读项目介绍(README.md),大体上了解项目的内容。 69 | 70 | (2)了解如何引用和使用。你可以通过阅读项目介绍(README.md)或者wiki获得。 71 | 72 | (3)如果项目比较大的话,建议参考项目的demo源码。 73 | 74 | 5.出问题了如何解决。【这步很关键】 75 | 76 | > 使用别人的开源项目,难免需求会有些出入,当然也会遇到一些未知的bug,这很正常。下面我将介绍一个最优的解决路线。 77 | 78 | (1) 再回头仔细阅读README.md和wiki,确保自己没有误使用。 79 | 80 | (2) 如果项目有【常见问题】这一项,先看一下能否解决自己的问题。 81 | 82 | (3) 提取问题的关键字,在项目的issue中进行搜索,查看有无可解决的方案,这一点很重要,开源者一般都很讨厌别人提一些重复性的issue。 83 | 84 | (4) 确保你出现的问题,别人都没有提过的情况下,尝试提issue。这里注意将issue的内容描述清楚,如果项目提供issue模板的话就按要求认真填写,开源者没时间在那里揣测你的想法,这样能解决大家的时间。 85 | 86 | (5) 如果你提的issue作者一直不回复的话,这时候有三条路给你选择: 87 | 88 | * 看项目README.md最后有没有交流群或者作者的联系方式,加入后直接反应问题。 89 | 90 | * fork项目,直接修改源码,自己解决问题。如果你想将自己修改后的代码上传代码仓库的话,这里有一篇文章可供参考:[手把手教你使用Gradle脚本上传代码仓库](https://blog.csdn.net/xuexiangjys/article/details/80160954) 91 | 92 | * 放弃该项目,找一个更适合自己的项目。 93 | 94 | ### 三、如何通过github借鉴别人的知识和经验,提升自我的能力 95 | 96 | > 正如Linux之父的那句"Talk is cheap. Show me the code.",我一直奉行源码是最好的老师,看别人吹的技术文章千遍,也不如自己真刀实枪地撸一遍源码来得有效果。事实上,我也正是通过不断地阅读好的开源项目的源码,吸取他们其中好的设计和精髓,一步一步成长过来的,而这些好的开源项目正托管在github上,待你去发现。 97 | 98 | 1.寻找好的开源项目。搜寻好的github开源项目有两种途径: 99 | 100 | * github的[搜索功能](https://github.com/search):如果你对学习的内容有明确的方法,使用这种。 101 | 102 | * github的[每日趋势](https://github.com/trending):如果你对最新的技术热点感兴趣的话,使用这种。 103 | 104 | 2.找到你想要学习的开源项目后,下面介绍我的学习思路供大家参考: 105 | 106 | (1) 首先,clone项目源码到本地,自己试着跑一边源码,体验一下项目运行的效果。 107 | 108 | (2) 接着,大体上了解一下项目的组成结构,粗略阅读一下项目的源码,了解项目大体上的设计思路和主体架构。这一步对我们阅读源码的能力有一定的要求。 109 | 110 | (3) 然后,自己也新建一个项目,从0开始,一步一步尝试着模仿项目的实现思路,自己也跟着写一套。这一步对我们的自学能力有很大的要求,因为这中间你会遇到各种各样的问题,而且都非常困难,这就需要通过搜集大量的资料去了解和学习各种新的知识。也正是这样,你才能不断提升自己的技术水平和解决问题的能力。等你自己的项目能够运行出开源项目一样效果的时候,你就基本上获得了该开源项目80%的价值了。 111 | 112 | (4) 最后,光是单纯模仿别人的开源项目是远远不够的,因为别人的永远是别人的,并不是靠你去模仿就属于你的了。这里我们还需要举一反三的能力,能够从别人开源项目中汲取其中的精华来运用到自己的项目中。那么我们该怎么做呢?下面我将介绍两种途径: 113 | 114 | * 项目改进。在你模仿的过程中,你一定会发现项目中有一些不合理的设计或者先天的设计缺陷,改进他们,拓展和丰富项目的功能,并将其引入到实际的开发工作中去不断检验自己的改进,进行持续性的优化和改进。 115 | 116 | * 重新设计和实现属于自己的开源项目。这对你自身的学习动手能力和设计能力有很高的要求。首先你要有明确的项目实现目标和设计思路,其次你需要将之前模仿过程中汲取到的精华与自身的项目经验结合以及设计思路结合起来,最终设计和实现一个有使用价值的开源项目,并运用到实际的开发工作中去不断检验项目的价值,进行持续性的优化和改进。 117 | 118 | 119 | ### 四、如何通过github搭建自己的博客 120 | 121 | 这里我就不多讲了,网上有很多相关的教程,百度搜索`"使用github搭建博客"`,能搜索到很多结果。这里我推荐一个比较靠谱的文章[Github搭建个人博客](https://blog.csdn.net/xudailong_blog/article/details/78762262)供参考。 122 | 123 | ### 五、如何通过github提升自身行业的影响力和地位 124 | 125 | > 提升自身行业的影响力和地位,并不是一味地去加各种群去推销自己的开源项目或者相互star,从而骗取较高的star量。我们真正需要做的就是不断地去写开源项目,不断地提升开源项目的质量,这样star量和影响力也就自然水到渠成了。那么如何才能提升开源项目的质量呢?下面我将一一讲解。 126 | 127 | 1.优秀的设计思路。优质的开源项目一定是拥有好的设计思路。这里就需要我们对设计模式的七大原则有深刻的了解。这里我推荐我的博客专题[Java设计模式在Android中的实践](https://blog.csdn.net/xuexiangjys/column/info/21784)供大家参考。 128 | 129 | 2.完备的文档介绍。文档才是别人了解你项目的第一手资料。这里包括项目的主页、README.md、wiki和issue模版等内容。项目的文档除了内容上要丰富之外,还需要不断地随着项目的迭代进行文档的更新。 130 | 131 | 3.良好的代码规范。开源的项目,良好的代码规范是必须的,否则别人阅读你的代码将会非常难受,可能阅读一半就放弃你的项目了。这里推荐[阿里巴巴的代码规范利器](https://github.com/alibaba/p3c) 132 | 133 | 4.有效的沟通交流渠道。这里推荐使用QQ群。当然,如果你不怕骚扰的话,也可以放上你的qq号和邮箱。 134 | 135 | 5.丰富的使用案例。这点要求可能比较高了,一般个人开源者很少会去收集项目的使用案例。即使这样,我依然鼓励大家多多收集自己项目的使用案例,因为这会减少很多保守开发者采用本开源项目的顾虑。 -------------------------------------------------------------------------------- /Chatting/写给即将面试的你.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 | 26 | 建议:这里面试官主要是想了解你的工作状态,做过那些内容,考察你能否胜任面试的岗位。还是一句话:实话实说,尽管你可能没做过什么,或者行业也不大匹配。(其实做技术的对行业要求也不高,关键是你的能力和你对面试的这个公司所处行业是否看好)。 27 | 28 | ### 离职/跳槽原因 29 | 30 | 这个问题80%会问,一般只会问你最近一次离职/跳槽的原因。 31 | 32 | 建议:这里面试官主要是想了解你的工作态度、对公司的看法以及职业道德。这里尽量给出一些积极的原因。特别需要注意的是,无论你上一家公司如何糟糕,也不要在面试的过程中直接对其进行抨击,这样会让面试官觉得你人品不咋地。 33 | 34 | --- 35 | 36 | ## 技术专业性面试 37 | 38 | > 这部分的面试基本上包括:简历上涉及的技术问题、职位所需的基础性技术问题以及一些情景性考核技术应变能力的问题。 39 | 40 | ### 结合简历的项目技术问题 41 | 42 | 面试官会首先针对你简历上涉及到的技术知识点对你进行提问,这部分问题基本是主动权都掌握在你的手上,毕竟问的都是你属性的技术知识点,相对比较容易。 43 | 44 | 建议:简历上尽量写自己有过深入了解的内容,对于那些一知半解的知识点千万不要写上去,别给自己挖坑。还有简历中千万不要出现"精通"等字眼,可能会引起面试官的不适。当然也不要写得太泛泛而谈,没有任何深度。 45 | 46 | ### 基础性的技术问题 47 | 48 | 面完你熟悉的领域后,接下来就是和岗位联系较为紧密的基础性技术问题了。这部分考核的内容可能你之前压根就没听说过,又或者有过了解,但也是一知半解,非常模糊,总体上相对较难。 49 | 50 | 建议:这部分的内容绝大多数和岗位要求以及面试官的技术水平相挂钩。这部分的问题一般你都不太熟悉,相对较难,不过即使这样你最好也不要一句"不知道","没做过"了事,你可以结合你做过的类似的内容进行推测,向面试官展现你的问题分析能力和思维能力。 51 | 52 | ### 情境性的技术问题 53 | 54 | 这部分,面试官可能会拿项目目前遇到的问题对你进行提问,又或者是面试官之前一次让他印象非常深刻或者引以自豪的一次技术问题。 55 | 56 | 建议:这里面试官主要是想考察一下你应对突发事件实际解决问题的能力和抗压能力,是一个比较全面性的考核内容。从你的回答中,面试官可以很直观地了解你的实际工作能力、心理素质、分析处理问题的能力、团队协作的能力等。 57 | 58 | 这部分由于问题内容千奇百怪,也没法提出什么宝贵的建议。总之一条就是,千万不能紧张、心态要放正,知道多少说多少,就当是领导给你下发一个任务,你去解决便是,反正他又不会盯着你去完成😅 59 | 60 | --- 61 | 62 | ## 非技术面试 63 | 64 | > 能面到这部分的人,基本上技术大体上是没有问题了。这部分主要包括对个人学习能力、团队协作能力以及个人职业规划方面的考察。 65 | 66 | ### 学习能力 67 | 68 | > 这部分主要考核面试人对技术的兴趣度、学习总结的能力。 69 | 70 | 常见问题: 71 | 72 | * 你平时在工作中是如何积累和进行总结的? 73 | * 你在工作之余,是否有计划地进行学习。你是如何平衡工作、学习和生活的? 74 | * 你平时对新的技术有过了解吗?从何种渠道了解? 75 | 76 | 建议: 77 | 78 | 1.平时工作中尽量留出时间进行思考和总结,时间允许的话可以尝试写写博客。 79 | 2.多关注几个技术社区,推荐"掘金"社区。 80 | 3.有能力的话,可以考虑写一些开源项目,经营一下自己的Github。 81 | 82 | ### 团队协作能力 83 | 84 | > 这部分主要考核你对本公司的工作是否提前有过了解,岗位一致性以及团队协作的能力。 85 | 86 | 常见问题: 87 | 88 | * 你认为自己为什么喜欢这个工作? 89 | * 你喜欢在团队中扮演什么角色? 90 | * 你如何看待团队工作? 91 | 92 | 建议: 93 | 94 | 1.面试其实是一个双向选择的过程。公司既然招你过来面试,肯定是对你有过了解的,那么你对面试的这家公司也要有一定的了解,千万不要稀里糊涂地就过来面试了。 95 | 2.公司招你过来就是来工作的,既然工作肯定就离不开团队,所以尽量往团队协作、团队一致性方面答。 96 | 97 | ### 个人职业规划 98 | 99 | > 这部分主要考核你的个性与职位是否匹配,你的兴趣、价值观和动机与职位是否匹配,你是否能够胜任这份工作。 100 | 101 | 常见问题: 102 | 103 | * 你认为你的优势是什么? 104 | * 你的职业规划是什么? 105 | * 你对自己未来工作有什么期待或者规划? 106 | 107 | 建议: 108 | 109 | 其实面试官压根就不关心你真正的职业规划是怎样的,他们更关心在未来的3-5年里,你是否能在这家公司稳稳地工作下去。 110 | 111 | 所以,你的回答要让面试官既能看到你有事业心(成就动机),又能看到你有平和的心态。千万不要表现自己有多么厉害,那样会让人感觉有点飘,不够稳。 112 | 113 | --- 114 | 115 | ## 你有什么可以问我的吗? 116 | 117 | 在问完上面一系列的问题后,面试官可能还会反问你一句"你有什么可以问我的吗?",这个时候你千万不要一脸懵逼地望着面试官,然后抛出一句"没什么问题了"。 118 | 119 | 这其实是一个非常好的主动展现自己的机会,对于这样的机会一定要好好珍惜,以下问题可供参考: 120 | 121 | * 在未来XXX的时间里,该岗位部门的发展目标是什么? 122 | * 公司/项目组未来有什么样的发展计划? 123 | * 不知道公司在新入职的时候,是否会安排相关的技术培训? 124 | * 我有着XXX的专长,不知在贵公司能否用得上呢? 125 | 126 | 反正总结一点就是,多问一下和公司相关的问题,这样可能让你对公司更加得了解,同时也向面试官透露出你对公司的兴趣。千万不要盲目地问一些没有任何意义的问题,不要一直问一些和自己切身利益相关的问题,这样会让面试官认为你太急功近利了。 127 | 128 | ## 最后 129 | 130 | 祝大家都能找到一份自己喜欢的工作~~ 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /Chatting/同样都是开发,为什么你不如别人.md: -------------------------------------------------------------------------------- 1 | 2 | # 同样都是开发,为什么你不如别人? 3 | 4 | > 2020年由于疫情的影响,大批量的公司破产倒闭,即使能坚持下来的,也是推出了很多财务削减和人员裁减计划(也有美名为人员优化),这导致了大量人员的失业,当然也包括了我们这些做开发的程序猿。 5 | 6 | 疫情时间,为了能快速找到工作,很多人又开始四处寻找面试材料复习开始备战面试,但就在复习的过程中有些人可能会发现,原来自己工作了这么多年,水平可能都不及一个拥有三年开发经验的新人。 7 | 8 | 那么问题来了,同样是开发,为什么你不如别人?如何才能让自己变得更加优秀?下面我将从三个方面阐述我的思考。 9 | 10 | ## 1.做事的艺术 11 | 12 | > 在工作中,我们可能会碰到各种各样的问题,如何优雅地处理这些事情,非常考验一个人的能力。 13 | 14 | ### 严谨和一丝不苟的态度 15 | 16 | 有人经常这样向我抱怨:面试造火箭大炮,工作拧螺丝。我想即便你是拧螺丝的工作,也务必要保持一丝不苟的态度把这拧螺丝的工作做好,否则你的这步没能拧好,很有可能导致造出来的整个火箭还没飞上天就爆炸了。 17 | 18 | 我觉得干咱们研发这一行,严谨和一丝不苟的态度是必须具备的基本职业素养。因为可能就是你的一点小疏忽或者情况考虑不周,可能给别人带来多大的麻烦和后果。 19 | 20 | 可能一、两次地这样坑别人,别人还能原谅你,帮你填坑。但如果由于你的不严谨和不认真的态度,导致三番五次地坑别人,时间长了即便你人再好,别人也不会再相信你,这样你在团队中就举步维艰了。 21 | 22 | ### 把握问题的本质 23 | 24 | 在工作的时候,我们经常会遇到各种各样的奇怪的bug。面对这些bug,不同的人处理的方式也是不尽相同。有的人是恨得咬牙切齿,恨不得和提bug的人干一架;有的人则是非常淡定,一边询问bug的详细情况,一边静静地坐着打断点、打日志分析问题。两者处理的方式不同,带来的结果也不尽相同。 25 | 26 | 那么当我们在开发过程中遇到问题时,我们该如何解决呢?我想核心的解决方法就是`把握问题的本质`。 27 | 28 | 如何`把握问题的本质`,以下是我常用的方法论供大家参考: 29 | 30 | * 1.了解问题的详细情况,收集问题出现的条件和现象:只有了解问题才能解决问题。 31 | * 2.模拟问题出现的场景,对问题进行场景复现。 32 | * 3.将偶现问题转化为必现问题,从中寻找规律。 33 | * 4.善用排除法,筛除干扰项。 34 | * 5.`断点+日志`相结合进行问题跟踪,深入源码探寻问题的本质。 35 | 36 | 一旦把握了问题的本质之后,一切便会迎刃而解。后面你需要做的就是找到方法,并解决它。你可以自己想办法;搜索网上有没有人和你遇到同样的问题;请教这方面熟悉的人... 37 | 38 | ### 事前永远比事后更重要 39 | 40 | 事前埋下的坑,也许需要事后付出数倍的努力才能把坑填完。很多时候我们经常会忽视事前计划、设计的重要性,往往是走一步看一步,等功能实现到一半的时候才突然发现这条路越走越崎岖或者根本行不通,这个时候你是非常难受的:继续走下去,可能前面的坑会越来越多;不继续走,重新想方案,项目延期,进度赶不上,要被问责。 41 | 42 | 因此在做任何事前之前,一定要自己要做的事情想清楚了再去做,避免南辕北辙的尴尬。 43 | 44 | 以下是我给出的几点建议: 45 | 46 | * 1.在做一些较为复杂的功能前,尽量做好设计。这里的设计主要包括: 47 | 48 | * 流程图:把所有可能出现的情况都考虑进去,越详细越好。 49 | * 设计类图:包括UML图和时序图等。好的设计望望事半功倍,这里我推荐大家多学学[设计模式](https://github.com/xuexiangjys/architect-java/tree/master/src/designpattern) 50 | * 性能设计和可拓展设计。 51 | 52 | * 2.养成良好的编码规范,在关键的、难懂的地方多加些注释,这样可以避免长时间后的遗忘,导致代码晦涩难懂,大大增加维护难道和bug产生的几率。 53 | 54 | * 3.提高代码的质量,在实现功能的同时,注重代码的性能,对于一些常见的性能问题要烂熟于心。 55 | 56 | * 4.在问题出现任何端倪之前就立马进行解决,即使不能完全解决也要预先想出替代方案。否则时间长了或者上线了之后,你可能需要付出数倍的精力才能解决,并有可能带来非常不好的影响。 57 | 58 | ### 低调做人高调做事 59 | 60 | `Talk is cheap. Show me the code.`这句话可谓是IT圈里最朗朗上口的一句话。 61 | 62 | 咱们干研发这一行的不同于其他职业,并不需要极力向外推销自己来获取更高的业绩。我们绝大多数的研发人员都是务实派,靠的是一行一行码出来的代码去实现自己的价值,少说话多敲几行代码会更有价值得多。 63 | 64 | 所以,那些成天夸夸其谈,开口就是讲上一堆技术架构,闭口写起代码又是一团乱麻的人,是比较不受欢迎的。 65 | 66 | 我们做技术的不要成天拿着技术出来显摆。要知道人外有人天外有天,比你技术牛逼的大有人在,没必要整天要在技术上比个高低贵贱的,也不需要刻意让别人知道自己有多么厉害,因为你写的代码就能证明你的技术水平,时间一长大家自然心知肚明。 67 | 68 | ### 帮助别人的艺术 69 | 70 | > 在帮助别人的同时,还能让自己对这块的技术掌握得更加透彻,何乐而不为呢? 71 | 72 | 帮助别人,而不是施舍,这一点尤为重要。我们要乐于助人,但是也要注重方法。帮助他人是建立在相互尊重的基础上的,否则你的好心帮助会被别人理解为同情施舍或者多管闲事。 73 | 74 | 因此我们在帮助别人的时候要注意以下几点: 75 | 76 | * 不要有帮助人的企图,只有在别人需要帮助的时候才去伸出援助之手。 77 | * 给予被帮助人最起码的的尊重。 78 | * 不要借着帮助他人的名义去干预被帮助人的成长,最好的帮助就是点到即止,剩下的顺其自然。 79 | 80 | ---- 81 | 82 | ## 2.学习的艺术 83 | 84 | > 从事开发工作,无论你是在产品线上写业务代码,还是在技术平台进行技术研究,我们都不能放弃学习,放弃对新技术的尝试。放弃学习就好比战士上战场弄丢了自己的枪,很快你将会被一浪又一浪的技术浪潮所淘汰。 85 | 86 | ### 学习和汲取他人的长处 87 | 88 | 对于大多数的人来说,发现别人的缺点很容易,但是发现别人的优点却很难,这也是很多人不能快速成长的原因所在。 89 | 90 | 优秀的人总是善于发现别人的优点并加以学习。学习、模仿并最终超越是他们战无不胜的秘诀。他们并不在于你身上有多少缺点,他们只在乎能从你身上学到多少东西。 91 | 92 | 他们不仅会向身边的人学习,还会向以下几个方面进行学习: 93 | 94 | * 优秀的源码。这里包括系统源码和优秀的开源项目源码。 95 | * 优秀的技术书籍、文章。 96 | * 优秀的理念和思想。 97 | 98 | ### 把握学习的广度和深度 99 | 100 | 漫无目的地学习必然导致效率的极其低下,我们在学习之前一定要给自己设定一个目标:到底是想学习不同领域额的技能,成为一名全才;还是想就某一领域深入研究,成为一名专才,这就涉及到学习的广度和深度的选择了。 101 | 102 | 因为你不同的选择可能导致不同的人生轨迹,就目前而言,大厂更偏向于某一领域的专才,而小厂更偏向于拥有更多技能的全才,当然这也不是那么绝对。就选择而言,大厂固然很好,但是又有多少人能进入大厂呢;小厂虽说待遇各方面都不敌大厂,但是小厂的机遇多啊,说不定哪天公司发展得不错,你就能爬上领导的位置了呢。 103 | 104 | 所以无论你是选择学习的广度还是学习的深度,其实都是没有错的,唯一错的就是你压根就没有思考过这事。 105 | 106 | 当然这里的选择也不是绝对的,每个人在不同的阶段可能选择的方向并不相同。当你初涉社会刚开始工作,你可能追求的是学习的广度,但慢慢的当你对某一领域感兴趣或者表现出异于常人的天赋时,你可能又会转而追求学习的深度。 107 | 108 | 每个人的技术都有可能在某一刻达到瓶颈。如果现在的你翻开一年前你提交的代码,却发现和你现在提交的代码并无差别时,这个时候你就要小心了,很可能你已经达到你的技术瓶颈了,这个时候考虑换一个学习的纬度可能是不错的选择。 109 | 110 | ### 持续不断地学习 111 | 112 | 技术在日新月异地不断变化和发展着,前几年还比较流行的技术,可能没过几年就被人们所抛弃。当革命性的突破技术取代旧的技术时,这是历史巨轮不断向前发展、不可阻挡的趋势。 113 | 114 | 不要以为你现在掌握的技术就能够养活你一辈子,我们需要对技术的发展保持着极大的敏锐触觉。一旦你所掌握的技术逐渐被新技术所替代时,你就要小心了,可能留给你学习的时间不多了。 115 | 116 | 117 | ### 利用好学习的工具 118 | 119 | > 利用好学习的工具,能够极大地提高我们的学习效率。 120 | 121 | 这里我主要介绍关于自学一门技术可以利用的工具: 122 | 123 | * 专业性的入门书籍。对于新手和小白而言,我还是建议大家先找几本专业性和权威性最强的书籍作为自己的入门指南。因为书本讲解得更详细也更成体系,对于入门而言还是相当不错的。 124 | 125 | * 专业领域较强的技术论坛和博客。在这里我们可以学到很多书本上所没有的一些前沿技术的资讯和技术交流心得。这里推荐[掘金](https://juejin.im/timeline)和[思否](https://segmentfault.com/)。 126 | 127 | * 开源代码托管平台。在上面拥有海量开源的项目,其中也不乏许多优秀的开源项目可以供我们学习和参考。学习和借鉴别人优秀的代码和设计思想,能让我们快速提高自我的coding能力。这里我主要推荐[Github](https://github.com/)和[Gitee](https://gitee.com/)。关于如何使用开源代码托管平台,可以参考我之前写的一篇文章:[《你真的会使用github吗?》](https://xuexiangjys.blog.csdn.net/article/details/98127483)。 128 | 129 | --- 130 | 131 | ## 3.提问的艺术 132 | 133 | > 我们每个人都不是万能的,都会遇到很多我们不懂的问题,需要向别人进行求助。但是并不是每个人的问题都能够得到别人的答复,这完全取决于提问者提问的水平。 134 | 135 | 这里我先模拟一个场景:当你在github上使用了某人开源的轮子时,遇到了问题需要向作者提问或者提issue,你会怎样进行提问? 136 | 137 | * 提问者A:大佬帮帮忙,我在使用xxx的使用遇到问题了,请问怎么解决啊? 138 | 139 | * 提问者B:老哥,我说xxx小白,在使用你的xxxx搞了一天了都没有运行得起来,能不能帮帮忙,我实在没办法了。 140 | 141 | * 提问者C:您好,大佬。我在使用xxxx时,频繁xxxx,导致xxxx,但是xxxx, 又会xxxxx, 但是呢...(以下省略500字)。可能我形容地不是很清楚,你可以试一下就知道了。 142 | 143 | * 提问者D:请问怎么解决,......(以下省略数百行的日志) 144 | 145 | * 提问者E:你这xxx根本不能用,......(以下省略约一百字的抱怨的话) 146 | 147 | * 提问者F:您好,我在使用xxx的xxx版本的时候,遇到了xxx问题。下面是我出现问题时的现象(....)以及日志(....)。我是这样xxx,然后xxx,最后导致xxxx。我出现问题的设备型号是xxxx,在xxxx上没有出现问题,是不是xxxxx导致的? 148 | 149 | 上面6个人提问的方式是完全不同,我想可能只有提问者F才能够最终得到别人的答复并顺利解决问题,下面我将帮你逐一分析原因: 150 | 151 | * 提问者A是很多人经常犯的错误,那就是只抛出了问题,并没有给出问题出现的现象和依据,这会让被提问者无从下手,没有丝毫回复的欲望。毕竟你是求别人帮你解决问题,而不是领导发号施令。 152 | 153 | * 提问者B是很多初学者(学生)常犯的毛病,没有明确的问题,没有明确的解决预期,有的就是祈求式的求助,甚至连要帮什么忙都没有表述清楚。对于这种提问,绝大多数人是不予理睬的,因为他们并不想把时间浪费在一个什么都不明白的菜鸟身上,毕竟他们不是你的老师,没有义务教你基础知识。 154 | 155 | * 提问者C的问题就是说得太多,没能精确描述问题是什么。这样表述不清的提问只会让被提问者满脸的问号,然后直接回复:???。 156 | 157 | * 提问者D也是很多人经常犯的错误。出现问题之后的第一反应不是先去进行一番思考尝试自己解决,而是无脑地将一堆无用的错误日志贴出来,请求别人给出解决方案。对于这样的问题,可能绝大多数人的第一反应就是:能百度解决问题的,请不要来烦我,谢谢! 158 | 159 | * 提问者E就不用多说了,这种提问明显不是冲着解决问题的目的来的。对于这种不友善,怀有敌意的提问,我想大部分人的反应不是去帮忙解决问题,而是在想:这人不会是傻x吧? 160 | 161 | 分析了上面人的提问方式后,我们可以总结出如下几个问题时的技巧: 162 | 163 | * 首先明确问题是什么。 164 | * 优先自我思考解决,解决不了再向别人寻求帮助。 165 | * 清晰地描述问题。 166 | * 问题解决及时反馈并表达谢意。 167 | 168 | ### 明确问题是什么 169 | 170 | 在提问之前,首要任务是要搞明白自己到底要问什么,诉求是什么,这是对被提问者最起码的尊重。 171 | 172 | 什么都没搞明白就稀里糊涂地跑去问别人,这会让别人觉得你很唐突无知,给人留下非常不好的印象,这也会直接导致别人不愿意帮助你解决问题。 173 | 174 | 为什么这么说?因为别人要想帮你解决问题,还得先搞明白你的问题到底是什么,你的诉求是什么,然后还需要帮你分析问题出现的原因,最后才能帮你想出解决的办法,这花费的精力和代价实在是太大了。要知道这不是在学校或者医院,没人会愿意这么大费周章地帮你。 175 | 176 | 所以,要想自己的问题能够得到别人的答复和帮助,你必须想明白你要问的问题到底是什么! 177 | 178 | ### 优先自我思考解决 179 | 180 | 你可能也会遇到这样的情况,经常有人会在论坛上、qq群里、博客评论区里,动不动就贴出数百行的报错日志,然后不加一点说明,开口就问:这是什么问题,能帮我解决吗? 181 | 182 | 像这样不经大脑思考就草率的发问只能得到草率的回答,或者根本得不到任何答案便会石沉大海。其实我特别不建议大家在QQ群或者微信群里向别人问问题,因为懂的人可能不屑于回答(觉得这样的问题太low了,即使回答对了也体现不出自己的厉害),不懂的人即便回复你了也没有任何价值,反而有可能会把你带偏了。现在这社会,大家的时间都很宝贵,没有哪个真正厉害的技术大牛是在QQ群和微信群里活跃的,大牛的时间都很宝贵。那些成天在QQ群或者微信群里活跃的人,八成是闲的没事干的人。 183 | 184 | 因此要想得到别人高质量的答复,必须拥有与之相匹配的高质量的问题才行,这样别人才会愿意帮你解决。所以并不是什么问题都是值得向别人提问的。 185 | 186 | 我们在提问之前,一定要有自己的思考,优先尝试自己解决问题。下面是我提供的几个自我解决问题的途径: 187 | 188 | * `断点+日志`相结合进行问题跟踪,深入源码探寻问题的本质并予以解决。 189 | 190 | * 仔细通读作者编写的使用手册,不要拉下每一个细节(切忌想当然),试着自己找答案。 191 | 192 | * 在FAQ里找答案。如果有开源地址的话,建议优先在Issue中查找是否有相关的问题,并借鉴其中的解决方法。 193 | 194 | * 在网上搜索相关问题(条件允许的话,优先使用google,百度太坑,搜到的大多千篇一律) 195 | 196 | * 向你身边精于此道的朋友咨询。 197 | 198 | 如果经过以上5种方法你都没能解决问题,这个时候你再向别人进行提问,我相信你一定能够把问题解决。因为带着思考向别人提问题,才更能够得到别人高质量的答复。如果可以的话,你可以直接把自己想的几个解决方法阐述出来,这样别人可能会更愿意帮你解决,毕竟大家都喜欢做选择题,而不是论述题。 199 | 200 | ### 清晰地描述问题 201 | 202 | 有这样一部分人,每次遇到问题需要向别人求助的时候,经常表现的是举足无措,慌张地描述了一大堆看上去和问题有关又或者无关的话,滔滔不绝口若悬河,问得被提问者一脸懵逼。 203 | 204 | 面对这样的问题难免会让别人头大。讲了一大堆却分不出主次和主要矛盾,就连问题是什么都没有搞得很明白,别人怎么帮你解决? 205 | 206 | 因此我们在阐述问题的时候,需要注意以下几点: 207 | 208 | * 问题描述要言简意赅,尽量控制在50字以内。 209 | 210 | * 问题描述要条理清晰,把握主要矛盾。与问题无关或者相关性较低的话就不要说了。 211 | 212 | * 建议问题中包括如下几部分的内容: 213 | * 问题描述 214 | * 使用版本 215 | * 如何重现 216 | * 期望的效果 217 | * 出错现象(截图或者视频) 218 | * 设备信息(环境) 219 | * 附加信息(可以是日志或者源码链接等) 220 | 221 | ### 问题解决及时反馈并表达谢意 222 | 223 | 现实生活中常常有这样一部分人,在得到别人帮助了之后,连一句问题是否被解决的答复或者感谢也没有便消失得无影无踪,这会极大地打击被求助者的积极性。因为问题久拖未决会让人灰心,他们渴望看到问题被解决,并从中得到帮助别人带来的满足感,这点非常重要。否则下次再有人向他提问题时,可能就不太愿意帮忙了。 224 | 225 | 所以,在问题解决后,向所有帮助过你的人发个说明,让他们知道问题是怎样解决的,并再一次向他们表示感谢。 226 | 227 | -------------------------------------------------------------------------------- /Chatting/应届生如何成为一名合格的Android开发工程师.md: -------------------------------------------------------------------------------- 1 | 2 | # 应届生如何成为一名合格的Android开发工程师 3 | 4 | ## 前言 5 | 6 | 前段时间,一位计算机专业大四即将毕业的同学关注了我的公众号,然后问了我一系列的问题,表达出他对未来的迷茫: 7 | 8 | ``` 9 | 我是一个二本科班的应届生,今年毕业,想知道需要掌握什么样的安卓技能才能参加工作? 10 | 11 | 我在b站上看了很多安卓的教学视频,很多是16年左右的,内容很旧,想知道我学完这些,距离参加工作,还有哪些要去补充的?非常迷茫! 12 | ``` 13 | 14 | 我仔细看了一下他提出的问题,归纳总结无非就是:计算机专业的应届生,需要掌握哪些技能才能找到Android方面的工作? 15 | 16 | 我相信有这种疑惑的同学一定也不在少数,那么今天我就结合我的实际经验,来简单介绍一下,作为一名即将毕业的计算机专业的应届生,我们需要做哪些功课才能帮助我们更快地找到Android相关的工作。 17 | 18 | ## 自我介绍 19 | 20 | 在开讲之前,我先简单地做一下自我介绍: 21 | 22 | 我是南通大学12级网络工程毕业,一个再普通不过的二流地方院校。16年毕业,15年10月找到的实习单位参加工作,中间跳槽了几家公司,一直从事Android相关的开发工作,目前已有5年有余。 23 | 24 | 平时喜欢逛逛github,看看掘金,有空就维护一下github上的开源项目,没空就写写文章,发发牢骚,是个再普通不过的屌丝程序猿。 25 | 26 | 说了我的这些经历,无非就是想告诉大家:我一个普通二流本科生能做到的,大家也一定能做到。所以大家不必担心,只要你能够按照我的指导去做的话,你一定可以比我更加优秀! 27 | 28 | ## 掌握扎实的基本功 29 | 30 | > 俗话说:基础不牢,地动山摇。 31 | 32 | 作为应届生,由于没有什么实际的项目经验,所以在面试过程中,用人单位会更看重应届生的基础知识掌握程度以及发展潜质。 33 | 34 | 所以,作为一名应届生,我们一定要把基本功的提升放在首位。那么要想成为一名Android开发者,我们需要着重掌握哪些内容呢? 35 | 36 | * 基础学科知识。如计算机组成原理、数据结构、计算机网络、操作系统、数据库设计、数字电路等。这些科目向我们介绍了计算机的工作原理,掌握了这些就可以帮助我们更好地理解程序的本质。 37 | * Java基础知识。包括语言的特性、语法、规范等。例如:Java数据类型、运算符、面向对象、集合类、泛型、注解、反射、多线程并发、Java虚拟机、设计模式等内容。 38 | * Android基础知识。例如:Android四大组件、Activity的生命周期以及启动模式、动画、自定义View、Android不同版本的特性、SQLite、内存泄漏、ANR、Context、Handler机制、View绘制渲染机制、事件分发机制等内容。 39 | 40 | 只要我们牢牢地掌握了上面列举的内容,那么外面的公司绝对是抢着要你。 41 | 42 | ## 认清学校与社会的差距 43 | 44 | > 不要天真的认为把学校里学到的东西都学会了就可以顺利找到工作了,当然也不要认为学校里学的都是些没有用的东西。 45 | 46 | 作为一名应届生,我们非常有必要认清学校与社会存在的差距: 47 | 48 | * 学校里教的都是些非常重要且基础的东西,我称之为专业素养。这些东西非常重要,它直接决定了我们日后发展的天花板在哪里。 49 | 50 | * 社会上教的都是些具体工作领域方面的知识,我称之为职业技能。这些内容可以帮助我们更好地完成工作。 51 | 52 | 所以,我们除了要把学校里学的内容打扎实,还需要尽早地接触社会,找到对口的实习工作,掌握必要的职业技能。 53 | 54 | 我们在找实习单位的时候,可以参考以下几点: 55 | 56 | * 无论是大厂还是小厂,进去都会有所收获。进大厂我们可以收获不错的履历和人脉(这非常有用),学习到优秀的管理制度和流程控制,提高技术的深度。而在小厂可以磨练你的意志、获得更多主导的机会、更大的自由度,拓展技术的广度等。 57 | * 第一家实习单位可能极大影响个人的职场观和价值观,选择时需慎重。切忌去小微企业或者创业型公司,因为这类公司每天都可能徘徊在倒闭的边缘上,所以并不会重视人才的培养。 58 | * 在选择实习单位的时候,要多了解和掌握企业的信息,走正规渠道。不正规不可靠的千万别去;入职前,以任何借口让你交钱的公司不要去。 59 | 60 | ## 提高自己的动手能力 61 | 62 | > 做技术搞开发的,动手能力永远是需要作为首要能力进行培养的。除非你是业内公认的技术大佬,否则秀出你的代码才是证明你实力的最佳方式。 63 | 64 | 很多小白在入门某一项语言或者技术的时候,通常都是购买网课或者买一本入门书籍翻看,抄一抄视频或者书中的源码,就天真地以为自己已经彻底掌握了。他们殊不知,这些只是一些皮毛而已,当条件稍微发生一点变化的时候,他们就彻底懵逼,手足无措了。 65 | 66 | 那么怎样才能摆脱这样的困境呢?其实非常简单,多敲代码,多练习,多思考,熟能生巧嘛。 67 | 68 | 下面我简单举几个例子,给大家一点启发: 69 | 70 | * 假如你今天学了Android如何自定义View控件,那么你就可以按照自己的想法写一个Android尚未提供给我们的控件或者是仿xxx的控件等。 71 | * 假如你今天学了Android的事件分发机制,那么你就可以尝试着解决几个层级较为复杂的事件冲突问题。 72 | * 假如你今天学了Google提供给我们进行应用开发的Jetpack框架组件,那么你就可以尝试用这个Jetpack组件去写个属于自己的app。 73 | 74 | ### 如何提高自己的动手能力 75 | 76 | > 正如Linux之父的那句"Talk is cheap. Show me the code.",嘴上说千遍,也不如直接上代码撸一遍更加令人印象深刻。 77 | 78 | 说到这儿,可能又有朋友想问了:对于一个从来就没有具体项目经验的人来说,该从何做起呢? 79 | 80 | 这里我还是推荐大家多到github或者gitee这种开源代码托管平台去找一些优秀的开源项目,这些项目很多都是历经了各种复杂业务考验的高价值项目,临摹他们的代码,可以极大地提升我们的技术水平和编码水平。 81 | 82 | 那么当初我是怎么锻炼我的编码能力的呢?下面我就简单介绍一下供大家参考: 83 | 84 | * 首先到开源代码托管平台寻找到适合自己的目标项目。 85 | * 阅读项目的代码,初步了解项目的实现原理以及架构。 86 | * 自己新建一个项目,然后按照自己的理解去自己实现一个类似的项目,不懂的时候可以借鉴或者copy目标项目的代码(目标项目就类似于参考答案)。 87 | * 最低的要求是能够正常运行并且实现目标项目的主要功能,要求高一点的就是能够有一些属于自己的优化点或者特性等。 88 | * 最后对自己做的这个项目进行归纳总结,优秀一点的可以写一篇README介绍或者原理分析。 89 | 90 | 如果你能够按照我上面所说的,来回写个4~5个项目之后,相信你的动手能力一定会有质的飞跃。 91 | 92 | 不过在这里,我想给新手朋友们一点建议:很多时候,很多事情并不像你想象中的那么容易,千万不要自认为简单而不愿意去做,很多东西只有你亲手去做了之后,才会发现事情并不像你最初想的那么简单。 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | > 我是xuexiangjys,一枚热爱学习,爱好编程,致力于Android架构研究以及开源项目经验分享的技术up主。获取更多资讯,欢迎微信搜索公众号:**【我的Android开源之旅】** 115 | -------------------------------------------------------------------------------- /Chatting/开源项目创作指南.md: -------------------------------------------------------------------------------- 1 | # 开源项目创作指南 2 | 3 | ## 前言 4 | 5 | > 开源,是这个时代的主旋律。作为一名Android开发工程师,我有理由相信我们是开源的最大受益者,因为那个养活我们的Android其本身就是Google的开源项目。在这样一个开源的时代,就连之前那个最排斥开源的"微软"也不得不积极拥抱开源,大手笔收购github以表自己的开源决心。 6 | 7 | 前段时间我在逛github的时候,偶然间发现,我的github已经拥有12个star过百的开源项目,2个star过千的项目。回首一想,原来我做开源项目已经快3年了,想想这一路走下来真的非常不易。 8 | 9 | ![github_xuexiangjys.png](https://img.rruu.net/image/5fb9222270a08) 10 | 11 | 深夜和周末是我做开源项目的主战场,电脑和AndroidStudio是我创作的纸笔,各种bug和issues是我通往卓越项目的拦路虎,经常为了解决一个问题独自思考到凌晨。这其中还要饱受喷子们的挖苦和质疑,以及白嫖党的夺命连环call。当然,也会有人站出来说几句公道话,但是平心而论,国内的开源环境真的很差。 12 | 13 | 如果不是因为对技术的热爱以及支持者的鼓励,我相信我很难坚持下去,就像那些渐渐消失的开源项目创作者一样,提交记录永远地停留在了那一刻。 14 | 15 | 所以,为了能够记录下我的这段艰苦开源之旅,同时也是希望能够改善国内的开源环境,帮助更多希望从事开源项目的有志青年,我决定写下这篇开源项目经验总结。 16 | 17 | ## 为什么做开源项目 18 | 19 | > 在决定做开源项目之前,你非常有必要问一下自己:我到底为什么要做开源项目?无论是出于什么目的,只要你有答案的话,那么你就可以继续往下看,否则以下的内容可能对你来说没有任何意义。 20 | 21 | 说起开源,就不得不提Google这家极具开源精神的公司。作为一家美国科技公司,每年都在不断地对外输出着无数优质的开源项目,与此同时,近几年我们国家的科技公司们也开始着手开源计划,开源了不少有趣的项目。 22 | 23 | 那么为什么大型的科技公司都在积极地做开源项目呢?其实原因很简单,无非就是为了名气和企业的形象嘛。作为一家市值超百亿的科技公司,不搞点开源项目出来装装逼,都不好意思说自己是大厂。 24 | 25 | 那么作为我们个人开发者而言,有必要做开源项目吗?在回答这个问题前,你有必要问一下自己:你真的热爱做技术吗?如果说你做技术的目的只是为了养家糊口的话,我觉得接接私活,做做外包比较适合你。因为做开源项目真的是那些"闲得蛋疼"的人打发时间的玩具,在这条道路上你会发现非常没有"钱途"。只有当你真正将技术作为一种兴趣来热爱的时候,你才能体会到那种开源项目被无数人引用后的价值和喜悦感。 26 | 27 | 所以,说了这么多,我们究竟为什么要做开源项目呢?以下列举我的几点理由,供大家参考: 28 | 29 | * `提高自己的技术水平。`毕竟开源项目就相当于把自己扒光了给别人看,也许这样比喻不是很恰当,但确实能提高自己代码的质量和解决问题的能力。毕竟如果你的开源项目真的有人用的话,你必然会收到很多的issue以及建议,这些东西可能你之前根本就没有想到。 30 | * `让项目变得更加健壮。`开源的最大妙处就在于,任何人都有权利看到你项目的代码,提出自己的建议。这其中就有可能发现项目中存在的漏洞,以及一些非常有建设性的建议,同时也能让你明白自己项目中存在的不足,这样就会推动项目不断的优化升级,项目的质量也会呈螺旋型上升而变得更加健壮。 31 | * `结交志同道合的朋友。`你在做开源项目的同时,很容易就能结交到与自己志同道合的开源创作者,毕竟大家都是希望开源项目越做越好,很有可能你的开源项目被其他的开源者所赏识而一同开发和维护。这样一来一去你们很有可能成为"同志",哈哈。 32 | * `帮助他人,提升自己的行业影响力。`这也是很多人做开源的重要目的。如果你有幸能够在某个领域做出一个非常优质的开源项目并且有很多人引用的话,那么你在该领域的名气一定会蹭蹭的往上冒,到那时什么都会有的,具体可参考vue.js的创作者:尤雨溪。 33 | * `展示个人的技术水平。`很多人做开源项的目的就是它能够在应聘面试中加分,尤其是应聘某些大厂。因为通过那短短几小时的面试其实并不能很全面地考察出一个人的技术水平,而且面试通常仅仅是两个人面对面空谈几小时,如果这个时候你能拿出一个较为优秀的开源项目的话,一定会加分不少,至少能在面试前给面试官营造一个不错的印象。 34 | * `实现自己的人生价值。`开源给予了你无穷大的创作自由,在上面没有产品经理提出的各种脑残需求,也没有测试提出的各种过度测试bug,更没有各种技术条条框框以及企业规范。你可以做任何你认为对或者感兴趣的事情;你可以实现自己儿时改变世界的梦想;你可以留下你在这个世上存在过的印记;你可以...开源给予你无穷大的舞台,你可以做很多有意义的事情,当热前提还是不能违法法律...触犯法律的事情我们不能做,毕竟还是保命要紧,哈哈。 35 | * `收获意想不到的业余收入。`虽然开源是免费的,不应以盈利为目的。但是不得不说没有经济效益的驱使,单纯靠对技术的热爱其实是很难坚持下去的,尤其是当你面对很多现实生活的压力时,情怀就显得那么苍白无力,这也是为什么各大开源平台都为开源者提供了赞助和打赏的渠道,毕竟开源者也要赚钱生活。好的开源项目其实是能够收获相当可观的打赏的,更有甚者可以搞一个商用版,授权使用,赚取服务费等,当然能做到这一步的开源项目还是不多的。 36 | 37 | ## 如何做好开源项目 38 | 39 | > 上面我简单讲述了我们为什么要做开源项目,如果此刻你心中有了答案,那么恭喜你,你已经成为一名准开源创作者,那么下一步我们就是探讨如何做好一个开源项目。 40 | 41 | 我做了近3年的开源项目,其中最为成功的 [XUI](https://github.com/xuexiangjys/XUI) 近600次的代码提交,齐全的文档和视频教程,目前也不过收获了2.7k的star量,所以说做好一个开源项目其实是非常不易的。 42 | 43 | 下面我就简单拿 [XUI](https://github.com/xuexiangjys/XUI) 项目为例,简单介绍一下衡量一个开源项目质量的几项指标。 44 | 45 | ![open_project.png](https://img.rruu.net/image/5fba7684a3771) 46 | 47 | * 项目的热门程度:项目的star、fork和watch量。 48 | * 项目的活跃度:这里考量的因素包含issue的总体数量、open issue和closed issue的数量、issue回复和解决的速度、pull requests的数量、项目最后一次提交的时间。 49 | * 文档是否齐全:是否有wiki或者README.md。 50 | * 项目的稳定性:代码提交的频率,项目版本发布的频率。 51 | * 项目的潜力:项目开发的分支数、项目的开发计划以及项目参与者的数量等。 52 | * 项目代码的质量:设计是否合理,是否符合设计模式原则,考虑项目的可扩展性、便利性和稳定性。 53 | * 开源作者的水平:作者其他项目的star量和行业影响力。 54 | 55 | 只有了解了以上指标,我们才能创作出更加优质的开源项目。那么说了这么多,我们如何才能做好一个开源项目呢?请继续往下看! 56 | 57 | ### 1.选对开源托管平台 58 | 59 | 开源托管平台以 [github](https://github.com/) 作为首选,[码云](https://gitee.com/) 作为备份。 60 | 61 | 虽然目前市面上开源项目的托管平台非常多,比如:码云、码市、gitlab、BitBucket、SourceForge,不过我还是极力推荐github,毕竟github使用的人群最广,人数最多,谁不想自己幸苦创作的开源项目能被更多人看见呢。虽然github是一家美国企业,日后有被禁掉的风险,但是我相信一个崇尚自由、民主的国家,对禁开源平台这件事情还是不会那么顺利的,毕竟开源无国界,开源不应政治化,商业化!如果你还是担心github日后会被禁,那么很简单,你直接把你的github项目一键导入到码云中作为备份,毕竟码云是得到国家认可的,还是比较可靠的。 62 | 63 | ### 2.好的创意或者理念 64 | 65 | 如果一项功能、一件事情大家每次都需要重复去做,但是又没有什么好的解决方案或者轮子的话,这个时候我们就可以尝试去做一个。 66 | 67 | 一个好的开源项目都是为了解一个问题而诞生的。如果你有好的创意或者理念,那么你就更能吸引更多的人参与到项目的建设中,那样也会有更多的人关注到你的项目,这样你的项目想不火起来都难。 68 | 69 | 我当初创作 [XUI](https://github.com/xuexiangjys/XUI) 就是希望能够简化Android界面开发的难度,提升Android界面开发的效率而做的尝试。相信做过Android的人都知道Android原生组件在国内很不受设计师的待见,至于Google推行的Material Design设计风格更是无人问津,这就导致了设计师给出的原型图几乎是清一色的IOS风格,更尴尬的是,网上Android相关的开源UI库是少之又少,几乎所有的基础组件都需要自己重写。正巧当时我接触到了React和Vue,发现它们都有非常方便的UI库,直接在示例代码的基础上修修改改就能大致上实现自己想要的效果,极大地提高了开发的效率,后来我又借鉴了[QMUI](https://github.com/Tencent/QMUI_Android) 相关的思想,最终创作出了 [XUI](https://github.com/xuexiangjys/XUI) 这个开源项目。 70 | 71 | 所以,一个好的创意或者理念对于开源项目来说非常重要,可以说是开源项目的灵魂。 72 | 73 | ### 3.好的设计和代码质量 74 | 75 | > 如果说一个好的创意或者理念是开源项目的灵魂的话,那么一个好的设计和代码质量就是开源项目的骨骼和肉体了。 76 | 77 | #### 3.1掌握设计模式 78 | 79 | 要想有好的设计,首先你需要非常熟练地掌握`设计模式`,那么如何才能熟练地掌握`设计模式`呢?在这里我可以教大家一些经验: 80 | 81 | * 1.首先了解设计模式的几大基本原则。这里我有一篇讲关于 [设计模式原则的博客](https://xuexiangjys.blog.csdn.net/article/details/78924201) 可供大家参考。 82 | * 2.其次初步学习现有的二十几种设计模式,并在平时的工作或者开源项目中尝试使用。这里我有一个专门介绍[设计模式使用的开源项目](https://github.com/xuexiangjys/architect-java/tree/master/src/designpattern) ,里面有相应的介绍和源码可供大家参考。 83 | * 3.最后等你熟练使用了上面的二十几种设计模式后,忘掉他们,重新回顾之前的设计模式基本原则,并以此作为日后项目设计的基本原则。 84 | 85 | 其实学习`设计模式`非常像武侠小说中修练一门武功,学习设计原则是修炼心法、内功,而学习现成设计模式则是修炼招式。只有提升内功,牢记心法,忘记招式才能真正意义上掌握了`设计模式`这一项技艺。 86 | 87 | #### 3.2 严格的代码规范 88 | 89 | 提高代码质量最简单的途径就是严格遵循通用的代码规范,这里我推荐[阿里巴巴Java开发手册](https://github.com/alibaba/p3c/blob/master/Java开发手册(嵩山版).pdf) 和它的IDE插件[p3c项目](https://github.com/alibaba/p3c) 。 90 | 91 | 只有遵循通用的代码规范,这样才更加利于开源项目的多人协作,除非你想一个人维护整个项目,否则你的代码写得那么骚,谁能看得懂? 92 | 93 | ### 4.丰富的案例或者测试用例 94 | 95 | 作为一个合格的开源项目,提供一些单元测试用例还是非常有必要的,因为你写出来的东西并没有专门的人给你测试,这个时候如果还没有相应的单元测试用例,你如何保证你写出来的东西不是个坑呢? 96 | 97 | 如果你的项目不太适合写单元测试用例的话,那么你最好能提供丰富的使用案例,这样才能让你的开源项目更具吸引力,让别人有东西可以上手实践,否则光秃秃的啥也没有,你写这个项目的意义又在哪里? 98 | 99 | ### 5.完善的文档 100 | 101 | 这里的文档主要包括`README`(简介)和`wiki`(使用文档)。下面是文档的几项基本要求: 102 | 103 | * 1.文档内容应当尽可能的简洁明了,层次分明,并且将其放置于显眼的位置,便于他人寻找。 104 | * 2.文档应当及时更新,避免出现文档与代码不一致的情况。 105 | * 3.提供多语言版本的文档,至少英文版文档是要有的。 106 | 107 | #### 5.1 README编写 108 | 109 | > README可谓是开源项目的门户,每个人都是从阅读你写的README开始了解你的开源项目的。README写得好与坏,可能直接关系到别人在你的开源项目主页上停留的时间,以及他们是否会给你的项目一个小星星,记住这里很关键! 110 | 111 | 如果你之前从来都没有写过README,那么这里我推荐一个国外人写的教科书式的项目[Standard Readme](https://github.com/RichardLitt/standard-readme) ,里面写的内容非常经典,可以拿来借鉴。 112 | 113 | 那么,一个写得好的README应当包含哪些内容呢?以下是我总结出来的经验仅供参考: 114 | 115 | * 项目简介(必须):用几句话简要描述你的项目特点、优势以及项目目标(解决何种问题)。 116 | * 作者简介(选填):这里是你推广自己或者其他项目的地方。 117 | * 项目背景(选填):这里可以写你创作该项目的缘由和过程。 118 | * 项目特点(必须):这里是你区别于其他项目的关键,可以写你项目独有的功能、优势、理念等。 119 | * 项目设计(选填):这里可以阐述项目的设计思想和理念。你可以借助流程图、思维导图、UML类图、时序图等方式进行阐述。 120 | * 项目演示(必填):这里是别人能最直观感受项目魅力的地方。你可以通过以下5种途径进行演示。 121 | * gif动画演示 122 | * 视频演示 123 | * 图片演示 124 | * 在线演示 125 | * Demo下载 126 | * 集成/安装指南(必填):这里你得告诉别人如何才能快速地使用上你的项目。 127 | * 使用文档(必填):这里你需要告诉别人该如何正确使用你的项目,越详细越好。当然如果内容过多的话,还是建议直接给一个使用文档首页的链接或者视频教程,这样可能会更友好一点。 128 | * 相关项目(选填):这里是你推广其他开源项目的地方,是让你的项目粉丝数产生裂变的地方,一定不要错过。 129 | * 如何贡献(选填):这里的贡献包括两个方面:代码贡献和打赏和赞助。对于代码贡献,你需要写出你的提交规范和行为准则,以避免不必要的麻烦。 130 | * 特别感谢(选填):这里写上对你项目有过帮助或者启发的人或者项目的地址,以此表达对他们的感谢。 131 | * 联系方式(选填):这里可以写你为此项目创建的QQ技术交流群、微信公众号等。 132 | * 许可声明(选填):这里你可以写你项目的开源协议或者许可使用的范围(比如:禁止用于商业用途,仅供学习)。如果你分免费版或者商用版的话,也可以在这里进行说明。 133 | 134 | 具体内容你可以参考 [XUI项目的README](https://github.com/xuexiangjys/XUI/blob/master/README.md) 或者我的[README模版](https://github.com/xuexiangjys/AndroidAdvancedLearning/blob/master/Chatting/XREADME.md) 。 135 | 136 | #### 5.2 wiki编写 137 | 138 | > wiki的编写和及时更新非常重要。wiki最好分模块进行编写,做到条理清晰,层次分明,通俗易懂。 139 | 140 | 下面我就以我的另一个开源项目[XUpdate](https://github.com/xuexiangjys/XUpdate/wiki) 的wiki来简单说一下,wiki我们该如何编写。如果你会在线文档编写的话,这里可以直接跳过。 141 | 142 | ![wiki_example.png](https://img.rruu.net/image/5fbbef15d0e1b) 143 | 144 | wiki主要可以分为三块,如上图所示,最上面是项目的简要描述,左侧是文档的首页,右侧是文档的目录。这里我为了偷懒,左侧的文档首页直接照抄了右侧的文档目录。 145 | 146 | 下面我简单列举我们的wiki中应当包含哪些内容: 147 | 148 | * 简介:虽然这里的简介绝大多数是和README重复的,但是最好还是不能漏。 149 | * 项目简介:这里包括项目描述、项目特点、项目背景、项目设计思想等内容。 150 | * 集成/安装指南:如果你的项目是轮子库,那么就编写集成指南。如果你的项目是完整项目,那么就编写安装指南。 151 | * 项目演示:gif动画、视频、图片演示,在线演示和demo下载二维码任选几项提供即可。 152 | * 如果使用 153 | * 基础使用:顾名思义,能够满足最基础需求的使用介绍。 154 | * 进阶使用:这里主要介绍一些个性化(自定义)、深入使用的操作。 155 | * 常见问题:常见问题的整理非常重要,它往往产生于issue或者技术交流群中常见的问题,对刚接触该项目的人非常有用。 156 | * 配套设施:一个优秀的开源项目往往会自建相应的生态,这时配套设施就非常有必要了。 157 | 158 | ### 6.合理的版本管理和规划 159 | 160 | > 新版本的发行不宜过于频繁,也不宜间隔时间过长,胡乱无规律的版本发行对使用者而言简直是场灾难。 161 | 162 | 最恰当的做法应该是:在保证充分测试没有问题之后,定期发布最新版本,实时更新项目的最新进度以及未来的开发计划。 163 | 164 | 这里我提供几点建议供大家参考: 165 | 166 | * 每次在进行新版本的开发时,建议单独拉一个dev版本分支,减少对主分支的影响。 167 | ![](https://img.rruu.net/image/5fbd12aa1248d) 168 | * 可以使用github上的projects来规划你的项目开发计划。 169 | ![](https://img.rruu.net/image/5fbd0e5b3ba74) 170 | * 可以在issues中建一个置顶的issue来收集使用者的反馈,以此作为新版本开发的规划素材。 171 | ![](https://img.rruu.net/image/5fbd111f072df) 172 | * 每次新版本发布的时候,一定要提供详细的版本日志,方便使用者参考和追踪。同时,对于几个改动比较大的版本调整一定要有明确的说明,否则使用者会非常疑惑。 173 | ![](https://img.rruu.net/image/5fbd141bdf1d4) 174 | 175 | ### 7.关注反馈,持续更新 176 | 177 | #### 7.1 及时处理issue 178 | 179 | > issues是使用者与项目开发者之间沟通的桥梁。很多使用者提出的issue还是非常有建设意义的,及时高效地处理掉它们,可以让我们的项目变得更加完美。 180 | 181 | 在处理issues的过程中,我们可以收集整理"常见问题",收获好的idea,了解自己项目存在的不足等。这就要求我们需要及时关注和处理使用者提出的这些issues。 182 | 183 | 那么我们怎样才能快速高效地处理issues呢?这里我提供几点建议: 184 | 185 | * 1.提供issue模版,过滤无效issue,提升沟通的效率。这里你可以参考 [XUI的issue模版](https://github.com/xuexiangjys/XUI/blob/master/.github/ISSUE_TEMPLATE/bug_report.md) 。 186 | 187 | ![issues_template.png](https://img.rruu.net/image/5fbd18b2bf7ca) 188 | 189 | * 2.自定义issue标签,对issue做分类管理,部分issue可以优先处理。 190 | 191 | ![issue_labels.png](https://img.rruu.net/image/5fbd1a8d30dad) 192 | 193 | #### 7.2 欢迎PR,及时处理PR 194 | 195 | 在开源项目初期,靠一个人维护一个项目还是不难的。一旦项目热度上去来,光靠一个人的精力是远远不足以维护整个项目的,这里我深有体会。 196 | 197 | 所以我们需要提供PR提交规范和行为准则,积极欢迎更多的人参与到项目的维护中。同时对于别人提出的PR,我们要及时review验证,对于没有问题的提交应当适时合入以提高别人参与贡献的积极性。 198 | 199 | ### 8.做好推广 200 | 201 | > 酒香不怕巷子深,这句名言在互联网时代是行不通的。你就是有再好的创意、再好的设计,如果没做好推广的话,也是没人能看到你的开源项目的。 202 | 203 | 如何推广自己的开源项目,对此我还是非常有经验的,下面我就提供几个途径: 204 | 205 | * 在各大技术论坛、博客、社区不断地发文章介绍自己的开源项目。我常用的几个平台有:[CSDN](https://blog.csdn.net/) 、[掘金](https://juejin.im/) 、[思否](https://segmentfault.com/) 、[简书](https://www.jianshu.com/) 、[知乎](https://www.zhihu.com/) 、[哔哩哔哩](https://www.bilibili.com/) 。 206 | 207 | * 在一些开源项目推荐分享平台上提交自己的项目请求收录。比如我的开源项目[XUI](https://github.com/xuexiangjys/XUI) 和 [XUpdate](https://github.com/xuexiangjys/XUpdate) 提交到了[HelloGitHub](https://github.com/521xueweihan/HelloGitHub) 上。 208 | 209 | * 组建自己的开源社区。你可以创建qq交流群、微信公众号或者社区网址来推广你的项目。 210 | 211 | * 加入知名的组织。比如我就加入了[B3log 开源社区](https://github.com/b3log) 。 212 | 213 | ### 9.不忘初心,摆正心态 214 | 215 | 做开源项目是一件非常漫长的过程,你可能根本想象不到前方的道路有多么的曲折。 216 | 217 | 假如你的开源项目做了几个月了也无人问津,不要气馁,专心去做你认为有价值的事情即可,总有一天会有人发现你项目的价值。 218 | 219 | 假如你的开源项目做成功了,在收获到不少人赞赏的同时,那么势必会遭到很多喷子们的冷嘲热讽以及各种无脑喷。不要理会那些只会喷但是啥也不会做的麻瓜,不要把你有限的精力放在这些人身上。请把你有限的精力放在那些给你开源项目提出宝贵建议的人身上,专心去做你认为对的事情。 220 | 221 | 222 | ## 最后 223 | 224 | 我花了整整一周的时间整理才写下了这篇文章,也是真诚地希望能够改善国内的开源环境,帮助更多希望从事开源项目的有志青年。如果你觉得有用的话,建议你收藏此文章。最后,还是祝愿大家能早日写出属于自己的优秀的开源项目!!! 225 | 226 | -------------------------------------------------------------------------------- /Chatting/我的2020年年终总结.md: -------------------------------------------------------------------------------- 1 | # 我的2020年年终总结 2 | 3 | 2020年即将过去,在这个多灾多难的一年里,我似乎并没有受到太大的影响,开源项目的维护仍在继续,技术博客有空也上来更新几篇,该搬的砖依旧在搬,只不过换了个地儿搬而已。 4 | 5 | 如果用一句话来描述我的2020年,那就是:在开源、博客和搬砖的路上砥砺前行。 6 | 7 | ## 疫情中学习flutter 8 | 9 | 因为疫情的影响,整个春节我都宅在了家里。刚开始的时候我是非常开心的,因为这样我就可以名正言顺地宅在家里打游戏啦!就这样我连续打了一个星期的农药,赛季刚开始就一路打上了王者,打到最后把我自己都给打吐了。 10 | 11 | 老是打游戏也不是个事儿啊,难得有时间可以做自己想做的事情,那为什么不去做呢? 12 | 13 | 正巧当时在放假前整了一段时间的flutter,而且还顺手在github写了个开源项目[FlutterSample](https://github.com/xuexiangjys/FlutterSample) ,想着就顺手完善一下,顺便加深一下对flutter的理解。 14 | 15 | 于是我就啪啦啪啦地写了一堆flutter项目,说实话flutter写起来那是真的爽,至少我觉得比写Android原生有意思多了。 16 | 17 | 下面是我这个时期写的flutter项目,感兴趣的可以拿去把玩把玩。 18 | 19 | ![](https://img.rruu.net/image/5feb5bbeb1a34) 20 | 21 | * flutter完整学习项目`FlutterSample`: [https://github.com/xuexiangjys/FlutterSample](https://github.com/xuexiangjys/FlutterSample) 22 | * flutter模版项目`flutter_template`: [https://github.com/xuexiangjys/flutter_template](https://github.com/xuexiangjys/flutter_template) 23 | * flutter版本更新插件`flutter_xupdate`: [https://github.com/xuexiangjys/flutter_xupdate](https://github.com/xuexiangjys/flutter_xupdate) 24 | * flutter版本更新弹窗组件`flutter_update_dialog`: [https://github.com/xuexiangjys/flutter_update_dialog](https://github.com/xuexiangjys/flutter_update_dialog) 25 | * flutter版本更新案例`flutter_app_update_example`: [https://github.com/xuexiangjys/flutter_app_update_example](https://github.com/xuexiangjys/flutter_app_update_example) 26 | 27 | ## 换个地儿搬砖 28 | 29 | 说实话,疫情对我这种技术宅的影响几乎是忽略不计的,反倒是把我的前东家给整跨了。于是乎我在3月初开始广撒简历,准备各种面试。 30 | 31 | 无奈在那时那种防疫的紧张背景下,疫情没有明显的拐点,很多企业都推迟了面试招聘,所以整整一周我就只收到三四个面试的机会。而且在南京这个破地儿,是真的没有什么像样的公司,所以那段时间招聘的绝大多数都是给华为做外包的。你懂的,华为那帮人2月3号就正常上班了,试问那会儿能有几家公司是能正常按时上班的? 32 | 33 | 这种情况下我没有选择,只能选择了个和华为合作的公司--掌阅科技,去做阅读器开发了。 34 | 35 | 刚进去的那段时间真的是把我给整懵逼了:华为的加班文化真的是名不虚传,华为的狼性文化真的不是盖的。在高强度的迭代任务面前,你是没有讨价还价的余地的,再加上我刚去的那段时间人员极缺,于是乎我开启了我职业生涯中最漫长的加班旅程:足足连续加班一个多月,每天都是晚上10点以后的那种。 36 | 37 | 不过好在大家都很团结,沟通也很顺畅,同时也结交了不少道友,所以也没有想象中那么累。 38 | 39 | ## 成为一名技术up主 40 | 41 | 一个偶然的机会,在知乎上有读者留言让我做几期关于我的 [XUI](https://github.com/xuexiangjys/XUI) 框架的教学视频,当时就激发了我做视频的兴趣。 42 | 43 | 在此之前,我是没有做教学视频的意识的。因为我最初的想法就是,我写的 [XUI](https://github.com/xuexiangjys/XUI) 是给那些已经入了门的Android开发提高开发效率用的,那些没入门的小白不属于我框架的使用人群。好家伙,结果一个疫情下来,全民开始上网课,我的项目受众范围明显扩大了许多。 44 | 45 | 既然现在有了这个想法,于是我就迅速去哔哩哔哩注册了个账号,成为了一名技术up主。 46 | 47 | 我花了整整一周的时间来梳理,最终勉强做了几期(因为我发现剪辑好难啊~)。 48 | 49 | 下面是我的哔哩哔哩主页:[https://space.bilibili.com/483850585](https://space.bilibili.com/483850585) 50 | 51 | ![](https://img.rruu.net/image/5feb684912560) 52 | 53 | 未来一年,我可能会针对我所开源的X-Library系列开源项目,做一个完整系列的教学视频,同时我还会在上面分享一些我平时开发的经验感悟给大家。感兴趣的可以关注我一波,记得三连支持一下哦! 54 | 55 | ## 开源项目小有成就 56 | 57 | 今年,我的github拥有了第12个star过百的开源项目,第2个star过千的项目。说实话,一次性同时维护这么多开源项目,对人的精力挑战是巨大的。 58 | 59 | ![](https://img.rruu.net/image/5fecad8c90dc5) 60 | 61 | 2020年这一整年,我总共提交了682次,平均下来每天有2次提交。 62 | 63 | ![github_commit.png](https://img.rruu.net/image/5fecbff61b33e) 64 | 65 | 未来,我还会继续开源更多有趣的项目,感兴趣的可以follow一下我的github: [https://github.com/xuexiangjys](https://github.com/xuexiangjys) ,这样就可以及时获取到我的状态。 66 | 67 | ## 创作技术博客到深夜 68 | 69 | 2020年是非常忙碌的一年,尽管如此,我依然没有放弃技术博客的创作。为了能够写出一篇质量较高的文章,我需要构思、收集素材、列出提纲,然后才开始一点一点撰写,写完后还需要配图,检查是否有错别字,语句是否通顺等等,常常因此干到深夜,从下图可以看到,我的很多文章绝大多数都是在凌晨一两点完成的。 70 | 71 | ![](https://img.rruu.net/image/5fedae19903ae) 72 | 73 | 在这一年里,我的技术博客不再仅仅是局限于做技术的分享和开源项目的介绍,我还把我平时工作中的点点滴滴的经验总结出来,写出了几篇质量较高、思考程度较深的好文章: 74 | 75 | * [同样是开发,为什么你不如别人?](https://blog.csdn.net/xuexiangjys/article/details/106184931) 76 | * [技术博客,从零到数万访问,这两年我都做了什么](https://blog.csdn.net/xuexiangjys/article/details/109268303) 77 | * [史上最全的开源项目创作指南](https://blog.csdn.net/xuexiangjys/article/details/110100980) 78 | * [程序员的bug修复宝典](https://blog.csdn.net/xuexiangjys/article/details/111827819) 79 | 80 | 在我看来,一个程序员的技术水平并不是衡量这个人能力的唯一标准,有的时候一些软实力才是凸显他价值的关键。因为在这个技术迭代频繁的时代,那些令人眼花缭乱的新技术只能算是武功招式,而像个人的学习能力、思维方式、沟通交流能力,设计思想等才是一个人真正的核心竞争力,只有不断提升自己的这些核心竞争力,才能无畏日后的各种挑战。 81 | 82 | 目前我的博客粉丝量还不是很多,希望明年我在产出更加优质文章的同时,也能够斩获更多的粉丝! 83 | 84 | * [CSDN](https://xuexiangjys.blog.csdn.net/) 85 | ![csdn_2020.png](https://img.rruu.net/image/5fedb6026fb26) 86 | * [掘金](https://juejin.cn/user/3421335915342504) 87 | ![juejin_2020.png](https://img.rruu.net/image/5fedb60323356) 88 | * [知乎](https://www.zhihu.com/people/xuexiangjys) 89 | ![zhihu_2020.png](https://img.rruu.net/image/5fedb6011e772) 90 | * [简书](https://www.jianshu.com/u/6bf605575337) 91 | ![jiansu_2020.png](https://img.rruu.net/image/5fedb602292d1) 92 | 93 | ## 运营微信公众号 94 | 95 | 2020年是我运营微信公众号【我的Android开源之旅】的第二年,由于我微信公众号的运营比较佛系,因此关注的用户量增长并不是很快。以下是我这一年微信公众号用户总数的增长趋势图: 96 | 97 | ![](https://img.rruu.net/image/5fecc2c33919f) 98 | 99 | 目前,我的微信公众号运营仍以开源资料管理为主,定期分享一些我的经验感悟,开源项目的最新资讯。 100 | 101 | 未来一年,我会加大在微信公众号上的投入,希望能够给大家带来更多有意思的文章。 102 | 103 | ## 大胆撩妹呀 104 | 105 | 2020年是我单身的第二年,当初在开始单身生活的时候,我就默默在心中给自己立了一个flag:两年内不打算谈恋爱,潜心发展我的事业,让自己成为一个靠得住的人。如今,两年的时间已经过去,当初立下的flag仍然高高地挂着,而我依然是单身汪一枚。虽然中途也认识过一些很好的小姐姐,但是无奈工作太忙,加上我脸皮薄,在女生面前不善于表达我内心的想法和感受,因此聊着聊着可能就会发现我人没了... 106 | 107 | 新的一年,我还是希望我能够再大胆一点,就像在别人面前吹技术那样,在女生面前也能大胆地表达我内心的想法和感受,结束我的单身汪生涯。 108 | -------------------------------------------------------------------------------- /Chatting/我的三年Android开发总结.md: -------------------------------------------------------------------------------- 1 | 2 | # 我的三年Android开发总结 3 | 4 | > 从2015年10月进入第一家公司实习,从事Android开发到现在,不知不觉已有三年之久。这期间我做过不少项目,从事许多技术框架的研究以及开发,也写过许多技术博客,进行多次的技术分享,从事过QQ技术群的建设,也试着进行微信公众号的推广等等,脚步一直都没停下来,更别说写什么经验总结了。就在前几天,QQ群里有个小伙子突然问我从事Android开发几年了,我这才意识到,从事Android开发已有三年了,是应该稍微放慢下脚步,总结一下过去,展望一下未来了。 5 | 6 | ## 职业背景 7 | 8 | 三年对于我们程序猿而言,说长不长,说短也不短,毕竟这不是个能让你干一辈子的职业。很多人说我们程序猿就像古代卖艺的艺妓一样,是吃青春饭的,其实我觉得这个比喻非常贴切。尤其是在中国,表面上看上去我们程序猿拿着高薪资,做着高大上的技术研究,看上去是风光无限,很高大上的样子。但真实的情况却是,工作通常是995或者996,在公司处于最低阶层,没有任何话语权,对于产品不可以有自己的任何想法,面对产品经理或者甲方天马行空的需求,你必须无条件服从并在极其苛刻的条件下`保量不保质`地完成一切功能。然后好不容易加班加点赶完了,还要面对测试无休止地找茬儿...很多程序猿自嘲自己就是高级工地搬砖工,其实这一点都不假。 9 | 10 | ## 职业选择 11 | 12 | 一直到现在我还记得那年高考分数下来,填志愿的时候,由于分数不太理想,只能选择一个普通的二本大学上上。第一志愿填的是老爸要我上的南京工程学校,填报个土木工程,以后好接他的班,但其实我内心是拒绝的;第二志愿我想了好久,最后才决定选择南通大学,在填报专业的时候,当然是填了一堆父母当时认为好找工作的专业,巧的是,就在我准备提交前的那一刻,我也不知道是怎么的,将我最后一个专业换成了`网络工程`。按我当时的想法是,以后互联网一定大有前途,我做个企业的网络管理员【现在又俗称运维】,每天坐机房里打打游戏岂不妙哉,哈哈。然后巧的事情就发生了,当我收到录取通知书的那一刻,我内心其实是有点小激动的,因为我被录取的正是我最后换上去的南通大学的网络工程专业... 13 | 14 | 上了大学之后才发现,我们学的这内容貌似和我想的差距有点大...老师要么只顾自己一个人在上面嗨,要么就是埋头读PPT唱催眠曲,反正总结一点就是:难学的东西不实用,实用的东西根本不需要学... 15 | 16 | 不甘心寒窗苦读12年,就为了上课睡觉,下课开黑的我,于是毅然决定去选个二学历学来玩玩(现在看来,完全是为了打发时间)。由于当时对企业金融感兴趣,二学历学的是企业工商管理(国际管理方向),这期间读过不少有关的书籍,看公开课,纪录片,还真刀实枪地杀入到中国A股市场经历一波牛熊市转换,自认为毕业以后一定能在金融投行市场里如鱼得水,现在看来,当时还是太年轻了。 17 | 18 | 一个人一生中如遇一位恩师,那一定是他这辈子莫大的幸福,而我在大三的最后一门专业课(Android开发基础教程)上就遇到了。这门课改变了我此前对本专业的看法,同时也是我大学期间唯一一门我真正喜欢的课程(恨不得天天上的那种)。从那以后,我就被Android的艺术给深深吸引住了。比起C++黑色的命令框,Java的控制台console,我更喜欢能直接运行在我手机里的Android。 19 | 20 | 大三结束后的暑假,我在家闭门玩Android玩了整整两个月,实习工作也顺理成章地选择了Android开发。 21 | 22 | ## 工作历程 23 | 24 | ### 懵懂期 25 | 26 | 刚开始实习工作的时候,真的就像是在打怪练级一样,对什么都感兴趣,不管是什么活我都愿意去干,根本不挑活。什么工资待遇、员工福利、团建活动啥的完全不关心,一心只想做Android,其他什么的对我毫无吸引力,这样的状态一直持续到我大学毕业。这期间,工作时写Android,下了班我也在写Android,即使是到周末了也依然在写Android,基本是那段时间除了写Android,基本上不干其他的事情...也正是这样,我得到了领导的极力夸奖和同事们鄙夷的目光,哈哈,开玩笑的,反正认为我是个奇葩就对了。 27 | 28 | ### 迷茫期 29 | 30 | #### 第一次失望 31 | 32 | 毕业了,转正了,同时也开始了我的加班之旅(毕业前一直是不需要加班的,毕竟是实习生,临时工,做的都是些无关紧要的工作)。刚转正,我就被委派带领组内人开发公司的核心产品,在此我还是非常感谢领导如此信任我这名新人的。毕竟我年龄是最小的(94),要带领一帮93、91、89的一起做项目,说实话,要换做是我我肯定不会这么干。那段时间对我来说其实是蛮艰难的,让我带领一帮比我工作经历丰富的人来一起写项目,根本就不太现实。首先,你说话根本就没有分量(资历浅,一个刚毕业转正的人谁会听你的);其次,我根本就没有带领团队做项目的经验;最后,年龄摆在那,谁愿意听一个小弟弟在自己面前指挥项目开发。 33 | 34 | 最后的结果很显然,与其说是让我去做项目开发负责人,不如说是让我去做项目背锅侠。不过好在我加班加点背锅,最后还是顺利完成了任务。到年终的时候,由于我表现优秀,在组织架构调整时,顺利成为了组内的副组长,在谈涨薪的时候,我也是不假思索地提出涨薪4k的要求,我认为并不过分,毕竟我实习期间和转正期间都是全心全力地为公司卖命,这点要求应该不算过分吧,当时领导也是欣然答应了。然而在后面收到工资条的时候,我发现我的要求被打了半折,这是我第一次失望。 35 | 36 | #### 第二次失望 37 | 38 | 后来,我的领导由于要休陪产假,我就只能独自一人扛起组内日常开发管理的重担(老实说,我对领导突然的不辞而别挺气愤的,休假之前好歹也嘱咐我一下东西嘛,什么都没说就走了,留下一只懵逼的我)。那段时间我是没日没夜地加班(连续加班一个月,周末两天也全加班),生怕领导不在的这段时间出什么岔子,愧对大家对我的信任。再加上当时总监又给我面前吊着一根胡萝卜(加薪4k),我也是拼了老命去干,最终我还是顺利完成了任务,很平稳地过渡了。从那个时候开始,我内心就开始萌发了一个念想:原来我也可以独当一面了呀。 39 | 40 | 那个时候我和领导的关系特别好,我也是一心想好好辅佐他把组内建设好。我的想法就是,对内我抓紧进行技术框架建设,对外极力提高组员的话语权。为此,我制定了开发规范,重构了组内的技术框架,不定期地进行技术分享,为的就是能够更好地服务大家,提高大家的开发效率,帮助大家进步。最终,我的付出得到了大家的肯定,他们也越来越信任我。当时的我认为,我做得这么好,领导应该嘉奖我,答应给我加薪4k应该没任何问题吧。然而当我将调薪申请书交给领导后,我等了两个月都没有动静...眼看着调薪的窗口已经结束,我前去找他理论,得到的却是他忘记了,还没向总监提。当时我就火冒三丈了,我做得这么好你却这样对我,是存心和我过不去嘛,这是我第二次失望。 41 | 42 | #### 第三次失望 43 | 44 | 后来我气不过,直接找总监谈调薪问题,就在聊天的过程中,我隐隐约约得知到公司有人穿我小鞋,而其中就有我的领导,得知之后我是又气愤又失望。之后领导为此事又特地找我谈话,期间又给我列出来各种莫须有的罪名,搞得我是莫名其秒。我心想,我那个时候好心为你,脏活累活都我来,为了组内利益不惜得罪了别人组的组长,而你却跟着别人在总监面前穿我小鞋,现在还在这边给我列罪行,要脸不?那次谈话我没能控制情绪,当场就和他怼了起来。那次谈话可能是我们最后一次谈话了,因为之后的一年内,我们单独谈话的时间不会超过5分钟,因为我觉得没什么好谈的了。最后我和总监达成了一笔交易,我放弃了组内的职务(也就等于放弃了高额绩效),然后获得了4k的加薪。这是我第三次失望。 45 | 46 | ### 建设期 47 | 48 | 这段时间由于我没了组内职务,反倒清闲了下来,让我可以流出更多的时间用以观察和思考(对,这就是典型的薪资涨了,活反而少了)。这期间,我退出了公司内部权利的争斗,将重心从公司逐渐转移到个人的建设上,闭门专心从事技术的研究,学习新的技术,开始尝试写博客和开源项目。你可能很难想象,我能和一个已经撕破脸皮的领导继续在一起工作一年,那是因为我已经对这家公司失望了,继续待下去是因为刚涨了薪资,现在跑路就太亏了...再说,我现在的工作强度也不算太大,我可以拿出更多的时间用来更新自己的技术栈,提升自己的实力,这样在以后才不会被别人轻视。 49 | 50 | 也正是因为我的这种无欲无求的状态,让我观察到了以往我看不到的东西,同时也让我明白了为什么我的领导对我的态度变化那么大。虽说我和领导的关系降至冰点,但是我和组内的其他成员关系却是越来越好,出现任何技术问题的时候,他们都会直接来问我,因为我是真诚想帮助大家,大家也同样信任我。不过这样的关系引起了我领导的不适,他开始不断地给组员增加任务,并且要求制定学习计划,并以此作为年终调薪的筹码。最后,组员们被一个个工作任务和学习任务击垮了,放弃了加薪的念头,开始准备跑路,寻找下家了。 51 | 52 | ### 洞察期 53 | 54 | 2018年年后,我的那些被逼疯的同事们开始陆续跑路、跳槽了。期间不信任、失落充斥着全组,虽然大家表面上看上去很和睦,实则很尴尬。可能是大家都已经看淡了,领导在群内喊话,我们都没人搭理他,这期间我一直扮演的是旁观者的角度。就这样,原先8个人的小组,陆陆续续地走了5个人,等我回过神来的时候,才发现大家全都走光了,就剩我和另一个女生...我在综合考虑后,决定开始面试,尝试着跑路跳槽。很快我找到了下家,那天我找到领导谈话,提出离职的申请,和我预想的一样,他并没有挽留我(估计想让我走很久了吧),不过我还是很礼貌地说了声谢谢,非常感谢他将我领进门,也非常感谢他让我明白了办公室政治的黑暗,让我能专注于技术的研究,做纯粹的技术。 55 | 56 | 之后我换了一家公司,可能是第一次跳槽没什么经验,跳到了一家严重老龄化的公司(国企改制),极度悠闲的工作状态让我非常地不适应,干了一个月就让我体会到进养老院的感觉...在进行了深刻的思想斗争后,我毅然决定在年前离职,开始修养生息一段时间,等年后再进行面试,好好挑一家可以长期为之奋斗的公司。 57 | 58 | 即使休息在家,我也没闲着,一直在进行开源项目的开发,从2018年起到2019年这一年间,我开源了30多个项目,项目的总star量也突破了2k,单库的月下载量也突破了7k,QQ技术群也突破了200人,这一切都是在这短短的一年间完成的。 59 | 60 | ### 展望未来 61 | 62 | 如今,我已经找到了一家我很看好的公司,未来我将继续从事我所擅长的技术研究,并为大家带来更多便捷方便的开源框架~~ 63 | 64 | ## 联系方式 65 | 66 | [![](https://img.shields.io/badge/点击一键加入QQ交流群-602082750-blue.svg)](http://shang.qq.com/wpa/qunwpa?idkey=9922861ef85c19f1575aecea0e8680f60d9386080a97ed310c971ae074998887) 67 | 68 | ![](https://github.com/xuexiangjys/Resource/blob/master/img/qq/qq_group.jpg) 69 | 70 | ![](https://github.com/xuexiangjys/Resource/blob/master/img/qq/winxin.jpg) 71 | 72 | -------------------------------------------------------------------------------- /Chatting/我的两年博客经验总结.md: -------------------------------------------------------------------------------- 1 | # 我的两年博客经验总结 2 | 3 | > 作为一名有追求的程序猿,我是不会满足于每天重复性的搬砖和写bug的。为了能够让自己显得更有"价值",我决定去做一件非常有意义的事情,那就是写技术博客。因为我觉得"分享"才是我们程序猿最高尚的品格,因为在我刚入这一行的时候,也是无数的前辈们,通过他们字字带血的技术文章,才让我少走了许多弯路,少踩了很多次坑。现在,也是轮到我回报的时候了。 4 | 5 | ## 做开源项目 6 | 7 | > 刚开始的时候,也是最困难的时候。因为肚子里没什么货,所以根本就不知道要写什么。 8 | 9 | 我开始着手写技术博客大概是在2018年的3月份左右,那时候主要是工作不是很忙,所以才能有闲暇时间去做自己感兴趣的事情,比如:倒腾代码和写博客。后来渐渐的养成了习惯,一有空就要倒腾个几篇技术博客出来,目前算下来也有个70多篇左右吧,由于都是原创,所以每一篇文章都需要花费我很长时间去构思和准备。 10 | 11 | 刚开始的时候, 因为肚子里没什么货,所以根本就不知道要写什么。我思前想去,既然没什么可写的,那索性就不写了吧!我也不想为难自己,毕竟这本身就是件业余兴趣,我也不想把它作为任务。 12 | 13 | 可能你以为我就这样放弃了?不不不,你想多了,我是不会这么容易就放弃的。我当时在想,既然我肚子里没什么货,那么我现在首先需要做的事情就是先让自己肚子里有货。于是我做的第一件事情就是整理,整理我这些年在工作中的经验和感悟,以及我平时业余倒腾过的代码。 14 | 15 | 由于当时我比较热衷于逛github,尤其是对github上那些大神们精湛的技术以及巧妙的设计思想所吸引,所以我当时给自己定的方向就是做"开源"项目,目标是能在一年内作出一个star过百的项目。 16 | 17 | 说干就干,我立马开始着手我的第一个开源项目[XPage](https://github.com/xuexiangjys/XPage) , 那段时间可以说我是白天在公司敲代码,下班回家吃完饭也继续敲代码,经常能敲到凌晨1、2点,有的时候能干到3点多,然后第二天照常8点半起床上班。说实在的还是挺佩服我那时的冲劲儿的。 18 | 19 | 下图是我GitHub账号的代码提交统计,从图中可以明显感觉到从2018年的第一季度开始,我的代码提交数量是达到了几何倍的爆增。在2018年的第二季度达到了416次,平均下来一天4.6次的提交,这想想是有多么疯狂。之后基本上每个季度都保持在150~200之间,这样平均下来基本上每天都至少提交有一次。 20 | 21 | ![github_work.png](https://img.rruu.net/image/5f8dbfb1c5c2c) 22 | 23 | 当然,光是闷着头写代码是不够的,我还是希望有更多的人能够看到我写的东西,尤其是开源项目,要是没有人看到或者没人参与进来的话,岂不是没有任何的价值... 24 | 25 | 于是乎,我开始寻找各种渠道能够让更多的人看到我的开源项目,下面是我简单列举的渠道(Android): 26 | 27 | * 玩Android: https://www.wanandroid.com/, 鸿洋大神的,还在维护 28 | * CTOLib码库: https://www.ctolib.com/, 貌似还在维护 29 | * 安卓巴士: http://www.apkbus.com/, 最近貌似凉了, 进不去了... 30 | * 泡在网上的日子: http://www.jcodecraeer.com/, 目前半死不活,也没人维护... 31 | * 干货集中营: https://gank.io/, 也没什么人维护了... 32 | * 开发者头条: https://toutiao.io/, 也没什么人用了现在 33 | 34 | 从上面Android的技术分享论坛目前的维护状态来看,和2015年的时候相比,Android确实凉了不少啊。 35 | 36 | 最终,在我的不懈努力,持续输出了3个月后,我迎来了我的第一个star过百的开源项目:[XPage](https://github.com/xuexiangjys/XPage) ,这比我的预期目标整整提前了9个月!有了这一次成功后,我更加坚定了我做"开源项目"的决心,随后又陆陆续续地写了很多的开源项目。 37 | 38 | 39 | ## 尝试写博客 40 | 41 | > 在经历了初期的疯狂撸码模式以及疯狂推广模式后,下面需要我考虑的就是该如何写技术博客了。 42 | 43 | 最开始因为没有什么写作经验,所以写的内容基本上是以我的开源项目的推广文为主,主要就是把开源项目的readme直接就搬了过来,然后在首尾加上推广的链接基本上就差不多了。 44 | 45 | 就这样写了三个月后,我发现虽然篇篇都是原创,而且也都是满满的干货,但是我却发现文章的浏览量并不是很多,除非你写的内容是行业内比较有名的项目,比如说Google的`DataBinding`、`Navigation`等内容。 46 | 47 | 后来我尝试写了几篇源码分析的文章,发现阅读量更是少得可怜。之后我又写了几篇关于经验感悟、设计模式等内容,发现阅读的人也同样不是很多。 48 | 49 | ### 选对文章类型 50 | 51 | 做了一段时间后,我才渐渐地发现那些阅读量高的文章,基本上都是如下几种类型的文章: 52 | 53 | * 标题型文章。这种文章标题非常吸引人,常常是带感叹号和问号的标题。 54 | 55 | ``` 56 | RabbitMQ实现即时通讯居然如此简单!连后端代码都省得写了? 57 | 拜托,别再问我怎么自学 Java 了!和盘托出 58 | ``` 59 | 60 | * 实战型文章。教你一步一步完成某个功能。 61 | 62 | ``` 63 | Vue + Spring Boot 项目实战(一):项目简介 64 | Flutter完整开发实战详解 65 | ``` 66 | 67 | * 解决问题型文章。教你如何解决某个开发中常见的问题。 68 | 69 | ``` 70 | JavaScript内存泄露的4种方式及如何避免 71 | Android 解决竖向RecyclerView嵌套横向RecyclerView时的滑动冲突 72 | ``` 73 | 74 | * 指南型文章。教你如何使用某个比较有名的项目、工具或者库。 75 | 76 | ``` 77 | 史上最简单的 SpringCloud 教程 | 终章 78 | 手把手教你使用腾讯的热修复框架-Tinker 79 | ``` 80 | * 经历分享型文章。分享个人的成长经历、面试经历、职场经历等。 81 | 82 | ``` 83 | 打工四年总结的数据库知识点 84 | 程序人生|从网瘾少年到微软、BAT、字节offer收割机逆袭之路 85 | ``` 86 | 87 | * 新技术型文章。分享目前行业内最新的前沿技术。 88 | 89 | ``` 90 | Android Studio 4.1 发布啦 91 | Flutter 升级 1.12 适配教程 92 | ``` 93 | 94 | ### 注重写作技巧 95 | 96 | 除了文章的类型要选对外,文章的内容也同样非常重要,这就要求我们要注重写作的技巧: 97 | 98 | * 标题一定要足够吸引人。 99 | * 文章的前言或者摘要一定要能够勾起读者的兴趣。 100 | * 关键的地方一定要配好图片(大小最好统一),图文并茂更能勾起读者的兴趣。 101 | * 注重文章的排版,条理要清晰。 102 | * 在文章的开头或者结尾最好能有推荐阅读或者关联阅读。 103 | 104 | ### 关注平台特性 105 | 106 | 有的时候,即使你文章选型好,内容也不错,但是可能发表了很多天也没什么阅读量,这是为什么呢? 107 | 108 | 有的时候,同样一篇文章,你在这个平台上的阅读量却远远低于另一个平台的阅读量,这是为什么呢? 109 | 110 | 这时候就涉及到平台的特性问题了。就打一个最简单的比方,同样一篇技术文章,你在CSDN上的阅读量会远远地高于哔哩哔哩,而同样的一个技术教学视频,你在哔哩哔哩的阅读量会远远地高于CSDN。很显然,哔哩哔哩是做视频的平台,而知乎是做问答的平台。 111 | 112 | 下面我就简单列举几个常见技术交流平台的特性: 113 | 114 | * CSDN 115 | 116 | 老牌技术交流平台,做技术博客起家,用户量庞大,但质量鱼龙混杂,博客内容以解决问题型、指南型、基础技术型文章为主,是博客新手练手的好地方。 117 | 118 | * 掘金 119 | 120 | 掘金是新晋的技术交流平台,在技术新人圈内比较火,由于有沸点模块的存在,所以成为划水圣地也是情理之中。用户量偏小,博客内容偏爱新技术,博客整体质量较高。 121 | 122 | * 简书 123 | 124 | 简书,综合型的创作、写作平台。虽说是非技术博客起家,但因其界面美观而深受大家喜爱。就技术博客内容和CSDN类似,不过整体质量要高一些。但是平台曾经违规导致被网新办请去喝茶,所以现在审核非常严,对作者也不尊重,动不动就把之前发表的文章全部给禁了,导致很多人已经开始抛弃它了。 125 | 126 | * 知乎 127 | 128 | 知乎,又被戏称为"逼乎",做知识问答起家,综合型的交流平台,平台知名度较高。但是由于是做问答起家,所以技术博客这块的阅读量也并不是很大。 129 | 130 | * 思否 131 | 132 | 思否,又名segmentfault,中国版stackoverflow,技术问答平台,整体偏向前端,用户量和掘金差不多,不是很大。 133 | 134 | 135 | 就这样,我根据以上的原则,开始不断地学习,不断地在各大技术交流平台输出文章,终于有了一点小成就,成为了CSDN的签约博客专家,在掘金和知乎上都收获了近2k的粉丝,每天各大平台文章的阅读量加起来大概也有近1000次左右。 136 | 137 | 说老实话,写技术博客这条路并不好走,需要你不懈的努力和坚定不移的信念,否则你很容易因为一些琐碎而放弃。 138 | 139 | ## 运营微信公众号 140 | 141 | 在做了一段时间技术博客小有成就之后,我并没有开始懈怠,那时我发现那些做得好的博主早已开始运营自己的微信公众号了,于是我想也没想,在 [微信公众号](https://mp.weixin.qq.com/) 官网申请表上填了一堆信息后,我开始了微信公众号的尝试。 142 | 143 | 刚开始写第一篇文章,打开微信公众号文章编辑器的时候,我就傻眼了:因为在此之前,我写博客都是使用的markdown编辑器,而微信公众号并不支持这一编辑器。不光如此,微信公众号编辑器对文章中链接有严格的规定,非微信公众号文章链接都将失效。 144 | 145 | 不过说到底,这些东西都是可以克服的,然而最难克服的还是这流量问题。由于微信公众号文章是非开放的,所以在创建初期基本是没有任何阅读量的。其次就是即使你有了一点的关注者,但是微信公众号文章推送的点击率又是非常低的,能达到20%的点击率已经是非常不错的了。 146 | 147 | 虽说微信公众号有很多劣势,运营起来非常困难,不过由于它拥有自动回复,自定义菜单,话题标签等功能,用它作为我开源项目资料的管理和引流平台还是非常不错的。 148 | 149 | ## 经验总结 150 | 151 | * 写技术博客的前提是你得有真才实学,否则你写出来的东西简直就是误人子弟。所以还是先把提升自己的技术水平作为首要任务。 152 | * 写技术博客前最好明确自己的写作主题,这样写出来的东西才更具吸引力。 153 | * 选对技术博客的类型很重要,同时需要注意写作技巧。 154 | * 不同交流平台的特性不同,适合的写作内容和方式也不同。在选择的平台,选择适合的写作方式和内容很关键。 155 | * 个人微信公众号的运营相对困难,不过作为开源项目资料的管理和引流平台还是非常不错的。 156 | 157 | ## 资源分享 158 | 159 | > 下面是我这两年来倒腾博客用过的好的工具和平台,分享给大家,仅供参考。 160 | 161 | ### 开源项目托管平台 162 | 163 | * github: https://github.com/ 164 | * gitee: https://gitee.com/ 165 | 166 | ### 技术交流平台 167 | 168 | * CSDN: https://blog.csdn.net/ 169 | * 掘金: https://juejin.im/ 170 | * 思否: https://segmentfault.com/ 171 | * 简书: https://www.jianshu.com/ 172 | * 知乎: https://www.zhihu.com/ 173 | * 哔哩哔哩: https://www.bilibili.com/ 174 | * 微信公众号: https://mp.weixin.qq.com/ 175 | 176 | ### 工具网址 177 | 178 | * markdown文章排版工具:https://www.mdnice.com/ 179 | * 免费图床(上传图片):https://img.rruu.net/ 180 | * 阿里巴巴矢量图标库:https://www.iconfont.cn/ 181 | * 微信公众号插件(壹伴助手):https://yiban.io/ 182 | * 草料二维码生成工具:https://cli.im/ 183 | * 在线ps工具:https://www.uupoop.com/ 184 | * 应用分发平台蒲公英:https://www.pgyer.com/ 185 | * MP4转gif工具(mac):https://github.com/mortenjust/droptogif 186 | -------------------------------------------------------------------------------- /Chatting/我的五年Android开发总结.md: -------------------------------------------------------------------------------- 1 | # 我的五年Android开发总结 2 | 3 | ## 前言 4 | 5 | > 两年前, 我写过一篇《我的三年Android开发总结》,时光流逝, 转眼间我已经从事Android开发已五年有余,很庆幸我依然从事着我热爱的Android开发. 6 | 7 | 我们做技术开发的人都有个习惯,那就是喜欢不定期地进行总结,当然我也不例外.我有个特别的爱好,那就是把我一年前的代码拿出来和我现在写的代码进行对比,看是否有所长进. 8 | 9 | **一个人回首过去,如果发现现在的自己和一年前或者更久前的自己没有任何积极的变化的话,那么你这些年就算是虚度光阴了.** 10 | 11 | 回首我这五年, 如果说前三年我是野蛮生长,疯狂撸码,较为浮躁的三年,那么这两年我就是沉下心来,潜心修炼积累经验的两年. 12 | 13 | ## 工作环境发生变化 14 | 15 | 我从事Android开发的前三年只在一家公司待过,每天都做着几乎相同的工作,做的项目也是较为简单的小项目,一两个人就能承担的应用开发,用的也是相对简单,没有什么核心竞争力的技术. 16 | 17 | 但是这两年来,我接触到了不少的公司.它们有改革的老国企,有刚刚成立的创业公司,也有已经上市的公司.不同类型、不同行业、不同规模的企业,必然导致工作内容、团队、企业文化的不同. 18 | 19 | 相比较我待的第一家公司来说,后面的几家公司更加尊重我的个人看法,这也是我决定跳槽的原因.虽然我也非常感谢我的第一家公司给予我更多的机会去成长,但是我们出来工作本质上是出于经济目的的,光谈理想而不给予现实的恩惠,一味强调你是公司培养出来的,时间长了是留不住人的. 20 | 21 | ### 老旧国企 22 | 23 | 俗话说,跳槽是有成本的.由于工作内容、团队的变化,我需要重新学习特定行业的专业知识,重新建立自己的同事关系,这就要求我需要非常强大的学习能力和适应能力. 24 | 25 | 我的第一次跳槽相对来说是比较失败的,因为没有经验,我居然跳槽到了一个严重老龄化的前国企公司,这是一家做汽车硬件的企业,周围的同事平均年龄35岁以上,每天他们讨论的话题都是自己家小孩的学习教育,这让我这个连女朋友都没有的单身汪情何以堪. 26 | 27 | 更让我无法适应的是它那非常慢的工作节奏,一周的工作半天就完成了,剩下的时间完全不知道做什么...真的是无事可做. 28 | 29 | ### 创业公司 30 | 31 | 后来我被一位非常赏识我的老板看中,加入到他的创业公司,成为了一名创业者.这是一家从事儿童可穿戴设备的公司,作为最早加入的员工之一,我可谓是见证了一家创业公司从起步到发展再到衰落的全过程,而这仅仅只需要一年的时间. 32 | 33 | 创业公司的坑虽然不少,不过这对我们个人的成长还是非常有价值的.在创业公司的这一年,我做了很多普通程序员这一辈子可能都无法触及的工作内容,也积累了很多人生哲学. 34 | 35 | * 1.应用开发框架的搭建. 36 | * 2.产品需求讨论. 37 | * 3.企业文化建设. 38 | * 4.企业税务处理. 39 | * 5.应用的上架和运营. 40 | * 6.产品生产、组装、销售和售后维护. 41 | * 7.企业商标、专利注册. 42 | 43 | 在创业公司,职位没有明显的划分,很多时候你需要做很多与你岗位无关的工作.从好的角度来说,这可以极大地丰富个人的人生阅历,提升一个人独立思考、自主学习和解决问题的能力.如果你的经济能力有一定的支持,那么去创业公司历练一把也是非常不错的,万一它日后发展成为一家独角兽呢? 44 | 45 | ### 上市公司 46 | 47 | 之后我来到了我现在待的这家公司.比起我之前待的创业公司就我一个Android开发,在这里我算是找到了失去很久的团队归宿感.大公司除了人多以外,工作的强度和难度也大大地增加,加班当然也成为了家常便饭,当然与之相对应的福利也是提升了不少. 48 | 49 | 在大公司,技术水平并不是决定这个人发展潜力的决定性因素.要知道大公司最不缺的就是人才,能进大公司的,哪个不是有两把刷子的呢?对于大公司而言,他们需要的是拥有极强责任心以及任劳任怨的员工,能否承担某一模块功能的稳定性就直接决定了你的去留.初此之外,沟通能力也是非常重要的能力.因为大公司的项目功能相对复杂,参与的人员众多,很多复杂的问题往往可以通过沟通予以解决.而且及时表达出你的想法,主动承担一些紧急的任务都能给你很多的加分. 50 | 51 | 可以说在大公司的这些日子里,让我学习和领悟到了很多非技术方面的能力: 52 | 53 | * 语言表达能力(大公司经常需要在很多人面前发言). 54 | * 与不同人沟通的能力. 55 | * 复杂问题的定位和解决能力. 56 | * 团队协作能力. 57 | * 帮助(指导)他人的能力. 58 | * 团队建设和管理的能力. 59 | 60 | 这也是为什么我去年写了那么多经验感悟文章的原因.而上面的这些能力,很多是仅仅靠撸码是无法获取到的. 61 | 62 | ## 心态上发生变化 63 | 64 | 我从事Android开发的前三年,之所以那么拼命地工作和学习,一部分是出于兴趣,而另一部则是想要证明自己. 65 | 66 | * 对于公司而言: 我要证明自己是公司不可或缺的人才. 67 | * 对于领导而言: 我要证明自己的技术水平是组内第一. 68 | * 对于家人而言: 我要证明自己有能力养活自己. 69 | * 对于女友而言: 我要证明自己有能力独自照顾一个人. 70 | 71 | **马斯洛需求层次理论告诉我们, 当一个人的需求得到满足之后,他会追寻更高层次的需求.** 72 | 73 | 当这些都被我一一证明了之后,一切都变了.证明自己已经显得不那么的重要,追求自身价值的体现成为了我现阶段的目标. 74 | 75 | 于是,我离开了当年的那家公司,放弃了父母规划的道路,离开了女友,开始遵从着自己的内心,寻找人生的答案... 76 | 77 | 此时的我,需要的并不是别人的肯定.我并不会在意别人的赞赏或者是诋毁,我所做的都是发自内心的事情,而不随外物而影响. 78 | 79 | 于是,我选择成为了一名开源项目的创作者和布道者.将我平时积累的技术经验全部分享出来,目的就是帮助全世界所有热爱技术的人士,同时让我的价值在别人身上得到体现. 80 | 81 | 在这两年内,我积极维护我开源的项目,同时还在各大技术社区和论坛上发布一些相关的技术文章,同时还运营了自己的微信公众号.近期,我还积极创作一些开源技术相关的视频.这样做的目的,一方面是可以把自己的经验和技术分享给更多需要的人,让知识发挥它更大的作用(知识在于传承), 另一方面也是满足了自己的虚荣心,让自己的价值得以体现. 82 | 83 | ## 思维方式发生变化 84 | 85 | 从事Android的前三年,我的想法是相对激进的.那时的我,对所有的新技术都是那么的趋之若鹜,而对于那些相对陈旧的技术是嗤之以鼻.我天真地认为那些新技术都是革命性的,都需要去掌握、学习并运用到实际的工作中去,而那些旧技术就是需要替代的对象. 86 | 87 | 于是,我就不计代价,大张旗鼓地把公司之前使用的旧技术一股脑地都准备换了,而当时领导的处处阻挠在我看来就是技术上的不思进取,中间还闹了不少矛盾,这也成为了我后来离开公司的原因之一.现在想来,当时的我想法真的是非常的幼稚! 88 | 89 | ### 技术不是唯一的生产力 90 | 91 | > 技术是生产力,但并不是唯一的生产力. 92 | 93 | 在创业公司的那一年里,让我彻彻底底地明白了,技术并不是唯一的生产力.一家公司的发展,更多靠的是强力的融资能力(资本),强大的渠道推广能力(营销),很多时候技术只是其中的一部分,充当的仅仅是工具人的角色. 94 | 95 | 当年那个极力崇尚技术至高无上的青年被现实狠狠地教育了一把.那时的我认为只有纯做技术的才能被人尊重,而那些在产品线做业务、测试,或者跑业务跑市场的人都是不值一提的.然而也正是我那时瞧不起的那些人,直接导致了我待的那家创业公司的衰败. 96 | 97 | ### 技术没有好坏 98 | 99 | > 技术没有好坏,更没有高低贵贱,有的只是合适与不合适. 100 | 101 | **新技术一定比旧技术好吗?** 如果这个问题在两年前问我,那么我的回答一定是肯定的! 但如果你现在问我,我的回答就不一样了. 102 | 103 | 任何的新技术一定是在旧技术上有了一些改进,否则它就不为是一项新技术.那么我们为何不直接使用它呢?因为这里我们考虑的更多是成本的问题: 104 | 105 | * 新技术必然带来更高的学习成本. 106 | * 新技术的引入必然带来更多的替代成本. 107 | * 新技术如果尚未成熟,当遇到一个坑时必然带来更大的解决成本. 108 | * 如果使用新技术遇到一些无法解决的问题,会带来一些风险成本. 109 | 110 | 当一项技术的使用所带来的收益要远远大于它带来的成本时,这个时候我们才会考虑采用这项技术. 111 | 112 | 可以看到上述我列举的成本对于大公司来说是相当巨大的: 113 | 114 | * 大公司的人员较多,新技术的学习成本非常高(不可能全裁了重招吧) 115 | * 大公司的项目通常偏大,替换的成本相对较高. 116 | * 大公司产品的功能较为复杂,碰到一些没人遇到过坑也是常有的事. 117 | * 大公司对项目的风险一般是零容忍的:宁可不做也不能冒风险. 118 | 119 | 这也是为什么很多大公司不愿意采用新技术,而是选择沿用之前成熟的技术的原因. 120 | 121 | 所以我们在思考一项技术能否被我们采用的时候,压根就没有考虑过这项技术的新旧以及高低贵贱.至于那些成天在技术社区里发一些技术焦虑文章的人,我觉得社区就应该屏蔽他们,以免误导那些刚入门的小朋友. 122 | 123 | ### 追本溯源 124 | 125 | > 当你抛开事物的表面,去追寻其本质之后,你会发现很多东西都是相通的. 126 | 127 | 两年前我在学习和使用技术的时候,更多的是遵循拿来主义和实用主义,天真地认为我会用这项技术就等于我掌握了这项技术. 128 | 129 | 之所以这么想,归结原因还是见的世面太小了.之前待的小公司,业务并没有那么复杂,用例也没那么多,遇到的情况也相对简单单一,所以简单地使用基本就完事了. 130 | 131 | 然而,到了大公司之后,那业务的复杂程度以及用例覆盖到的场景远远是你所想象不到的.很多时候,一些问题你是不能通过简单的使用就能解决的.尤其是发生一些诡异的问题时,你不阅读源码了解其实现原理,是无法进行解释的. 132 | 133 | 举个例子,我们做Android的都用过RecyclerView,可你有阅读过RecyclerView的源码,了解其缓存机制吗?相信你看完RecyclerView的源码之后,你就会知道,其实你所谓的掌握RecyclerView,只不过是RecyclerView的冰山一角. 134 | 135 | 学习一项技术,如果你只是掌握了如何去使用,那么这只是你迈出去的第一步,后面更重要的是要追本溯源,深究其底层实现原理,这个时候你才会发现,很多技术原来是相通的. 136 | 137 | ## 最后 138 | 139 | 作为一名Android开发的热爱者,只要Android不死,我就会一直在Android开发的道路上前行.无论前途如何曲折,我相信Android会变得更加美好! 140 | 141 | > 我是xuexiangjys,一枚热爱学习,爱好编程,勤于思考,致力于Android架构研究以及开源项目经验分享的技术up主。获取更多资讯,欢迎微信搜索公众号:**【我的Android开源之旅】** 142 | 143 | 144 | -------------------------------------------------------------------------------- /Chatting/技术人的宅家指南.md: -------------------------------------------------------------------------------- 1 | # 技术人的宅家指南 2 | 3 | ## 前言 4 | 5 | > 2021年的春节假期即将临近,但是最近国内又出现了疫情反复的情况,很多省市都发出了隔离政策,这不禁让我们又开始担心2020年春节宅家一个月的场景会不会再次上演。 6 | 7 | 作为一名技术宅,虽说疫情对我的影响可以忽略不计,不过我还是疫情能够早日结束的。毕竟我们每个人都是社交动物,隔离14天真的是太让人绝望了。再说我们绝大多数人的春节假期也不过7天,这一口气直接隔离14天,简直是不给人活路嘛... 8 | 9 | 未来,我们无法预计疫情到底什么时候可以结束,我们能够做的就是适应它,做好长期抗疫的准备。 10 | 11 | 假如因为疫情被再次要求居家隔离的话,你会怎么做呢?难道你准备一直打游戏,看视频或者刷抖音吗?难度你就准备这样一直虚度光阴下去吗? 12 | 13 | **适度的娱乐可以是精神调味剂,但是过度的娱乐可能就是精神鸦片了。** 14 | 15 | 如果你还希望自己能够有所提升,做一些有价值有意义的事情的话,这里我给出我的五点建议供大家参考,有备无患: 16 | 17 | * 阅读一本好书 18 | * 学习一项新技能 19 | * 尝试做一个项目 20 | * 培养一项兴趣 21 | * 总结经验 22 | 23 | 下面,我将以一名技术人的视角来具体阐述我们应该怎么做,才能更好地宅在家里,提升自己。 24 | 25 | --- 26 | 27 | ## 阅读一本好书 28 | 29 | > 阅读一本好书,那种能够引发你思考的书,是一件非常享受的事情。 30 | 31 | 无论什么时候,阅读一本好书都是没有错的。 32 | 33 | 尽管现在是信息化时代,我们获取知识和信息的途径有很多,但是书籍是永远不可以被替代的。因为我们从网络上获取到的知识和信息,绝大多数都是碎片化的,很少有人有能力去将它们组织起来。 34 | 35 | 这个时候,一本好书的价值就能充分体现出来了:它能够将碎片化的知识和信息整合成一套体系,由简入深,层层递进,能够引起你的思考,激发你的灵感。这类书的作者一般都是行业内比较知名的大咖,阅读他们写的书籍可以让我们少走很多弯路。 36 | 37 | 所以,如果你想快速地提升自己的知识面的话,阅读一本好书吧! 38 | 39 | --- 40 | 41 | ## 学习一项新技能 42 | 43 | > 学习一项新技能,提升自己的竞争力,这样在未来你就能有更多选择的机会。 44 | 45 | 学习一项新技能的目的,并不是要求你能立马将它运用到你的工作或者生活中去,这样功利性太强反而有可能误入歧途。 46 | 47 | 学习一项新技能,可以是和你工作相关的技能: 48 | 49 | * 如果你是一名Java程序员,你可以尝试去学习一下JavaScript语言; 50 | * 如果你是一名后台服务开发者,你可以尝试去学习一下前端开发技术,例如Android或者Html5页面开发技术。 51 | * 如果你是一名移动原生开发者,你可以尝试去学习一下跨平台开发技术,例如flutter或者React-Native技术。 52 | 53 | 学习一项新技术,可以拓宽我们的知识面,这样在未来我们思考问题的时候,就不会那么的片面和绝对。 54 | 55 | 当然,你也可以学习与自己工作无关的技能: 56 | 57 | * 尝试学习理财,让钱生钱。 58 | * 尝试学习摄影、视频剪辑。 59 | * 尝试学习烹饪。 60 | * 尝试学习一门舞蹈、一项乐器。 61 | * 尝试考一个专业技能证。 62 | 63 | 学习一项新技能,虽不能立竿见影地改善我们的生活,让我们获得更高的收入,但是作为一种知识储备,它能让我们在未来有更多选择的机会。 64 | 65 | 俗话说,机会总是留给有准备的人。当机会来临的时候,你再去学习的话,那肯定是来不及的了。 66 | 67 | --- 68 | 69 | ## 尝试做一个项目 70 | 71 | > 尝试做一个项目,在实践中检验真理,重构知识体系。 72 | 73 | 《实践是检验真理的唯一标准》,这是1978年《光明日报》特约评论员胡福明撰写的,由此改变了一代人的思想,为后来中国伟大的改革开放扫平了障碍。 74 | 75 | 早在明朝时期,著名的大思想家王守仁就提出过"知行合一"的理念:知而不行,那么就是不知。 76 | 77 | 我们在大学学习的时候,有一个教学环节是我们在大学之前从来都没有的,那就是课程设计。尤其是像我们计算机专业的课程,基本上每门课都有相对应的课程设计。很多时候学期期末的考试并不能准确检验我们掌握知识的程度,那么课程设计就是另一种检验的方式。 78 | 79 | 在我们实践的时候,很多平时学习忽略掉的细节可能就显得尤为重要,这个时候我们有可能需要重头再学习一遍,实践的过程同时也是学习和加深理解,重构知识体系的过程。 80 | 81 | 尝试做一个项目,可大可小,关键是要贴近生活,能够解决工作或者生活中遇到的一些问题。 82 | 83 | * 如果你是一名Android开发者,你可以写一款App能够方便大家的生活。 84 | * 如果你是一名后端服务开发者,你可以尝试写一个框架或者工具提升大家的开发效率。 85 | * 如果你是一名算法工程师,你可以尝试改进一项算法或者用算法改进一项技术。 86 | * 如果你是一名测试工程师,你可以尝试写一个自动化测试的脚本。 87 | * 如果你是一名设计师,你可以尝试为某个主题设计一套logo或者宣传海报。 88 | 89 | 很多时候我们不要因为简单事小而懒得去做,很多事情并不是你想象中的那样,只有真正去做了才知道其中的价值。 90 | 91 | --- 92 | 93 | ## 培养一项兴趣 94 | 95 | > 培养一项兴趣,丰富自己的业余生活,增添生活的乐趣! 96 | 97 | 培养一项兴趣可能和上面的学习一项新技能有些类似,不过目的完全不同。 98 | 99 | 兴趣并不一定做得好,做得好的也不一定是兴趣。那么如何去界定什么是兴趣呢? 100 | 101 | 所谓兴趣,就是一件长期以往你一直愿意去做的事情。你可以不用做得很好,也不用一直去做,但只要你去做就能给你带来快乐。 102 | 103 | 读书、写作、歌唱、乐器、健身、体育运动、烹饪、摄影、绘画、舞蹈等,这些都是值得我们去培养的兴趣,我们只需结合自身情况,选择几个我们感兴趣的培养即可。 104 | 105 | 没有兴趣的人生,生活是枯燥的,世界的单调的。所以,为了丰富自己的业余生活,增添生活的乐趣,赶紧着手培养一项兴趣吧! 106 | 107 | --- 108 | 109 | ## 总结经验 110 | 111 | > 学会思考,学会总结,这样你才能有所成长。 112 | 113 | 纵观历史,我们可以发现许多伟人、名人或各行各业的佼佼者都有一个显著特点: 善于总结经验。 114 | 115 | 俗话说失败乃成功之母,但如果每次失败你都不吸取教训,总结经验的话,可能失败就是真的失败了。 116 | 117 | 著名的学习金字塔理论就曾告诉我们:最好的学习方式是教别人。 118 | 119 | ![](https://pics3.baidu.com/feed/a044ad345982b2b7e582587970b76ee976099b0a.jpeg?token=74d1a7efaccbd8d82ec441effa978038) 120 | 121 | 我们在总结经验的时候,同样也可以使用教别人的这种方式,这样可以让我们总结得更加全面和彻底。 122 | 123 | 这里,作为技术人,我还是推荐大家有时间可以整理一下平时工作或者生活中积累的一些感悟或者心得,总结下来,并以文字(博客)或者视频的方式分享给大家,这样你即加深了印象同时也造福了大家,岂不是一举多得的事情嘛? 124 | 125 | --- 126 | 127 | ## 最后 128 | 129 | 说了这么多,相信此刻的你心中应该也有了份属于自己的假期宅家计划了吧。不过还是那句话--"知行合一",光知道没有用,你还得去付诸实践才行! 130 | 131 | 最后,原创不易,喜欢我的话,一定记得三连支持一下哦。 132 | 133 | > 更多资讯内容,欢迎搜索我的微信公众号:【我的Android开源之旅】 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /Chatting/浅谈App的启动优化.md: -------------------------------------------------------------------------------- 1 | # 浅谈App的启动优化 2 | 3 | ## 1. 应用启动的方式 4 | 5 | 在Android中,应用启动一般可分为三种:**冷启动**、**温启动**、**热启动**。 6 | 7 | 那么什么是**冷启动**、**温启动**和**热启动**呢?下面我们来简单看一下它们的定义: 8 | 9 | * 冷启动:当启动应用时,后台没有该应用的进程。这时系统会又一次创建一个新的进程分配给该应用,这个启动方式就是冷启动。 10 | 11 | * 温启动:当启动应用时,后台已有该应用的进程,但是Activity可能因为内存不足被回收。这样系统会从已有的进程中来启动这个Activity,这个启动方式叫温启动。 12 | 13 | * 热启动:当启动应用时,后台已有该应用的进程,且Activity仍然存在内存中没有被回收。这样系统直接把这个Activity拉到前台即可,这个启动方式叫热启动。 14 | 15 | 由于冷启动相对于其他启动方式多了进程的创建(Zygote进程fork创建进程)以及应用的资源加载和初始化(Application的创建及初始化),所以相对来说会比较耗时,所以我们一般说的App启动优化一般指的都是App的冷启动优化。 16 | 17 | ## 2. 优化方案 18 | 19 | **App启动优化的本质就是:启动速度和体验的优化**。 20 | 21 | 这就好比早些年你去饭店吃饭,你想要点餐但等了半天都没有服务人员过来,可能就等得不耐烦直接走开了。同样的,对于APP来说,如果用户点击App后长时间都打不开,用户就很可能失去耐心而卸载应用。 22 | 23 | 所以**启动速度是用户对我们App的第一体验**。如果启动速度过慢,用户第一印象就会很差,这样即使你功能做出花来,用户也不会愿意去使用。 24 | 25 | 其实要想优化App的启动体验,关键就是要让用户更快地获取到应用的内容(流畅,不卡顿、不等待),那么我们应该怎么做呢? 26 | 27 | ### 2.1 案例分析 28 | 29 | 这里我们还是先以之前说的去饭店吃饭为例来展开讨论。现在的饭店竞争是尤为得激烈,为了能够提高顾客的体验、留住顾客,真的是使出了浑身解数,除了口味之外,服务品质也是被摆在了越来越重要的位置。 30 | 31 | 就比方说: 32 | 33 | * 为了能够更快地给用户提供点餐服务,现在的饭店每个座子上基本都贴上了点餐的二维码。 34 | * 一些热门需要排队的餐馆,在前台张贴了二维码,提供排队取号和提前点餐服务。 35 | * 一些常年爆满需要排队的餐馆,还提供了零食、茶水、棋牌以及美甲等服务。 36 | * 有些餐厅为了提高上菜的速度和体验,设置了倒计时免单的服务。 37 | 38 | 以上的改进措施,都是这些年餐饮行业在竞争下,不断提高服务质量的产物。可以说谁的服务质量落下了,谁就有可能被淘汰。 39 | 40 | 其实我们仔细分析一下上面所列举的,不难看出有很多是可以让我们借鉴的。 41 | 42 | (1)案例1 43 | 44 | **分析**:饭店提供了点餐的二维码,本质上是将一件被动等待转变为主动请求的一种过程,客观上减少了等待的时间,从而提高了速度。 45 | 46 | **类比**:这对应我们的应用程序,就像原先一些耗时不必要的三方库需要被动等待其初始化完毕程序才会继续进行,转变为先不初始化这部分耗时的三方库,等真正用到时再进行初始化;又类似我们应用程序的游客模式,无需被迫进行一堆复杂的用户注册过程,就可以直接进入程序使用,待涉及一些用户信息功能的时候再提示用户注册。 47 | 48 | (2)案例2 49 | 50 | **分析**:热门餐馆同时提供排队取号和提前点餐服务,本质上是将原先无法同时进行的操作(需要先排上队等到座位,才能扫描座位号点餐)变成了可同时进行的操作,将串行任务转化为并行任务,从而节省了时间。 51 | 52 | **类比**:这对应我们的应用程序,就是将一些原本在主线程串行执行的耗时资源/数据加载,改为在子线程中并发执行。这在几个耗时任务耗时差距不大的时候优化尤为明显。 53 | 54 | (3)案例3 55 | 56 | **分析**:在顾客排队等待的时候,提供零食、茶水、棋牌以及美甲等服务,本质上就是让顾客提前享受本餐馆的服务,从而缓解顾客等待的焦虑。 57 | 58 | **类比**:这对应我们的应用程序,就是开屏启动页。在Android12上,Google强制增加了这个开屏页,就是为了让用户提前看到你的应用页面,让用户产生应用启动很流畅的假象,从而提高用户的启动体验。 59 | 60 | (4)案例4 61 | 62 | **分析**:设置倒计时免单服务,本质上就是给等待加上了进度条上限以及超时赔偿。俗话说最让人害怕的是等待,比等待更让人害怕的是看不见尽头的等待。而为等待设置上限,可以极大地缓解顾客等待的焦虑,毕竟等待超时了也是会有补偿的。 63 | 64 | **类比**:这对应我们的应用程序,就是一些应用(比如游戏)初次启动会非常耗时,所以它们通常会在启动页增加一个初始化/加载进度条页面,来告诉用户啥时候能加载完,而不是无止境未知的等待。 65 | 66 | 通过分析,我们可以看到(1)、(2)两种操作是从技术的角度来实现的优化,而(3)、(4)两种操作则更多的是从业务的角度去实现的优化。 67 | 68 | ### 2.2 优化策略 69 | 70 | 从上面的案例分析,我们可以得出,应用启动优化,我们可以分别从技术和业务的角度来进行决策。 71 | 72 | #### 2.2.1 技术优化 73 | 74 | (1)针对启动流程任务进行梳理。 75 | 76 | * 无耗时任务 -> 主线程 77 | * 耗时且需要同步任务 -> 异步线程 + 同步锁 78 | * 耗时且无需同步任务 -> 异步线程 79 | 80 | (2)非必要不执行。 81 | 82 | * 非必要数据 -> 懒加载 83 | * 非必要任务 -> 延迟/空闲执行 84 | * 非必要界面/布局 -> 延迟加载 85 | * 非必要功能 -> 删除/插件化 86 | 87 | (3)数据结构优化,减小初始化时间。 88 | 89 | * 数据结构尽可能复用,避免内存抖动 90 | * 数据结构申请的空间要恰到好处,不能过大(占用空间,创建慢)也不能过小(频繁扩充,效率低) 91 | * 对于必要且量大的数据,可采取分段加载。 92 | * 重要的资源本地缓存 93 | 94 | #### 2.2.2 业务优化 95 | 96 | (1)业务流程整合。 97 | 98 | * 多个相关的串行业务整合为统一的一个业务。 99 | * 不相关的串行业务整合为并行的业务。 100 | 101 | (2)业务流程拆分调整。 102 | 103 | * 对业务进行拆分,拆分出主要(必要)业务和次要(非必要)业务。 104 | * 分别对主要业务和次要业务进行优先级评估,业务执行按优先级从高到底依次执行。 105 | 106 | ### 2.3 优化方向 107 | 108 | 在优化之前,让我们先来分析一下冷启动的过程: 109 | 110 | ``` 111 | Zygote创建应用进程 -> AMS请求ApplicationThread -> Application创建 -> attachBaseContext > onCreate -> ActivityThread启动Activity -> Activity生命周期(创建、布局加载、屏幕布置、首帧绘制) 112 | ``` 113 | 114 | 以上过程,只有Application和Activity的生命周期这两个阶段对我们来说是可控的,所以这就是我们的优化方向。 115 | 116 | ## 3. 优化措施 117 | 118 | ### 3.1 启动流程优化 119 | 120 | 1.依据之前我们列举的技术优化策略,首先需要对启动的所有任务流程进行梳理,然后对其执行方式进行优化。 121 | 122 | * 只有必要且非耗时的任务在`主线程`执行。 123 | * 耗时且需要同步的任务,使用`异步线程 + 同步锁`的方式执行。 124 | * 耗时且无需同步的任务在`异步线程`执行。 125 | 126 | 2.第三方SDK初始化优化。 127 | 128 | * 对于那些在启动时非必要的第三方SDK,可以延迟初始化。 129 | * 对于初始化耗时的第三方SDK,可以开启一个后台服务/异步线程进行初始化。 130 | * 一些三方sdk使用自定义的ContentProvider进行初始化。这里我们可以使用JetPack提供的`Startup`将多个初始化的`ContentProvider`聚合成一个来进行优化。 131 | 132 | 3.使用任务执行框架。 133 | 134 | 这里我们还可以使用一些第三方的任务启动框架,对启动流程进行优化。下面我就拿我开源的[XTask](https://github.com/xuexiangjys/XTask) 简单介绍一下: 135 | 136 | 这里我们模拟了三种类型的任务: 137 | 138 | * 1.优先级最重要的任务,执行时间50ms; 139 | * 2.单独的任务,没有执行上的先后顺序,但是需要同步,每个执行200ms; 140 | * 3.耗时、最不重要的任务,等所有任务执行完毕后执行,每个执行需要1000ms。 141 | 142 | **优化前** 143 | 144 | ```java 145 | /** 146 | * 优化前的写法, 这里仅是演示模拟,实际的可能更复杂 147 | */ 148 | private void doJobBeforeImprove(long startTime) { 149 | new TopPriorityJob(logger).doJob(); 150 | for (int i = 0; i < 4; i++) { 151 | new SingleJob((i + 1), logger).doJob(); 152 | } 153 | new LongTimeJob(logger).doJob(); 154 | log("任务执行完毕,总共耗时:" + (System.currentTimeMillis() - startTime) + "ms"); 155 | } 156 | ``` 157 | 158 | 执行结果: 159 | 160 | 由于所有的任务都是执行在主线程,串行执行,所以花了大约1865ms。 161 | 162 | ![27Svq.md.png](https://c2.im5i.com/2022/11/20/27Svq.md.png) 163 | 164 | **优化后** 165 | 166 | ```java 167 | /** 168 | * 优化后的写法, 这里仅是演示模拟,实际的可能更复杂 169 | */ 170 | private void doJobAfterImprove(final long startTime) { 171 | ConcurrentGroupTaskStep groupTaskStep = XTask.getConcurrentGroupTask(); 172 | for (int i = 0; i < 4; i++) { 173 | groupTaskStep.addTask(buildSingleTask(i)); 174 | } 175 | XTask.getTaskChain() 176 | .addTask(new MainInitTask(logger)) 177 | .addTask(groupTaskStep) 178 | .addTask(new AsyncInitTask(logger)) 179 | .setTaskChainCallback(new TaskChainCallbackAdapter() { 180 | @Override 181 | public void onTaskChainCompleted(@NonNull ITaskChainEngine engine, @NonNull ITaskResult result) { 182 | log("任务完全执行完毕,总共耗时:" + (System.currentTimeMillis() - startTime) + "ms"); 183 | } 184 | }).start(); 185 | log("主线程任务执行完毕,总共耗时:" + (System.currentTimeMillis() - startTime) + "ms"); 186 | } 187 | 188 | @NonNull 189 | private XTaskStep buildSingleTask(int finalI) { 190 | return XTask.getTask(new TaskCommand() { 191 | @Override 192 | public void run() throws Exception { 193 | new SingleJob((finalI + 1), logger).doJob(); 194 | } 195 | }); 196 | } 197 | ``` 198 | 199 | 执行结果: 200 | 201 | 由于只有优先级最重要的任务在主线程执行,其他任务都是异步执行,所以主线程任务执行消耗57ms,所有任务执行消耗1267ms。 202 | 203 | ![](https://c2.im5i.com/2022/11/20/27gEm.md.png) 204 | 205 | ### 3.2 IO优化 206 | 207 | 3.网络请求优化。 208 | 209 | * 启动过程中避免不必要的网络请求。对于那些在启动时非必要执行的网络请求,可以延时请求或者使用缓存。 210 | * 对于需要进行多次串行网络请求的接口进行优化整合,控制好请求接口的粒度。比如后台有获取用户信息的接口、获取用户推荐信息的接口、获取用户账户信息的接口。这三个接口都是必要的接口,且存在先后关系。如果依次进行三次请求,那么时间基本上都花在网络传输上,尤其是在网络不稳定的情况下耗时尤为明显。但如果将这三个接口整合为获取用户的启动(初始化)信息,这样数据在网络中传输的时间就会大大节省,同时也能提高接口的稳定性。 211 | 212 | 4.磁盘IO优化 213 | 214 | * 启动过程中避免不必要的磁盘IO操作。这里的磁盘IO包括:文件读写、数据库(sqlite)读写和SharePreference等。 215 | * 对于启动过程中所必须的数据加载,选择合适的数据结构。可以选择支持随机读写、延时解析的数据存储结构以替代SharePreference。 216 | * 启动过程中避免大量的序列化和反序列化。 217 | 218 | ### 3.3 线程优化 219 | 220 | 我们在开发应用的过程中,都或多或少会使用到线程。当我们创建一个线程时,需要向系统申请资源,分配内存空间,这是一笔不小的开销,所以我们平时开发的过程中都不会直接操作线程,而是选择使用线程池来执行任务。 221 | 222 | 但问题就在于如果线程池设置不对的话,很容易被人滥用,引发内存溢出的问题。而且通常一个应用会有多个线程池,不同功能、不同模块乃至是不同三方库都会有自己的线程池,这样大家各用各的,就很难做到资源的协调统一,劲不往一处使。 223 | 224 | ##### 3.3.1 线程优化分析 225 | 226 | 1.线程池耗时分析。 227 | 228 | 要想进行线程优化,首先我们就需要了解线程池在使用过程中,哪些地方比较耗时。 229 | 230 | * 首先,从常规上来讲,线程池在使用过程中,线程创建、线程切换和CPU调度比较耗时。 231 | * 其次,需要从业务的角度去分析当前应用的线程使用情况,到底是哪些任务导致线程池大量的创建和执行耗时。这里我们可以使用Hook的方式,去Hook线程的创建(ThreadFactory的`newThread`方法)和执行进行统计。 232 | * 最后,我们需要结合业务的重要性(优先级)以及其相应的线程执行开销,进行综合考量,调整线程池的执行策略。 233 | 234 | 2.线程池执行分析。 235 | 236 | 然后再让我们看看线程池的执行逻辑: 237 | 238 | 我们知道,一个线程池通常由一个核心线程池和一个阻塞队列组成。那么当我们调用线程池去执行一个任务的时候,线程池是如何执行的呢? 239 | 240 | ``` 241 | 核心线程池 -> 阻塞队列 -> 最大线程数(新建线程) -> RejectedExecutionHandler(拒绝策略) 242 | ``` 243 | 244 | * 当线程池中的核心线程池线程饱和后,任务会被塞进阻塞队列中等待。 245 | * 如果阻塞队列也满了,线程池会创建新的线程。 246 | * 但是如果目前线程池中的线程总数已经达到了最大线程数,这个时候会调用线程池的拒绝策略(默认是直接中断,抛出异常)。 247 | 248 | 其中核心线程池的最大线程数并不是设置的越大越好,为什么这么说?因为CPU的处理能力是有限的,四核的CPU一次也只能同时执行四个任务,如果核心线程池数设置过大,那么各任务之间就会互相竞争CPU资源,加大CPU的调度消耗,这样反而会降低线程池的执行效率。 249 | 250 | 一般来说,核心线程池的最大线程数满足下面的公式: 251 | 252 | ``` 253 | 最佳核心线程数目 = ((线程等待时间 + 线程CPU时间)/ 线程CPU时间 )* CPU数目 254 | ``` 255 | 256 | (1)线程等待时间所占比例越高,需要越多线程。 257 | (2)线程CPU时间所占比例越高,需要越少线程。 258 | 259 | 这里我们考虑两种最常见的情况: 260 | 261 | * **CPU密集型任务**:这种任务绝大多数时间都在进行CPU计算,线程等待时间几乎为0,因此这时最佳核心线程数为 n + 1(或者为n),这里n为CPU核心数。 262 | * **IO密集型任务**:这种任务,CPU通常需要等待I/O(读/写)操作,这样CPU的处理时间和等待时间几乎差不多,因此这时最佳核心线程数为 2n + 1(或者为2n),这里n为CPU核心数。 263 | 264 | ##### 3.3.2 线程优化目标 265 | 266 | 通过上面对线程池的分析,我们可以知道: 267 | 268 | * 线程池设置过大,会侵占内存,互相竞争CPU资源,增加CPU的调度耗时。 269 | * 线程池设置过小,任务会阻塞串行,降低线程池执行效率。 270 | 271 | 因此,我们**线程优化的目标**是: 272 | 273 | (1)拥有可以统筹全局的统一的线程池。 274 | (2)能根据机器的性能来控制数量,合理分配线程池大小。 275 | (3)能够根据业务的优先级进行调度,优先级高的先执行。 276 | 277 | ##### 3.3.3 线程优化具体措施 278 | 279 | 1.建立主线程池+副线程池的组合线程池,由线程池管理者统一协调管理。主线程池负责优先级较高的任务,副线程池负责优先级不高以及被主线程池拒绝降级下来的任务。 280 | 281 | 这里执行的任务都需要设置优先级,任务优先级的调度通过`PriorityBlockingQueue`队列实现,以下是主副线程池的设置,仅供参考: 282 | 283 | * 主线程池:核心线程数和最大线程数:2n(n为CPU核心数),60s keepTime,PriorityBlockingQueue(128)。 284 | * 副线程池:核心线程数和最大线程数:n(n为CPU核心数),60s keepTime,PriorityBlockingQueue(64)。 285 | 286 | 2.使用Hook的方式,收集应用内所以使用`newThread`方法的地方,改为由线程池管理者统一协调管理。 287 | 288 | 3.将所有提供了设置线程池接口的第三方库,通过其开放的接口,设置为线程池管理者管理。没有提供设置接口的,考虑替换库或者插桩的方式,替换线程池的使用。 289 | 290 | ### 3.4 闪屏优化 291 | 292 | > 闪屏优化属于启动用户体验的优化。毕竟谁也不想使用页面一闪一闪的应用。 293 | 294 | 1.设置自定义闪屏页。 295 | 296 | 设置自定义的闪屏页可以提高我们启动的"视觉速度"。通常会设置一个背景,然后把logo居中显示,可以使用xml文件来布局(注意,该图片不可展示动画,并且展示时间也不可控)。这种方式可以给用户一种启动非常快的感觉,不仅解决了启动白屏的问题,并且展示了品牌logo也有助于提升品牌认知。 297 | 298 | (1) 使用xml自定义一张带有logo的图片。 299 | ```xml 300 | 301 | 303 | 304 | 305 | 306 | 309 | 310 | 311 | 312 | 315 | 316 | 317 | ``` 318 | 319 | (2) 把这张图片通过设置主题的`android:windowBackground`属性方式显示为启动闪屏。 320 | 321 | ```xml 322 | 327 | ``` 328 | 329 | (3) 在manifest中将主页的主题设置为刚才带启动图片的`Launch`主题。 330 | 331 | ```xml 332 | 335 | 336 | ``` 337 | 338 | (4) 代码执行到主页面的onCreate的时候设置为程序正常的主题,这样就切回到正常主题背景了。 339 | 340 | ```java 341 | public class BaseActivity extends XPageActivity { 342 | @Override 343 | protected void onCreate(Bundle savedInstanceState) { 344 | setTheme(R.style.AppTheme); 345 | super.onCreate(savedInstanceState); 346 | } 347 | } 348 | ``` 349 | 350 | 2.使用Android12提供的启动画面Splash Screen。 351 | 352 | [https://developer.android.google.cn/guide/topics/ui/splash-screen](https://developer.android.google.cn/guide/topics/ui/splash-screen) 353 | 354 | 3.如果是因为某些不可抗拒的原因(例如一些大型游戏的首次加载),导致第一次启动非常慢的话,可以在主页面进入之前,增加一个进度条加载页面。这样的好处就是要告诉用户,到底需要多久才能加载完毕,给用户一个明确的信息,缓解用户的等待焦虑。 355 | 356 | 4.减少首页的跳转层次。除非某些不可抗拒的原因(比如广告作为一项重要收入来源),尽量不要设置启动页(SplashActivity),因为打开和关闭任何一个Activity都是需要消耗时间的,每多一个Activity的跳转就意味着我们主页面的打开时间会被延长。 357 | 358 | ### 3.5 主页面优化 359 | 360 | > 主页面的启动和显示也是app启动非常重要的一部分。 361 | 362 | #### 3.5.1 布局优化 363 | 364 | > 布局优化的核心就是:提高页面渲染的速度,防止页面过度渲染导致耗时。 365 | 366 | * 降低视图层级。减少冗余或者嵌套布局,防止页面过度渲染。合理使用`merge`标签和约束布局`ConstraintLayout`。 367 | * 不必要的布局延迟加载。用`ViewStub`替代在启动过程中不需要显示的`UI`控件。 368 | * 首页懒加载。首页不需要立即显示的页面,可以使用懒加载。 369 | * 使用自定义`View`替代复杂的`View`叠加。 370 | * 布局异步预加载。详情可参考:[View 的异步 Inflate+ 全局缓存](https://blog.csdn.net/alienttech/article/details/106759615) 371 | 372 | #### 3.5.2 页面数据预加载 373 | 374 | 一般来说,我们喜欢在每个页面内部才开始加载和显示数据,因为这样写可能更容易让人看懂,利于以后的维护。但是如果等页面UI布局初始化完毕后,我们才去加载数据的话,势必会增加页面启动显示的时间。 375 | 376 | 因为每个页面(Activity)的启动本身就是比较耗时的过程,我们可以将需要显示的数据进行预加载(即页面启动和数据加载同时进行,串行->并行),这样等页面UI布局初始化完毕后,我们就可以拿着预加载的数据直接渲染显示了,这样可以减少数据加载的等待,从而达到加快页面显示的目的。 377 | 378 | 这里我们可以参考开源预加载库:[PreLoader](https://github.com/luckybilly/PreLoader) 379 | 380 | ![](https://c2.im5i.com/2022/11/20/27QrP.png) 381 | 382 | ### 3.6 系统调度优化 383 | 384 | * 1.**启动阶段不启动子进程**,只在主进程执行Application的`onCreate`方法。因为子进程会共享CPU的资源,导致主进程CPU紧张。 385 | 386 | * 2.**启动过程中减少系统调用**,避免与`AMS`、`WMS`竞争锁。因为`AMS`和`WMS`在应用启动的过程中承担了很多工作,且这些方法很多都是带锁的,这时应用应当避免与它们进行通信,避免出现大量的锁等待,阻塞关键操作。 387 | 388 | * 3.**启动过程中除了`Activity`之外的组件启动要谨慎**。因为四大组件的启动都是通过主线程Handler进行驱动的,如果在应用启动的同时他们也启动,Handler中的Message势必会增加,从而影响应用的启动速度。 389 | 390 | * 4.**启动过程中减少主线程Handler的使用**。原因同上面一样,Activity的启动都是由主线程Handler进行驱动的,应用启动期间减少主线程Handler的使用,可以减小对主页面启动的影响。对于那些量大且频繁的任务调度,可以使用`HandlerThread`中的`Looper`创建属于子线程的handler来代替。 391 | 392 | * 5.**使用IdleHandler**。利用`IdleHandler`特性,在消息队列空闲时,对延迟任务进行分批初始化。 393 | 394 | ### 3.7 GC 优化 395 | 396 | 启动过程中应当减少`GC`的次数。因为GC会暂停程序的执行,从而会带来延迟的代价。那么我们应当如何避免频繁的GC呢? 397 | 398 | * 1.避免进行大量的字符串操作,特别是序列化和反序列化。不要使用+(加号)进行字符串拼接。 399 | * 2.避免临时对象的频繁创建,频繁创建的对象需要考虑复用。 400 | * 3.避免大量bitmap的绘制。 401 | * 4.避免在自定义View的`onMeasure`、`onLayout`和`onDraw`中创建对象。 402 | 403 | ### 3.8 Webview启动优化 404 | 405 | > 如果你的应用使用到了Webview,可以按需对Webview进行优化。 406 | 407 | * 1.由于WebView首次创建比较耗时,需要预先创建WebView,提前将其内核初始化。 408 | * 2.使用WebView缓存池,用到WebView的时候都从缓存池中拿。 409 | * 3.应用内置本地离线包,如一些字体和js脚本,即预置静态页面资源。 410 | 411 | ### 3.9 应用瘦身 412 | 413 | > 对应用瘦身,可以最直接地加快资源加载的速度,从而提高应用启动的效率。 414 | 415 | * 1.删除没有引用的资源。我们可以使用Inspect Code或者开启资源压缩,自动删除无用的资源。 416 | * 2.能用xml写Drawable的,就不要使用UI切图。 417 | * 3.重用资源。同一图像的着色不同,我们可以用android:tint和tintMode属性调整使用。 418 | * 4.压缩png和jpeg文件。这里推荐一个非常好用的图片压缩插件:[img-optimizer](https://github.com/chenenyu/img-optimizer-gradle-plugin) 419 | * 5.使用webp文件格式。Android Studio可以直接将现有的bmp,jpg,png或静态gif图像转换为webp格式。 420 | * 6.使用矢量图形,尤其是那些与分辨率无关,且可伸缩的小图标尽可能使用矢量图形。 421 | * 7.开启代码混淆。使用proGuard代码混淆器工具,包括压缩、优化、混淆等功能。 422 | * 8.对于一些独立也非必须的功能模块,采用插件化,按需加载。 423 | 424 | ### 3.10 资源重排 425 | 426 | 利用Linux的IO读取策略,PageCache和ReadAhead机制,按照读取顺序重新排列,减少磁盘IO次数。具体可参见[《支付宝 App 构建优化解析:通过安装包重排布优化 Android 端启动性能》](https://juejin.cn/post/6844903710020091917) 这篇文章。这种技术门槛较高,一般应用都不会用到。 427 | 428 | ### 3.11 类重排 429 | 430 | 类重排的实现通过ReDex的Interdex调整类在Dex中的排列顺序。调整Dex中类的顺序,把启动时需要加载的类按顺序放到主dex里。具体实现可以参考[《Redex初探与Interdex:Andorid冷启动优化》](https://mp.weixin.qq.com/s/Bf41Kez_OLZTyty4EondHA?) 这篇文章。 431 | 432 | ## 4. 如何进行优化 433 | 434 | 上面讲了那么多应用启动优化的策略和措施,可能有些人就会问了:那么具体到我们每个不同的项目上,我们应该如何进行优化呢? 435 | 436 | 以下是我个人的优化步骤,仅供参考: 437 | 438 | * 1.**明确优化的内容和目标**。首先,做任何优化一定是需要带着问题(目的)去优化的。任何不带目的进行的优化都是耍流氓。 439 | * 2.**分析现状、确认问题**。当我们目前需要优化的内容后,接下来就是需要进行大量的埋点统计、比较与分析,确认到底是因为什么原因导致的应用启动过慢,找到需要优化的部位。 440 | * 3.**进行针对性的优化**。找到导致应用启动过慢的问题之后,就是按照本篇讲述的优化策略和措施,进行针对性的优化。 441 | * 4.**对优化结果进行总结并进行持续跟进**。对优化前后的数据进行统计和比较,总结优化的经验并在组内进行分享,并在后续的版本中进行持续跟进。有条件的可以结合CI,增加线上的启动性能监控。 442 | 443 | ## 最后 444 | 445 | 讲了这么多,还是希望大家在平时开发的过程中,多重视一些应用启动优化的相关技巧,这样等别人让你优化应用启动的时候,也就不会那么手足无措了。 446 | 447 | > 我是xuexiangjys,一枚热爱学习,爱好编程,勤于思考,致力于Android架构研究以及开源项目经验分享的技术up主。获取更多资讯,欢迎微信搜索公众号:**【我的Android开源之旅】** 448 | -------------------------------------------------------------------------------- /Chatting/由一个进度条更新所引发的思考.md: -------------------------------------------------------------------------------- 1 | # 由一个进度条更新所引发的思考 2 | 3 | ## 事情的起因 4 | 5 | 事情是这样子的,我前阵子在Github上开源了一个Android全量版本更新的框架[XUpdate](https://github.com/xuexiangjys/XUpdate), 受到了大家一致的好评。 6 | 7 | 但是随着使用者基数的不断增大,中间也暴露了很多问题,其中问的最多的就是:"为啥我的版本更新进度条不显示或者不更新呢?" 8 | 9 | ## 出现的问题 10 | 11 | 如果是我遇到了这个问题,我想我最直接的解决方式就是阅读源码或者打断点、打日志去定位问题了。 12 | 13 | ***为啥别人都可以,而我却不行呢?*** 14 | 15 | 然而遗憾的是,我发现很多人出现问题的第一反应就是:这什么破玩意儿,怎么不起作用。然后就开始去百度各种搜,又或者加QQ交流群上来就@我一下,把我整得很懵,如下图所示: 16 | 17 | ![](https://img.rruu.net/image/6019598202f01) 18 | 19 | 起初对于这类问题,我也是非常苦恼: 20 | 21 | 首先,导致版本更新进度条不显示或者不更新的原因会有很多种情况,上来什么上下文都没有就让我帮忙分析问题,那简直就如同你是单身,却问同样是单身狗的我如何摆脱单身是一个级别的问题,你让我怎么回答你呢? 22 | 23 | 其次,我的很多开源框架都会有丰富的wiki资料,里面会有常见问题一栏,里面都罗列了很多使用者常犯的错误已经解决方法。例如XUpdate,就有一份详细的[常见问题](https://github.com/xuexiangjys/XUpdate/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98) 说明,里面就介绍了进度条不更新的原因以及解决方案。但是很遗憾,很多人都选择视而不见。很明显的,答案都给你了,你却不愿意去抄,我是真的无能为力。 24 | 25 | 最后,当问题没有得到解决,绝大多数人会选择默默离开,转身去寻找一个更为适合自己的框架,而另一部分人就没有那么友善了,他会对你的框架进行恶语相向,不断贬低你的框架,搞得一副我欠他的样子...就类似下面这位网友这样: 26 | 27 | ![](https://img.rruu.net/image/601968cfed261) 28 | 29 | ## 我的反思 30 | 31 | 做开源项目这么多年,我一直在思考两个问题: 32 | 33 | * 为什么要做开源? 34 | 35 | * 怎样才能做好开源? 36 | 37 | ### 为什么要做开源 38 | 39 | 在这里我不得不承认,最初我做开源项目的目的就是想提升自己在行业内的知名度,这样也方便以后找工作。但是随着我的开源项目被越来越多的人认可和使用之后,这种观念就渐渐发生变化了,尤其是当我得知有人通过我的开源框架,在短短几天内就写出了一个商用级的App应用后,我的内心其实是非常喜悦的。 40 | 41 | ***现在的我做开源项目,目的非常纯粹:就是想尽可能地帮助大家更好地进行开发,提升开发的效率和能力,让大家有更多的时间用来学习和生活,而不是在公司无尽地重复搬砖。*** 42 | 43 | 但是据我观察,很多人在利用开源项目方面,只是为了解决他们遇到的问题,正所谓拿来主义,他们并不会去对项目的设计结构或者实现原理加以研究和学习,这其实完全背离了我做开源项目的初衷。 44 | 45 | 我一直强调,会用框架并不代表你掌握了框架。只会用框架而不去学习框架的设计思想和实现原理,那么你就是用得再熟练,框架依旧还是别人的,你依旧还只是个初级开发工程师。 46 | 47 | 更何况你在完全不了解原理的情况下,就冒然去使用一个框架,那么等以后开发遇到坑了之后你咋办,难道准备继续换框架吗? 48 | 49 | 所以,我现在还是鼓励我的框架使用者遇到问题自己解决,鼓励他们多看文档、源码,多学习框架中巧妙的设计思想,而不是单纯为了解决问题而解决问题。 50 | 51 | ### 怎样才能做好开源 52 | 53 | 我在做开源之初,犯了一个开源者常犯的一个错误:认知错误! 54 | 55 | 我天真的认为,我的开源项目使用者都是和自己水平差不多的专业人士,很多行话或者基础知识默认他们都是知道的,因此导致了开源项目初期产生很多漏洞或者描述不清的地方。 56 | 57 | 就拿我的XUpdate项目来说,三年间我陆陆续续地解了100+的issue,你能相信吗,一个简简单单的Android版本更新我可以处理这么多的问题。 58 | 59 | ![](https://img.rruu.net/image/6019731a5df32) 60 | 61 | 有一天,有个使用者在qq交流群里面又是上来就直接@我:有下载进度,但是进度条为什么不显示?并且发了自己下载功能的实现逻辑代码如下图: 62 | 63 | ![](https://img.rruu.net/image/601975ca2c9f0) 64 | 65 | 刚开始看这串代码,我就发现不对劲:这小伙子不讲武德啊,怎么能在子线程里面直接回调进度更新呢,这怕是没写过Android吧... 66 | 67 | 于是,我直接在群里回了一句"你真的人才,在子线程里面回调",然后就默默打开XUpdate的源码,给所有的回调涉及到UI更新的地方都加了一下线程判断,保证在主线程处理UI。 68 | 69 | 但是写着写着,我又开始觉得不对劲,如果真是在子线程里处理UI的话,为啥程序没崩呢? 70 | 71 | 果不其然,我的疑惑是对的。小伙在听取了我的建议:将回调放到了主线程之后,依旧还是不能显示进度条。 72 | 73 | 看到这儿,直觉告诉我可能和这边的进度回调无关。于是我又重新打开进度条更新的源码,仔细看了一遍之后,然后又对比了他之前发来的代码,我恍然大悟: 74 | 75 | ![](https://img.rruu.net/image/60197a2a12ded) 76 | 77 | 这里`handleStart`对应的是下载回调的`onStart`,而`handleProgress`对应的是下载回调的`onProgress`,下载回调接口的完整定义如下: 78 | 79 | ```java 80 | /** 81 | * 下载回调 82 | */ 83 | interface DownloadCallback { 84 | /** 85 | * 下载之前 86 | */ 87 | void onStart(); 88 | 89 | /** 90 | * 更新进度 91 | * 92 | * @param progress 进度0.00 - 0.50 - 1.00 93 | * @param total 文件总大小 单位字节 94 | */ 95 | void onProgress(float progress, long total); 96 | 97 | /** 98 | * 结果回调 99 | * 100 | * @param file 下载好的文件 101 | */ 102 | void onSuccess(File file); 103 | 104 | /** 105 | * 错误回调 106 | * 107 | * @param throwable 错误提示 108 | */ 109 | void onError(Throwable throwable); 110 | } 111 | ``` 112 | 很明显,这个小伙儿只实现了`onProgress`的回调接口,却没有实现`onStart`的回调接口,这就导致了`handleStart`方法里面的`mNumberProgressBar.setVisibility(View.VISIBLE);`一直没有得到执行,而在`handleProgress`方法里面又没有对进度条设置显示的方法,所以进度条当然一直不显示啦... 113 | 114 | 看到这儿,我只想说一句"我大意了,没有闪啊!"。要不是这位小伙儿贴出了他那段骚气的代码,我是怎么也不会想到居然有人会这样写! 115 | 116 | 吐槽归吐槽,但这里确实有逻辑漏洞,所以还是需要兼容一下的。于是乎我就在更新进度条的地方增加了一层判断:如果进度条当前不在显示,那么就先显示它,再更新进度。代码变动如下: 117 | 118 | ![](https://img.rruu.net/image/60197e073a2df) 119 | 120 | 在经过这次惨痛的教训之后,让我更加深彻地领悟了一点:你永远都猜想不到电脑的那头是谁,以及如何使用你的开源项目的。 121 | 122 | 为此,我特地为[XUpdate](https://github.com/xuexiangjys/XUpdate) 开源项目做了如下改进措施: 123 | 124 | * 转变思维,站在小白的角度,重新梳理一下现有的使用文档, 让文档更加简单易懂。 125 | * 编写了一个XUpdate的简化库XUpdateAPI:[https://github.com/xuexiangjys/XUpdateAPI](https://github.com/xuexiangjys/XUpdateAPI) ,降低XUpdate的使用难度。 126 | * 陆续做了几期XUpdate相关的教学视频: [https://space.bilibili.com/483850585/channel/detail?cid=164280](https://space.bilibili.com/483850585/channel/detail?cid=164280) 127 | 128 | ## 总结 129 | 130 | 说了这么多,让我们来简单总结一下我的几点反思: 131 | 132 | * 1.我们在使用开源项目的时候,要注重对其设计思想和实现原理加以研究和学习,拒绝拿来主义。 133 | * 2.做开源项目的目的应当是为了学习和交流,帮助更多的人成长,而不是为了自己的一己私利。 134 | * 3.好的开源项目,应当是站在使用者角度去分析和解决问题,脱离使用者的项目终将会被遗弃。 135 | 136 | > 我是xuexiangjys,一枚热爱学习,爱好编程,致力于Android架构研究,开源项目经验分享的技术up主,想要获取更多资讯内容,欢迎搜索我的微信公众号: **【我的Android开源之旅】** 137 | -------------------------------------------------------------------------------- /Chatting/程序员的bug修复宝典.md: -------------------------------------------------------------------------------- 1 | # 程序员的bug修复宝典 2 | 3 | ## 前言 4 | 5 | > bug, 又名程序缺陷或者程序漏洞, 是每个程序员每天都回避不了的东西。程序员对bug的感情可谓是五味杂陈:一方面bug非常可恶,尤其是一些偶现的bug,它强大到可以摧毁一个优秀程序员的意志;另一方面很多bug又是程序员自己亲手写下的,无奈之余只能自嘲一句:不写bug我们就要失业了! 6 | 7 | 作为一名职业程序员,同时也是一名开源创作者, 夸张点说,我解过的bug可以绕地球一圈。每天写bug解bug几乎是我的日常。 8 | 9 | 但是,作为一个善于思考和总结的技术up主,我怎么会止步于每天写bug和解bug呢?更何况,人生在世,总得有点追求。既然我不能够阻止bug的产生,那么就让我总结一点bug的修复技巧,让bug消失地更快。 10 | 11 | --- 12 | 13 | ## bug修复的生命周期 14 | 15 | > 中医讲究"望闻问切",其实修复一个bug就像给病人看一场病,本质上是相通的。 16 | 17 | 当我们遇到一个bug(问题)的时候,一般我们需要经历如下6个步骤: 18 | 19 | * 1.`了解bug`。我们首先需要到底出了什么bug,现象是什么,怎样发生的。 20 | * 2.`复现bug`。在了解了bug的大致情况之后,我们需要能够找到复现的路径,这就为后面bug的定位提供可靠的依据。 21 | * 3.`定位bug`。当有了稳定的复现途径之后,要做的就是打断点、打日志进行调试,来一步一步分析和定位bug,到底是那块代码导致的错误。 22 | * 4.`确认bug`。当我们定位到bug出错的地方之后,我们就需要分析这到底是不是bug。如果是bug,那么这个bug出现的根源是什么,到底能不能解决。 23 | * 5.`修复bug`。在明确了bug的根本原因之后,下面就需要发挥我们的聪明才智去修复这个bug了。 24 | * 6.`验证bug`。并不是每次我们修复完bug之后就可以万事大吉了,此时我们还需要去重现bug以确保bug被真正修复。除此之外,有条件的我们还需要去验证相关场景,以保证修复该bug不会引入其他bug。 25 | 26 | 以上可以总结为12字方针--"了解、复现、定位、确认、修复、验证"bug。一般在稍微大一点的公司,都会有对应的流程对bug的修复进行流程控制,最终形成闭环。 27 | 28 | 可以看到的是,其实修复bug只是解决一个bug的6个步骤中的其中一步。很多刚刚参与工作的程序员经常犯的错误就是一遇到bug,就开始漫无目的地看代码或者是上网各种瞎搜索,又或者各种无脑问,最后搞了一圈可能连自己要解决的bug到底是什么都不知道,这样解决bug的效率可想而知。 29 | 30 | 可能读到这的你此刻非常想问:怎样才可以更快地修复一个bug呢?那么下面我就根据上面讲的六个步骤来分别讲解一下对应的技巧。 31 | 32 | ## 解决bug的艺术 33 | 34 | > 在我看来,修复一个bug是相对容易的。因为修复一个bug的方法可能有很多种,但是如何从根本上解决一个bug,并保证这个bug下次不再复现的话,其实是非常难的,这就需要我们学习一下解决bug的艺术。 35 | 36 | ### 了解bug 37 | 38 | > 俗话说,知己知彼百战不殆。bug修复的第一步当然是先了解bug了。 39 | 40 | 了解bug是解决bug最重要的一步,它直接决定了后面五步执行的效率和质量。糟糕的错误报告和不负责任的问题描述都是埋葬程序员修复bug意志的罪魁祸首。 41 | 42 | 在了解bug之前,我们需要收集足够的信息,了解它产生的现象、描述、复现步骤、以及解决后的预期是什么等等。那么我们应该怎么做才能更加全面地了解它呢? 43 | 44 | 下面我给几点建议供大家参考: 45 | 46 | #### 1.观察现象 47 | 48 | 光凭文字说明是无法准确领悟到bug的精髓的。因为每个人思考问题的角度以及文字表达描述都是千差万别的。 49 | 50 | 如果说这个时候能提供一段出错的视频或者问题截图,又或者能够现场演示错误的话,这样观察现象,然后再结合问题描述之后,一定能够帮助你快速地了解这个bug。 51 | 52 | #### 2.询问相关人员 53 | 54 | 这里你询问的可以不仅限于发现bug的人(一般是测试人员),当然你首先应当询问的还是这个发现bug的人。 55 | 56 | 这里你需要着重询问bug报告上你觉得迷惑的点,比如出现bug的应用版本、设备型号、bug出现的频率、bug产生的步骤和恢复的途径、bug修复的预期效果等。这里你需要进行刨根问底地询问,因为可能bug发现人觉得一些无关紧要的细节,对你来说却是至关重要的点。 57 | 58 | 问完发现bug的人之后,我们还可以向bug对应模块的负责人(测试、开发、产品)询问该模块的业务逻辑,说不定能够获取到有价值的信息哦。 59 | 60 | #### 3.提供bug报告模板 61 | 62 | 一份优秀的bug报告模板,可以让程序员直接跳过bug修复的前三步,直接进入到`确认bug`步骤,从而能够极大地提高bug修复的效率。那么一份优秀的bug报告模板应当具备哪些内容呢? 63 | 64 | * bug的标题和问题描述 65 | * 出现bug的应用版本 66 | * 出现bug的设备信息(型号、版本等) 67 | * bug产生相关的视频、截图和错误日志 68 | * bug复现的步骤 69 | * bug出现的必要条件(环境)和恢复途径 70 | * bug修复后的预期效果 71 | * bug对应的模块或者关联bug 72 | 73 | 有了以上的内容之后,相信程序员能够很快地了解这个bug,定位出bug产生的原因并予以解决。 74 | 75 | ### 复现bug 76 | 77 | 如果你在第一步`了解bug`中获得了良好的bug报告的话,则此部分可以很容易。你只需要按照bug报告中的bug复现步骤,按顺序操作即可稳定复现bug。 78 | 79 | 当然,很多时候往往并不是一帆风顺的,即使你手握bug报告,做了非常详细的调查工作,然而bug就是无法复现,那么这个时候我们应该怎么做呢? 80 | 81 | #### 1.重新了解bug 82 | 83 | 此时为了能够复现bug,你可能需要回到上一步,重现去了解bug。因为你之前对bug的理解可能产生偏差,又或者你第一步并没有做好才导致了bug未能复现。这个时候带着疑问去重现了解bug,可能你会发现新的大陆。 84 | 85 | #### 2.将偶现bug转为必现bug 86 | 87 | 偶现bug算是bug家族中最调皮的一个了。它们以没有规律,难以复现等特性,经常能把以一个好好的程序员给逼疯。 88 | 89 | 但是既然是要复现bug,那么肯定要找到bug稳定复现的路径,这样才能方便下面bug的定位。这里我推荐大家的一个做法就是想办法把偶现的bug转化为必现的bug。因为即使是偶现的bug,很多也是特定条件下必现的bug,只不过此时你还没发现这个特定条件而已。 90 | 91 | 那么怎样才能将偶现bug转为必现bug呢?这里我简单介绍一下我常用的技巧: 92 | 93 | * 1.`对比法`:观察并对比复现和不复现的各方面条件,找到那个必现的特定条件。 94 | 95 | * 2.`注释(删除)代码法`:对怀疑的代码进行注释(删除),直到彻底将偶现bug转变为必现bug。 96 | 97 | ### 定位bug 98 | 99 | > 一旦我们找到了bug稳定复现的步骤之后,下面的工作就是开始定位bug产生的地方了。 100 | 101 | 这一步可以说是解决bug的关键环节,这一步骤的难易程度一般取决于以下几个因素: 102 | 103 | * 1.程序员自身的代码量(工作经验) 104 | * 2.对项目代码(业务)的熟悉程度。 105 | * 3.分析问题和解决问题的能力 106 | 107 | 那么我们如何才能更快地定位出bug产生的位置呢?下面我提供一些思路供大家参考: 108 | 109 | * 1.`断点调试`。这是程序员通用,同时也是最有效的定位问题的方式。一个不会断点调试的程序员和瞎子没有本质上的区别。 110 | * 2.`日志分析`。其实并不是所有bug都可以进行断点调试的。比如在一些循环调用或者业务较为复杂的场景下,打日志分析定位是较为适合的方式。 111 | * 3.`排除法分析`。如果一个bug产生的原因可能有多种情况的时候,这个时候采取排除法的方式是最优的。你可以把可能导致bug产生的代码块都打上日志或者断点,然后重现一下bug进行问题的定位。 112 | * 4.`代码回滚分析`。如果你这个bug在之前的版本是好的,但是在现在版本上又出现了,这个时候就可以使用代码回滚大法。把你的代码回滚到你怀疑的版本,运行看bug是否消失,然后对两个版本之前代码有何区别,最终定位出bug产生的位置。这里我们可以使用二分法来提高代码的回滚效率。 113 | * 5.`注释(删除)代码法`。这个我在上一个步骤中也提到过。对于一些难以理解和定位的bug,我们可以使用这个方法进行尝试。不过这个方法使用起来有一定的风险,因为可能你删除的那一串代码虽然能够解决bug,但是却不是bug产生的根源,这个时候你可能会将必现bug改成了偶现bug,让问题变得更加复杂。 114 | * 6.`源码分析法`。有的时候有些bug可能并不是你的代码导致的问题。可能是第三方库本身的bug,又或者是系统本身的bug,又或者是你误用api导致的问题,这个时候就需要你拥有源码分析的能力。深入源码中,一层层分析直到最终找到bug产生的原因。 115 | * 7.`联想法`。通过一些类似的bug修改经验从而联想猜测出bug产生的位置。这个方法对使用者本身有较高的要求。需要使用者对项目代码和业务逻辑非常熟悉,同时对问题分析的能力有较高的要求。这就是我们常说的牛人能够一眼就能看出问题,他们常用的就是这种方式。 116 | * 8.`场外支援法`。这是实在定位不出bug才采取的下下策。因为它并不能提高你定位bug的能力,同时请别人帮忙定位bug,你就需要把你之前所做的工作都要全盘地向他表述一遍,这样不仅会降低bug修复的效率,同时还不一定能保证定位出bug产生的位置,它取决于你表述问题的能力和帮你的人分析和解决问题的能力。 117 | 118 | ### 确认bug 119 | 120 | > 在我们定位出bug产生的位置后,下面的工作就是分析bug产生的根源了。 121 | 122 | 这一步可以说是bug修复6个步骤中最为关键的一步。这一步直接决定了这个bug能否被彻底地解决,同时也是最能体现bug修复艺术的步骤。 123 | 124 | 但是很遗憾的是,这一步往往被很多人给忽视了。我为什么会这样说呢?因为很多时候我们修复bug的时候,都会受到各方面的限制: 125 | 126 | * 1.`自身经验水平的限制`:一些初入项目的程序员经常因为修复一个bug而导致了另一个bug,又或者只是看到了bug的表象却不能感知到bug产生更深层次的原因,所以10种产生bug的情况他可能只改了一种,将必现问题改成了偶现问题,让bug变得更加复杂。 127 | * 2.`时间限制`:这是我们程序员经常碰到的限制。这在互联网企业非常普遍,通常解决一个bug是有时间限制的,例如:这个项目明天就要上线了,并强制要求你今晚就得想办法解决。这个时候你可能就被逼无奈,采取硬编码的方式去临时把这个bug给按住。其实这样虽然bug是被临时解决了,但是却会让这个bug变得愈加复杂。很多公司出现的那些祖传代码就是在这种情况下产生的,动一下就可能产生无数未知的bug,令人绝望。 128 | * 3.`业务限制`:很多时候导致代码逻辑非常复杂难懂的罪魁祸首就是这种业务限制。我们在修复一个bug的时候,很多时候就是因为这种业务的限制,导致bug的修复一种不能从根源上予以解决,只要业务一调整就可能导致这个bug反复地出现。 129 | 130 | 说了这么多限制,那么我们如何才能找到问题的根源呢? 131 | 132 | * 1.`多积累经验,提升自身的水平`:这是打破自身经验水平的限制。 133 | * 2.`及时处理bug`:在收到bug报告的第一时间就去处理,尽可能打破时间的限制。 134 | * 3.`多熟悉业务`:有时间就多去了解和梳理业务,深入研究项目代码。这样我们在解决bug的时候也不会因为对业务不熟悉而无法分析出bug产生的根源。 135 | * 4.`准确定位bug`:bug定位的准确性直接决定了你能否分析出bug产生的根源。因此你需要仔细分析和定位bug,使用我上面介绍的8种方式去定位bug。 136 | 137 | ### 修复bug 138 | 139 | 其实前面的四步都是为这一步所做的铺垫。有了前面四步的工作,相信到这儿也是相对微不足道的了,剩下的就是如何优美地解决这个bug了。 140 | 141 | 到了这个阶段,bug通常不需要大的修改来修复,因此这一步往往会非常快,当然也就没有什么好的技巧啦。 142 | 143 | ### 验证bug 144 | 145 | > 作为bug修复的最后一步,它是确保bug被真正修复的最后保障。 146 | 147 | 在这里需要我们着重注意以下几点: 148 | 149 | * 1.重复之前复现bug的步骤来验证bug是否被彻底解决。 150 | * 2.验证bug修复可能改动到的相关模块是否正常,保证bug修复不引入新的bug。 151 | 152 | 如果上述有任何一点没有达到的话,请返回步骤四和步骤五,重新修复bug! 153 | 154 | --- 155 | 156 | ## 如何提高bug修复的效率 157 | 158 | > 上文我们着重讲解了解决bug的艺术,为的是能够更好地解决bug。但是如何才能保证既有效,又快速地修复bug,提高bug修复的效率呢? 159 | 160 | 通过上面对于解决bug的艺术的讲解,我们可以总结出以下影响bug修复效率的几个关键点: 161 | 162 | * 1.bug信息收集的效率以及有效性。 163 | * 2.时间限制的压力。 164 | * 3.人员对项目代码(业务)的熟悉程度。 165 | * 4.人员自身经验和分析问题的能力。 166 | 167 | 以上4点可以说直接决定了bug修复的效率。那么如何才能提高bug修复的效率呢?下面我将一一给出我的看法。 168 | 169 | ### 建立健全的信息收集机制 170 | 171 | > bug信息的收集可以说是修复bug过程中最为耗时的环节。提升bug信息收集的效率以及有效性可以大幅度地提升我们修复bug的效率。 172 | 173 | 那么我们应该如何建立健全的信息收集机制呢?这里我给出我的几点建议: 174 | 175 | #### 1.提供优秀的bug报告模板 176 | 177 | 上文我们在`了解bug`一步中提到过:一份优秀的bug报告模板,可以让程序员直接跳过bug修复的前三步,直接进入到`确认bug`步骤,从而能够极大地提高bug修复的效率。 178 | 179 | 这里我再次重复一步,一份优秀的bug报告模板应当具备哪些内容: 180 | 181 | * bug的标题和问题描述 182 | * 出现bug的应用版本 183 | * 出现bug的设备信息(型号、版本等) 184 | * bug产生相关的视频、截图和错误日志 185 | * bug复现的步骤 186 | * bug出现的必要条件(环境)和恢复途径 187 | * bug修复后的预期效果 188 | * bug对应的模块或者关联bug 189 | 190 | #### 2.建立完备的日志体系 191 | 192 | > 一套完备的日志体系,可以让我们更加清晰地知道用户到底做了什么才导致bug的出现。 193 | 194 | 在项目的初期,我们可能为了赶工期而常常忽视了日志打印的重要性,这很可能就会导致该项目日后的维护将非常得艰难。 195 | 196 | 那么我们为什么要建立一套完备的日志体系呢? 197 | 198 | * 1.并不是所有的用户(测试)都能够给你描述清楚bug产生的信息。 199 | * 2.即使用户(测试)给你描述了bug产生相关的信息,但是他们并不理解你的代码逻辑,他们只能根据bug出现的现象告诉你问题,可能他们的表述和你的理解不在一个频道。 200 | 201 | 如果你有这么一套完备的日志体系,那么你就可以在用户(测试)不用开口的情况下,直接get到用户的操作行为以及对于的代码逻辑。同时,可能一句关键点的报错日志就可以帮你直接定位到bug产生的位置,从而直接进入第四步`确认bug`。 202 | 203 | 说了这么多,我们应该如何打印高效的日志呢? 204 | 205 | * 1.在异常分支返回前打印日志。 206 | * 2.在复杂业务流程的关键点打印日志。 207 | * 3.在对外交互或者模块交互点打印日志。 208 | * 4.在用户交互或者生命周期的关键点打印日志。 209 | * 5.对重要的信息点打印日志,记录用户画像。 210 | * 6.按重要性分等级打印日志。 211 | * 7.禁止在循环中打印日志,禁止打印无效的日志。 212 | * 8.禁止打印用户隐私相关的信息。 213 | 214 | ### 建立自动化测试机制 215 | 216 | > 建立自动化测试机制,可以让突破`时间限制`成为可能。 217 | 218 | #### 1.更早地发现bug 219 | 220 | 很多时候来自时间限制的压力,往往是测试不充分导致的。很多bug直到产品临近上线或者交付的时候才被发现,这个时候唯一解决问题的方式就是在时间上做出限制,无情压迫我们这些活在公司权力最底层的程序员们。 221 | 222 | 如果这个时候能有一套自动化测试机制,每天下班后都进行自动测试的话,那样很多bug就能被提前发现,从而为我们修复bug预留了不少宝贵的时间。 223 | 224 | #### 2.节约bug验证的时间 225 | 226 | 对于一些复杂难解、偶现的bug,我们往往会在`确认bug`到`验证bug`之间花费大量的时间。这个时候如果有一套自动化测试机制或者工具帮助我们验证bug的话,就可以极大地缩减我们修复bug的时间。 227 | 228 | ### 提高项目代码(业务)的熟悉程度 229 | 230 | > 提高人员对项目代码(业务)的熟悉程度,这样就可以极大地提高人员`定位bug`的效率。 231 | 232 | #### 1.建立丰富的知识库体系 233 | 234 | > 建立一套丰富的知识库体系,可以帮助我们加深对自己责任内项目代码(业务)的理解,同时还能帮助我们快速了解我们所不了解的业务模块。 235 | 236 | 那么如何才能建立起丰富的知识库体系呢?下面我给出我的几点建议: 237 | 238 | * 1.对知识进行分类。 239 | * 2.定期添加和更新知识库的内容。 240 | * 3.提高知识库的检索效率。 241 | * 4.定期组织知识的分享。 242 | * 5.激励贡献知识库的人员。 243 | 244 | #### 2.建立责任田划分机制 245 | 246 | > 划分责任田的目的就是:让专业的人做专业的事情。 247 | 248 | 划分责任田的好处: 249 | 250 | * 1.专业的人做专业的事情。划分完责任田后,田主需要对自己负责的模块负责,这就必然要求其对模块内的代码(业务)更加熟悉。 251 | * 2.责任到人利于追责和bug跟踪。 252 | 253 | 当然责任田也不是想象中的那么完美,它也存在一定的缺陷: 254 | 255 | * 1.职责明确之后可能导致缺少全局视野。一些复杂的bug可能是几个模块共同作用下才产生的,对于这类bug的定位势必会大大增加难度。 256 | * 2.划分责任田之后,可能导致踢皮球的情况。 257 | 258 | 责任田划分机制,它是一把双刃剑。所以是否需要建立责任田划分机制,还是需要结合企业自身的情况而定的。 259 | 260 | ### 提高人员的综合素质 261 | 262 | > 提高人员的综合素质,可以帮助我们提高`定位bug`、`确认bug`以及`修复bug`三个步骤下的效率。 263 | 264 | * 1.`建立公平的员工晋升制度`。这样可以充分调动人员的主动性的积极性,提升人员的个人素质和业务能力。 265 | * 2.`建立岗位轮换制度`。让人员定期负责不同的模块,可以极大地提升人员的综合素质和全局视野。 266 | * 3.`定期组织培训学习`。 267 | 268 | --- 269 | 270 | ## 最后 271 | 272 | 以上内容,是我参与工作五年,开源六年以来所总结下来的全部经验,喜欢的可以点击收藏或者三连支持一下!最后,还是祝愿大家从此代码无bug,哈哈哈!!! 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | -------------------------------------------------------------------------------- /Chatting/通用技术面试问题.md: -------------------------------------------------------------------------------- 1 | # 通用技术面试问题 2 | 3 | ## 基础常规性面试 4 | 5 | 1.简单做一个自我介绍。 6 | 7 | 2.目前的在职情况,简述一下你上家公司主要做什么,你担任的角色和负责的内容。 8 | 9 | 3.你最近一次离职/跳槽的原因是什么。 10 | 11 | --- 12 | 13 | ## 技术专业性面试 14 | 15 | ### 结合简历的项目技术问题 16 | 17 | ··· 18 | 19 | ### 基础性的技术问题 20 | 21 | ··· 22 | 23 | ### 情境性的技术问题 24 | 25 | ··· 26 | 27 | --- 28 | 29 | ## 非技术面试 30 | 31 | 常见问题: 32 | 33 | * 你平时在工作中是如何积累和进行总结的? 34 | * 你在工作之余,是否有计划地进行学习。你是如何平衡工作、学习和生活的? 35 | * 你平时对新的技术有过了解吗?从何种渠道了解? 36 | 37 | ### 团队协作能力 38 | 39 | 常见问题: 40 | 41 | * 你认为自己为什么喜欢这个工作? 42 | * 你喜欢在团队中扮演什么角色? 43 | * 你如何看待团队工作? 44 | 45 | ### 个人职业规划 46 | 47 | 常见问题: 48 | 49 | * 你认为你的优势是什么? 50 | * 你的职业规划是什么? 51 | * 你对自己未来工作有什么期待或者规划? 52 | 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidAdvancedLearning 2 | 3 | Android进阶学习(源码分析、经验技术、感悟等) 4 | 5 | --- 6 | 7 | ## 源码分析 8 | 9 | * [Android系统启动](./SourceCodeAnalysis/AndroidSystemStartup.md) 10 | 11 | * [应用程序进程(AppProcess)启动](./SourceCodeAnalysis/AppProcessStartup.md) 12 | 13 | * [应用程序(App)启动](./SourceCodeAnalysis/AppStartup.md) 14 | 15 | * [Service的启动](./SourceCodeAnalysis/ServiceStartup.md) 16 | 17 | * [Service的绑定过程](./SourceCodeAnalysis/ServiceBind.md) 18 | 19 | * [Broadcast的注册、发送和接收过程](./SourceCodeAnalysis/BroadcastRegisterSendReceive.md) 20 | 21 | --- 22 | 23 | ## 技术框架 24 | 25 | * [X-Library系列Android应用框架详解](framework/X-Library系列Android应用框架详解.md) 26 | 27 | * [整套的版本更新解决方案-XUpdate](./framework/整套的版本更新解决方案-XUpdate.md) 28 | 29 | * [XUI 一个简洁而优雅的Android原生UI框架](https://github.com/xuexiangjys/XUI) 30 | 31 | * [XAOP 一个轻量级的AOP(Android)应用框架](https://github.com/xuexiangjys/XAOP) 32 | 33 | * [XUpdate 一个轻量级、高可用性的Android版本更新框架](https://github.com/xuexiangjys/XUpdate) 34 | 35 | * [XHttp2 一个功能强悍的网络请求库](https://github.com/xuexiangjys/XHttp2) 36 | 37 | * [XPage 一个非常方便的fragment页面框架](https://github.com/xuexiangjys/XPage) 38 | 39 | * [XQRCode 一个非常方便实用的二维码扫描、解析、生成库](https://github.com/xuexiangjys/XQRCode) 40 | 41 | * [XVideo 一个能自动进行压缩的小视频录制库](https://github.com/xuexiangjys/XVideo) 42 | 43 | * [XUtil 一个方便实用的Android工具类库](https://github.com/xuexiangjys/XUtil) 44 | 45 | * [XTCP 一个便捷的TCP消息包拼装和解析框架](https://github.com/xuexiangjys/XTCP) 46 | 47 | * [XIPC 一个Android通用的IPC(进程通信)框架](https://github.com/xuexiangjys/XIPC) 48 | 49 | * [XOrmlite 一个方便实用的OrmLite数据库框架,支持一键集成](https://github.com/xuexiangjys/XOrmlite) 50 | 51 | * [XRouter 一个轻量级的Android路由框架](https://github.com/xuexiangjys/XRouter) 52 | 53 | * [XLog 一个简易的日志打印框架(支持打印策略自定义,默认提供2种策略:logcat打印和磁盘打印)](https://github.com/xuexiangjys/XLog) 54 | 55 | * [XFloatView 一个简易的悬浮窗实现方案](https://github.com/xuexiangjys/XFloatView) 56 | 57 | * [RxUtil2 一个实用的RxJava2工具类库](https://github.com/xuexiangjys/RxUtil2) 58 | 59 | * [RxBus 一个简易的Android事件通知库,使用RxJava和Javassist设计](https://github.com/xuexiangjys/RxBus) 60 | --- 61 | 62 | ## 经验 63 | 64 | * 命令 65 | * [常用的ADB命令](https://blog.csdn.net/xuexiangjys/article/details/81027606) 66 | 67 | * [Git常用命令](https://blog.csdn.net/xuexiangjys/article/details/79875167) 68 | 69 | * [如何使用Git命令提交项目代码](https://blog.csdn.net/xuexiangjys/article/details/79874571) 70 | 71 | * Gradle 72 | 73 | * [Gradle使用指南--基础配置](https://blog.csdn.net/xuexiangjys/article/details/79865873) 74 | 75 | * 手把手系列 76 | 77 | * [手把手教你使用Gradle脚本上传代码仓库](https://blog.csdn.net/xuexiangjys/article/details/80160954) 78 | 79 | * [手把手教你如何在Android下进行JNI开发(入门)](https://juejin.im/post/5c05d0776fb9a049ca371cb6) 80 | 81 | * [手把手教你使用腾讯的热修复框架-Tinker](https://juejin.im/post/5b6e8a75e51d45191d7a55e1) 82 | 83 | * [手把手教你使用ProtoBuf,通过gRPC服务在Android上进行网络请求](https://juejin.im/post/5c976210f265da6111674f4d) 84 | 85 | * [手把手教你如何巧用Github的Action功能](./experience/手把手教你如何巧用Github的Action功能.md) 86 | 87 | * 快速上手系列 88 | 89 | * [快速上手系列--Android应用开发模板](https://blog.csdn.net/xuexiangjys/article/details/105154712) 90 | 91 | * Android基础 92 | 93 | * [Android开发性能优化大总结](https://blog.csdn.net/xuexiangjys/article/details/83177844) 94 | 95 | * [AndroidAPK混淆打包以及反编译技巧](https://blog.csdn.net/xuexiangjys/article/details/64904698) 96 | 97 | * [极光推送之Android客户端使用指南--基础篇](https://blog.csdn.net/xuexiangjys/article/details/103994622) 98 | 99 | * Java基础 100 | 101 | * [Java面向对象的设计模式七大设计原则](https://blog.csdn.net/xuexiangjys/article/details/78924201) 102 | 103 | * [Java设计模式之创建型模式](https://blog.csdn.net/xuexiangjys/article/details/78924434) 104 | 105 | * [Java设计模式之结构型模式](https://blog.csdn.net/xuexiangjys/article/details/78924859) 106 | 107 | * [Java设计模式之行为型模式](https://blog.csdn.net/xuexiangjys/article/details/78924918) 108 | 109 | * [Java中的反射使用](https://xuexiangjys.blog.csdn.net/article/details/88146035) 110 | 111 | * [Java中Math函数的使用](https://xuexiangjys.blog.csdn.net/article/details/79849888) 112 | 113 | * [五分钟搞定正则表达式](https://blog.csdn.net/xuexiangjys/article/details/102798641) 114 | 115 | * 运营 116 | 117 | * [Android应用商店上架技巧](./experience/Android应用商店上架技巧.md) 118 | 119 | --- 120 | 121 | ## 感悟 122 | 123 | * [我的三年Android开发总结](./Chatting/我的三年Android开发总结.md) 124 | 125 | * [写给即将面试的你](./Chatting/写给即将面试的你.md) 126 | 127 | * [你真的会使用github吗](./Chatting/你真的会使用github吗.md) 128 | 129 | * [同样都是开发,为什么你不如别人](./Chatting/同样都是开发,为什么你不如别人.md) 130 | 131 | * [我的两年博客经验总结](./Chatting/我的两年博客经验总结.md) 132 | 133 | * [开源项目创作指南](./Chatting/开源项目创作指南.md) 134 | 135 | * [程序员的bug修复宝典](./Chatting/程序员的bug修复宝典.md) 136 | -------------------------------------------------------------------------------- /SourceCodeAnalysis/AndroidSystemStartup.md: -------------------------------------------------------------------------------- 1 | # Android系统启动 2 | 3 | > 其实Android系统的启动最主要的内容无非是init、Zygote、SystemServer这三个进程的启动,他们一起构成的铁三角是Android系统的基础。 4 | 5 | ## 启动大纲 6 | 7 | 1. 启动电源以及引导程序加载 8 | 2. 引导程序BootLoader启动 9 | 3. Linux内核启动 10 | 4. init进程启动 11 | 5. Zygote进程启动 12 | 6. SystemServer进程启动 13 | 7. Launcher启动 14 | 15 | ## 启动流程图 16 | 17 | ![](../img/systemstart.png) 18 | 19 | --- 20 | 21 | ## 启动详解 22 | 23 | ### init进程启动的准备工作 24 | 25 | > init进程是在Linux内核加载完成后启动的,因此要想启动init进程,必须先加载Linux内核,而Linux则是由引导程序BootLoader拉起来的。 26 | 27 | * 当电源被按下后,引导芯片代码(ROM)执行,并将引导程序BootLoader加载到RAM中执行。 28 | 29 | * BootLoader运行,它的作用就是把系统OS拉起来并运行。 30 | 31 | * Linux内核被拉起运行后,会设置缓存、被保护存储器、计划列表、加载驱动等,最后会在系统文件中寻找init.rc文件,启动init进程。 32 | 33 | ### init进程启动 34 | 35 | > init进程主要用于初始化和启动属性服务,并启动Zygote进程。init进程的源码在[system/core/init/init.cpp](http://androidxref.com/9.0.0_r3/xref/system/core/init/init.cpp)`下。 36 | 37 | * 创建和挂载启动所需要的文件目录(tmpfs、devpts、proc、sysfs和selinuxfs),他们都是系统运行时目录。 38 | 39 | * 初始化并启动属性服务(类似window里面的注册表)。 40 | 41 | * 解析[init.rc](http://androidxref.com/9.0.0_r3/xref/system/core/rootdir/init.rc)配置文件。 42 | 43 | * 启动Zygote进程。 44 | 45 | ### Zygote进程启动 46 | 47 | > 在Android系统中,DVM和ART、应用程序进程以及运行系统的关键服务SystemServer进程都是由Zygote进程来创建的,我们也可以将其称为孵化器。它通过fork的形式来创建应用程序进程和SystemServer进程。 48 | 49 | * init进程调用[app_main](http://androidxref.com/9.0.0_r3/xref/frameworks/base/cmds/app_process/app_main.cpp)创建了[AndroidRuntime](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/jni/AndroidRuntime.cpp),通过调用它的start方法来启动Zygote。 50 | 51 | * 在[AndroidRuntime](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/jni/AndroidRuntime.cpp)中创建了Java虚拟机,并为其注册了JNI方法 52 | 53 | * 通过JNI反射调用[ZygoteInit](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java)进入Zygote的Java框架层,最终启动了Zygote进程。 54 | 55 | #### ZygoteInit的main方法 56 | 57 | > 这里需要注意的是,之前的所以初始化操作都是在Native层进行的,直到我们通过JNI调用ZygoteInit的main方法后,我们才进入了Java框架层。 58 | 59 | * 创建了一个Server端的socket(主要用于跨进程通信)。 60 | 61 | * 预加载类和资源。 62 | 63 | * 启动SystemServer进程。 64 | 65 | * 等待AMS(ActivityManagerService)请求创建新的应用程序进程。 66 | 67 | ### SystemServer进程启动 68 | 69 | > SystemServer进程主要用于创建和启动系统服务,包括我们常用的AMS、WMS和PMS等。 70 | 71 | * 启动Binder线程池,用于与其他进程进行通信。 72 | 73 | * 创建[SystemServiceManager](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/SystemServiceManager.java),其用于对系统的服务进行创建、启动和生命周期的管理。 74 | 75 | * 启动各种系统服务(引导服务、核心服务和其他服务等) 76 | 77 | ### Launcher启动 78 | 79 | > Launcher通俗地说就是Android系统的桌面,是系统的门户,用于启动应用程序,其本质就是一个Activity,类名为"com.android.launcher3.Launcher",[点击查看Launcher的AndroidManifest.xml](http://androidxref.com/9.0.0_r3/xref/packages/apps/Launcher3/AndroidManifest.xml)。 80 | 81 | * [SystemServer](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/java/com/android/server/SystemServer.java)调用[AMS](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java#15122)的`systemReady`方法准备启动Launcher 82 | 83 | * AMS中又会调用ActivityStackSupervisor和ActivityStack进行一系列的准备工作,最终又调用了AMS的`startHomeActivityLocked`方法,使用[ActivityStarter](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java)的`startHomeActivityLocked`启动Launcher。 84 | 85 | ### 启动架构示意图 86 | 87 | 我们都知道Android系统架构是Linux Kernel、Android Runtime、Liberaries、Application Framework和Application这五个部分组成的,如下图所示: 88 | ![](https://images0.cnblogs.com/blog/432441/201310/26133735-b9747c9f0d364527977bc278199aea98.jpg) 89 | 90 | 那么Android系统在启动过程中,都经历了哪些架构组成部分呢?以下给出简要的示意图: 91 | 92 | ![](../img/systemstart2.png) 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /SourceCodeAnalysis/AppProcessStartup.md: -------------------------------------------------------------------------------- 1 | # 应用程序进程(AppProcess)启动 2 | 3 | > 启动一个应用程序首先要保证该应用程序的进程已经被启动。AMS在启动应用程序时,会先检查应用程序进程是否存在,如果不存在就需要请求Zygote进程创建并启动应用程序进程。 4 | 5 | ## 启动大纲 6 | 7 | 1. AMS发送启动应用程序进程请求. 8 | 9 | 2. Zygote接收请求并创建应用程序进程. 10 | 11 | --- 12 | 13 | ### AMS发送启动应用程序进程请求 14 | 15 | ![](../img/appprocess.png) 16 | 17 | * AMS通过调用[startProcessLocked](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java#4188)方法向Zygote进程发送请求。 18 | 19 | * [Process](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/os/Process.java#479)调用`start`方法,使用[ZygoteProcess](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/os/ZygoteProcess.java#220)的`start`方法。 20 | 21 | * 在ZygoteProcess的`start`方法中,先后调用了`startViaZygote`、`zygoteSendArgsAndGetResult`和`openZygoteSocketIfNeeded`等方法,最后在`openZygoteSocketIfNeeded`方法中调用了[ZygoteState](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/os/ZygoteProcess.java#106)的connect方法建立与Zygote进程的连接。 22 | 23 | ### AMS发送启动应用程序进程请求 24 | 25 | ![](../img/appprocess1.png) 26 | 27 | * [ZygoteServer](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/com/android/internal/os/ZygoteServer.java#173)执行`runSelectLoop`方法,一直等待AMS的请求数据到来。 28 | 29 | * 当AMS请求到来,与Zygote进程建立连接后,由[ZygoteConnection](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java#123)的`processOneCommand`方法处理请求的数据。对请求数据进行解析,获取程序进程的启动参数,并通过[Zygote](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/com/android/internal/os/Zygote.java#133)的`forkAndSpecialize`方法进行应用程序进程的创建。 30 | 31 | * 进程创建完成后,交由[ZygoteInit](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java#902)的`zygoteInit`方法和[RuntimeInit](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/com/android/internal/os/RuntimeInit.java#345)的`applicationInit`方法分别进行进程和应用的初始化。在`zygoteInit`方法中,为应用程序进程创建了Binder线程池,这样进程就可以跨进程进行通信了。而`applicationInit`方法通过反射最终会调用[ActivityThread](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/ActivityThread.java#6623)的`main`方法,从而完成应用程序进程的创建。 -------------------------------------------------------------------------------- /SourceCodeAnalysis/AppStartup.md: -------------------------------------------------------------------------------- 1 | # 应用程序(App)启动 2 | 3 | > 应用程序的启动,又可称为根Activity的启动。 4 | 5 | ## 启动大纲 6 | 7 | 1. Launcher请求AMS。 8 | 9 | 2. AMS请求ApplicationThread。 10 | 11 | 3. ActivityThread启动Activity。 12 | 13 | ## 启动时序图 14 | 15 | ![](../img/appstart.png) 16 | 17 | --- 18 | 19 | ### Launcher请求AMS 20 | 21 | ![](../img/appstart1.png) 22 | 23 | * 当我们点击应用程序的图标时,就会自动调用[Launcher](http://androidxref.com/9.0.0_r3/xref/packages/apps/Launcher3/src/com/android/launcher3/Launcher.java#1667)的`startActivitySafely`方法, 最终会调用[Activity](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/Activity.java#4899)的`startActivity`方法。 24 | 25 | * 在Activity的`startActivity`中又调用了`startActivityForResult`方法,而`startActivityForResult`方法内部又调用了[Instrumentation](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/Instrumentation.java#1635)的`execStartActivity`方法。 26 | 27 | * 在Instrumentation的`execStartActivity`方法中又通过[ActivityManager](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/ActivityManager.java#4125)的`getService`方法获取了IBinder类型的AMS引用`IActivityManager`,最后调用了[AMS](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java#5079)的`startActivity`方法。 28 | 29 | ### AMS请求ApplicationThread 30 | 31 | ![](../img/appstart2.png) 32 | 33 | * 在AMS的`startActivity`方法中,又调用了其本身的`startActivityAsUser`方法,进行权限的检查。 34 | 35 | * 权限检查完后,调用[ActivityStarter](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java#945)的`startActivityMayWait`方法,并在该方法中解析处理应用程序需要的参数,并进行相关参数的初始化,最终会调用其`startActivity`方法。而在`startActivity`方法中又调用了`startActivityUnchecked`方法来处理与栈管理相关的逻辑。 36 | 37 | * 在处理完栈的关系后,紧接着会调用[ActivityStackSupervisor](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java#2214)的`resumeFocusedStackTopActivityLocked`方法获取需要启动的Activity所在栈的栈顶。 38 | 39 | * 当需要启动的Activity的状态不是RESUMED状态,就需要调用[ActivityStack](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActivityStack.java#2282)的`resumeTopActivityUncheckedLocked`方法,而它的内部又调用了`resumeTopActivityInnerLocked`方法进行一系列的栈状态的判断,最终又回调了ActivityStackSupervisor的`startSpecificActivityLocked`方法。 40 | 41 | * 在[ActivityStackSupervisor](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java#1678)的`startSpecificActivityLocked`方法中先是获取了即将启动的Activity所在的应用程序进程(就是在这个地方判断应用所在进程是否存在且已启动,如果没有启动,就需要启动应用程序进程),然后调用`realStartActivityLocked`方法。 42 | 43 | * 在[ActivityStackSupervisor](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java#1377)的`realStartActivityLocked`方法中,对启动的应用程序进程进行一系列的判断和处理,最终会调用IBinder类型的ApplicationThread引用`IApplicationThread`,通过传入`IApplicationThread`建立`ClientTransaction`,加入执行`LaunchActivityItem`任务,最终实现跨进程执行调用[ActivityThread](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/ActivityThread.java#3024)的`handleLaunchActivity`方法。 44 | 45 | ### ActivityThread启动Activity 46 | 47 | ![](../img/appstart3.png) 48 | 49 | * 在ActivityThread调用了它的`handleLaunchActivity`方法中,会先调用其`performLaunchActivity`方法,之后调用`handleResumeActivity`,将Activity的状态置为Resume。 50 | 51 | * 在ActivityThread的[performLaunchActivity](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/ActivityThread.java#2808)方法中做了很多事情。 52 | 53 | 1. 首先,执行了`createBaseContextForActivity`方法,创建要启动Activity的上下文; 54 | 2. 其次,调用执行了Instrumentation的`newActivity`方法来创建Activity实例; 55 | 3. 接着,调用[LoadedApk](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/LoadedApk.java#1037)的`makeApplication`方法,创建应用程序的Application; 56 | 4. 之后,调用需要启动的Activity的`attach`方法初始化Activity,创建Window对象并与Activity自身进行关联。 57 | 5. 最后,调用执行了[Instrumentation](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/Instrumentation.java#1282)的`callActivityOnCreate`方法来启动Activity。 58 | 59 | * 在Instrumentation执行了`callActivityOnCreate`方法中,会调用[Activity](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/Activity.java#7130)的`performCreate`方法,最终会调用Activity的`onCreate`方法,这样应用程序也就启动了。 60 | 61 | --- 62 | 63 | ## 应用程序启动的进程关系图 64 | 65 | > 应用程序启动的过程中,主要涉及了Launcher进程、SystemServer进程、Zygote进程和应用程序进程这四个进程,它们之间的关系如下。 66 | 67 | ![](../img/appstart4.png) 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /SourceCodeAnalysis/BroadcastRegisterSendReceive.md: -------------------------------------------------------------------------------- 1 | # 广播的注册、发送和接收过程 2 | 3 | > 广播的注册、发送和接收都与AMS有着密不可分的关系。 4 | 5 | ## 广播的注册 6 | 7 | > 广播的注册可分为静态注册和动态注册两种,静态注册在应用安装时由`PackageManagerService`来完成注册过程,下面我主要来分析动态广播注册。 8 | 9 | ### ContextImpl请求AMS注册广播 10 | 11 | ![](../img/register_receiver.png) 12 | 13 | * 当我们需要动态注册广播时,需要调用Context的`registerReceiver`方法,然后在[ContextWrapper](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/content/ContextWrapper.java#621)的`registerReceiver`中调用[ContextImpl](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/ContextImpl.java#105)的`registerReceiver`方法,最终会调用其[registerReceiverInternal](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/ContextImpl.java#1467)方法。 14 | 15 | * 在ContextImpl的`registerReceiverInternal`方法中,首先是和服务绑定类似的,通过`LoadedApk`类型的mPackageInfo对象的`getReceiverDispatcher`方法来获取`IIntentReceiver`类型的rd对象,用于广播的跨进程通信。然后调用IActivityManager的`registerReceiver`方法,最终调用AMS的`registerReceiver`方法,并将`IIntentReceiver`类型的rd对象传入。 16 | 17 | * 在AMS的[registerReceiver](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java#20801)方法中,首先是调用`getRecordForAppLocked`方法获取调用注册广播的应用程序进程信息,然后根据进程信息获取对应在AMS中存储的所有粘性广播的intent,然后和传入的参数filter的粘性广播进行对比,找到所有匹配的intent存入到allSticky列表中,最终加入到广播队列中执行。 18 | 19 | * 除此之外,在AMS的`registerReceiver`中还调用了HashMap类型,存放了所有应用进程的广播接收者列表mRegisteredReceivers,通过传入之前的`IIntentReceiver`对象获取到对应的广播接收者列表ReceiverList,并将其传入创建BroadcastFilter,用以描述注册的广播接收者。最后将BroadcastFilter添加到IntentResolver类型的mReceiverResolver中,这样当AMS接收到广播时,就可以从mReceiverResolver中直接找到对应的广播接收者,从而达到注册广播的目的。 20 | 21 | 22 | ## 广播的发送 23 | 24 | > 广播可以发送多种类型,包括无序广播(普通广播)、有序广播和粘性广播。 25 | 26 | Android广播的分类: 27 | 28 | 1、 普通(无序)广播:使用`sendBroadcast`发送广播。这种广播可以依次传递给各个处理器去处理。 29 | 30 | 2、 有序广播:使用`sendOrderedBroadcast`发送广播。这种广播在处理器端的处理顺序是按照处理器的不同优先级来区分的,高优先级的处理器会优先截获这个消息,并且可以将这个消息删除。 31 | 32 | 3、 粘性消息:使用`sendStickyBroadcast`发送广播。粘性消息在发送后就一直存在于系统的消息容器里面,等待对应的处理器去处理,如果暂时没有处理器处理这个消息则一直在消息容器里面处于等待状态,粘性广播的Receiver如果被销毁,那么下次重建时会自动接收到消息数据。 33 | 34 | 注意:普通广播和粘性消息不能被截获,而有序广播是可以被截获的。 35 | 36 | 这里我们以最简单的普通广播发送为例进行分析。 37 | 38 | ### ContextImpl请求AMS发送广播 39 | 40 | ![](../img/send_broadcast.png) 41 | 42 | * 当我们需要发送无序广播时,需要调用Context的`sendBroadcast`方法,然后在[ContextWrapper](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/content/ContextWrapper.java#443)的`sendBroadcast`中调用[ContextImpl](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/ContextImpl.java#1004)的`sendBroadcast`方法,最终会调用AMS的[broadcastIntent](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java#21929)方法。 43 | 44 | * 在AMS的`broadcastIntent`方法中,首先对发送的广播进行合法性校验,然后调用其[broadcastIntentLocked](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java#21207)方法。 45 | 46 | * 在AMS的`broadcastIntentLocked`方法中做了很多事情,对广播做了一系列的处理后,最终调用`broadcastQueueForIntent`构建了广播队列,然后新建BroadcastRecord对象并将其传入广播队列中,同时执行广播队列的[scheduleBroadcastLocked](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java#386)方法。 47 | 48 | ## 广播的接收 49 | 50 | ### AMS到BroadcastReceiver接收广播 51 | 52 | ![](../img/receive_broadcast.png) 53 | 54 | * 在BroadcastQueue的`scheduleBroadcastLocked`方法中,发送了类型为BROADCAST_INTENT_MSG类型的消息,并在消息处理中最终调用了其[processNextBroadcastLocked](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java#processNextBroadcastLocked)方法,并在其中遍历存储了无序广播的列表,然后调用`deliverToRegisteredReceiverLocked`将这些无序广播的信息描述发送给对应的广播接收者。 55 | 56 | * 在BroadcastQueue的`deliverToRegisteredReceiverLocked`方法中主要检查广播发送者和广播接收者的权限,并最终会调用其[performReceiveLocked](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java#performReceiveLocked)方法,然后在其方法中调用ApplicationThread的[scheduleRegisteredReceiver](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/ActivityThread.java#981)方法。 57 | 58 | * 在ApplicationThread的`scheduleRegisteredReceiver`方法中会调用`IIntentReceiver`类型的对象receiver的performReceive方法,而`IIntentReceiver`是Binder通信的客户端,[InnerReceiver](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/LoadedApk.java#1279)在本地的代理,它会调用InnerReceiver的`performReceive`方法,最终会调用`ReceiverDispatcher`的[performReceive](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/LoadedApk.java#1467)方法。 59 | 60 | * 在ReceiverDispatcher的`performReceive`方法中,会构建类型为[Args](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/LoadedApk.java#1336)类型的对象,最终通过mActivityThread(H),将Args对象的getRunnable方法获取的Runnable发送到线程的消息队列中执行。在Args对象的Runnable方法中会调用BroadcastReceiver类型的receiver对象的`onReceive`方法,这样注册的广播接收者就收到了广播并得到了intent。 -------------------------------------------------------------------------------- /SourceCodeAnalysis/ServiceBind.md: -------------------------------------------------------------------------------- 1 | # Service的绑定过程 2 | 3 | > 除了使用Context的startService来启动Service外,我们也可以通过Context的bindService来绑定Service。绑定Service的过程要比启动Service的过程复杂一些。不清楚[Service启动](./ServiceStartup.md)的可以点击了解一下。 4 | 5 | ## 启动大纲 6 | 7 | 1. ContextImpl请求AMS绑定Service. 8 | 9 | 2. AMS请求ActivityThread处理Service绑定. 10 | 11 | 3. AMS进行Service的绑定. 12 | 13 | --- 14 | 15 | ### ContextImpl请求AMS绑定Service 16 | 17 | ![](../img/servicebind.png) 18 | 19 | * 当我们需要绑定一个Service时,我们会使用`context.bindService`。而Context只是一个抽象的类,它的实现是在[ContextWrapper](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/content/ContextWrapper.java#696)中。 20 | 21 | * 在ContextWrapper的`bindService`方法中,又会调用其内部的Context类型的mBase变量,而该变量的创建详见[ActivityThread](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/ActivityThread.java#2990)的`createBaseContextForActivity`方法,它的实现类是`ContextImpl`。 22 | 23 | * 在[ContextImpl](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/ContextImpl.java#1609)的`bindService`方法中,又会调用其自身的`bindServiceCommon`方法。 24 | 25 | * 在[ContextImpl](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/ContextImpl.java#1651)的`bindServiceCommon`方法中,首先调用[LoadedApk](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/LoadedApk.java#1492)的`getServiceDispatcher`方法获取ServiceConnection的封装类(本地的代理)`IServiceConnection`(用于跨进程通信),然后使用`ActivityManager`获取AMS的代理`IActivityManager`,调用其`bindService`方法并将`IServiceConnection`对象传入。 26 | 27 | ### AMS请求ActivityThread处理Service绑定 28 | 29 | ![](../img/servicebind1.png) 30 | 31 | * [AMS](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java#20492)的`bindService`方法中调用方法中调用[ActiveService](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActiveServices.java#1428)的`bindServiceLocked`方法,其中同样的和`startServiceLocked`一样调用`retrieveServiceLocked`用于获取启动服务的Intent参数所对应的`ServiceRecord`,然后调用[ServiceRecord](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ServiceRecord.java#503)的`retrieveAppBindingLocked`方法来获取应用和服务的绑定信息`AppBindRecord`,最后调用`requestServiceBindingLocked`方法,将之前获取的`AppBindRecord`信息传入,来发出服务绑定的请求。 32 | 33 | * 除此之外,在`bindServiceLocked`方法调用`requestServiceBindingLocked`请求绑定前,还调用了`bringUpServiceLocked`方法去启动服务。 34 | 35 | * 在[ActiveService](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActiveServices.java#requestServiceBindingLocked)的`requestServiceBindingLocked`方法中最终会调用`ActivityThread`的[scheduleBindService](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/ActivityThread.java#scheduleBindService)方法,然后封装BindServiceData数据并将其传入`sendMessage`方法中,向H发送`BIND_SERVICE`消息。在H对应的消息处理中会调用[handleBindService](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/ActivityThread.java#handleBindService)方法。在`handleBindService`方法中,对未绑定服务的,先后调用Service的`onBind`方法和AMS的[publishService](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java#20518)方法;对已绑定服务的,先后调用Service的`onRebind`方法和AMS的[serviceDoneExecuting](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java#20543)方法。 36 | 37 | ![](../img/servicebind2.png) 38 | 39 | * 在AMS的[publishService](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java#20518)方法中又调用了`ActiveService`的[publishServiceLocked](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActiveServices.java#1699)方法。 40 | 41 | * 在`publishServiceLocked`方法中会调用`IServiceConnection`的`connected`方法来建立服务连接,最终会调用执行`LoadApk`中的[RunConnection](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/LoadedApk.java#1753)任务,执行[doConnected](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/LoadedApk.java#doConnected)方法建立服务绑定连接。 42 | 43 | 44 | ### 与Service绑定相关的对象类型介绍: 45 | 46 | * ServiceRecord:用于描述一个Service。 47 | 48 | * ProcessRecord:一个进程所包含的信息。 49 | 50 | * ConnectionRecord:用于描述应用程序进程和Service建立的一次通信。 51 | 52 | * AppBindRecord: 用于维护Service与应用进程之间的绑定信息。 53 | 54 | * IntentBindRecord:用于描述绑定Service的Intent信息。 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /SourceCodeAnalysis/ServiceStartup.md: -------------------------------------------------------------------------------- 1 | # Service的启动过程 2 | 3 | > Service的启动和根Activity的启动很类似。Service的启动同样需要保证该应用程序的进程已经被启动。 4 | 5 | ## 启动大纲 6 | 7 | 1. ContextImpl请求AMS启动Service. 8 | 9 | 2. AMS请求ActivityThread启动Service. 10 | 11 | --- 12 | 13 | ### ContextImpl请求AMS启动Service 14 | 15 | ![](../img/servicestartup.png) 16 | 17 | * 当我们需要启动一个Service时,我们会使用`context.startService`。而Context只是一个抽象的类,它的实现是在[ContextWrapper](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/content/ContextWrapper.java#663)中。 18 | 19 | * 在ContextWrapper的`startService`方法中,又会调用其内部的Context类型的mBase变量,而该变量的创建详见[ActivityThread](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/ActivityThread.java#2990)的`createBaseContextForActivity`方法,它的实现类是`ContextImpl`。 20 | 21 | * 在[ContextImpl](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/ContextImpl.java#1530)的`startService`方法中,又会调用其自身的`startServiceCommon`方法。 22 | 23 | * 在[ContextImpl](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/ContextImpl.java#1557)的`startServiceCommon`方法中,会使用`ActivityManager`获取AMS的代理`IActivityManager`,并调用其`startService`方法。 24 | 25 | ### AMS请求ActivityThread启动Service 26 | 27 | ![](../img/servicestartup1.png) 28 | 29 | * [AMS](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java#20342)的`startService`方法中调用[ActiveService](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActiveServices.java#389)的`startServiceLocked`方法,其中调用`retrieveServiceLocked`用于获取启动服务的Intent参数所对应的`ServiceRecord`,它主要用于描述一个Service,启动Service所必须的参数。 30 | 31 | * 在ActiveService的`startServiceLocked`方法中获取到相应的`ServiceRecord`之后,就会调用其自身的[startServiceInnerLocked](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActiveServices.java#660)方法,而它又会去调用`bringUpServiceLocked`方法。在[bringUpServiceLocked](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActiveServices.java#2287)方法中主要做了以下三个工作: 32 | 33 | (1)获取Service运行所在的进程。 34 | 35 | (2)如果Service运行所在的应用程序进程`ProcessRecord`存在,则调用其自身的`realStartServiceLocked`方法来启动Service. 36 | 37 | (3)如果Service运行所在的应用程序进程`ProcessRecord`不存在,则需要调用AMS的`startProcessLocked`方法来启动应用程序进程。 38 | 39 | * 在[ActiveService](http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/am/ActiveServices.java#2433)的`realStartServiceLocked`方法中,会使用`ProcessRecord`的`IApplicationThread`类型的引用(实现类是ActivityThread的内部类ApplicationThread),调用其[scheduleCreateService](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/ActivityThread.java#805)方法。 40 | 41 | * 在ActivityThread的`scheduleCreateService`方法中,会向其内部类并继承自Handler的`H`发送`CREATE_SERVICE`消息,并由`H`进行处理,最终会调用`handleCreateService`方法。 42 | 43 | * 在[ActivityThread](http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/ActivityThread.java#3503)的`handleCreateService`方法中主要做了如下几件事: 44 | 45 | (1)获取要启动Service的应用程序的LoadApk(包信息),并通过它获取类的加载器,通过反射创建Service的实例。 46 | 47 | (2)调用`ContextImpl`的`createAppContext`方法,为Service创建上下文环境ContextImpl对象。 48 | 49 | (3)调用`Service`的`attach`方法,对Service进行初始化。 50 | 51 | (4)调用`Service`的`onCreate`方法,这样Service就启动了。 52 | 53 | -------------------------------------------------------------------------------- /experience/Android应用商店上架技巧.md: -------------------------------------------------------------------------------- 1 | 2 | # Android应用商店上架技巧(企业版) 3 | 4 | > 说老实话,这些年随着Android系统的逐渐成熟,各大应用市场对Android应用的上架审核也越加严格,因此在上架应用之前,还是要做好准备工作的,不然审核被打回数次都是有可能的。 5 | 6 | ## 目前比较常用的几个Android应用市场的链接 7 | 8 | 以下是按照上架的难易程度进行排序的,三星应用商店上架最困难,oppo应用商店上架最容易(个人观点): 9 | 10 | * [三星应用商店](http://samsungapps.com/) 11 | 12 | * [应用宝](http://op.open.qq.com/appregv2/) 13 | 14 | * [华为应用商店](https://developer.huawei.com/consumer/cn/appgallery/) 15 | 16 | * [小米应用商店](https://dev.mi.com/console/app/phone.html) 17 | 18 | * [360应用市场](http://dev.360.cn/) 19 | 20 | * [vivo应用商店](https://dev.vivo.com.cn/distribute/appStore) 21 | 22 | * [oppo应用商店](https://open.oppomobile.com/newservice/capability?pagename=app_store) 23 | 24 | 25 | ## 准备内容 26 | 27 | 以下是应用上架前所需要准备的内容: 28 | 29 | * 企业基本信息 30 | 31 | * 应用的软件著作权证明 32 | 33 | * 应用的相关信息 34 | 35 | * 测试账号和使用说明 36 | 37 | ### 企业基本信息 38 | 39 | > 企业基本信息主要是在应用商店中注册企业账号和进行账号认证时使用。 40 | 41 | 企业基本信息主要包括以下几个内容: 42 | 43 | 1.企业邮箱、技术支持电话以及官网:注册账号和应用时需要使用。 44 | 45 | 2.企业的营业执照、证号:账号认证和填写应用信息的时候使用。 46 | 47 | 3.企业法人的身份证号、身份证复印件或者手持身份证的照片:账号认证时使用。 48 | 49 | 4.企业对公账号:账号认证时使用。 50 | 51 | ### 软件著作权 52 | 53 | > 现在的所有Android应用商店上架一款应用,基本上都需要提交应用的软件著作权的扫描件和电子件。尤其是腾讯的应用宝,对软件著作权的审核极其严格,软著申请的名称和应用的名称有一点不一样都不让通过。 54 | 55 | 下面我主要列举申请软著所需要的材料: 56 | 57 | 1.软件著作权申请表 58 | 59 | 2.软件的使用说明文档 60 | 61 | 3.软件的源码(前30页和后30页) 62 | 63 | 软件著作权的申请方法: 64 | 65 | * 自己去[中国版权保护中心](http://www.ccopyright.com.cn/)注册账号,并在线填报软著申请,然后将资料寄给中国版权保护中心软件登记部进行办理。详细的软著申请流程可[点击查看](http://www.ccopyright.com/index.php?optionid=1057)。 66 | 67 | * 自己提交申请的话,审核时间会很慢,至少得等满3个月才有可能下来。如果你比较紧急的话,可寻求第三方中介机构加急代办,费用大概在500~3000不等,就要看你加急的程度了。 68 | 69 | ### 应用的相关信息 70 | 71 | > 应用相关信息涵盖的内容就比较多了,而且每个应用市场要的内容也不一样。 72 | 73 | 下面我主要列举应用的相关信息: 74 | 75 | 1.上架应用的名称(尽可能和软著上的保持一致)、方形图标(16×16、216×216、512×512)、应用简介、应用的截图(480×800、1080×1920)等。 76 | 77 | 2.应用的安装包(apk)。 78 | 79 | 3.应用发布地区、支持语言、适配设备类型、应用的分类。 80 | 81 | 4.应用申请权限的说明、版本说明(特性)。 82 | 83 | 5.应用资质证明,包括软件著作权证书、免责函、应用版权证书等。特殊行业软件还需要提供相应的行业资质证明。 84 | 85 | 6.应用的官网、用户的隐私条款和服务条款网页、客服邮箱等内容。 86 | 87 | ### 测试账号和使用说明 88 | 89 | > 在我们应用提交审核后,应用市场会安排一些简单的测试。如果你的应用有登录功能的话,就必须要提供相应的测试账号,否则将无法正常通过审核。 90 | 91 | 这里有几个注意点需要大家关注: 92 | 93 | 1.测试账号最好和后端开发人员商量好,享有所有权利,并且在提交审核后最好不要随意动环境,以防应用市场的测试人员无法正常测试,导致审核不通过。 94 | 95 | 2.如果软件中做了版本更新的话,在提交期间最好不要再发布新版本,保证提交到应用市场上的版本是最新的版本。 96 | 97 | 3.如果你的应用需要结合相关硬件才能使用的话,最好能提供相关的产品使用说明和视频介绍等链接,提交审核的通过率。 98 | 99 | 4.一般来说,第一次上架应用市场都比较困难,审核也最为严格。最好能提前提交应用上架申请(一般提前1个月比较稳妥),这样可以留出一定的预留时间用于整改。一旦上架成功之后,后面进行版本升级的审核会非常快,一般最多半天就能审核通过了。 100 | 101 | -------------------------------------------------------------------------------- /experience/手把手教你如何巧用Github的Action功能.md: -------------------------------------------------------------------------------- 1 | 2 | # 手把手教你如何巧用Github的Action功能 3 | 4 | ## 概念 5 | 6 | [GitHub Actions](https://github.com/features/actions) 是 [GitHub](https://github.com/) 于2018年10月推出的持续集成服务。 7 | 8 | 那么何谓持续集成呢? 9 | 10 | ### 持续集成 11 | 12 | 持续集成(Continuous integration),也就是我们经常说的CI。它是一种软件开发实践,可以让团队在持续的基础上收到反馈并进行改进,不必等到开发后期才寻找和修复缺陷,常运用于软件的敏捷开发中。Jenkins就是我们常用的持续集成平台工具。 13 | 14 | 理解了持续集成的概念之后,下面我简单讲一下使用持续集成的好处: 15 | 16 | * 提高效率,减少了重复性工作:一些重复性的工作写成脚本交给持续集成服务执行。 17 | * 减少了人工带来的错误:机器通过预先写好的脚本执行犯错的几率比人工低很多。 18 | * 减少等待的时间:一套完备的持续集成服务涵盖了开发、集成、测试、部署等各个环节。 19 | * 提高产品质量:很多大公司在代码提交后都会有一套代码检视脚本(俗称门禁)来检查代码的提交是否符合规范,从而从源头遏制问题的产生。 20 | 21 | ### Actions 22 | 23 | 相比较持续集成这个大概念,GitHub推出的 Actions 就显得非常轻量和巧妙了。Actions就相当于持续集成中的某个特定功能的脚本,通过多个actions的自由组合,便可实现自己特定功能的持续集成服务。 24 | 25 | 同时,Github为了方便大家使用 Actions,还专门做了一个 [Actions市场](https://github.com/marketplace?type=actions), 真的是非常方便! 26 | 27 | ![](https://img.rruu.net/image/5ff5f152d64ce) 28 | 29 | GitHub Actions 有一些自己的术语: 30 | 31 | * 1.`workflow(工作流程)`:持续集成一次运行的过程,就是一个`workflow`。 32 | * 2.`job(任务)`:一个`workflow`由一个或多个`jobs`构成,含义是一次持续集成的运行,可以完成多个任务。 33 | * 3.`step(步骤)`:每个`job`由多个`step`构成,一步步完成。 34 | * 4.`action(动作)`:每个`step`可以依次执行一个或多个命令(action)。 35 | 36 | ### workflow文件 37 | 38 | GitHub Actions 的配置文件叫做`workflow`文件,存放在代码仓库的`.github/workflows`目录, 如下图所示: 39 | 40 | ![](https://img.rruu.net/image/5ff5f4a84125b) 41 | 42 | `workflow`文件采用`YAML`格式,文件名可以任意取,但是后缀名统一为`.yml`,比如上图的`package.yml`。 43 | 44 | `workflow`文件的配置字段非常多,详见[官方文档](https://help.github.com/en/articles/workflow-syntax-for-github-actions) 。下面是一些基本字段: 45 | 46 | * 1.`name`: workflow的名称。如果省略该字段,默认为当前workflow的文件名。 47 | * 2.`on`: 触发workflow的条件,通常是某些事件,例如:`release`、`push`、`pull_request`等。详细内容可以参照 [官方文档](https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows) 。 48 | * 3.`jobs`: workflow文件的主体内容,表示要执行的一项或多项任务。 49 | * `jobs..name`: `job_id`是任务的id,`name`是任务的描述。 50 | * `jobs..runs-on`: `runs-on`运行所需要的虚拟机环境,它是必填字段。 51 | * `jobs..needs`: `needs`指定当前任务的依赖关系,即运行顺序。 52 | * `jobs..steps`: `steps`指定每个任务的运行步骤,可以包含一个或多个步骤。 53 | 54 | ## Actions的应用 55 | 56 | ### 如何使用Action发布flutter插件 57 | 58 | 之前我写过一篇[《Flutter Plugin插件开发填坑指南》](https://xuexiangjys.blog.csdn.net/article/details/104351616) ,讲的就是如何开发一个flutter插件并进行发布。但由于我们发布插件到 [flutter插件平台](https://pub.dev/flutter/packages) 需要访问外网,而且还需要给命令终端设置代理,所以每次的发布都非常的麻烦。 59 | 60 | 一个偶然的机会,我就在Action市场中发现了一个`publish-dart-flutter-package`插件,可以一键把自己的插件发布到 [flutter插件平台](https://pub.dev/flutter/packages) ,脚本如下: 61 | 62 | ```yaml 63 | name: Pub Publish plugin 64 | 65 | on: workflow_dispatch 66 | 67 | jobs: 68 | publish: 69 | runs-on: ubuntu-latest 70 | steps: 71 | - name: Checkout 72 | uses: actions/checkout@v1 73 | - name: Publish 74 | uses: sakebook/actions-flutter-pub-publisher@v1.3.0 75 | with: 76 | credential: ${{ secrets.CREDENTIAL_JSON }} 77 | flutter_package: true 78 | skip_test: true 79 | dry_run: false 80 | ``` 81 | 当然你也可以参考我的[flutter_xupdate](https://github.com/xuexiangjys/flutter_xupdate/blob/master/.github/workflows/pub_publish.yml) ,它就是利用这个Action进行发布的。 82 | 83 | 这里我们可以看到,我们定义的触发条件是`workflow_dispatch`,也就是手动触发任务执行:需要我们点击 `Run workflow` => 选择 `master` 分支 => 点击 `Run workflow`,入下图所示: 84 | 85 | ![](https://img.rruu.net/image/5ff727d699e24) 86 | 87 | 这里我们注意到定义了一个`secrets.CREDENTIAL_JSON`常量,也就是我们的google账号认证证书,这里需要我们在项目的`Settings` => 选择 `Secrets` => 点击 `New repository secret`来创建一个属性名为`CREDENTIAL_JSON`的常量。对应的值你可以到你的用户Home目录下的`.pub-cache`文件夹下找到`credentials.json`文件。 88 | 89 | ![](https://img.rruu.net/image/5ff72a57b2d19) 90 | 91 | 下图是我执行了一次发布action的结果,只需2分钟,无需科学上网工具和给命令终端配置代理,即可完成flutter插件的发布,真的是非常方便! 92 | 93 | ![](https://img.rruu.net/image/5ff7275ec0f9f) 94 | 95 | ### 如何使用Action打包apk 96 | 97 | 作为一名Android开发,你有没有想过每次提交代码或者发布版本的时候,github能够对应帮你自动打包出一个apk? 98 | 99 | 这样你既可以省去打包apk的时间,还免去了应用包的管理,岂不美哉? 100 | 101 | 那么我们应该怎么做呢?下面就是我实现的一个`workflow`脚本,主要的功能就是:在提交代码或者发布版本的时候,自动构建脚本打包出apk,同时直接上传至`Artifacts`存储。 102 | 103 | ```yaml 104 | name: Android CI 105 | 106 | on: 107 | release: 108 | types: [published] 109 | push: 110 | branches: 111 | - master 112 | tags: 113 | - '2.*' 114 | pull_request: 115 | branches: 116 | - master 117 | 118 | jobs: 119 | build: 120 | runs-on: ubuntu-latest 121 | steps: 122 | - uses: actions/checkout@v2 123 | - name: set up JDK 1.8 124 | uses: actions/setup-java@v1 125 | with: 126 | java-version: 1.8 127 | - name: release apk sign 128 | run: | 129 | echo "给apk增加签名" 130 | cp $GITHUB_WORKSPACE/.github/workflows/android.keystore $GITHUB_WORKSPACE/app/android.keystore 131 | sed '$a\RELEASE_STORE_FILE=./android.keystore' $GITHUB_WORKSPACE/gradle.properties -i 132 | - name: build with gradle 133 | run: | 134 | echo "开始进行release构建" 135 | chmod +x gradlew 136 | ./gradlew app:assembleRelease 137 | - name : upload apk 138 | uses: actions/upload-artifact@master 139 | if: always() 140 | with: 141 | name: xupdate_apk 142 | path: ${{ github.workspace }}/app/build/outputs/apk/release 143 | ``` 144 | 详细配置可以参考我的[XUpdate](https://github.com/xuexiangjys/XUpdate/blob/master/.github/workflows/package.yml) 中的配置。 145 | 146 | 这里我们可以看到,我们定义的触发条件是`release`,`push`和`pull_request`,触发的分支是`master`,tags是`2.*`开头的。 147 | 148 | 整个任务主要分为4个步骤: 149 | 150 | * 1.`set up JDK 1.8`: 构建java1.8的环境。 151 | * 2.`release apk sign`:配置应用的签名。这里需要注意的是,这个地方的签名配置还是需要结合着[build.gradle](https://github.com/xuexiangjys/XUpdate/blob/master/app/build.gradle) 文件的配置来编写的。 152 | * 3.`build with gradle`:编译构建apk。运行`assembleRelease`命令打release包。 153 | * 4.`upload apk`:上传apk至`Artifacts`。 154 | 155 | 最后执行的效果如下: 156 | 157 | ![](https://img.rruu.net/image/5ff750a4f2c1b) 158 | 159 | ### 如何使用Action来反击白嫖党 160 | 161 | > 我在做开源项目的时候,经常能够碰到一些个无名小号(白嫖党),项目看都不看就提一些没有任何价值的`issues`,然后你好心好意地回复了,他却消失不见了...真的是让人恨得牙痒痒的! 162 | 163 | 是的,你没有看错,Action居然还可以用来反击白嫖党!这也是之前我在逛掘金的时候偶然看到一篇文章[《❌ 对白嫖怪 SAY NO !!! —— 如何在 GitHub 上阻止无耻白嫖》](https://juejin.cn/post/6869626477831749640) 发现的。 164 | 165 | 那么他是怎么做的呢?其实也很简单,就是设置触发的条件是`issues`的创建,在创建的时候去查询一下`issues`的创建者是否`star`或者`fork`了该仓库,如果满足条件则不做处理,否则将自动锁住并关闭`issues`。 166 | 167 | 当然,这位作者也是把这个非常骚的Action做成了一个插件,插件的地址是:[https://github.com/marketplace/actions/no-free-usage-action](https://github.com/marketplace/actions/no-free-usage-action) ,使用起来非常简单。 168 | 169 | 以下是我简单使用的脚本案例: 170 | 171 | ```yaml 172 | name: No Free usage issue checker 173 | 174 | on: 175 | issues: 176 | types: [opened, reopened] 177 | 178 | jobs: 179 | build: 180 | runs-on: ubuntu-latest 181 | 182 | steps: 183 | - uses: actions/checkout@v2 184 | - name: Check issue actor 185 | uses: fluttercandies/no-free-usage-action@v1.0.1 186 | with: 187 | token: ${{ secrets.GITHUB_TOKEN }} # 由GitHub提供的临时Token,必须在此处进行传递,且必须为这个值。 188 | forked: '--no-forked' 189 | words: To support our project, please file the issue after you starred the repo. Thanks! 🙂 190 | ``` 191 | 192 | 这里,我设置的触发条件是`issues`的打开和重新打开事件,设置不强制`fork`,但是需要`star`。当一只野生的白嫖党出没并在你的项目上提`issues`的时候,就会触发下图的效果: 193 | 194 | ![](https://img.rruu.net/image/5ff75730b246c) 195 | 196 | 看到上图的效果,是不是感到很惊喜,很刺激?你以为你做白嫖党我就没有办法治你?哈哈,给我老实点! 197 | 198 | 都看到这儿了,还不赶紧三连支持一下,难道你也想做白嫖党吗? 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /framework/X-Library系列Android应用框架详解.md: -------------------------------------------------------------------------------- 1 | # X-Library系列Android应用框架详解 2 | 3 | > 自2017年初开始,我就致力于Android应用框架的研究,到2018年开始在Github上陆续开源系列作品,再到2019年收获我的第一个star过千的项目,期间我付出了很多,失去了很多,同时也获得了很多。 4 | 5 | ## 前言 6 | 7 | 为了能够让更多的人了解到我的开源项目,我也是使出了浑身解数,写了不少文章和文档来提高项目的曝光率,不过在这期间我也发现了不少问题:读者的水平参差不齐,以往我写的文章都是建立在有一定开发基础之上的,这就导致了很多新手小白、学生党看不懂,不会用,瞎折腾,这完全违背了我的初衷。我希望我的开源项目不仅能够服务那些有一定开发经验的人,还能帮助那些热爱Android的人学习并提升自己的开发水平,早日能够跟上我们的步伐。 8 | 9 | 在接下来的数月里,我将一一详细讲解我开源的几个热门项目,介绍他们所使用的场景,解决的问题以及分析其中实现的逻辑。 10 | 11 | ## 概述 12 | 13 | > 所有的技术框架都必须服务于实际生产,否则就是耍流氓。 14 | 15 | 我一直认为这世上没有绝对完美的事物,当然技术也并不例外。在做Android的最初几年里,我一直认为技术是产品的灵魂,用于创造产品而又高于产品,是无可替代的,这也是我初期为何执着于技术的原因。渐渐地,当一项技术趋于成熟的时候,你会发现其实技术也并不是想象中的那么重要,同样的功能或是产品,你可以用2种或者更多的技术方案来实现,这个时候你才会发现,原来技术也如同资本、人力、市场和物料等资源,只是我们实现目的的工具而已。 16 | 17 | 其实`X-Library`正是我早期做Android开发过程中积累沉淀下来的技术经验,并通过我后期不断完善之后形成的。虽说可能不及大厂和google爸爸他们开源的项目那么牛掰,不过相信我,这些框架都是在实际生产中诞生出来的优秀项目,相比大厂和google爸爸他们开源的项目,他们可能更适合中小企业或者独立开发者的你使用哦! 18 | 19 | 下面是`X-Library`的思维导图: 20 | 21 | ![](../img/x_library.png) 22 | 23 | * 基础组件 24 | * [页面框架:XPage](#xpage) 25 | * [AOP切片库:XAOP](#xaop) 26 | * [页面路由:XRouter](#xrouter) 27 | * [数据库:XOrmlite](#xormlite) 28 | * 信息通讯 29 | * [网络通讯:XHttp2](#xhttp2) 30 | * [TCP通讯:XTCP](#xtcp) 31 | * [进程通讯:XIPC](#xipc) 32 | * UI组件 33 | * [UI框架:XUI](#xui) 34 | * [悬浮窗:XFloatView](#xfloatview) 35 | * 功能库 36 | * [版本更新:XUpdate](#xupdate) 37 | * [二维码扫描:XQRCode](#xqrcode) 38 | * [消息推送:XPush](#xpush) 39 | * [日志记录:XLog](#xlog) 40 | * [视频录制:XVideo](#xvideo) 41 | * 工具 42 | * [工具类:XUtil](#xutil) 43 | * [RxJava工具类:RxUtil2](#rxutil2) 44 | 45 | --- 46 | 47 | ## Library简介 48 | 49 | ### XPage 50 | 51 | > 一个非常方便的fragment页面框架 52 | 53 | XPage是我开源的第一个项目,也是最实用、最方便的项目之一。设计的初衷是希望能做一个通用的Activity作为壳,Fragment作为页面填充展示,并且能够实现自由的切换和数据交互。 54 | 55 | #### 设计原由 56 | 57 | 当初做Android开发时每当我写一个页面,都需要创建一个Activity,并且还需要在manifest中注册一堆Activity信息,这样既不方便,而且对资源的开销也比较大。因此当时我就设想能否创造出一个通用万能的Activity容器,可以全权负责Fragment的切换展示和数据交互,只需要一行代码即可完成所有的操作,还不需要自己手动去注册,可以一键生成。 58 | 59 | #### 设计思路 60 | 61 | 刚开始的时候真的很难,没有什么好的思路,最初只是简单封装了一个Activity,通过传入一些key值从而获取并加载对应的fragment,类似`ARouter`中Fragment发现那种。其实这样做并没有解决一个容器的问题,而且页面切换也不是很灵活,不够通用,使用起来也不是很方便。 62 | 63 | 突然有一天我发现Github上有个开源项目[CorePage](https://github.com/lizhangqu/CorePage/)写得非常好,完美地解决了我对一个Activity容器的问题,于是我决定仔细研究其代码,并在其基础上设计出了XPage的最初版本。 64 | 65 | 就在XPage正式投入使用的过程中,我发现还是存在不少问题的: 66 | 67 | * 1.对外API不够灵活,使用起来不够方便; 68 | 69 | * 2.每个Fragment仍需要手动注册,很麻烦; 70 | 71 | 对于API不够灵活的问题,我在之后的版本中陆续通过构造者模式设计以及Android主题属性等手段解决了。 72 | 73 | 而对于手动注册的问题,我正是借鉴了ARouter的思路,通过Android APT技术,从而实现了Fragment信息的自动注册。 74 | 75 | #### 解决痛点 76 | 77 | * 只需要一个Activity容器就可以实现多个页面的交互。 78 | 79 | * Fragment自由切换和数据交互。 80 | 81 | * 无需在manifest中注册一堆Activity信息,通过@Page注解一键自动注册。 82 | 83 | #### 项目地址 84 | 85 | https://github.com/xuexiangjys/XPage 86 | 87 | --- 88 | 89 | ### XAOP 90 | 91 | > 一个轻量级的AOP(Android)应用框架。囊括了最实用的AOP应用。 92 | 93 | XAOP是我刚接触到AOP(面向切片编程)思想后,灵光乍现编写的应用库,应该说是我使用得最多的库了,因为有了它,编码真的很方便! 94 | 95 | #### 设计原由 96 | 97 | 在我们平时开发的过程中,一定会遇到权限申请、线程切换、数据缓存、异常捕获、埋点和方法执行时间统计等问题。这些都是非常常见的问题,实现起来也不是很难,不过就是太麻烦了,还会让程序多出很多重复性、模版化的代码。 98 | 99 | #### 设计思路 100 | 101 | 让我最初接触到AOP思想的是JakeWharton的[hugo](https://github.com/JakeWharton/hugo),通过阅读它的源码之后,让我对`aspectj`这项技术的动态代码编织深深地着了迷。之后我详细研究了`aspectj`相关的技术,并不断搜集AOP在Android上的典型应用场景,然后通过`aspectj`这项技术去逐一实现。最后就成就了XAOP这个库。 102 | 103 | #### 解决痛点 104 | 105 | * 可以解决快速点击的问题 106 | * 解决Android6.0以上动态权限申请的问题 107 | * 线程自由切换的问题 108 | * 日志埋点问题 109 | * 缓存问题(磁盘缓存和内存缓存) 110 | * 异常捕获处理 111 | * 业务拦截(登陆验证、有效性验证等) 112 | 113 | #### 项目地址 114 | 115 | https://github.com/xuexiangjys/XAOP 116 | 117 | --- 118 | 119 | ### XUI 120 | 121 | > 一个简洁而又优雅的Android原生UI框架,解放你的双手! 122 | 123 | XUI可以说是我花费心血最多的开源项目了,目前稍微大一点的项目我都会选择引入它。XUI几乎涵盖了目前Android开发所需要的所有组件,可以说有了XUI之后,可以大大提高我们的开发效率,让我们可以将精力很多地放在业务功能和数据处理上。可以说XUI是目前Github上组件最全、文档最详细、案例最多的Android原生UI库。 124 | 125 | #### 设计原由 126 | 127 | 相信做过Android的人都知道Android原生组件在国内很不受设计师的待见,至于Google推行的Material Design设计风格更是无人问津,这就导致了设计师给出的原型图几乎是清一色的IOS风格,更尴尬的是,网上Android相关的开源UI库是少之又少,这可就为难死我们了,几乎所有的基础组件都需要自己重写。之前也写过React和Vue,发现它们都有非常方便的UI库,而且使用起来也非常方便,直接在示例代码的基础上修修改改就能大致上实现自己想要的效果,极大地提高了开发的效率。 128 | 129 | #### 设计思路 130 | 131 | 在开始着手做这样一个开源库之前,我是一点思路都没有的。好在在2017年的某一天,我接触到了[QMUI](https://github.com/Tencent/QMUI_Android),通过阅读它的源码,我发现它的设计思路非常好,可以通过设置不同的主题样式、组件属性等实现不同的组件效果,非常灵活;除此之外,它还对UI主题风格做了较为详细的制定和归类,可以说很有启发意义。于是我就遵循了QMUI的思路,开启了XUI的编写。 132 | 133 | #### 解决痛点 134 | 135 | * 简洁优雅,尽可能少得引用资源文件的数量,项目库整体大小不足1M。 136 | * 组件丰富,提供了绝大多数我们在开发者常用的功能组件。 137 | * 使用简单,为方便快速开发,提高开发效率,对api进行了优化,提供一键式接入。 138 | * 样式统一,框架提供了一系列统一的样式,使UI整体看上去美观和谐。 139 | * 兼容性高,框架还提供了3种不同尺寸设备的样式(4.5英寸、7英寸和10英寸),并且最低兼容到Android 17, 让UI兼容性更强。 140 | * 扩展性强,各组件提供了丰富的属性和样式API,可以通过设置不同的样式属性,构建不同风格的UI。 141 | 142 | #### 项目地址 143 | 144 | https://github.com/xuexiangjys/XUI 145 | 146 | --- 147 | 148 | ### XUpdate 149 | 150 | > 一个轻量级、高可用性的Android全量版本更新框架。 151 | 152 | XUpdate是为了解决在不同项目组、不同平台之间进行统一的Android全量版本更新的库。它具有轻量、灵活、低耦合、高可用等特点,可以很方便地定制属于自己的版本更新。 153 | 154 | #### 设计原由 155 | 156 | 在没有XUpdate之前的版本更新,Android版本更新基本都是靠写各种版本更新工具类来实现版本更新,更可怕的是有时在不同项目组或者平台之间,它们的版本更新完全是不一样的,这样的结果就是会写无数的版本更新工具类,并且每次更换一个项目组或者平台就需要从头重写再写一遍,非常得麻烦。当时我就在想,版本更新作为一个Android应用基本都有,且内容相对稳定的功能,有没有可能设计出一个通用的、不为业务或者平台所影响的基础库呢? 157 | 158 | #### 设计思路 159 | 160 | 在着手写XUpdate之前,我特地去Github上搜了一圈有关Android版本更新的内容,发现[AppUpdate](https://github.com/WVector/AppUpdate)这个项目star数量最多。但是当我翻阅它的源码之后发现,它设计得并不优美,内部耦合非常严重,不过优点就是Android版本更新的功能基本都涵盖了。于是我就照着它所拥有的功能,结合了我对版本更新的理解进行了重新设计,感兴趣的可[点击查看框架UML设计图](https://github.com/xuexiangjys/XUpdate/blob/master/img/xupdate_uml.png)。 161 | 162 | #### 解决痛点 163 | 164 | * 使用简单,只需一行代码即可完成版本更新功能。 165 | * 功能强大,兼容Android6.0、7.0、8.0,支持静默更新和自动更新,支持国际化。 166 | * 扩展性强,可自定义请求API接口、提示弹窗、下载服务、文件加密器等。 167 | * 搭建简单,只需提供json内容即可支持版本更新。 168 | * 配套齐全,默认提供了后台服务和管理界面。 169 | 170 | #### 项目地址 171 | 172 | * Android基础库: https://github.com/xuexiangjys/XUpdate 173 | * 版本更新后台服务: https://github.com/xuexiangjys/XUpdateService 174 | * 版本更新管理系统: https://github.com/xuexiangjys/xupdate-management 175 | 176 | --- 177 | 178 | ### XHttp2 179 | 180 | > 一个功能强悍的网络请求库,使用RxJava2 + Retrofit2 + OKHttp组合进行封装。 181 | 182 | XHttp2的出现主要是为了解决网络请求前后端统一、灵活性、易用性和可拓展性等问题。它提供了丰富的API调用和功能,可以灵活地设置请求参数、拦截器、缓存策略,动态添加参数、异常拦截捕获、自定义请求等。 183 | 184 | #### 设计原由 185 | 186 | 在没有设计XHttp2之前,网络请求我用过async-http、Volley、okhttp等网络请求,普遍的做法就是写一个网络请求的工具类,提供几种常用的请求方法进行调用,这样做确实可以,但是也存在很多问题: 187 | 188 | * 灵活性差。请求参数一般都是固定的,不可以灵活地设置,每次有新的请求方式都需要增加更多的方法。 189 | 190 | * 易用性差。每次请求可能都需要构建一个请求实体,并且不同的请求需要调用不同的方法,传入不同的参数,往往一个请求需要写很多重复的代码。 191 | 192 | * 耦合度高。如果需要切换一种请求方式的话,需要修改所有工具类调用相关的代码,非常麻烦。 193 | 194 | * 请求的行为不好控制。例如请求策略的控制、请求线程的控制、缓存策略的控制、请求响应以及异常处理的控制等。 195 | 196 | * 可拓展性差。无法自定义请求的形式,很难对请求进行统一和有效的管理,不利于解决前后端统一的问题。 197 | 198 | 但是自从有了Retrofit之后,以上的问题都得到了很好的解决。可以说Retrofit真的是一个不错的网络请求框架,很好地体现了设计模式的优美。当然,Retrofit也有自己的问题: 199 | 200 | * Retrofit定义的接口返回类型不支持二次泛型。 201 | 202 | * Retrofit虽具备高度的灵活性,但却缺乏易用性,无法对请求进行统一的管理,所以使用起来不是那么方便。 203 | 204 | * Retrofit的扩展性不强。不支持自定义请求形式,只能在其提供的框架内进行网络请求。 205 | 206 | #### 设计思路 207 | 208 | XHttp2最初的设计思路来源于[RxEasyHttp](https://github.com/zhou-you/RxEasyHttp) 和[axios](https://github.com/axios/axios)。综合使用了原型模式、构建者模式、代理模式、策略模式、模板模式、装饰模式、外观模式、中介者模式、责任链模式和观察者模式,并且严格遵循Java设计模式的七大设计原则进行了严格地设计。想了解更多设计细节的点击查看[XHttp2的设计类图](https://github.com/xuexiangjys/XHttp2/blob/master/img/xhttp_uml.png)。 209 | 210 | #### 解决痛点 211 | 212 | * 提供了一整套统一的请求形式、拦截器、缓存、线程控制、请求响应、异常处理的解决方案。 213 | * 解决网络请求前后端统一的问题。 214 | * 解决Retrofit易用性差的问题。 215 | * 解决网络请求灵活性、易用性和可拓展性等问题。 216 | 217 | #### 项目地址 218 | 219 | https://github.com/xuexiangjys/XHttp2 220 | 221 | --- 222 | 223 | ### XPush 224 | 225 | > 一个轻量级、可插拔的Android消息推送框架。一键集成推送(极光推送、友盟推送、信鸽推送、华为、小米推送等),提供有效的保活机制,支持推送的拓展,充分解耦推送和业务逻辑,解放你的双手! 226 | 227 | XPush是对Android各大消息推送平台错综复杂的API进行统一的整合和管理,提供一致性的入口和出口,简化消息推送的集成和使用。 228 | 229 | #### 设计原由 230 | 231 | 做过Android消息推送的人都知道,Android不仅设备碎片化严重,推送平台也是五花八门的。早在2017年工信部就号召所有的厂商来制定统一的Android消息推送平台,可到现在也没有下文(究其原因还是这其中的利益太大了,谁也不想妥协)。我们不能将希望全都寄托在这个完全没有定数的事件上,代码终归要写,功能终归要上,与其受制于人,不如自己革命,搞一个自己能控制的消息推送全平台解决方案来得靠谱。 232 | 233 | #### 设计思路 234 | 235 | 虽然目前市面上各家提供的消息推送服务都各不相同,但仔细研究了之后就会发现它们其中是有很多共性的地方。其实我们完全可以提取一下公因数,将他们共性的地方提取出来并建立统一的管理,这样就可以非常方便地接入和切换各大消息推送平台了。这样带来的好处就是,无论后台推送平台或者方式如何变化,我们都不需要修改业务代码,只需要简单切换一下推送客户端的实现方式就行了,做到消息推送和业务代码的隔离。 236 | 237 | #### 解决痛点 238 | 239 | * 弱化了Android各大消息推送平台的差异。 240 | * 简化了Android各大消息推送平台的集成和使用。 241 | * 提供了一致性的消息推送入口和出口。 242 | * 支持推送消息的过滤处理。 243 | 244 | #### 项目地址 245 | 246 | https://github.com/xuexiangjys/XPush 247 | 248 | --- 249 | 250 | ### XQRCode 251 | 252 | > 一个非常方便实用的二维码扫描、解析、生成库 253 | 254 | XQRCode作为一个二维码扫描的应用库,是基于zxing的识别功能实现的。它的设计目标就是方便、好用以及易拓展。 255 | 256 | #### 设计原由 257 | 258 | 二维码扫描功能在App中可以说是一个非常常见的功能了,而且在网上也有很多相关的开源库,那我为何还要自己重复造轮子呢?其实最初我使用的也是别人的开源库:[android-zxingLibrary](https://github.com/yipianfengye/android-zxingLibrary).使用起来很方便,但问题也很多。还是那句话,易用性和灵活性不能很好地共存。虽然使用起来非常方便,但是默认提供的扫描界面效果并不是很理想,而且想自定义扫描界面非常地麻烦,很多扫描参数都无法自定义设置,不支持多次扫描,代码的耦合性非常高。 259 | 260 | #### 设计思路 261 | 262 | 通过提供两种自定义的方式:1.组件属性自定义(自定义Fragment) 2.主题样式自定义(自定义Activity) 这两种方式以解决界面UI自定义难的问题。同时为一些重要的参数提供可设置的API。 263 | 264 | #### 解决痛点 265 | 266 | * 二维码扫描界面自定义难的问题。 267 | * 二维码多次扫描的问题。 268 | * 二维码生成和解析的问题。 269 | 270 | #### 项目地址 271 | 272 | https://github.com/xuexiangjys/XQRCode 273 | 274 | --- 275 | 276 | ### XLog 277 | 278 | > 一个简易的日志打印框架(支持打印策略自定义,默认提供2种策略:logcat打印和磁盘打印) 279 | 280 | XLog是一个非常方便易用的日志打印框架,主要提供日志打印输出的能力。可以灵活地控制日志打印的样式和策略。 281 | 282 | #### 设计原由 283 | 284 | 在没有XLog之前做日志打印的时候,基本都是基于工具类进行打印的,这就出现了一个比较严重的问题:定制化的问题。因为不同等级的日志需要打印的内容是不一样的,而且不同业务下打印的日志信息内容也是不一样的。例如:崩溃日志需要将尽可能的信息都记录下来,单独存成一个文件;一般性的错误日志需要将堆栈信息打印出来;关键点的日志需要将入参、出参、耗时以及所处线程等信息都打印出来;一般性的埋点信息可能只需要打印极少的内容.... 285 | 286 | 当日志打印出现如上需求的时候,想只通过简简单单的工具类来实现日志打印就显得非常蹩脚了。 287 | 288 | #### 设计思路 289 | 290 | 为了解决定制化的问题,这里我借鉴了[logger](https://github.com/orhanobut/logger)的设计思想,将日志打印拆分为两个部分:日志格式策略和打印策略。日志格式策略主要负责日志输出信息和样式的处理,而打印策略主要负责日志输出打印。 291 | 292 | 除此之外,为了能够对异常崩溃进行定制化处理,我还专门设计了一套崩溃处理的定制化方案,支持崩溃信息展示、邮件发送等形式。 293 | 294 | #### 解决痛点 295 | 296 | * 解决日志定制化的问题。支持自定义日志格式策略IFormatStrategy和打印策略ILogStrategy。 297 | * 支持自定义日志文件存储形式(文件前缀、时间片存储等)。 298 | * 支持自定义崩溃日志处理【默认提供了3种处理方式】。 299 | * 支持第三方打印接口的适配。 300 | 301 | #### 项目地址 302 | 303 | https://github.com/xuexiangjys/XLog 304 | 305 | --- 306 | 307 | ### XRouter 308 | 309 | > 一个轻量级的Android路由框架,基于ARouter上进行改良,优化Fragment的使用,可结合XPage使用。 310 | 311 | XRouter是我在仔细研读ARouter框架的源码之后,结合我使用XPage过程中遇到的问题,而进行重新改写的一个框架,一般是配合XPage使用。 312 | 313 | #### 设计原由 314 | 315 | 在我使用ARouter的时候,我发现它对Fragment的支持并不是很友好。说到底它主要还是为Activity路由服务的。而在我的XPage中Activity类非常少,因此使用起来极为不方便,不过ARouter的依赖注入设计得还是挺好的,因此改进它对Fragment的支持就显得尤为重要。 316 | 317 | #### 解决痛点 318 | 319 | * 让ARouter对Fragment的支持更加友好。 320 | * 配合XPage使用。 321 | 322 | #### 项目地址 323 | 324 | https://github.com/xuexiangjys/XRouter 325 | 326 | --- 327 | 328 | ### XOrmlite 329 | 330 | > 一个方便实用的OrmLite数据库框架,支持一键集成。 331 | 332 | XOrmlite是我在接触了APT(编译时注解处理)技术后,在数据库框架构建上的一项应用。通过它,你可以一键集成ormlite数据库框架,非常得方便。 333 | 334 | #### 设计原由 335 | 336 | 做Android都必定会和SQLite打交道,无奈在Google还没有提供Room数据库框架的时候,真的是要被SQLite折腾废了,好在后来有了ormlite数据库框架。 337 | 338 | 在使用了ormlite一段时间后,我发现应用使用的数据库不一定都是内存数据库,可能还需要读取操作外部存储的数据库,于是我又对其做了一定的封装,让其同时支持内部数据库和外部存储数据库,同时增加了数据库连接池的功能。 339 | 340 | 但就是这样,在使用的过程中我仍然发现库在项目间的移植非常麻烦,每次引入都需要创建几个几乎完全类似的类,而应对的通常做法就是复制粘贴,有时有的地方不修改就可能导致出错,总之还是比较麻烦的。同时,在数据库第一次打开的时候,我们还需要根据数据库类去创建对应的数据库表,有的时候漏了个没发现,结果就有可能出现排查了一下午问题最后发现是漏写了一个类的尴尬。因此需要使用APT技术,在程序编译时自动帮我们生成那几个我们每次都需要创建的类以及收集我们所有使用到的数据库实体类信息,这样就可以大大减少错误的发生,降低了库的引入难度。 341 | 342 | #### 设计思路 343 | 344 | XOrmlite的设计思路很简单,就是基于享元模式做了一个数据库连接池,然后对Ormlite数据库进行了二次封装,然后通过APT技术分别生成数据库框架构建所需要的连接池和实现接口,并自动收集项目中所使用到的所有数据库实体信息类用于数据库表的初始创建。 345 | 346 | #### 解决痛点 347 | 348 | * 支持自动生成数据库管理仓库DataBaseRepository和自动搜索所有的数据库表类,并自动创建数据库表,简化了数据库框架的引入。 349 | * 支持内部存储和外部存储两种数据库,支持自定义数据库存储位置。 350 | * 提供了常用的数据库操作API,简化了数据库的使用。 351 | 352 | #### 项目地址 353 | 354 | https://github.com/xuexiangjys/XOrmlite 355 | 356 | --- 357 | 358 | ### XTCP 359 | 360 | > 一个便捷的TCP消息包拼装和解析框架 361 | 362 | XTCP是一套能够自动进行TCP消息包拼包、拆包和解析的框架,类似于Google的[protobuf](https://github.com/protocolbuffers/protobuf). 363 | 364 | #### 设计原由 365 | 366 | 相信做过Android嵌入式开发或者智能硬件的人都知道,设备间的通讯基本都是基于自定义TCP协议来实现的。Http协议其实也是TCP协议的一种,但由于它携带的附带信息过多,效率并不高,因此在做嵌入式开发的时候一般的做法都是自定义TCP协议来实现通讯。但是自定义协议这就需要牵涉到组包和拆包的工作。如果协议少的话,手动拆解包还是可行的。但是如果当协议的数量达到百来条以上的话,这个时候再进行手动拆解包的话就相当累了,而且如果协议发生变化的话,改起来不但非常痛苦,而且也容易出错,代码的可读性和可维护性都比较差。 367 | 368 | #### 设计思路 369 | 370 | 通过APT(编译时注解处理)技术,对标注了@Protocol和@ProtocolField类进行自动统计和建立映射关系,解析时借鉴了Gson的思路,采用注解反射和递归的方式进行拼包和解析。 371 | 372 | #### 解决痛点 373 | 374 | * 简单通过@Protocol和@ProtocolField的配置,即可让实体对象拥有自动转化为TCP传输的byte数据和自动byte数据解析。 375 | * 支持byte、short、int、long、byte[]、short[]、int[]、long[]、String等常用基础类型,支持类型的拓展。 376 | * 支持无符号数和有符号数两种。 377 | * 支持BCD编码格式【时间、int、float、double等】。 378 | * 支持大端和小端两种存储方式,支持设置全局默认存储方式和局部存储方式。 379 | * 支持short、int、long读取长度的自定义。 380 | * 支持对实体字段进行排序,避免解析错乱。 381 | * 支持自定义协议项和协议解析器。 382 | * 支持对不定长数组解析【需要注意的是,在一条协议中有且只能有一个不定长的数组,否则将无法解析成功】。 383 | * 支持自动协议映射,自动根据读取的opcode识别出对应的协议并进行解析,并根据对应注册的协议信息判断协议是否有响应。 384 | 385 | #### 项目地址 386 | 387 | https://github.com/xuexiangjys/XTCP 388 | 389 | --- 390 | 391 | ### XUtil 392 | 393 | > 一个方便实用的Android工具类库 394 | 395 | XUtil主要是我平时在开发过程中对一些实用工具类的整理。除此之外还包括一些常用的代码混淆配置和Android Gradle脚本。 396 | 397 | #### 项目地址 398 | 399 | https://github.com/xuexiangjys/XUtil 400 | 401 | --- 402 | 403 | ### RxUtil2 404 | 405 | > 一个实用的RxJava2工具类库 406 | 407 | RxUtil2主要包含了RxJava2常用操作符的相关应用。 408 | 409 | #### 解决痛点 410 | 411 | * RxBus 支持多事件定义,支持数据携带,支持全局和局部的事件订阅和注销。 412 | * 提供统一的订阅池管理。 413 | * 支持非侵入式的订阅生命周期绑定。 414 | * 提供简易的线程调度辅助工具。 415 | * RxBinding 和 RxJava 常用操作符使用工具。 416 | 417 | #### 项目地址 418 | 419 | https://github.com/xuexiangjys/RxUtil2 420 | 421 | --- 422 | 423 | ### XIPC 424 | 425 | > 一个Android通用的IPC(进程通信)框架。该项目主要是模仿饿了么开源项目Hermes的设计进行的自我理解改写。 426 | 427 | XIPC是一套非常方便的IPC(进程通信)框架。它可以将进程间通信的蹩脚方式(写AIDL接口)简化为像调用本地服务一样方便。当初也是因为想摸清Hermes的实现原理,于是从0开始跟着源码自己手撸了一个。 428 | 429 | #### 设计原由 430 | 431 | 其实Android进程间通讯的方式有很多种,例如:Bundle、AIDL、Socket、ContentProvider等,其中因AIDL方式提供的功能更强大,且支持实时通讯,因此成为很多人进行进程通讯的方式。但问题就是,使用AIDL方式来实现进程通讯较为复杂,且需要处理好线程同步等问题,使用起来不是很方便。如果进程通讯使用的场景不多的话,姑且使用AIDL的方式还算凑合,但如果使用的地方非常多的话,那就非常麻烦了,因为可能需要定义一堆接口和服务,那用起来是真的很麻烦了。 432 | 433 | #### 设计思路 434 | 435 | XIPC主要借鉴了Retrofit的设计思路,采用动态代理、注解反射、AIDL、服务绑定和进程间垃圾回收等技术实现。详细实现原理请[点击查看](https://github.com/xuexiangjys/XIPC/blob/master/principle.md). 436 | 437 | #### 解决痛点 438 | 439 | * 支持自定义服务接口实现进程通信,无需定义AIDL接口,所有IPC通信就像调用本地函数一样简单。 440 | * 支持自定义接口服务(服务发现)、获取单例和获取工具类方法。 441 | * 支持进程通信的接口回调。 442 | * 支持接口回调的线程控制。 443 | * 拥有垃圾回收机制,防止接口回调内存泄漏。 444 | * 支持跨进程和跨应用通信。 445 | 446 | #### 项目地址 447 | 448 | https://github.com/xuexiangjys/XIPC 449 | 450 | --- 451 | 452 | ### XVideo 453 | 454 | > 一个能自动进行压缩的小视频录制库 455 | 456 | XVideo主要采用FFmpeg技术实现视频录制。主要解决使用系统API视频录制文件过大的问题,支持在视频录制的过程中自动进行视频压缩。 457 | 458 | #### 解决痛点 459 | 460 | * 支持自定义小视频录制时的视频质量。 461 | * 支持自定义视频录制的界面。 462 | * 支持自定义最大录制时长和最小录制时长。 463 | * 支持自定义属性的视频压缩。 464 | 465 | #### 项目地址 466 | 467 | https://github.com/xuexiangjys/XVideo 468 | 469 | --- 470 | 471 | ### XFloatView 472 | 473 | > 一个简易的悬浮窗实现方案 474 | 475 | #### 解决痛点 476 | * 支持自定义布局的悬浮窗。 477 | * 支持自定义拖动事件、点击事件。 478 | * 支持悬浮窗自动吸附效果。 479 | * 支持初始化悬浮窗的位置。 480 | * 支持悬浮窗翻转吸附。 481 | * 支持各手机厂商Rom的悬浮窗权限申请。 482 | 483 | #### 项目地址 484 | 485 | https://github.com/xuexiangjys/XFloatView 486 | 487 | -------------------------------------------------------------------------------- /framework/XPage系列|这次终于是全自动化注册了!.md: -------------------------------------------------------------------------------- 1 | # XPage系列|这次升级后终于是全自动化注册了! 2 | 3 | ## 前言 4 | 5 | 作为 [X-Library系列框架](https://xuexiangjys.blog.csdn.net/article/details/102639857) 的灵魂所在,[XPage](https://github.com/xuexiangjys/XPage) 开源两年以来,一直致力于降低Fragment使用的难度,努力实现一个Activity多Fragment的Android开发模式。 6 | 7 | 就在前不久,我就整理了XPage开源这几年来的使用情况,写了一篇[《史上最方便的Android页面框架XPage使用指南》](https://xuexiangjys.blog.csdn.net/article/details/109085587) ,并且还录了几期视频单独讲解了[XPage的使用](https://space.bilibili.com/483850585/channel/detail?cid=150979) ,让越来越多地人看到了XPage使用的便捷性。 8 | 9 | 但就在前几天,在交流群里突然有人问我下面几个问题: 10 | 11 | * 1.我如果想在多个module中使用XPage,我该怎么办呀? 12 | * 2.为什么我使用XPage之后,一直找不到`AppPageConfig`这个类啊? 13 | 14 | 上面的问题让我突然认识到一点:并不是所有人都对APT技术有所了解的。 15 | 16 | 看来我之前参考`ARouter`实现的自动注册功能可能并没有完善,难怪`ARouter`后来会写一个`arouter-register`插件来实现自动注册的功能。 17 | 18 | 于是乎,为了能够让XPage的自动注册功能更加完美,我加班加点开发,于是就有了[XPage的3.1.1版本--彻底的自动化注册](https://github.com/xuexiangjys/XPage/releases/tag/3.1.1) 。 19 | 20 | ## 升级后有什么变化 21 | 22 | > 在感受全自动化页面注册带来的便利之前,让我们先来感受一下之前版本的使用。 23 | 24 | ### 3.1.1之前版本 25 | 26 | 在3.1.1之前版本,在使用自动注册功能的时候,还是需要实现`PageConfiguration`接口,并调用编译时自动生成的页面配置类“moduleName”+PageConfig 的getPages()方法返回注册的页面。 27 | 28 | ``` 29 | PageConfig.getInstance() 30 | .setPageConfiguration(new PageConfiguration() { //页面注册 31 | @Override 32 | public List registerPages(Context context) { 33 | //自动注册页面,是编译时自动生成的,build一下就出来了。如果你还没使用@Page的话,暂时是不会生成的。 34 | return AppPageConfig.getInstance().getPages(); //自动注册页面 35 | } 36 | }) 37 | .debug("PageLog") //开启调试 38 | .setContainActivityClazz(XPageActivity.class) //设置默认的容器Activity 39 | .enableWatcher(false) //设置是否开启内存泄露监测 40 | .init(this); 41 | ``` 42 | 43 | 可以看到,这里的自动注册还是需要一部分手动配合才能完成的。如果说你当前只有一个module的话,可能还好说。但是如果你使用了多个module之后,你就需要把多个module生成的配置类像上面那样一个一个地加进去,这样用起来会让人感觉非常的不方便,这明显违背了我写`XPage`框架的初衷! 44 | 45 | 不仅如此,这样写死还会带来其他很多问题: 46 | 47 | * 1.如果module名变了,还需要对应地去修改配置类的类名。 48 | * 2.如果当前module没有使用`@Page`注解修饰Fragment的话,配置类也不会自动生成,这样会让很多初次使用者非常疑惑。 49 | * 3.项目要是没有编译过的话,配置类是不会自动生成的,这样代码就会报错说类找不到,然后很多新手就懵逼了。 50 | 51 | ### 3.1.1之后版本 52 | 53 | 为了能够解决以上的问题,我实现了一个自动注册的配置类[AutoPageConfiguration](https://github.com/xuexiangjys/XPage/blob/master/xpage-lib/src/main/java/com/xuexiang/xpage/AutoPageConfiguration.java) 。那么下面就看一下最新版本的XPage是如何注册的吧: 54 | 55 | ``` 56 | PageConfig.getInstance() 57 | .debug("PageLog") //开启调试 58 | .setContainActivityClazz(XPageActivity.class) //设置默认的容器Activity,按需设置(非必须) 59 | .init(this); //初始化页面配置 60 | ``` 61 | 是的,你没有看错,这里没有手动实现`PageConfiguration`接口的部分了,可以说是真正实现了全自动页面注册,是不是非常方便呀? 62 | 63 | ## 如何实现注册的自动化 64 | 65 | > 看到上面的变化,你是不是非常想知道我是如何实现彻底的自动化注册的? 66 | 67 | 想要回答这个问题,还是让我们先看一看这个编译时自动生成的配置类是如何实现的。 68 | 69 | ### APT技术实现页面配置类的自动生成 70 | 71 | 其实当初实现页面配置类的自动生成的方案,也是我研读了ARouter源码之后,受到了APT技术的启发后完成的。 72 | 73 | 因为XPage实现路由跳转主要就是靠 [PageInfo](https://github.com/xuexiangjys/XPage/blob/master/xpage-annotation/src/main/java/com/xuexiang/xpage/model/PageInfo.java) 和 Fragment 建立起来的映射关系。当时的思路就是采用APT技术,利用`@Page`注解去标识需要注册的Fragment,然后在编译的时候通过APT技术去扫描出所有使用了`@Page`注解标识了的Fragment,将注解信息转化为`PageInfo`, 并按module生成对应的页面配置类,在这个配置类里面存放了该module下所有标注了`@Page`的页面信息`PageInfo`。 74 | 75 | 下面是自动生成的一个简单的配置类例子: 76 | 77 | ![](https://img.rruu.net/image/5ffb30bf2c7d3) 78 | 79 | 可以看到,自动生成的配置类都会存放在`com.xuexiang.xpage.config`包下,类名都是以`PageConfig`作为结尾。注意这里非常关键,它是我后面实现自动化注册的关键。 80 | 81 | 详细的实现细节,可以参见XPage的页面配置自动生成器[PageConfigProcessor的源码](https://github.com/xuexiangjys/XPage/blob/master/xpage-compiler/src/main/java/com/xuexiang/xpage/compiler/PageConfigProcessor.java) 。 82 | 83 | ### 运行时扫描指定包下的配置类反射实现自动注册 84 | 85 | 上面我们在了解页面配置类是如何自动生成的时候发现一个规律: 86 | 87 | **自动生成的配置类都会存放在`com.xuexiang.xpage.config`包下,类名都是以`PageConfig`作为结尾。** 88 | 89 | 那么我们可不可以在运行的时候,直接扫描`com.xuexiang.xpage.config`包下的所有类,然后找到以`PageConfig`作为结尾的配置类,然后反射它的`getPages`方法直接获取到所有的配置信息,然后注册进去? 90 | 91 | 下面是我根据上面的猜想,实现的AutoPageConfiguration类源码: 92 | 93 | ``` 94 | public class AutoPageConfiguration implements PageConfiguration { 95 | /** 96 | * 页面配置所在的包名 97 | */ 98 | private static final String PAGE_CONFIG_PACKAGE_NAME = "com.xuexiang.xpage.config"; 99 | /** 100 | * 页面配置生成类的类后缀名 101 | */ 102 | private static final String PAGE_CONFIG_CLASS_NAME_SUFFIX = "PageConfig"; 103 | 104 | @Override 105 | public List registerPages(Context context) { 106 | List pageInfos = new ArrayList<>(); 107 | Set classSet = null; 108 | try { 109 | classSet = ClassUtils.getClassNames(context, PAGE_CONFIG_PACKAGE_NAME); 110 | } catch (Exception e) { 111 | e.printStackTrace(); 112 | } 113 | if (classSet != null) { 114 | for (String className : classSet) { 115 | if (className != null && className.endsWith(PAGE_CONFIG_CLASS_NAME_SUFFIX)) { 116 | try { 117 | pageInfos.addAll(getPagesByClass(Class.forName(className))); 118 | } catch (Exception e) { 119 | PageLog.e(e); 120 | } 121 | } 122 | } 123 | } 124 | return pageInfos; 125 | } 126 | 127 | private List getPagesByClass(Class clazz) throws Exception { 128 | // 获取单例对象 129 | Method getInstanceMethod = clazz.getDeclaredMethod("getInstance"); 130 | getInstanceMethod.setAccessible(true); 131 | Object instance = getInstanceMethod.invoke(null); 132 | // 获取页面信息 133 | Method getPagesMethod = clazz.getDeclaredMethod("getPages"); 134 | getPagesMethod.setAccessible(true); 135 | return (List) getPagesMethod.invoke(instance); 136 | } 137 | } 138 | ``` 139 | 140 | 从源码中我们可以看到,我是这样做的: 141 | * 1.使用`ClassUtils.getClassNames`获取到`com.xuexiang.xpage.config`包下的所有类的类名。这里的`ClassUtils`也是我借鉴了ARouter里面的源码。 142 | * 2.遍历这个类名集合,并根据类名结尾是否是`PageConfig`筛选出所有的配置类。 143 | * 3.调用`getPagesByClass`方法,反射获取到配置类的所有页面信息,然后加入到页面集合中,最终返回所有module配置页面的信息。 144 | 145 | 有了`AutoPageConfiguration`之后,下面就非常简单啦,我们只需要将mPageConfiguration默认设置成`AutoPageConfiguration`,这样就可以实现自动化注册啦! 146 | 147 | ``` 148 | /** 149 | * 初始化页面信息 150 | * 151 | * @param context 上下文 152 | */ 153 | private void initPages(Context context) { 154 | if (mPageConfiguration == null) { 155 | // 没有设置的话,就使用自动注册配置 156 | mPageConfiguration = new AutoPageConfiguration(); 157 | } 158 | registerPageInfos(mPageConfiguration.registerPages(context)); 159 | CoreConfig.init(context, getPages()); 160 | } 161 | ``` 162 | 163 | ### 增加混淆配置 164 | 165 | 你以为到这儿就结束了?没那么简单!可以发现,上面的实现方案主要是依赖于扫描类并进行反射注册的。所以如果代码做了混淆了之后,该方案就会失效了,所以我们还需要在混淆配置清单中增加如下的配置来避免混淆: 166 | 167 | ``` 168 | -keep class com.xuexiang.xpage.config.** { *; } 169 | ``` 170 | 171 | 我们要保证`com.xuexiang.xpage.config`包下的类不能被混淆。 172 | 173 | 到这儿,自动注册的实现算是真正的讲完了,下面让我们来瞧瞧XPage的新版本还有那些地方更新了! 174 | 175 | ## 其他更新 176 | 177 | ### 去除LeakCanary依赖 178 | 179 | 在此之前,XPage一直依赖了`LeakCanary`,主要原因还是`LeakCanary`在2.0版本之前的使用还是相当不方便的,于是我就做了一下默认集成以方便使用。 180 | 181 | 但是当`LeakCanary`升级到2.0以上版本的时候,这个问题似乎就没了。因为进行了重新的设计,`LeakCanary`的使用变得没那么具有侵入性,因此我就考虑去除了`LeakCanary`的依赖。 182 | 183 | ### 优化了页面点击的键盘处理 184 | 185 | 之前在XPageActivity里面做了简单的页面点击处理:当用户点击到非输入框区域就隐藏键盘。 186 | 187 | 但是这样做了之后发现效果并不是很好,因为有些页面可能并不需要这个功能,如果把这个写到Activity里面的话,那么在这个Activity下的所有Fragment都将拥有这个功能,这样非常不灵活。 188 | 189 | 除此之外,使用者可能也想自定义屏幕的触摸事件,因此我对此做了重新设计,将触摸事件的处理下放至每个Fragment之中,由Activity调用指定的方法进行处理。 190 | 191 | ## 相关链接 192 | 193 | * [史上最方便的Android页面框架XPage使用指南](https://xuexiangjys.blog.csdn.net/article/details/109085587) 194 | * [Navigation和XPage框架相比谁更香](https://xuexiangjys.blog.csdn.net/article/details/108923027) 195 | * [XPage项目地址:https://github.com/xuexiangjys/XPage](https://github.com/xuexiangjys/XPage) 196 | 197 | ## 最后 198 | 199 | 非常感谢大家对[XPage](https://github.com/xuexiangjys/XPage) 的支持,喜欢的可以到项目的Github主页:[https://github.com/xuexiangjys/XPage](https://github.com/xuexiangjys/XPage) 上点击star支持一下哦! 200 | 201 | > 更多资讯内容,欢迎微信搜索公众号:「我的Android开源之旅」 202 | 203 | 204 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /framework/整套的版本更新解决方案-XUpdate.md: -------------------------------------------------------------------------------- 1 | # 整套的版本更新解决方案-XUpdate 2 | 3 | > XUpdate是一套基于Android的全量版本更新整体解决方案。他除了提供了Android SDK外,还附带了Spring Boot搭建的后台服务以及Vue.js编写的后台管理界面。 4 | 5 | ## 为什么选择XUpdate 6 | 7 | * 使用简单,只需一行代码即可完成版本更新功能。 8 | 9 | * 功能强大,兼容Android6.0、7.0、8.0,支持静默更新和自动更新,支持国际化。 10 | 11 | * 扩展性强,可自定义请求API接口、提示弹窗、下载服务等。 12 | 13 | * 搭建简单,只需提供json内容即可支持版本更新。 14 | 15 | * 配套齐全,默认提供了后台服务和管理界面。 16 | 17 | ## 资源链接 18 | 19 | * [使用说明文档](https://github.com/xuexiangjys/XUpdate/wiki) 20 | 21 | * [XUpdate版本更新库](https://github.com/xuexiangjys/XUpdate) 22 | 23 | * [后台服务](https://github.com/xuexiangjys/XUpdateService) 24 | 25 | * [后台管理界面](https://github.com/xuexiangjys/xupdate-management) 26 | 27 | ## 演示 28 | 29 | * Android版本更新界面 30 | 31 | ![](https://github.com/xuexiangjys/XUpdate/blob/master/img/update_1.png) 32 | 33 | ![](https://github.com/xuexiangjys/XUpdate/blob/master/img/update_3.png) 34 | 35 | * 后台管理界面 36 | 37 | ![](https://github.com/xuexiangjys/xupdate-management/blob/master/art/2.png) 38 | 39 | ![](https://github.com/xuexiangjys/xupdate-management/blob/master/art/6.png) 40 | 41 | -------------------------------------------------------------------------------- /img/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/.DS_Store -------------------------------------------------------------------------------- /img/android_framework.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/android_framework.png -------------------------------------------------------------------------------- /img/appprocess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/appprocess.png -------------------------------------------------------------------------------- /img/appprocess1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/appprocess1.png -------------------------------------------------------------------------------- /img/appstart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/appstart.png -------------------------------------------------------------------------------- /img/appstart1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/appstart1.png -------------------------------------------------------------------------------- /img/appstart2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/appstart2.png -------------------------------------------------------------------------------- /img/appstart3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/appstart3.png -------------------------------------------------------------------------------- /img/appstart4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/appstart4.png -------------------------------------------------------------------------------- /img/github_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/github_1.png -------------------------------------------------------------------------------- /img/github_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/github_2.png -------------------------------------------------------------------------------- /img/receive_broadcast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/receive_broadcast.png -------------------------------------------------------------------------------- /img/register_receiver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/register_receiver.png -------------------------------------------------------------------------------- /img/send_broadcast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/send_broadcast.png -------------------------------------------------------------------------------- /img/servicebind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/servicebind.png -------------------------------------------------------------------------------- /img/servicebind1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/servicebind1.png -------------------------------------------------------------------------------- /img/servicebind2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/servicebind2.png -------------------------------------------------------------------------------- /img/servicestartup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/servicestartup.png -------------------------------------------------------------------------------- /img/servicestartup1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/servicestartup1.png -------------------------------------------------------------------------------- /img/systemstart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/systemstart.png -------------------------------------------------------------------------------- /img/systemstart2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/systemstart2.png -------------------------------------------------------------------------------- /img/x_library.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/img/x_library.png -------------------------------------------------------------------------------- /video/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/AndroidAdvancedLearning/881ab98acb32e05fe86dbbd1ae5f97189893bec3/video/.DS_Store -------------------------------------------------------------------------------- /video/flutter/flutter_xupdate_chapter_1.md: -------------------------------------------------------------------------------- 1 | 2 | # flutter如何实现 3 | 4 | # 版本更新功能? 5 | 6 | --- 7 | 8 | ## 常见思路 9 | 10 | * 1.使用 `package_info` 插件获取当前应用的版本信息。 11 | * 2.使用 `dio` 插件进行网络请求获取最新版本信息。 12 | * 3.使用 `flutter_downloader` 插件下载最新APK。 13 | * 4.使用 `install_plugin` 插件安装APK。 14 | 15 | --- 16 | 17 | ## 偷懒的做法 18 | 19 | 毕竟版本更新是一个相对稳定且独立的功能,每次就为一个小小的版本更新都需要写这么多冗余的代码实在是不够优雅! 20 | 21 | 那么,有没有一个可以只通过一行代码就可以实现版本更新的插件呢? 22 | 23 | --- 24 | 25 | ## 答案就是 26 | 27 | ### flutter_xupdate插件! 28 | 29 | --- 30 | 31 | ## 项目介绍 32 | 33 | > flutter_xupdate是一个可以一键实现Android内版本更新的flutter插件。内部集成的是XUpdate。 34 | 35 | 项目地址: https://github.com/xuexiangjys/flutter_xupdate 36 | 37 | --- 38 | 39 | ## 如何集成 40 | 41 | 1.在你的flutter项目中的`pubspec.yaml`文件中添加`flutter_xupdate`依赖. 42 | 43 | ``` 44 | dependencies: 45 | flutter_xupdate: ^1.0.2 46 | ``` 47 | 48 | --- 49 | 50 | 2.修改Android项目的主题为`AppCompat`主题,文件路径: `android/app/src/main/res/values/styles.xml`, 例如: 51 | 52 | ``` 53 | 54 | 57 | 58 | ``` 59 | 60 | --- 61 | 62 | ## 初始化 63 | 64 | * 调用`FlutterXUpdate.init`方法进行初始化. 65 | 66 | * 调用`FlutterXUpdate.setErrorHandler`方法设置错误监听. 67 | 68 | 69 | --- 70 | 71 | ## 如何使用 72 | 73 | * 默认版本更新 74 | 75 | * 支持后台版本更新 76 | 77 | * 自定义版本样式 78 | 79 | 80 | --- 81 | 82 | ## 自定义Json解析 83 | 84 | * 1.定义一个自定义的版本更新解析器 85 | 86 | * 2.调用`checkUpdate`方法,并设置`isCustomParse`参数为true. 87 | 88 | --- 89 | 90 | ## 欢迎关注我 91 | 92 | ![](https://ss.im5i.com/2021/06/14/6tqAU.png) 93 | 94 | * 微信公众号:我的Android开源之旅 95 | * Github:https://github.com/xuexiangjys 96 | * 喜欢的话,一定记得三连支持一下哦! 97 | -------------------------------------------------------------------------------- /video/flutter/flutter_xupdate_chapter_2.md: -------------------------------------------------------------------------------- 1 | 2 | # flutter_xupdate 3 | 4 | # 盘点常见的几大问题 5 | 6 | --- 7 | 8 | ## 常见问题 9 | 10 | * 1.无法显示版本更新弹窗。 11 | * 2.版本更新提示显示异常。 12 | * 3.下载完毕无法安装更新。 13 | * 4.强制更新不起作用。 14 | * 5.国际化问题。 15 | 16 | --- 17 | 18 | ## 无法显示版本更新弹窗 19 | 20 | * 接口的问题 21 | * 版本号的问题 22 | * 混淆打包的问题 23 | 24 | --- 25 | 26 | ## 版本更新提示显示异常 27 | 28 | * 打包的问题 29 | 30 | 31 | --- 32 | 33 | ## 下载完毕无法安装更新 34 | 35 | * apk下载的问题 36 | 37 | * md5设置的问题 38 | 39 | * 签名的问题 40 | 41 | * 设备终端的问题 42 | 43 | --- 44 | 45 | ## 强制更新不起作用 46 | 47 | * 强制更新和版本可忽略为互斥的模式 48 | 49 | --- 50 | 51 | ## 国际化问题 52 | 53 | * 原生的国际化 54 | 55 | --- 56 | 57 | ## 欢迎关注我 58 | 59 | ![](https://ss.im5i.com/2021/06/14/6tqAU.png) 60 | 61 | * 微信公众号:我的Android开源之旅 62 | * Github:https://github.com/xuexiangjys 63 | * 喜欢的话,一定记得三连支持一下哦! 64 | -------------------------------------------------------------------------------- /video/github/DNS_github.txt: -------------------------------------------------------------------------------- 1 | # Github 2 | 199.232.69.194 github.global.ssl.fastly.net 3 | 140.82.112.4 github.com 4 | 103.245.222.133 assets-cdn.github.com 5 | 23.235.47.133 assets-cdn.github.com 6 | 203.208.39.104 assets-cdn.github.com 7 | 204.232.175.78 documentcloud.github.com 8 | 204.232.175.94 gist.github.com 9 | 107.21.116.220 help.github.com 10 | 207.97.227.252 nodeload.github.com 11 | 199.232.68.133 raw.github.com 12 | 107.22.3.110 status.github.com 13 | 204.232.175.78 training.github.com 14 | 199.232.96.133 avatars.githubusercontent.com 15 | 199.232.68.133 avatars0.githubusercontent.com 16 | 199.232.68.133 avatars1.githubusercontent.com 17 | 199.232.96.133 camo.githubusercontent.com 18 | 199.232.96.133 githubusercontent.com 19 | 185.199.108.133 raw.githubusercontent.com 20 | 185.199.109.133 raw.githubusercontent.com 21 | 185.199.110.133 raw.githubusercontent.com 22 | 185.199.111.133 raw.githubusercontent.com 23 | 199.232.96.133 user-images.githubusercontent.com 24 | -------------------------------------------------------------------------------- /video/github/github_chapter_1.md: -------------------------------------------------------------------------------- 1 | # 如何正确地使用github 2 | 3 | --- 4 | 5 | ## github是什么 6 | 7 | > github是全球最大的开源软件托管平台,官网地址: https://github.com/ 8 | 9 | --- 10 | 11 | ## github加载失败处理 12 | 13 | > 国内的网络,经常因为DNS服务器污染导致图片或者资源加载失败。 14 | 15 | 方案一:更新DNS服务器。 16 | 17 | 推荐:阿里提供的:223.5.5.5和223.6.6.6 18 | 19 | --- 20 | 21 | 方案二:配置本地hosts。 22 | 23 | * 域名ip地址查找:https://www.ipaddress.com/ip-lookup 24 | 25 | * 配置本地静态映射hosts文件。 26 | 27 | --- 28 | 29 | ## github常用链接 30 | 31 | * github的搜索功能:https://github.com/search 32 | 33 | * github的每日趋势:https://github.com/trending 34 | 35 | --- 36 | 37 | ## 如何辨别一个开源项目的质量 38 | 39 | * 项目的热门程度:项目的star、fork和watch量。 40 | * 项目的活跃度:这里考量的因素包含issue的总体数量、open issue和closed issue的数量、issue回复和解决的速度、pull requests的数量、项目最后一次提交的时间。 41 | * 文档是否齐全:是否有wiki或者README.md。 42 | 43 | --- 44 | 45 | * 项目的稳定性:代码提交的频率,项目版本发布的频率。 46 | * 项目的潜力:项目开发的分支数、项目的开发计划以及项目参与者的数量等。 47 | * 项目代码的质量:设计是否合理,是否符合设计模式原则,考虑项目的可扩展性、便利性和稳定性。 48 | * 开源作者的水平:作者其他项目的star量和行业影响力。 49 | 50 | --- 51 | 52 | ## github项目克隆加速 53 | 54 | * 使用码云导入仓库进行克隆: https://gitee.com/ 55 | * Gitee 极速下载:https://gitee.com/mirrors 56 | 57 | --- 58 | 59 | ## 欢迎关注我 60 | 61 | ![](https://ss.im5i.com/2021/06/14/6tqAU.png) 62 | 63 | * 微信公众号:我的Android开源之旅 64 | * Github:https://github.com/xuexiangjys 65 | * 喜欢的话,一定记得三连支持一下哦! 66 | -------------------------------------------------------------------------------- /video/github/github_chapter_2.md: -------------------------------------------------------------------------------- 1 | # 如何正确地使用 2 | # 开源项目 3 | 4 | --- 5 | 6 | ## 目录 7 | 8 | * 如何下载开源项目 9 | 10 | * 如何使用开源项目 11 | 12 | * 出现问题如何解决 13 | 14 | --- 15 | 16 | ## 如何下载开源项目 17 | 18 | * 直接下载 19 | 20 | * git命令 21 | 22 | --- 23 | 24 | ## 如何使用开源项目 25 | 26 | * 首先关注项目的描述和特征 27 | 28 | * 看发布版本以及发布日志 29 | 30 | * 找到项目的集成文档或者使用文档 31 | 32 | * 看演示Demo以及源码示例 33 | 34 | * 看开源协议 35 | 36 | --- 37 | 38 | ## 出现问题如何解决 39 | 40 | * 回头再翻阅一遍使用说明,确保没有遗漏细节 41 | 42 | * 寻找是否有常见问题的文档 43 | 44 | * 在issue中寻找问题解决的方法 45 | 46 | * 创建具备有效信息的issue,有能力的可以尝试阅读源码来解决。 47 | 48 | * 寻找是否有交流群或者作者的联系方式 49 | 50 | --- 51 | 52 | ## 欢迎关注我 53 | 54 | ![](https://ss.im5i.com/2021/06/14/6tqAU.png) 55 | 56 | * 微信公众号:我的Android开源之旅 57 | * Github:https://github.com/xuexiangjys 58 | * 喜欢的话,一定记得三连支持一下哦! 59 | -------------------------------------------------------------------------------- /video/template/template_chapter_android.md: -------------------------------------------------------------------------------- 1 | 2 | # 如何使用模版工程 3 | 4 | --- 5 | 6 | ## 克隆项目 7 | 8 | ``` 9 | git clone https://github.com/xuexiangjys/TemplateAppProject.git 10 | ``` 11 | 12 | --- 13 | 14 | ## 项目修改 15 | 16 | * 修改项目名(文件夹名),并删除目录下的.git文件夹(隐藏文件) 17 | 18 | * 修改包名 19 | 20 | * 修改applicationId 21 | 22 | * 修改app_name 23 | 24 | * 修改启动页样式 25 | 26 | --- 27 | 28 | ## 打包 29 | 30 | * 修改工程根目录的gradle.properties中的isNeedPackage=true。 31 | 32 | * 添加并配置keystore,在versions.gradle中修改app_release相关参数。 33 | 34 | * 如果考虑使用友盟统计的话,在local.properties中设置应用的友盟ID:APP_ID_UMENG。 35 | 36 | * 使用./gradlew clean assembleReleaseChannels进行多渠道打包。 37 | 38 | 39 | --- 40 | 41 | ## 欢迎关注 42 | 43 | * 模版地址:https://github.com/xuexiangjys/TemplateAppProject 44 | 45 | * Github:https://github.com/xuexiangjys 46 | 47 | * 微信公众号:我的Android开源之旅【openandroidxx】 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /video/template/template_chapter_flutter_1.md: -------------------------------------------------------------------------------- 1 | # flutter 2 | 3 | # 模版工程入门介绍 4 | 5 | --- 6 | 7 | ## 项目介绍 8 | 9 | > Flutter空壳模板工程,已搭建基础框架,实现国际化、主题换肤、登录注册、自动路由注册等功能,可在此基础上简单修改实现自己的应用功能. 10 | 11 | 项目地址: https://github.com/xuexiangjys/flutter_template 12 | 13 | --- 14 | 15 | ## 克隆项目 16 | 17 | ``` 18 | git clone https://github.com/xuexiangjys/flutter_template.git 19 | ``` 20 | 21 | --- 22 | 23 | ## 项目修改 24 | 25 | * 修改项目名(文件夹名),并删除目录下的.git文件夹(隐藏文件) 26 | 27 | * 使用AS或者VSCode打开项目,然后分别修改flutter、Android、ios项目的包名、应用ID以及应用名等信息。 28 | 29 | --- 30 | 31 | ## 工程介绍 32 | 33 | * Flutter目录 34 | 35 | * Android目录 36 | 37 | * IOS目录 38 | 39 | --- 40 | 41 | ## 欢迎关注我 42 | 43 | ![](https://ss.im5i.com/2021/06/14/6tqAU.png) 44 | 45 | * 微信公众号:我的Android开源之旅 46 | * Github:https://github.com/xuexiangjys 47 | * 喜欢的话,一定记得三连支持一下哦! 48 | -------------------------------------------------------------------------------- /video/template/template_chapter_flutter_2.md: -------------------------------------------------------------------------------- 1 | # flutter 2 | 3 | # 模版工程使用详解 4 | 5 | --- 6 | 7 | ## 克隆项目 8 | 9 | ``` 10 | git clone https://github.com/xuexiangjys/flutter_template.git 11 | ``` 12 | 13 | --- 14 | 15 | ## 国际化 16 | 17 | > 使用的是flutter_i18n(国际化插件):https://marketplace.visualstudio.com/items?itemName=esskar.vscode-flutter-i18n-json 18 | 19 | * 安装国际化插件:[vscode-flutter-i18n-json](https://marketplace.visualstudio.com/items?itemName=esskar.vscode-flutter-i18n-json) 20 | * 插件初始化: Initialize 21 | * 增加支持的语言类型: Add locale 22 | * 增加多语言翻译并更新:Update 23 | 24 | --- 25 | 26 | ## Provider 27 | 28 | > Provider是一个非常好用的数据共享工具:https://pub.dev/packages/provider 29 | 30 | 类似redux,ViewModel.其核心都是观察者模式。 31 | 32 | 利用Provider实现语言切换和换肤功能。 33 | 34 | --- 35 | 36 | ## 路由注册 37 | 38 | > 使用的是auto_route(自动路由注册插件):https://pub.dev/packages/auto_route 39 | 40 | * 自动路由注册插件简介 41 | * 增加注册页面 42 | * 参数传递 43 | 44 | --- 45 | 46 | ## 欢迎关注我 47 | 48 | ![](https://ss.im5i.com/2021/06/14/6tqAU.png) 49 | 50 | * 微信公众号:我的Android开源之旅 51 | * Github:https://github.com/xuexiangjys 52 | * 喜欢的话,一定记得三连支持一下哦! 53 | -------------------------------------------------------------------------------- /video/xpage/xpage_chapter_1.md: -------------------------------------------------------------------------------- 1 | 2 | # XPage页面框架 3 | 4 | --- 5 | 6 | ## 没框架的Fragment使用 7 | 8 | * 使用FrameLayout作为占位容器 9 | 10 | * 使用`FragmentManager`进行管理 11 | 12 | ``` 13 | FragmentManager fragmentManager = getSupportFragmentManager(); 14 | FragmentTransaction transaction = fragmentManager.beginTransaction(); 15 | transaction.replace(R.id.fl_container, FirstFragment.newInstance()); 16 | transaction.commit(); 17 | ``` 18 | 19 | --- 20 | 21 | ## Navigation 22 | 23 | * 创建导航图 24 | 25 | * Fragment占位容器 26 | 27 | * 获取NavController进行导航 28 | 29 | ``` 30 | NavHostFragment.findNavController(FirstFragment.this) 31 | .navigate(R.id.action_FirstFragment_to_SecondFragment); 32 | ``` 33 | 34 | --- 35 | 36 | ## XPage 37 | 38 | * 使用`@Page`进行标注。 39 | 40 | * 直接调用`openPage`方法打开。 41 | 42 | ``` 43 | openPage(TestFragment.class); 44 | ``` 45 | 46 | --- 47 | 48 | ## 欢迎关注 49 | 50 | * 本期内容的源码地址:https://github.com/xuexiangjys/Navigation_XPage 51 | 52 | * XPage:https://github.com/xuexiangjys/XPage 53 | 54 | * 微信公众号:我的Android开源之旅【openandroidxx】 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /video/xpage/xpage_chapter_2.md: -------------------------------------------------------------------------------- 1 | 2 | # XPage页面框架使用详解 3 | 4 | --- 5 | 6 | ## 集成指南 7 | 8 | * 依赖添加 9 | 10 | * 页面注册 11 | 12 | * 混淆配置 13 | 14 | ``` 15 | # fastjson 16 | -dontwarn com.alibaba.fastjson.** 17 | -keep class com.alibaba.fastjson.** { *; } 18 | -keepattributes Signature 19 | 20 | # xpage 21 | -keep class com.xuexiang.xpage.annotation.** { *; } 22 | ``` 23 | 24 | --- 25 | 26 | ## 基础使用 27 | 28 | * 页面跳转 29 | 30 | * 数据传递 31 | 32 | * 转场动画 33 | 34 | --- 35 | 36 | ## 常见问题 37 | 38 | 39 | --- 40 | 41 | ## 欢迎关注 42 | 43 | * 本期内容的源码地址:https://github.com/xuexiangjys/Navigation_XPage 44 | 45 | * XPage使用文档:https://github.com/xuexiangjys/XPage/wiki 46 | 47 | * 微信公众号:我的Android开源之旅【openandroidxx】 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /video/xpage/xpage_chapter_3.md: -------------------------------------------------------------------------------- 1 | # XPage 2 | 3 | # 框架原理介绍 4 | 5 | --- 6 | 7 | 8 | 9 | 10 | 11 | 12 | --- 13 | 14 | ## 欢迎关注我 15 | 16 | ![](https://ss.im5i.com/2021/06/14/6tqAU.png) 17 | 18 | * 微信公众号:我的Android开源之旅 19 | * Github:https://github.com/xuexiangjys 20 | * 喜欢的话,一定记得三连支持一下哦! 21 | -------------------------------------------------------------------------------- /video/xui/xui_chapter_1.md: -------------------------------------------------------------------------------- 1 | 2 | # 如何在项目中使用XUI 3 | 4 | --- 5 | 6 | ## 添加Gradle依赖 7 | 8 | * 配置`jitpack`仓库 9 | 10 | * 增加XUI依赖 11 | 12 | --- 13 | 14 | ## 初始化XUI设置 15 | 16 | * 在Application中初始化XUI 17 | 18 | * 配置XUI主题 19 | 20 | 21 | --- 22 | 23 | ## 欢迎关注我 24 | 25 | ![](https://ss.im5i.com/2021/06/14/6tqAU.png) 26 | 27 | * 微信公众号:我的Android开源之旅 28 | * Github:https://github.com/xuexiangjys 29 | * 喜欢的话,一定记得三连支持一下哦! 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /video/xui/xui_chapter_2.md: -------------------------------------------------------------------------------- 1 | # XUI 2 | 3 | # 项目工程讲解 4 | 5 | --- 6 | 7 | ## XUI简介 8 | 9 | XUI是一个简洁而又优雅的Android原生UI框架。 10 | 11 | --- 12 | 13 | ## 源码下载 14 | 15 | * github: https://github.com/xuexiangjys/XUI 16 | 17 | * gitee: https://gitee.com/xuexiangjys/XUI 18 | 19 | --- 20 | 21 | ## 编译运行 22 | 23 | --- 24 | 25 | ## 项目结构 26 | 27 | * 演示Demo 28 | * 组件 29 | * 工具 30 | * 拓展 31 | * XUI库 32 | * 页面编译器 33 | 34 | --- 35 | 36 | ## 如果使用项目 37 | 38 | * 搜索你想要的演示案例 39 | 40 | --- 41 | 42 | ## 欢迎关注我 43 | 44 | ![](https://ss.im5i.com/2021/06/14/6tqAU.png) 45 | 46 | * 微信公众号:我的Android开源之旅 47 | * Github:https://github.com/xuexiangjys 48 | * 喜欢的话,一定记得三连支持一下哦! 49 | -------------------------------------------------------------------------------- /video/xui/xui_chapter_3.md: -------------------------------------------------------------------------------- 1 | # XUI 2 | 3 | # 框架原理介绍 4 | 5 | --- 6 | 7 | 8 | 9 | --- 10 | 11 | ## 欢迎关注我 12 | 13 | ![](https://ss.im5i.com/2021/06/14/6tqAU.png) 14 | 15 | * 微信公众号:我的Android开源之旅 16 | * Github:https://github.com/xuexiangjys 17 | * 喜欢的话,一定记得三连支持一下哦! 18 | -------------------------------------------------------------------------------- /video/xupdate/xupdate_chapter_1.md: -------------------------------------------------------------------------------- 1 | # XUpdate 2 | 3 | # 基础入门介绍 4 | 5 | --- 6 | 7 | ## XUpdate到底是什么? 8 | 9 | XUpdate是一套基于Android的全量版本更新整体解决方案。 10 | 11 | * 核心库:https://github.com/xuexiangjys/XUpdate 12 | * Android简化库:https://github.com/xuexiangjys/XUpdateAPI 13 | * flutter插件:https://github.com/xuexiangjys/flutter_xupdate 14 | 15 | --- 16 | 17 | * react-native插件:https://github.com/xuexiangjys/react-native-xupdate 18 | * 后台服务:https://github.com/xuexiangjys/XUpdateService 19 | * 后台管理系统:https://github.com/xuexiangjys/xupdate-management 20 | 21 | --- 22 | 23 | ## XUpdate有什么优势 24 | 25 | * 配套设施齐全。 26 | * 功能强大,支持多种更新方式,涵盖了版本更新绝大多数应用场景。 27 | * 可拓展性强,可根据业务自定义功能。 28 | * 丰富的使用案例和文档。 29 | 30 | --- 31 | 32 | ## 欢迎关注我 33 | 34 | ![](https://ss.im5i.com/2021/06/14/6tqAU.png) 35 | 36 | * 微信公众号:我的Android开源之旅 37 | * Github:https://github.com/xuexiangjys 38 | * 喜欢的话,一定记得三连支持一下哦! 39 | 40 | -------------------------------------------------------------------------------- /video/xupdate/xupdate_chapter_2.md: -------------------------------------------------------------------------------- 1 | # XUpdate 2 | 3 | # 框架基础使用详解 4 | 5 | --- 6 | 7 | ## 使用简化库API 8 | 9 | * 配置`jitpack`仓库 10 | 11 | * 增加`XUpdateAPI`依赖 12 | 13 | --- 14 | 15 | ## 简单使用 16 | 17 | * 使用`EasyUpdate` 18 | * EasyUpdate.create: 构建版本更新检查管理者 19 | * EasyUpdate.checkUpdate: 直接版本更新 20 | 21 | * 使用`AriaDownloader`断点续传 22 | 23 | --- 24 | 25 | ## 自定义初始化配置 26 | 27 | * 实现`IUpdateConfigProvider`接口 28 | 29 | --- 30 | 31 | ## 注意事项 32 | 33 | * 服务器Json格式 34 | 35 | ```json 36 | { 37 | "Code": 0, // 0代表请求成功,非0代表失败 38 | "Msg": "", // 请求出错的信息 39 | "UpdateStatus": 1, // 0代表不更新,1代表有版本更新,不需要强制升级,2代表有版本更新,需要强制升级 40 | "VersionCode": 3, 41 | "VersionName": "1.0.2", 42 | "ModifyContent": "1、优化api接口。\r\n2、添加使用demo演示。\r\n3、新增自定义更新服务API接口。\r\n4、优化更新提示界面。", 43 | "DownloadUrl": "https://raw.githubusercontent.com/xuexiangjys/XUpdate/master/apk/xupdate_demo_1.0.2.apk", 44 | "ApkSize": 2048, 45 | "ApkMd5": "..." // md5值没有的话,就无法保证apk是否完整,每次都会重新下载。 46 | } 47 | ``` 48 | 49 | --- 50 | 51 | * 混淆配置 52 | 53 | * XUpdate 54 | 55 | * AriaDownloader 56 | 57 | --- 58 | 59 | ## 欢迎关注我 60 | 61 | ![](https://ss.im5i.com/2021/06/14/6tqAU.png) 62 | 63 | * 微信公众号:我的Android开源之旅 64 | * Github:https://github.com/xuexiangjys 65 | * 喜欢的话,一定记得三连支持一下哦! 66 | 67 | -------------------------------------------------------------------------------- /video/xupdate/xupdate_chapter_3.md: -------------------------------------------------------------------------------- 1 | # XUpdate 2 | 3 | # 框架进阶使用详解 4 | 5 | --- 6 | 7 | ## 添加Gradle依赖 8 | 9 | * 配置`jitpack`仓库 10 | 11 | * 增加XUpdate依赖 12 | 13 | --- 14 | 15 | ## 初始化 16 | 17 | * 初始化XUpdate 18 | 19 | * 实现IUpdateHttpService接口 20 | 21 | --- 22 | 23 | ## 基础使用 24 | 25 | * 默认版本更新 26 | * 自动版本更新 27 | * 支持后台更新 28 | * 强制版本更新 29 | 30 | --- 31 | 32 | ## 高阶使用 33 | 34 | * 自定义弹窗的主题样式 35 | * 自定义版本更新检查器 36 | * 自定义版本更新解析器 37 | * 自定义文件加密校验器 38 | 39 | --- 40 | 41 | ## 欢迎关注我 42 | 43 | ![](https://ss.im5i.com/2021/06/14/6tqAU.png) 44 | 45 | * 微信公众号:我的Android开源之旅 46 | * Github:https://github.com/xuexiangjys 47 | * 喜欢的话,一定记得三连支持一下哦! 48 | -------------------------------------------------------------------------------- /video/xupdate/xupdate_chapter_4.md: -------------------------------------------------------------------------------- 1 | # XUpdate 2 | 3 | # 框架原理介绍 4 | 5 | --- 6 | 7 | 8 | 9 | 10 | --- 11 | 12 | ## 欢迎关注我 13 | 14 | ![](https://ss.im5i.com/2021/06/14/6tqAU.png) 15 | 16 | * 微信公众号:我的Android开源之旅 17 | * Github:https://github.com/xuexiangjys 18 | * 喜欢的话,一定记得三连支持一下哦! 19 | --------------------------------------------------------------------------------