又到了年尾,是回顾这一年概况的时候了,回过头来看之前每一年的年度总结,并没有觉得幼稚和不成熟,反倒感觉每年的小目标似乎都实现了,作为一个普通人,还是去年年度总结的那句话: 五年时间的积累可能不如一些刚工作一两年的小伙伴,但也可能不亚于一些工作年限更长的的前辈们,人与人本就不能相互比较,只要自己每年都在进步就好。对于我来说,在软件行业才算刚入门,今后有更长的路要走,还有很多要学习和沉淀的地方。 2014年度总结-蓄势待发 2015年度总结—沉淀的一年 年度盘点 拿到驾照,搞定该搞定的一切,开始了为银行挣钱之旅,原本以为为了还贷生活质量会降低很多,现在发现并没有~ 首次参加知乎盐活动,逛了一圈大上海; 作为2016 MDCC移动开发者大会Android技术专场的演讲嘉宾,在北京的国家会议中心做了《Android应用性能优化经验分享》的技术分享,据我了解到的信息,此次分享的听众评分在Android技术专场的几个分享中排在前三,从演讲嘉宾的履历来看,初次演讲能够达到这个效果也是让我自己比较满意; 知乎赞同数量接近40K,关注人数达到12K,知乎专栏关注人数超过5K,成为编程和Android开发话题下的优秀回答者,编辑了知乎Android开发话题的话题索引; 在知乎上举办了4场知乎Live,分别是:《Android应用性能优化必知必会》、《一些学校很少会教你的软件开发常识》、《Android 安装包瘦身指南》、《安卓开发书籍推荐和阅读心得分享》。从我自己和知乎官方的满意度统计来看,《一些学校很少会教你的软件开发常识》满意度为70.8%,《安卓开发书籍推荐和阅读心得分享》的满意度为90%,另外两次Live没有做统计,无法知道具体信息。4场Live中有3场参与人数超过了200人,其中《一些学校很少会教你的软件开发常识》参与人数接近1K。年后还有两场Live《安卓 SDK 开发实战经验分享》、《程序员的常见陋习》正在接受报名中; 简书粉丝数超过1.8K,喜欢数超过2.8K;《Android开发经验谈》专题关注人数接近15K; 开了微信公众号Open软件开发小组,关注人数3.6K+。创建了Android技术资源交流微信群(干货群)一周内满群,专注于Android技术资源的分享,每周一期,目前已在公众号发出了17期的技术资源分享文章,整理了近200篇优质文章; 沟通、思考能力有明显进步,在实践中已经验证了这一点;识人的能力也进步不少; 写了两个专利。 变化 对学习的认知更进了一步,花了更多的时间和花费在学习上面(看纸质书、知乎和微信阅读的电子书、思考和总结、订阅得到专栏),目前来看非常值得,买的书近一半都至少看过一遍,对开阔眼界、提升思维方式和格局的帮助很大; 做事更加关注目标、可行性和它能够产生的价值,工作效率变高了很多; 工作重心转向能够给用户或者团队带来更有价值的事情上面,比如专注于应用的性能优化(回顾来看整体做得不错)、专注于库的开发实现应用通用功能的重用(减少不必要的重复工作、应用内解耦、模块化)、团队知识沉淀、开发规范和流程的梳理(提升开发效率); 在工作上规划能力有明显进步,并且为了提前达到定下的目标乐意去不断调整,始终让自己远离舒适区; 工作上很多事情不再是亲力亲为,从“你做的太慢,我来。或者:你做的不好,我来。”的思想转变成“你做的太慢,我要想办法帮助你快起来。或者:你做的不好,怎么样才能让你(做的)好起来。这样我们的团队才会更好。”; 技术没有落下,这点很欣慰。 感悟 关于在大城市买房:你为了从农村走向城市历经千辛万苦,为了一套房子奋斗近半辈子,如果你不能或者不愿在大城市定居,你的后代会走你同样的路,他的人生同样是在为了房子、走向城市而奋斗,相比于很多人已经输在了起点。所以买房不要考虑价格,更应该考虑它能够带来的价值; 关于读书:一顿饭钱就能够买到别人可能是一辈子的心血,你的人生是有限的,想要少走弯路、走得更快就得看看成功人士的经历和思维方式,看他们是怎么思考和行事的。还有读书能够开阔你的眼界、格局和思维方式,这些往往是限制你上限的最大原因,当然环境也是很重要的一个因素,但更多的原因在于你自己; 关于考驾照:对于大多数人来讲,你这辈子肯定是要买车的,能早学就尽量早学,不要等到需要的时候才去学,那样成本会更高; 做任何事情,有明确的目标,效率会高很多,包括工作、沟通、开会、做饭等等都是这样; 成功总是相似的,但失败的原因有很多,所以关注他人成功而不是失败的经历,因为这往往有不错的参考价值。还有做对的事情比把事情做对更重要; 不要吝啬于为知识付费,他会节省你很多时间(如果没有别人的指导你可能会走很多弯路、你可能会为了获取资源浪费更多的时间等); 买东西的时候重点考虑其带来的价值,而不是价格。举个例子:买便宜的手机,一年换一个;买贵2-3倍的手机,好几年才换一个。这个过程并不仅仅是价格方面的问题,更重要的是便宜的手机给你带来了很多很多的烦恼和困扰,你每次买手机还得花费时间; 演讲、沟通和写作能力,真的可以做到你练得越多,就做得越好; 按照阮一峰老师在《未来世界的幸存者》一书中的说法:不要去想你怎样才能赚到钱,而要去想你对他人、对社会的价值在哪里。你要相信,如果你对社会是有价值的,你就一定能够赚到钱,虽然未必很多; 很赞同井底之蛙能靠自己跳出井底吗?一文中的说法:把比自己历害的人以前或者现在的认知能力拿过来学习消化,并解决自己的问题是提升自己认知能力的不错解决方案,具体做法如下:1、看很多比自己厉害的人的书;2、关注这些人现在在关注什么,学习他们看待和思考问题的方式; 你生活中的很多困扰都是因为自己导致的,比如产品给你提了需求,你在接手时如果多思考下合理性,并提出你的意见,就很有可能避免后期需求更改带来自己对工作上的抱怨; 已经开始意识到健康到底有多重要了,不健康的状态会扰乱你的心情,无法把注意力放在自己想做或者需要做的事情上面,所以即使不喜欢锻炼,哪怕每天多走走路也好; 关于敏捷软件开发的一些思考; 工作以来的一些感悟。 书单 重点列一下今年确实看了且有所收获的书: 《成为技术领导者》 《构建之法》 《移动App性能评测与优化》 《高性能Android应用开发》 《Android高级进阶》 《深入理解Java虚拟机》 知乎电子书-《改善你的驾驶》 知乎电子书-《如何做出好创意:18个切实有效的创意工具》 知乎电子书-《买个好房子》 《关键对话:如何高效能沟通》 《清醒思考的艺术》 《腾讯方法》 《打造Facebook》 《MacTalk 跨越边界》 《影响力 卡耐基最实用的说服术与社交技巧》 《从零开始做运营》 得到专栏-《吴军:硅谷来信》 2017计划(一个原则:重视做事的质量) 提升生活质量,多出去看看,丰富阅历、丰富生活,让生活变得有情调; 坚持看书,至少每个月一本,重心在消化并运用在实际生活和工作中,而不是泛读; 坚持目前做事和思考的方式,确保所做的工作有很长的半衰期,每做一件事情有明确目标、有效果、有沉淀; 多接触新技术和同行业的人,多在社区互动,拓展自己的视野和认知,借助同行的力量解决自己需要较长时间解决或者无法解决的问题; 加强团队的工程化和工程师文化建设: (1)实现应用和系统、机型完全解耦,核心应用实现应用内解耦; (2)重点从前端改善应用的质量和稳定性(严格推行程序设计、代码评审和开发规范); (3)技术方案向行业领头羊靠齐(使用经过验证的技术方案强于自己整一套); (4)谨慎尝试新技术,并运用在实际项目中,验证OK后再推广,不追求时髦,重点在于效果; (5)核心应用实现模块化开发; (6)在内部分享方面做得更有针对性,重质而不是量;
Android默认情况下会将每个多媒体文件的信息保存在一个数据库中(在系统收到某些消息,比如开机、插拔SD卡、设备连接上电脑这种涉及到可能更改文件系统内容的情况下,会触发系统扫描文件系统中的多媒体文件变化情况并同步到媒体数据库中;或者应用发送更新多媒体库广播时,也会触发多媒体数据库的更新),应用在需要读取设备内指定格式的多媒体文件信息时,可以直接读取这个数据库,相比于文件全盘检索效率会高很多。 但是,有时候我们并不希望某些多媒体文件被媒体库扫描到,比如: 应用的音效不希望被音乐播放器扫描到; 有些游戏的介绍视频不希望被视频播放器扫描到; 应用缓存的图片不希望被相册扫描到; 这种情况可以在不希望被保存到多媒体数据库中的文件夹下新建一个隐藏文件,文件名为”.nomedia”即可。官网并没有明确介绍.nomedia文件的使用,但可以通过搜索关键词,在Storage Options的页面中找到对.nomedia文件的解释,我的理解是有.nomedia文件的文件夹下的多媒体文件信息不会保存到多媒体数据库中,在系统更新媒体数据库时会视这个文件夹不见: Include an empty file named .nomedia in your external files directory (note the dot prefix in the filename). This prevents media scanner from reading your media files and providing them to other apps through the MediaStore content provider. 对Android多媒体库的详细介绍网上资料比较少,这篇文章介绍得比较全面,值得一读:Android扫描多媒体文件剖析
前言 通过这几天对好几个应用的内存泄露检测和改善,效果明显: 完全退出应用时,手动触发GC,从原来占有内存100多M降到低于20M; 手动触发GC后,通过adb shell dumpsys meminfo packagename -d查看Activity和View的数量也趋近于0了(没有做到归零是因为SDK中存在内存泄露,需要中间层去处理); 发现了一个SDK中的内存泄露(Android InputMethodManager 导致的内存泄露及解决方案); 发现一个MTK Webview的内存泄露(org.chromium.android_webview.AwPasswordHandler.java中private static AwPasswordHandler sInstance = null导致的内存泄露)。 从结果来看我分析和改善内存泄露的方法是对的,这个过程并不复杂,所以可以梳理总结出来作为分享。 原则 对于性能问题,分析和改善有必要遵循以下原则: 一切看数据说话,不能跟着感觉走,感觉哪有问题就去改,很有可能会适得其反; 性能优化是一个持续的过程,需要不断地改善,不要想着一气呵成; 对于性能问题,不一定必须要改善,受限于架构或者其它原因某些问题可能会很难改善,必须要先保证能用,再才考虑好用。 改善后一定要验证,任何一个地方的改动都需要验证,避免因为改善性能问题导致其它的问题。 步骤 下面是我在针对内存泄露这个性能问题上的解决步骤: 优先处理常见的内存泄露问题 首先解决常见的内存泄露问题,这个过程可以借助Android Studio的Analyze-Inspect Code对代码做静态分析,常见的内存泄露问题有: 非静态内部类导致的内存泄露,比如Handler,解决方法是将内部类写成静态内部类,在静态内部类中使用软引用/弱引用持有外部类的实例,eg: static class ExerciseHandler extends Handler{ private SoftReference exerciseActivitySoftReference = null; public ExerciseHandler(ExerciseActivity exerciseActivity){ exerciseActivitySoftReference = new SoftReference(exerciseActivity); } @Override public void handleMessage(Message msg) { ExerciseActivity exerciseActivity = exerciseActivitySoftReference.get(); if(null != exerciseActivity){ super.handleMessage(msg); switch (msg.what) { case MSG_XX: exerciseActivity.***; break; default: break; } } } } IO操作后,没有关闭文件导致的内存泄露,比如Cursor、FileInputStream、FileOutputStream使用完后没有关闭,这种问题在Android Studio 2.0中能够通过静态代码分析检查出来,直接改善就可以了; 自定义View中使用TypedArray后,没有recycle,这种问题也可以在Android Studio 2.0中能够通过静态代码分析检查出来,直接改善就可以了; 某些地方使用了四大组件的context,在离开这些组件后仍然持有其context导致的内存泄露,这种问题属于共识,在编写代码的过程中就应该按照规则来,使用Application的Context就可以解决这类内存泄露的问题了,至于什么情况下应该使用四大组件的Context,什么时候应该使用Application的context可以参见下表: 备注:大家注意看到有一些NO上添加了一些数字,其实这些从能力上来说是YES,但是为什么说是NO呢?下面一个一个解释: 1、数字1:启动Activity在这些类中是可以的,但是需要创建一个新的task,一般情况不推荐; 2、数字2:在这些类中去layout inflate是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用; 3、数字3:在Receiver为null时允许,在4.2或以上的版本中,用于获取黏性广播的当前值。(可以无视); 4、ContentProvider、BroadcastReceiver之所以在上述表格中,是因为在其内部方法中都有一个context用于使用。 还有一种不属于内存泄露,但在分析内存泄露的问题时应该一并解决:同一个APP,将图片放在不同的drawable文件夹下,在相同的设备上占用的内存情况不一样,具体可以参见:关于Android中图片大小、内存占用与drawable文件夹关系的研究与分析。解决这个问题遵循以下原则就可以了: 1、UI只提供一套高分辨率的图,图片建议放在drawable-xxhdpi文件夹下(放在xxxhdpi或者更高分辨率的文件夹下没有必要,权衡利弊,照顾主流设备即可),这样在低分辨率设备中图片的大小只是压缩,不会存在内存增大的情况; 2、涉及到桌面插件或者不需要缩放的图片,放在drawable-nodpi文件夹下,这个文件夹下的图片在任何设备上都是不会缩放的。 通过工具检查程序运行后的内存泄露 通过上面的步骤,应用中的大部分内存泄露问题都能够得到解决,还有一些内存泄露,需要运行程序,分析运行后的内存快照来解决,比如注册之后没有反注册、类中的静态成员变量导致的内存泄露、SDK中的内存泄露等。解决这类问题可以分两步进行: 通过内存泄露检测工具先定位是哪有问题,内存泄露的检测有两种比较便捷的方式: 1、一种是使用开源项目Leakcanary,需要添加到代码中,运行后生成分析结果; 2、另一种方式是使用adb shell dumpsys meminfo packagename -d命令,在进入一个界面之前查看一遍Activity和View的数量,在退出这个界面之后再查看一遍Activity和View的数量,对比进入前和进入后Activity和View数量的变化情况,如果有差异,则说明存在内存泄露(在使用命令查看Activity和View的数量之前,记得手动触发GC)。 备注:在Android Studio中,可以通过如下方式获取当前选中进程的内存信息: 然后通过MAT取程序运行时的内存快照做详细分析,对于MAT的使用,网上有很多优质的文章,比如:Android 性能优化之使用MAT分析内存泄露问题,在使用MAT前,有必要知道这几点: 1、 不要指望MAT明确告诉你哪里存在内存泄露,这需要你根据上一步骤首先定位到可能存在内存泄露的类,然后借助MAT确认是否真的存在内存泄露,具体哪个地方存在内存泄露; 2、借助Retained Size分析某一个类及与之相关的实例所消耗的内存,如果这个类的Retained Size比较大,优先分析; 3、检查某个类是否存在内存泄露时,排除其软/弱/虚引用,右键某个类→Merge Shortest Paths to GC Roots→exclude all phantom/weak/soft etc.references。 验证改善效果 根据个人经验,我一般是这样验证改善效果的,运行程序,各个功能跑一遍,确保没有改出问题,完全退出程序,手动触发GC,然后通过adb shell dumpsys meminfo packagename -d查看Activivites和Views的数量是否趋近于0;如果不是0,通过Leakcanary检查可能存在内存泄露的地方,继续通过MAT分析,周而复始,改善到自己满意为止。 推荐阅读 Speed up your app Android 性能优化之使用MAT分析内存泄露问题
养成良好的工作习惯(设计、代码质量、编码习惯、程序自测、版本管理等),有益于你的整个职业生涯; 迷茫的时候,做好手头上的事情; 脑力上的勤奋比体力上的勤奋重要很多倍,多思考; 危机感是让你持续前进的动力; 不能只靠经验去解决问题,要尽早形成一套属于自己的解决问题的方法; 不能为了快而牺牲质量,没搞定的事情迟早需要搞定,没弄明白的东西迟早需要弄明白,还不如一次性搞定; 最能提高工作效率的方式是工作不要返工; 生病了就要治,不要硬抗; 尽早考驾照,至于原因:你这辈子肯定会买车和买房吧; 不要为了省钱而降低生活质量,比如买二手物品、便宜的手机等,这些东西会给你带来持续的烦恼(就拿便宜的手机来讲:耗电、信号差、容易出问题,任何一件事情都能烦死你),会大大降低你的生活质量; 自信很重要,有了自信工作中没有什么问题是解决不了的; 学习的最好方式,是将自己的理解表达出来,可以是写作,也可以是帮助他人解决问题; 如果一件事情你花一个小时还没有思路,应该考虑寻求帮助,比如问同事、在社区中找答案、或者通过搜索引擎来解决; 搜索引擎尽量使用谷歌,至于原因:你用了就知道了; 书一定要看,书中的内容成体系,能够扩展你的视野,如果是要深入某一个知识点,阅读技术博客和源码更有价值; 有现成的轮子,就没必要再造一个同样的轮子,能用开源项目的尽量用开源项目; 网上的很多观点只能作为参考,不要过于迷信,有很多信息都只是告诉你结果,不会告诉你原因,或者有些结果是和环境有关的(比如不同API版本的AsyncTask实现原理不一样);还有只说优点不说缺点(比如Chrome插件多、体验好,但占内存;Genymotion确实快,但不支持arm架构的so等);所以很多东西需要自己实践之后才能得出结论,不然随意发表观点只会被别人笑话; 过早的优化是万恶之源; 工作忙的时候容易有情绪,此种情况下不要随意发表观点,甚至少说话,不然事后你多半会后悔; 出色的工作是在工作中有创造性,而不仅仅是一个执行者,简单来讲就是工作不仅仅要做完,还要做好,但做好很难; 不要为了哪门语言好、哪个工具棒去和别人争论,真的是浪费时间; 你必须承认人与人之间确实存在差距,横向比较可以是动力,但不能因此有负面情绪,没有意义; 作为团队的leader,想方设法发挥团队最大的价值,不要任何事情都亲力亲为,你的工作内容是给大家解决问题,想方设法提高大家的工作效率(比如持续集成、体力工作自动化、找趁手的工具、优化流程等); 不到万不得已,不要因为工作做不完而去招人,工作是永远忙不完的,应该是根据团队的短板去招对应的人(差哪方面的人才就找哪方面的人才); 评估工作量的时候,给出的时间至少是心里预期的2倍以上,不然最终坑的还是自己; 工作上并不是任何事情都需要通过技术手段来解决,技术投入大的可以和产品沟通,在能够达到同样效果的前提下,优化产品的交互。
在Eclipse中导入工程时,有时候导入的工程名不是我们想要的工程名,还需要手动修改,如果同时导入多个工程,还有可能存在多个工程名重复的情况导致不能一次性导入工程,eg,这里的New Project Name显然不是我们想要在Eclipse中显示的工程名: 解决这个问题最好的方式是,在需要导入的工程中新建一个”.project”隐藏文件,并在这个文件中指定工程的名称,这样就能够避免导入工程时还需要手动修改名字的问题: // 在这里指定工程的名次 LocalAppSearch com.android.ide.eclipse.adt.ResourceManagerBuilder com.android.ide.eclipse.adt.PreCompilerBuilder org.eclipse.jdt.core.javabuilder com.android.ide.eclipse.adt.ApkBuilder com.android.ide.eclipse.adt.AndroidNature org.eclipse.jdt.core.javanature 上面这种是最完美的方式,如果是开源项目最应该这么干,因为别人导入你项目的时候会方便很多,不需要手动修改工程名称(github上的Eclipse项目,导入的时候很多都需要手动修改工程名称,太不人性化了)。当然还有两种在导入工程时修改工程名的方式。 在如上图的导入工程界面,选中某一项,双击”Project to Import”,右边的”New Project Name”就处于编辑状态了,可以直接在这里修改; 将工程导入到Eclipse中后修改,选中工程,直接按F2,修改即可。
每次创建新的Android Studio工程时,都需要手动修改一些工程的配置,比如删除不必要的依赖、删掉Activity中不必要的代码 配置私有maven库的地址、增加公用的依赖库、修改.gitingore、关闭lint的严格检查、配置APK的输出路径等等;项目比较少还好,如果项目比较多,并且还不断有新人加入时,就可以考虑修改Android Studio默认的project和module模板,避免做无用功和口口相传。 工程模板路径 Android Studio的工程模板在安装目录的“\plugins\android\lib\templates\gradle-projects”文件夹下,这里面包含了导入工程模板、新建工程模板、新建module模板等。 提醒 修改前一定要备份,避免修改模板后导致新建project和module异常的情况(亲身经历,出现这样的情况你会很烦躁的); 模板文件说明 NewAndroidProject模板 NewAndroidModule模板 修改Android Studio工程模板 对工程模板中的每个文件和文件夹有一定的了解后,就可以按照自己的意愿修改模板中的内容了,如何修改得根据自己的需求而定,举个栗子,修改module的build.gradle模板(Android Studio\plugins\android\lib\templates\gradle-projects\NewAndroidModule\root\build.gradle.ftl),通常我们不需要依赖单元测试,去掉对单元测试的依赖,屏蔽lint的严格检查、并配置好APK的输出路径: buildscript { repositories { jcenter() maven { url '${mavenUrl}' } } dependencies { classpath 'com.android.tools.build:gradle:${gradlePluginVersion}' } } apply plugin: 'com.android.library' apply plugin: 'com.android.application' repositories { jcenter() maven { url '${mavenUrl}' } } android { compileSdkVersion ${buildApiString}'${buildApiString}' buildToolsVersion "${buildToolsVersion}" defaultConfig { applicationId "${packageName}" minSdkVersion ${minApi}'${minApi}' targetSdkVersion ${targetApiString}'${targetApiString}' versionCode 1 versionName "1.0" } compileOptions { sourceCompatibility JavaVersion.VERSION_${javaVersion?replace('.','_','i')} targetCompatibility JavaVersion.VERSION_${javaVersion?replace('.','_','i')} } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } sourceSets { main { jniLibs.srcDirs = ['libs'] } } // 屏蔽lint的严格检查 lintOptions { abortOnError false } } dependencies { compile '${dependency}' // 在这里删掉对单元测试的依赖,如果需要依赖公用控件,直接在这里添加 compile fileTree(dir: 'libs', include: ['*.jar']) } repositories { flatDir() { dirs 'libs' } } // 删掉没有签名的APK文件,同时在这里也可以配置APK文件的输出路径 android.applicationVariants.all { variant -> variant.assemble.doLast { variant.outputs.each { output -> println "aligned " + output.outputFile println "unaligned " + output.packageApplication.outputFile File unaligned = output.packageApplication.outputFile; File aligned = output.outputFile if (!unaligned.getName().equalsIgnoreCase(aligned.getName())) { println "deleting " + unaligned.getName() unaligned.delete() } } } }
前言 在Android应用开发过程中,不同IDE对工程的依赖方式不一样: 使用Eclipse开发时,项目之间的依赖关系是这样的:一个主工程(project)可以依赖多个libproject、so、jar包,对jar包和so的依赖是直接将jar和so放在工程的libs文件夹下(老版本的ADT需要手动配置Build Path),对libproject的依赖呈现在工程的“project.properties”文件中。 使用Android Studio开发时,除了可以依赖module(对应Eclipse中的libproject)、jar和so,还可以依赖aar(aar和jar包不同之处在于可以将so和资源文件一起打包),as的依赖关系全部(jar、so、aar、libproject)在build.gradle文件中的android标签中管理。 使用AS开发应用时,除了可以依赖本地的库之外,还可以依赖网上(公有maven服务器、私有maven服务器、jcenter等),如果是依赖本地的,必须要将依赖的module和主工程放在一个project里面,这就导致了每个project都需要配置这些依赖关系,如果是公司内多个工程依赖同一个公司内部的控件,控件有更新时,同步非常麻烦,但公司内部的控件不可能部署到公有maven服务器上,所以有必要搭建一个局域网内的maven服务器,方便管理公司内部的公共库。 maven私有服务器搭建 搭建maven私服使用得比较多的是Nexus,Nexus是基于maven仓库管理的社区项目,主要的使用场景就是可以在局域网搭建一个maven私服,用来部署第三方公共构件或者作为远程仓库在该局域网的一个代理。 关于Nexus的介绍和配置很简单,具体可以查看这里:Android 项目部署之Nexus私服搭建和应用。 上传库到私服 上传库到私服有两种方式,一种是库中配置,配置完成后执行upload这个task,另外一种方式是直接上传。下面分别对这两种方式做介绍: 在库中配置,步骤如下: 1. 在project下的gradle.properties文件中定义通用属性,方便如果有多个库需要部署时,不需要修改每一个库中的配置: #本地库 URLMAVEN_URL= http://172.28.1.*:8081/nexus/content/repositories/thirdparty/ MAVEN_SNAPSHOT_URL = http://172.28.1.*:8081/nexus/content/repositories/thirdparty-snapshot/ #对应maven的groupId值 GROUP=common #登录nexus oss的用户名 NEXUS_USERNAME=admin #登录nexus oss的密码 NEXUS_PASSWORD=admin123 # groupid GROUP_ID = common # type TYPE = aar # description DESCRIPTION = dependences lib 2. 修改module对应的build.gradle文件,配置以谁的名义上传这个库,上传到什么地方,这个库叫什么名字,属于哪个group,ID和version、description、packageing等信息 apply plugin: 'com.android.library' apply plugin: 'maven' android { compileSdkVersion 17 buildToolsVersion "23.0.2" defaultConfig { minSdkVersion 15 targetSdkVersion 17 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } sourceSets { main { jniLibs.srcDirs = ['libs'] } } lintOptions { abortOnError false } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) } uploadArchives { configuration = configurations.archives repositories { mavenDeployer { snapshotRepository(url: MAVEN_SNAPSHOT_URL) { authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD) } repository(url: MAVEN_URL) { authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD) } pom.project { version '1.0.0' artifactId 'TestLibrary' groupId GROUP_ID packaging TYPE description DESCRIPTION } } } } artifacts { archives file('TestLibrary.aar') } 3. 打开Gradle projects(在AS的右边栏),找到对应的module,展开,找到Tasks下面的upload标签并双击,在Gradle Console标签可以查看是否上传成功。 直接上传:直接上传很简单,直接按照下图的箭头操作即可,如果上传aar还没有研究过,有兴趣的可以自己研究一下: 在项目中使用私有仓库 在项目中使用私有仓库的步骤如下: 1. 在project的build.gradle文件中指定私有仓库的地址,like this: 2. 在需要依赖私有仓库的build.gradle文件中设置依赖关系:
这两天遇到了一个AS编译过程中报Exception的问题: ...//省略 :app:mergeDebugResources Exception in thread "png-cruncher_5" java.lang.RuntimeException: Timed out while waiting for slave aapt process, try setting environment variable SLAVE_AAPT_TIMEOUT to a value bigger than 5 seconds at com.android.builder.png.AaptProcess.waitForReady(AaptProcess.java:104) at com.android.builder.png.QueuedCruncher$1.creation(QueuedCruncher.java:107) at com.android.builder.tasks.WorkQueue.run(WorkQueue.java:206) at java.lang.Thread.run(Thread.java:745) Exception in thread "png-cruncher_10" java.lang.RuntimeException: Timed out while waiting for slave aapt process, try setting environment variable SLAVE_AAPT_TIMEOUT to a value bigger than 5 seconds at com.android.builder.png.AaptProcess.waitForReady(AaptProcess.java:104) at com.android.builder.png.QueuedCruncher$1.creation(QueuedCruncher.java:107) at com.android.builder.tasks.WorkQueue.run(WorkQueue.java:206) at java.lang.Thread.run(Thread.java:745) ...//省略 解决这个问题花了我一天时间,网上根本找不到解决方案,按照网上提示的设置环境变量“SLAVE_AAPT_TIMEOUT”,延长超时的时间、更改build-tools的版本、更新build-tools、升级AS、关掉杀毒软件都不管用。 最后只好结合编译时的日志进行分析,肯定是在编译时打包资源时出了问题,这就涉及到aapt打包资源的问题,所以最终还是怀疑到了build-tools上面,因为aapt.exe这个文件就在每个版本的build-tools文件夹下,但之前更新过build-tools、更改过build-tools的版本并不管用,再结合网上提示有可能是杀毒软件的问题,想到是不是杀毒软件将build-tools中的文件标记位病毒导致的,最后找同事要了一份他电脑上的build-tools,对比发现里面的很多文件内容的确不同,拷贝过来,重新编译,成功!!! 这是一个很简单的问题,可解决这个问题的方式出了问题,导致浪费了大把时间,如果早先就自己分析,而不是直接在网上去找答案,可能解决起来会更快。但也不尽然,对于这种问题排除法、调试、二分法这些都不管用,只能一个一个地去试,并且根据自己的经验分析判断才能够快速解决。
转眼已经工作了近五个年头。五年时间的积累可能不如一些刚工作一两年的小伙伴,但也可能不亚于一些工作年限更长的的前辈们,人与人本就不能相互比较,只要自己每年都在进步就好。对于我来说,在软件行业才算刚入门,今后有更长的路要走,还有很多要学习和沉淀的地方。 一 工作 今年在工作上算是非常踏实的一年,做了很多事情,把自己的一些想法沉淀在工作中,比如APK瘦身、持续集成、搭建属于自己团队的基础框架、Android应用性能优化、转AS等,有一些做得还不错,有些还需要进一步去实践。对应用性能的重点关注,也算是填了去年年终总结所说的重视用户体验的这个坑,性能优化是从技术层面来提升产品的用户体验。做这些事情算是今年最大的收获,因为不再只顾着知识的广度而很少深耕某一方面,只要有这个意识,没做好的事情可以明年继续努力。 闲暇时间逼着自己写了几篇专利,看了很多书,坚持每天看几篇技术文章,但还是玩手机的时间多。 二 机会 今年是不缺乏机会的一年,从年初到年末到处都是机会,猎头提供换工作的机会、出版社提供出书的机会、很多互联网公司提供工作机会、在线教育网站提供讲课、批改作业的机会、外包提供赚钱的机会……。 有些是自己没有抓住,比如外包,能力不够无法独自接手一个项目; 有些是争取过但最终放弃了,比如一些公司的邀请、讲课、写书,主要是感觉目前没有那个精力和实力去做这些事情,羽翼未丰,不能太过于膨胀,先踏实把工作做好再说; 有些只是尝试尝试,特意想受点打击,鞭策自己继续前进; 有些是纯碎扯淡,说是谈什么合作,就是想把你的文章搬到他的网站上去。 三 个人品牌 年初在简书创建了专题Android开发经验谈,到现在为止有4千多的订阅; 年中在知乎开通了属于自己的专栏,目前2000多的关注,很开心; 博客坚持每月至少一篇文章; 一年内收到了近千个回答邀请(中途忽略了几百个问题),个人能力和精力实在有限,只能定期挑能回答的回答,没有能力回答或者有些是自己搜一下就有答案的索性放弃。 多数文章和回答被转载,很开心; 当然还有很多因做得不好而放弃的,比如:读读日报的专题、个人微信公众号、github等。 四 圈子 今年认识了很多同行的朋友,有过线下交流,线上交流虽然多数时候是吹水,但还是长了不少眼界,得到了一些机会。和这群小伙伴在一个圈子内,让我知道人外有人,天外有天,也找到了不少值得追随的标杆。只有不断地沉淀、不断地思考、时刻有着危机感、不自我膨胀才有继续前进的机会。 五 生活 没积蓄、没买房、没买车、驾照没考完,但今年终于要结婚了,算是完成了父母布置的家庭作业。今年整体经济环境不太好,但家人健康就好,感恩。 六 其它 还有很多没有说到的,比如对工作的理解、对自己交流方式的反思、对项目的推动、解决问题的方式和方法、对人生的规划、对接受到信息的过滤、写作能力的问题等,越来越认识到,人与人的差别除了努力和天赋外,还和自己的思想有关,多思考才有大未来。 七 展望 来年只要保持今年的状态就很好,不同的level有不同的认识,我不是一个善于规划的人,踏实走好每一步就好。
AS出来一年多了,最近才从Eclipse转到AS,但我并不觉得使用Eclipse有多落后,它们都只是一个工具而已,哪个顺手就用哪个,用得好都能提高生产力,不会合理利用,再好的工具也是惘然。很多使用Eclipse的Android程序员不知道代码重构的快捷键、如何在运行时调试、一个Workspace一大堆工程……,我想即使转到Android Studio也并不见得比Eclipse顺手。 下面将自己在Eclipse转AS过程中遇到的一些问题以及对各个问题的理解列出来,方便后续查阅。 1、问:Eclipse的工程如何导入到AS? 答:我的处理方式是在AS中新建工程,然后将Eclipse中对应工程的文件拷贝过来;当然也可以通过Eclipse将project导成gradle版本的,然后在AS中导入该工程。 2、问:对于Native层的代码,是如何处理的,在AS上如何编译JNI的代码? 答:AS上同样可以开发JNI,只不过配置脚本的过程比较麻烦,各个gradle版本,配置的方式有些不一样;我的处理方式是AS上只做java开发,JNI还是在Eclipse中开发,方便编译和调试; 3、问:在AS上开发会和Eclipse一样,卡吗? 答:会,卡不卡和你整个工作空间的复杂度有关,如果Eclipse的一个工作空间工程比较少,是不会卡的;AS也一样,如果AS的一个工作空间有太多工程,同样会很卡,特别是编译的时候; 4、问:AS存在启动慢的问题吗,有没有Eclipse那种初始化进度一直在0%的状态? 答:目前为止我还没有遇到过,即使一个工作空间有上十个工程。 5、问:AS编译比Eclipse或者ant编译快吗? 答:不一定,这也跟你项目的复杂度有关,如果你的工程依赖关系简单,用gradle编译会很快,当然用Eclipse和ant编译也一样;如果你的工程依赖关系复杂,用gradle编译比用Eclipse、ant还慢。我的建议是:主工程不要依赖太多的libproject,否则会编译很慢,可以把这些libproject打包成aar,这样同样复杂的项目用gradle编译会比用Eclipse和ant编译快不少; 6、gradle与ant相比,有什么优点? 答:优点比较多,主要的优点是配置简单,特别是在持续集成的时候,如果是gradle,一条命令就行了,如果是ant,还得自己写编译脚本。 7、问:AS中如何配置工程的依赖关系? 答:在Eclipse中,会存在几种依赖: 一种是jar包,直接放在libs文件夹即可(早先的Eclipse版本需要设置buildpath依赖关系才算配置OK); 另外一种是libproject,这需要右键主工程—properties—Android—点击Add添加依赖项,配置完成后依赖关系会更新到工程根目录下的“project.properties”文件。 在AS中会很简单,右键主工程—Open Module Setting — 选中某一个工程,点击右边的Dependencies选项,点击“+”,分别添加Library/File/Module dependency,Library dependency和File dependency主要是添加jar包(File dependency的jar包是放在工程的libs文件夹下),Module dependency是添加libproject,so放在工程的”libs/架构文件夹”下,不需要配置依赖关系。AS的依赖关系配置完成后,可以在工程的”build.gradle”文件中查看。依赖关系配置完成后,记得在build.gralde文件的android标签下增加下面这句话,依赖关系才生效: sourceSets{ main { jniLibs.srcDirs = ['libs'] } } 8、问:Eclipse和AS中主工程对其它工程的依赖有什么异同? 答: 相同点:Eclipse和AS都可以依赖so、jar包和libproject;组织结构也一样,so和jar包放在libs文件夹,libproject是一个独立的工程,需要手动配置依赖关系。 不同点:AS还可以依赖aar,并且AS除了可以依赖本地的库,还可以依赖服务器上的库,但Eclipse只能依赖本地库。 9、问:jar包和aar有啥区别? 答:jar包不能将so和资源文件打包进去,但aar可以,看得到的就是这点区别。 10、问:AS使用过程遇到过哪些问题,是怎么解决的? 答: assets文件的存放目录在”src/main/”目录下,和java、res文件夹平级; aidl文件需要单独在”src/main/”目录下新建一个文件夹,然后创建对应的包名,将aidl文件放在包名对应的包下; 引用libs文件夹中的so,需要在对应module下的build.gradle文件的android标签下加上如下属性: android { sourceSets { main { jniLibs.srcDirs = ['libs'] } } } android studio的编译时屏蔽掉lint检查,可以避免由于编译条件太过严格而编译不过的问题: lintOptions { abortOnError false } 如果遇到多个jar包中的某个文件冲突,可以在对应module下的build.gradle文件的android标签下加上如下属性: packagingOptions { exclude 'META-INF/NOTICE.txt'// 这里是具体的冲突文件全路径 exclude 'META-INF/LICENSE.txt' } 调整logcat文件显示的颜色:File→Setting→Editor→Colors&Fonts→Android Logcat→在界面的右侧调节logcat每个级别日志的颜色; 显示行号:File→Setting→Editor→General→Appearance→勾选“Show line numbers”; Logcat的console中,显示”no debuggable applications”的问题:Tools→Android→Enable ADB Integration; 如果依赖工程和主工程中有同名同类型的资源文件,需要修改依赖工程中的资源名称编译时才不会报错,如果依赖工程中的这个资源文件是整个工程都不需要用到的,可以直接删掉; Android Studio中一个主工程依赖多个library的模式编译时很慢(clean和rebuild时,之前Eclipse中是这种模式),因为这种工程框架是主工程和每个依赖工程中都有一个build.gradle,编译起来会消耗比较长的时间,可以将没有资源文件和so的依赖工程打包成jar包,有资源文件和so的打包成aar文件,然后在主工程中引用,这样编译会很快; Android Studio对九图的要求很严格,如果文件以”.9.png”结尾但是图片不是9图,编译的时候会报错,解决方案是直接在AS中打开这张图片,通过9图编辑工具编辑成9图即可; 修改Module之间的依赖关系有两种方式: (1)直接修改每个module的build.gradle文件中的dependencies; (2)右键project→Open Module Settings→在弹出面板的左侧Modules一栏中选中要修改依赖关系的Module,点击右侧的Depencencies标签修改即可; Android Studio自动导包:File→Settings→Editor→General→Auto Import→Java→切换“Insert imports on paste”为“All”→勾选“Add unambigious imports on the fly”; 代码格式化快捷键:CTRL+ALT+L; 重命名文件夹或者文件的快捷键:ALT+SHIFT+R; 鼠标悬浮在某个方法上时,显示该方法的信息:Preferences→Editor→Show doc on mouse move; 删除一个Module,直接在IDE中选中Module后按Delete是删不掉的,需要先右键project→Open Module Settings→在弹出面板的左侧Modules一栏中选中要删除的Module→点击面板左上角的“-”符号→点击OK后回到IDE,然后选中要删掉的Module,按Delte快捷键删掉即可; Android Studio中执行Lint等工具对代码的检测,Analyze→Inspect Code; 导入aar:将aar拷贝到libs文件夹,在module的build.gradle文件增加下面这段话: repositories { flatDir() { dirs 'libs' } } 然后在build.gradle的dependencies标签中按照如下格式引用aar文件即可: compile(name:'aar包名不带扩展名', ext:'aar') 12、问:AS相比于Eclipse,有哪些新的工具或者更方便的功能? 答: 查看APP的内存占用、内存变化情况的工具; 查看APP运行过程中网络使用情况的工具; 查看CPU、GPU使用情况的工具; 代码清理(Analyze—Code cleanup….)、代码静态检查工具(增强的ling检查工具,Analyze—Inspect code….); 可以直接使用DOS窗口; 给打码加书签的功能(Eclipse也有,只是之前没用过); IDE类9图编辑功能;快捷键……很多很多一些小的功能,用熟了特别方便。 13、问:在使用AS的过程中,有什么忠告? 答:就像在使用Eclipse的时候不要轻易更新ADT一样,在使用AS的过程中不要轻易更新gradle和AS,每个版本会有一些差别,会有很多坑,还是等新版本出来一段时间,比较稳定后再用,毕竟IDE是提高生产力的工具,如果需要花大把时间去学习如何使用和解决使用过程中的问题就太没意思了。 14、问:Eclipse的工程转成AS的版本后,在同一个机器中安装会报”INSTALL_FAILED_VERSION_DOWNGRADE“这个错误,是什么原因导致的? 答:这是因为机器中存在一个版本号比安装apk版本号更高的版本,出现这个问题的原因是因为as除了可以在Manifest.xml文件中设置apk的版本名和版本号,还可以在build.gradle文件中设置apk的版本名和版本号,记得修改build.gralde中的版本名和版本号到最新就可以了。 15、导入Google官方的Sample不成功,如何处理? 答:Android Studio 导入 Google Sample 时,需要联网获取,由于某些原因,连接不了服务器。参考网上的方案,给出一种更为简便、快速的方法:在 host 文件中加入一句: 64.233.162.84 gsamplesindex.appspot.com 如果 ip 失效了,找个有用的替换即可(win7的host文件在:C:\Windows\system32\drivers\etc 目录下)。 16、问:如何在构建时删掉build\output目录下的unsigned-align.apk? 答:在build.gradle文件中加上下面这一段话: // delete unaligned files android.applicationVariants.all { variant -> variant.assemble.doLast { variant.outputs.each { output -> println "aligned " + output.outputFile println "unaligned " + output.packageApplication.outputFile File unaligned = output.packageApplication.outputFile; File aligned = output.outputFile if (!unaligned.getName().equalsIgnoreCase(aligned.getName())){ println "deleting " + unaligned.getName() unaligned.delete() } } }
在Android library中不能使用switch-case语句访问资源ID:在Android library中不能使用switch-case语句访问资源ID的原因分析及解决方案 不能在Activity没有完全显示时显示PopupWindow和Dialog:popupwindow - Problems creating a Popup Window in Android Activity 在多进程之间不要用SharedPreferences共享数据,虽然可以(MODE_MULTI_PROCESS),但极不稳定:android - MODE_MULTI_PROCESS for SharedPreferences isn’t working 有些时候不能使用Application的Context,不然会报错(比如启动Activity,显示Dialog等): 同一个应用的JNI代码,不要轻易换NDK编译的版本,否则会有很多问题(主要是一些方法实现不一样,并且高版本对代码的检测更严格),比如r8没有问题,但到r9就有问题了,这是个大坑; Android的JNI代码中,有返回类型的函数没有返回值编译的时候也不会报错; 当前Activity的onPause方法执行结束后才会执行下一个Activity的onCreate方法,所以在onPause方法中不适合做耗时较长的工作,这会影响到页面之间的跳转效率; 谨慎使用Android的透明主题,透明主题会导致很多问题,比如:如果新的Activity采用了透明主题,那么当前Activity的onStop方法不会被调用;在设置为透明主题的Activity界面按Home键时,可能会导致刷屏不干净的问题;进入主题为透明主题的界面会有明显的延时感; 不要在非UI线程中初始化ViewStub,否则会返回null; 公共接口一定要考虑到代码重入的情况,能设计为单例就尽量用单例; 不要通过Bundle传递大块的数据,否则会报TransactionTooLargeException异常:java - Issue: Passing large data to second Activity 尽量不要通过Application缓存数据,这不稳定:不要在Android的Application对象中缓存数据! 尽量不要使用AnimationDrawable,它在初始化的时候就将所有图片加载到内存中,特别占内存,并且还不能释放,释放之后下次进入再次加载时会报错; 9图不能通过tinypng压缩,不然会有问题; genymotion模拟器快是因为它是基于x86架构的,如果你的应用中用到了so,但没有x86架构的so,只能放弃使用它;Android Studio的模拟器也一样; Eclipse的Android开发环境配置好后不要轻易升级ADT和build tools,不然会浪费你很多时间,还有就是一个workspace中的工程不要太多,不然每次启动都会很慢; Android studio每个版本、gradle每个版本差别都比较大(我是这样认为的),对于jni代码的编译建议在Eclipse中进行,如果在Android studio中开发jni会浪费很多时间,主要是编译脚本的配置比较麻烦; Eclipse中的Lint太不靠谱,特别是主工程中依赖library的时候,很多提示都是有问题的,建议使用Android Studio的工程清理工具,特别推荐; 不同API版本的AsyncTask实现不一样,有的是可以同时执行多个任务,有的API中只能同时执行一个线程,所以在程序中同时执行多个AsyncTask时有可能遇到一个AsyncTask的excute方法后很久都没有执行。调用AsyncTask的excute方法不能立即执行程序的原因分析及改善方案 同一个应用,相同的图片分别放在drawable-xxhdpi、drawable-xhdpi、drawable-hdpi、drawable-mdpi、drawable-ldpi中,在同一设备中占用的内存会大不一样(设备的dpi是固定的,图片放在不同的dpi文件夹下,在设备上显示时需要将图片转换成和当前屏幕一样dpi后在设备中显示,所以即使该图片在不同dpi文件夹下大小一样,但放在内存中的大小却不是一样的,并不一定是长宽4),做应用的内存优化之前可以先看一看你的工程是如何做屏幕适配的,是否有优化的空间。强烈推荐这个屏幕适配视频教程,花两个半小时就能看完:Android-屏幕适配全攻略 谨慎对待数据库升级(比如需要在原数据库中增加字段),避免数据丢失或者操作数据库异常的情况,数据库升级方法可以查阅《第一行代码》P263; 多个程序共用一套代码(一套代码,在桌面上多个图标)时需要处理好不同入口进入时的堆栈问题; 使用Adapter的时候,如果你使用了ViewHolder做缓存,在getView的方法中无论这项的每个视图是否需要设置属性(比如TextView设置的属性可能为null,item的某一个按钮的背景为透明、某一项的颜色为透明等),都需要为每一项的所有视图设置属性(textview的属性为空也需要设置setText(“”),背景透明也需要设置),否则在滑动的过程中会出现内容的显示错乱。 谨慎使用Android的多进程,多进程虽然能够降低主进程的内存压力,但会遇到如下问题: (1)不能实现完全退出所有Activity的功能(如果有同行在应用内采用多进程成功实现过完全退出程序欢迎沟通交流); (2)首次进入新启动进程的页面时会有延时的现象(有可能黑屏、白屏几秒,是白屏还是黑屏和新Activity的主题有关); (3)应用内多进程时,新启动一个进程都会重新跑一次Application的onCreate方法,不上重新创建一个Application,但会重新跑Application的onCreate,这样就不能在Application中缓存数据作为内存共享的途径了; (4)多进程间通过SharedPreferences共享数据时不稳定,具体可以查阅《Android开发艺术探索》。 使用Toast时,建议定义一个全局的Toast对象,这样可以避免连续显示Toast时不能取消上一次Toast消息的情况(如果你有连续弹出Toast的情况,避免使用Toast.makeText); View的面积越大绘制的时间就越长,透明通道对View的绘制速度影响很大; 不要通过Msg传递大的对象,会导致内存问题; 关于AS的使用经验,参见:Android Studio使用过程中需要弄明白的一些问题 Eclipse的工程转成AS的版本后,在同一个机器中安装会报”INSTALL_FAILED_VERSION_DOWNGRADE“这个错误,原因是因为as除了可以在Manifest.xml文件中设置apk的版本名和版本号,还可以在build.gradle文件中设置apk的版本名和版本号,记得修改build.gralde中的版本名和版本号到最新就可以了; 通常情况下,在插入USB之后可能会跳转到一个新的界面,这时候可能你本来是横屏的,突然跳转到这个新界面是竖屏的,虽然你的界面被压在下面,但是还是会被强制横竖屏切换一次,如果这时候你的界面不做处理就会重载,如果你的界面里面有很多fragment,这时候的重载更加复杂,难以处理。所以建议不做横竖屏切换的界面都弄一下横竖屏切换不重载。 如果你在 manifest 中把一个 activity 设置成 android:windowSoftInputMode=”adjustResize”,那么 ScrollView(或者其它可伸缩的 ViewGroups)会缩小,从而为软键盘腾出空间。但是,如果你在 activity 的主题中设置了 android:windowFullscreen=”true”,那么 ScrollView 不会缩小。这是因为该属性强制 ScrollView 全屏显示。然而在主题中设置 android:fitsSystemWindows=”false” 也会导致 adjustResize 不起作用; 做自定义手写功能时,底层上报的点并不会都在MotionEvent中能够及时接收到,比如底层一秒钟200个点,上层收到的可能只有几十个点,为了提高手写的流畅度,在onTouchEvent中,通过MotionEvent中的getHistorySize能够获取到从底层传输到上层过程中所有的点。
由于之前的项目太复杂,主要是考虑到JNI在AS上编译不方便,还要考虑到项目进度,最近才从Eclipse转到AS,主要方案是AS中只引用jar包和so,JNI的编译还是在Eclipse中进行。这过程中遇到过很多问题,记录下来方便后续查阅,本文中遇到的所有问题都是在Windows系统下。 assets文件的存放目录在”src/main/”目录下,和java、res文件夹平级; 引用libs文件夹中的so,需要在对应module下的build.gradle文件的android标签下加上如下属性: android { sourceSets { main { jniLibs.srcDirs = ['libs'] } } } android studio的编译时屏蔽掉lint检查,可以避免由于编译条件太过严格而编译不过的问题: lintOptions { abortOnError false } 如果遇到多个jar包中的某个文件冲突,可以在对应module下的build.gradle文件的android标签下加上如下属性: packagingOptions { exclude 'META-INF/NOTICE.txt'// 这里是具体的冲突文件全路径 exclude 'META-INF/LICENSE.txt' } 调整logcat文件显示的颜色:File→Setting→Editor→Colors&Fonts→Android Logcat→在界面的右侧调节logcat每个级别日志的颜色; 显示行号:File→Setting→Editor→General→Appearance→勾选“Show line numbers”; Logcat的console中,显示”no debuggable applications”的问题:Tools→Android→Enable ADB Integration; 如果依赖工程和主工程中有同名同类型的资源文件,需要修改依赖工程中的资源名称编译时才不会报错,如果依赖工程中的这个资源文件是整个工程都不需要用到的,可以直接删掉; Android Studio中一个主工程依赖多个library的模式编译时很慢(clean和rebuild时,之前Eclipse中是这种模式),因为这种工程框架是主工程和每个依赖工程中都有一个build.gradle,编译起来会消耗比较长的时间,可以将没有资源文件和so的依赖工程打包成jar包,有资源文件和so的打包成aar文件,然后在主工程中引用,这样编译会很快; Android Studio对九图的要求很严格,如果文件以”.9.png”结尾但是图片不是9图,编译的时候会报错,解决方案是直接在AS中打开这张图片,通过9图编辑工具编辑成9图即可; 修改Module之间的依赖关系有两种方式:(1)直接修改每个module的build.gradle文件中的dependencies;(2)右键project→Open Module Settings→在弹出面板的左侧Modules一栏中选中要修改依赖关系的Module,点击右侧的Depencencies标签修改即可; Android Studio自动导包:File→Settings→Editor→General→Auto Import→Java→切换“Insert imports on paste”为“All”→勾选“Add unambigious imports on the fly”; 代码格式化快捷键:CTRL+ALT+L; 重命名文件夹或者文件的快捷键:ALT+SHIFT+R; 鼠标悬浮在某个方法上时,显示该方法的信息:Preferences→Editor→Show doc on mouse move; 删除一个Module,直接在IDE中选中Module后按Delete是删不掉的,需要先右键project→Open Module Settings→在弹出面板的左侧Modules一栏中选中要删除的Module→点击面板左上角的“-”符号→点击OK后回到IDE,然后选中要删掉的Module,按Delte快捷键删掉即可; Android Studio中执行Lint等工具对代码的检测,Analyze→Inspect Code。
主工程、依赖包、jar包、android.jar、Android Support Library的关系 一个Android工程通常包括主工程和依赖包,依赖包又有两种形式: 一种是单独的工程:在主工程中的配置文件指明主工程和依赖包的依赖关系之后,就可以在主工程中正常使用依赖包的类和接口了,这种适合于依赖包中有图片资源、so等不方便打包到jar包中的情况,比如Nine Old Androids、PullToRefresh、FancyCoverFlow等; 另一种是jar包:放在主工程的libs文件夹下,这种通常是依赖包中只有代码和可以打包到jar包中的文件,比如Fastjson.jar、Volley.jar、Gson.jar等。 为了程序能够编译通过和在设备中正常运行,主工程除了依赖第三方的工程和jar包之外,还需要依赖安卓系统本身的代码,也就是我们在sdk的每个版本中看到的android.jar,这里面集成了android的所有API,随着android sdk的升级,高版本的sdk中会增加很多新的API,比如ActionBar、Fragment、RecyclerView等,如果在低版本的sdk中需要使用高版本新增的API怎么办?不可能去更新移动设备中的android.jar吧,因为硬件设备集成的sdk版本是固定的,android.jar也是固定的,设备中的一些参数、硬件选型也是根据当前sdk版本来定的,所以最好的方式是将新增的API以依赖包的形式集成到需要使用高版本API的应用程序中。 谷歌早已经考虑到了这个问题,所以推出了一系列脱离于android.jar的依赖包,比如常见的android-support-v4.jar、appcompat-v7等。这些依赖包可以直接集成到应用程序中,依赖包有的是jar包,有的是独立的工程。命名的如下: jar包: android-support-v[API Level Value].jar,比如android-support-v4.jar、android-support-v13.jar。 依赖工程: [support包功能]_v[API Level Value],比如appcompat_v7、gridlayout_v7。 各个依赖包可以在“/extras/android/support/”文件夹下查看。 各个版本的Android Support Library介绍 V4 Support Library 这个包的名字是:“android-support-v4.jar”,是为Android 1.6(API版本为4)及以上的版本设计的,它包含大部分高版本中有而低版本中没有的API,包括application components、user interface features、accessibility、data handling、network connectivity、and programming utilities,下面是对V4中的一些关键API的介绍: App Components: Fragment:一个专为解决Android碎片化的类,通过它可以让同一个程序适配不同的屏幕。 NotificationCompat:支持更丰富的通知形式; LocalBroadcastManager:适合于应用内的消息传递。 User Interface: ViewPager:一个可以管理子view的viewgroup,用户可以在各个view之间自由切换,这个在很多应用中都有使用到; PagerTitleStrip:一个关于当前页面、上一个页面和下一个页面的一个非交互的指示器。它经常作为ViewPager控件的一个子控件被被添加在XML布局文件中。 PagerTabStrip:一个关于当前页面、上一个页面和下一个页面的一个可交互的指示器。它经常作为ViewPager控件的一个子控件被被添加在XML布局文件中。 DrawerLayout:抽屉 SlidingPaneLayout:用于实现两列面板的切换,在UI最上层的使用提供了一个水平的,多个面板的布局。左边的面板可以看作是一个内容列表或者是浏览,右边的面板的任务是显示详细的内容。 Accessibility: ExploreByTouchHelper:帮助自定义View实现accessibility的帮助类; AccessibilityEventCompat、AccessibilityNodeInfoCompat、AccessibilityNodeProviderCompat、AccessibilityDelegateCompat:Accessibility的适配类 Content: Loader:异步加载数据; FileProvider:应用间的私有文件共享。 关于V4的更多API介绍可以参见:android-support-v4.jar API References Multidex Support Library 该support包用于使用多dex技术编译APP,当一个应用的方法数超过65536个时需要使用multidex配置,关于multidex的更多信息,可以参见如何编译超过65K方法数的应用 V7 SupportLibraries  针对Android 2.1(API Level 7)及以上的版本谷歌提供了一系列的support包,这些support包各自对应着特定的功能,每一个都可以单独地被引用。 V7 appcompat library 这个包的主要作用是为了在低版本实现Android的Holo风格界面而引入的,主要包括ActionBar、AppCompat等类和主题,它是一个依赖工程而不是jar包。 注意:这个包需要依赖android-support-v4.jar,如果你使用的是Eclipse或者Ant编译你的APP,确保你在使用这个依赖包时集成了android-support-v4.jar这个jar包。 v7 cardview library 一个在Android 5.0才被引入的卡片布局support包。 v7 gridlayout library 一个支持网格布局的support包。 v7 mediarouter library 一个用于设备间音频、视频交换显示的support包。 v7 palette library 一个可以实现页面的颜色动态变换的support包,Palette是这个support包的核心类。 v7 recyclerview library 核心类是RecyclerView,用于替换ListView、GridView等需要依赖Adapter的View,具体可以查阅RecyclerView方面的资料。 v7 Preference Support Library 一个用于支持各种控件存储配置数据的support包。 v8 renderscript library 一个用于渲染脚本的support包。 v13 Support Library 这个包的作用主要是为Android3.2(API Level 13)及以上的系统提供更多地Framgnet特性支持,使用它的原因在于,android-support-v4.jar中虽然也对Fragment做了支持,由于要兼容低版本,导致他是自行实现的 Fragment 效果,在高版本的 Fragment 的一些特性丢失了,而对于 v13以上的 sdk 版本,我们可以使用更加有效,特性更多的代码。 v17 Leanback Library 一个主要作用是用于支持电视设备的support包,为电视设备提供了很多组件,比如:BroweFragment、DetailsFragment、PlaybackOverlayFragment、SearchFragment等。 Annotations Support Library 一个支持注解的support包。 Design Support Library 一个用于支持Design Patterns的support包。 Custom Tabs Support Library 一个提供了在应用中添加和管理custom tabs的support包。 Percent Support Library 一个提供了百分比布局的support包,通过这个包可以实现百分比布局。 在主工程中查看support包的源码 对于本来就是工程的support包来说,在主工程中查阅该support包中的代码非常简单,但如果support包是jar包,则需要在主工程中手动配置才能在主工程中查看support包的源码,关于在IDE中如何查看support jar包的源码可以参见:Android 如何在Eclipse中查看Android API源码以及support包源码。 参考资料 Support Library Features Android Support v4、v7、v13的区别和应用场景 Android 如何在Eclipse中查看Android API源码以及support包源码
最近在项目中遇到一个关于调用AsyncTask的excute方法不能立即执行程序的问题,项目的targetSdkVersion是15,最后分析发现是AsyncTask的运行机制导致,特地总结出来以免后面再犯同样的错误。 通过源码分析AsyncTask的工作原理 AsyncTask官方文档解读 AsyncTask介绍 AsyncTask是一个Android SDK中轻量级的异步任务类,它在线程池中执行后台任务,把执行进度和执行结果返回给主线程,并在主线程更新UI,AsyncTask实质上是对Thread和Handler的封装,通过AsyncTask能够更方便地在执行后台任务的过程中和结束后实现更新UI操作。 AsyncTask的用法 AsyncTask是一个抽象类,它需要被实现后才能正常使用,子类必须要复写doInBackground方法,如果需要在执行完后台任务后更新UI,则需要实现onPostExecute方法,下面是一个AsyncTask使用实例: class DownloadFilesTask extends AsyncTask{ @Override protected Void doInBackground(Integer... params) { int count = urls.length; long totalSize = 0; for (int i = 0; i 说明: Params:执行AsyncTask时传递的参数类型; Progress:执行后台任务时更新进度的进度值类型; Result:后台任务执行完成后的返回值类型。 注意:并不是所有的参数都需要指明类型,如果某一个参数你没有用到,改成Void类型即可。 执行流程 在AsyncTask执行的过程中,会经历如下四个步骤: onPreExecute:UI线程中调用,在调用excute方法后会立即被调用,这个方法适用于初始化task,比如显示后台任务进度条; doInBackground:非UI线程中调用,在onPreExecute执行后调用,这个方法适用于执行耗时的操作,excute方法中的Params参数就是通过这个方法传递的,该方法的返回值就是Task执行的结果,在doInBackground方法执行过程中,可以通过publishProgress方法来更新后台任务的执行进度; onProgressUpdate:UI线程中调用,在publishProgress方法后被调用,这个方法用于在后台任务执行过程中显示任务的进度UI,比如它可以用于显示进度条动画或者显示进度文本; onPostExecute:UI线程中调用,后台任务执行完成后被调用,Task返回的结果以参数的形式传递到该方法中。 取消Task AsyncTask可以在任何时候通过cancel(boolean)方法取消,调用这个方法后,isCancelled()方法的返回值会为true,当执行这个cancle方法后,doInBackground方法执行完成后不会调用onPostExecute方法而是执行onCancelled回调。 注意 在使用AsyncTask的过程中必须要遵守如下原则: AsyncTask必须在UI线程中实例化; excute方法必须要在UI线程中调用; 不要人为地调用AsyncTask的回调方法:onPreExecute、onPostExecute、doInBackground和onProgressUpdate; 一个AsyncTask实例只能执行一次,如果调用多次,将会报异常。 源码解读 执行AsyncTask很简单,先实例化,然后调用excute方法,excute方法的代码如下: public final AsyncTask execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); } 通过查看sDefaultExecutor的代码发现,AsyncTask默认自己维护一个静态的线程池,而该线程池只允许同时执行一个线程,也就是说,不管多少个AsyncTask,只要是调用execute()方法,都是共享这个默认进程池的,你的任务必须在之前的任务执行完以后,才能执行。可以理解为,默认情况下,所有的AsyncTask在一个独立于UI线程的线程中执行,任务需要排队,先execute的先执行,后面的只能等。具体的源码如下: private static class SerialExecutor implements Executor { final ArrayDeque mTasks = new ArrayDeque(); Runnable mActive; public synchronized void execute(final Runnable r) { mTasks.offer(new Runnable() { public void run() { try { r.run(); } finally { // 上一个任务执行完成后才会执行下一个 scheduleNext(); } } }); if (mActive == null) { scheduleNext(); } } protected synchronized void scheduleNext() { if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } } } 除了excute方法外,还可以调用executeOnExecutor(其实excute方法也是调用的executeOnExecutor方法,只是线程池是在AsyncTask中默认定义好的),如果使用executeOnExecutor方法,可以在外部自定义线程池,解决不能并发执行异步任务的问题。 AsyncTask在不同SDK版本中的区别 通过查阅官方文档发现,AsyncTask首次引入时,异步任务是在一个独立的线程中顺序地执行,也就是说一次只能执行一个任务,不能并行地执行,从1.6开始,AsyncTask中引入了线程池,支持同时执行5个异步任务,也就是说同时只能有5个线程运行,超过的线程只能等待,等待前面的线程某个执行完了才被调度和运行。换句话说,如果一个进程中的AsyncTask实例个数超过5个,那么假如前5个都运行很长时间的话,那么第6个只能等待机会了。这是AsyncTask的一个限制,而且对于2.3以前的版本无法解决。如果你的应用需要大量的后台线程去执行任务,那么你只能放弃使用AsyncTask,自己创建线程池来管理Thread,或者干脆不用线程池直接使用Thread也无妨。不得不说,虽然AsyncTask较Thread使用起来比较方便,但是它最多只能同时运行5个线程,这也大大局限了它的实力,你必须要小心的设计你的应用,错开使用AsyncTask的时间,尽力做到分时,或者保证数量不会大于5个,否则就可能遇到上面提到的问题。可能是Google意识到了AsyncTask的局限性了,从Android 3.0开始对AsyncTask的API做出了一些调整:每次只启动一个线程执行一个任务,完成之后再执行第二个任务,也就是相当于只有一个后台线程在执行所提交的任务,可以通过代码在不同sdk版本中执行的具体情况来验证官方文档的说法: 测试代码: private void taskTest(){ for(int index = 0; index { @Override protected Void doInBackground(Integer... params) { try { Thread.sleep(1000); SimpleDateFormat df = new SimpleDateFormat("yyyy-mm-dd hh:mm:ss"); System.out.println("targetSdkVersion == " + getTargetSdkVersion() + " LoadTask#" + params[0] + " time == " + df.format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(Void result) { super.onPostExecute(result); } } private String getTargetSdkVersion(){ int targetSdkVersion = 0; try { PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0); targetSdkVersion = packageInfo.applicationInfo.targetSdkVersion; }catch (PackageManager.NameNotFoundException e) { Log.e(TAG, e.getMessage()); } return targetSdkVersion+""; } 测试结果: 2.2中: 4.0.3中: 测试程序是同时执行10个AsyncTask任务,从不同sdk版本的打印结果来看,2.2中是一次执行5个任务,在4.0.3中一次执行一个任务,等上一个任务执行完成后才执行下一个任务。 excute方法不能立即执行的改善方案 习惯了参照官方文档和线程的代码,没有认真研读源码导致踩了AsyncTask的这个坑,如果知道使用executeOnExecutor方法,自己定义线程池就不会出现Task任务没有立即执行的情况,这再次印证了阅读android源码的重要性,最后具体的解决方式如下: LinkedBlockingQueue blockingQueue = new LinkedBlockingQueue(); ExecutorService exec = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, blockingQueue); new LoadTask().executeOnExecutor(1); 参考资料 Android实战技巧:深入解析AsyncTask Android开发:AsyncTask在调用execute()后没有马上执行的问题 AsyncTask源码
应用除了有内存占用、内存泄露、内存抖动等看不见的性能问题外,还有很多看得见的性能问题,比如进入界面慢、点击反应慢、页面卡顿等等,这些看得见的体验问题会严重影响用户使用APP心情,但用户的情绪又无法通过异常采集、数据分析来发现,尽早优化APP的性能体验问题非常重要,会在一定程度上提升用户的留存率。 本文结合最近一段时间对项目中APP各界面进入速度的优化,总结一下进入界面慢的优化方案。 先从Activity的生命周期说起 从一个界面FirstActivity跳转到另外一个界面SecondActivity,两个Activity的生命周期流程是这样的: 应用必须在走完FirstActivity的onPause方法后才会跑SecondActivity的onCreate方法,FirstActivity的onStop和onDestory方法不会影响到进入SecondActivity的速度。如果我们要优化从FirstActivity跳转到SecondActivity的速度,需要从FristActivity的onPause和SecondActivity的onCreate、onStart和onResume方法入手。onStart方法通常干的事情比较少,页面之间跳转慢主要是因为在FirstActivity的onPause和SecondActivity的onCreate、onResume方法耗时导致,这个过程需要执行的操作主要有: 保存FirstActivity界面中的一些状态; 加载SecondActivity的布局; 初始化SecondActivity。 针对上面的分析我们可以从如下四个方面入手: 耗时任务异步处理; 布局文件优化; 不可见视图需要时加载; 应用内慎用多进程。 优化实践 耗时任务异步处理 除了Android明令禁止在UI线程中执行网络操作外,还有一些耗时的操作也不能在UI线程中执行,比如IO操作、耗时较长的逻辑操作(比如算法),在Android中可以通过如下几种方式来实现异步任务: AsyncTask Thread Timer,TimerTask Handler 如果是在执行异步任务后需要更新界面,优先考虑使用AsyncTask和Handler,它们提供了刷新UI的方案;如果是定时任务可以考虑使用Handler和Timer,TimerTask;如果是使用Thread和Timer,TimerTask,更新UI时可以通过执行当前Activity的runOnUiThread方法实现更新UI操作。 布局文件优化 ViewStub 在优化过程中发现有的界面光是加载布局就需要500ms左右,再加上界面的初始化和上一个界面的状态保存操作,页面跳转时会有严重的迟滞感,对于布局文件的优化网上有很多有价值的文章,最重要的两条是: 布局文件不要嵌套太深; 对于不需要进入界面就需要显示的视图,强烈建议使用ViewStub。 布局文件嵌套太深标示着需要更多次的布局、测量和绘制,会导致耗时更多,这个可以使用android自带的“hierarchyviewer”查看,边优化边看效果;但有时候即使布局足够扁平,加载布局文件时还是会比较耗时,因为布局文件中的视图太多了,此时对于不需要进入界面就需要显示的视图,可以使用ViewStub来延迟加载,比如加载的进度条、特定状态下出现的倒计时和动画等,ViewStub的使用方式如下: /** * 在需要使用下载进度条的地方调用该方法加载下载进度条的布局 */ private void initDownloadProgress() { if(null == mDownloadViewStub){ mDownloadViewStub = (ViewStub)findViewById(R.id.downProgressViewStubId); View view = mDownloadViewStub.inflate(); mDownloadProgressLayout = (RelativeLayout) view.findViewById(R.id.progressBackLayoutId); mDownLoadProgressBar = (ArrowProgressBar) view.findViewById(R.id.arrowProgressBarId); mDownloadProgressLayout.setVisibility(View.GONE); mDownloadProgressLayout.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return true; } }); } } 不可见视图需要时加载 除了布局文件的优化外,代码中不需要立即显示的视图和动画都做成延迟加载,比如AnimationDrawable、TypedArray数组、Typeface、addView等,值得一提的是,初始化AnimationDrawable、TypedArray数组和Typeface会很耗时,并且AnimationDrawable特别耗内存,如果不是进入界面就需要使用,强烈建议在需要使用的地方再初始化,分开初始化可以大大减小页面初始化的耗时。 应用内慎用多进程 从FirstActivity跳转到SecondActivity,如果这两个界面不属于同一个进程,首次跳转的时候会创建一个新的进程,创建进程是比较耗时的,比跳转到同一进程内的新页面耗时更多,如果不是必须要在应用内使用多进程,强烈建议不要在应用内使用多进程。 总结 性能优化是一个持续的过程,界面跳转效率只是一个性能指标,更快地跳转对于用户来说有着更好地体验,优化界面跳转速度的步骤如下: 打印执行每一段代码执行需要的时间; 找到耗时较多的代码段,可能是setContentView,也有可能是在UI线程中的其它耗时操作; 根据耗时的代码段找解决办法; 优化后运行看效果。 特别说明: 初始化AnimationDrawable、TypedArray数组和Typeface会很耗时,并且AnimationDrawable特别耗内存,一定要注意他们的初始化时机; 不要迷信网上的一些优化技巧,一定要结合亲身实践,看数据说话; 只要认真分析,很多地方都会有优化空间,将优化的经验总结出来,并运用到后续的开发中; 优化APP的性能问题在一定程度上能够提高用户的留存率,是一件很有价值的事情。
通过软引用解决Handler内存泄露的问题 下面对软引用使用的方式适用于任何内部类,严格来说是通过软引用解决静态内部类无法调用当前类中的对象和方法的问题,真正解决内存泄露是需要将内部类改成静态内部类。 当在一个类中按照如下方式创建一个Handler内部类时,使用Lint工具检测时会给出“This Handler class should be static or leaks might occur”的警告,原因是Handler内部类可能持有当前类的引用,导致即使该类不再被使用时系统仍无法回收这给类持有的对象,Android中内存泄露很多都是由于持有类中的对象时间太长导致,如果很多地方出现类似的代码会导致应用占用的内存不断上涨,最终导致程序崩溃。 private TextView mUserNameTxt = null; class LoadDataHandler extends Handler{ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch(msg.what){ case 0:{ mUserNameTxt.setText(msg.obj.toString()); } break; default:{ } break; } } } 按照Lint的建议将Handler内部类改成static静态内部类后,由于不可能将当前类的所有全局对象都声明为static对象,所以会报“Cannot make a static reference to the non-static field”的错误,这时候可以使用软引用来解决这个问题,具体代码如下: private TextView mUserNameTxt = null; static class LoadDataHandler extends Handler{ private SoftReference activitySRF = null; public LoadDataHandler(MainActivity activity){ activitySRF = new SoftReference(activity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); // 因为Handler是异步的,存在退出当前类之后才接收到handler消息的情况,并且软引用持有的对象会在堆内存不足时存在被回收的可能,所以这里需要判空处理 if(null == activitySRF || null == activitySRF.get()){ return; } switch(msg.what){ case 0:{ activitySRF.get().mUserNameTxt.setText(msg.obj.toString()); } break; default:{ } break; } } } 通过软引用解决销毁某个Activity后,仍然在Activity显示Popupwindow或者Dialog时提示”Unable to add Window-token is null”的问题 在实际项目中踩过这个坑,特地分享出来,这个问题是由于在Activity中显示PopupWindow或者Dialog时,PopupWindow、Dialog还没显示出来就销毁了当前Activity导致(主要是快速切换)。因为PopupWindow和Dialog都是依附于当前Activity的某个View上的,当前Activity被销毁后依附的view为空,此时显示PopupWindow或者Dialog时会提示这个异常。 这个问题也可以通过软引用来解决(通过判断软引用中的activity对象是否为空或者是否已经finish即可判断是否可以显示PopupWindow或者Dialog),具体代码如下: private SoftReference activitySRF = null; public void initDialog(Activity activity){ activitySRF = new SoftReference(activity); if(null != activitySRF&&null!=activitySRF.get()&&!activitySRF.get().isFinishing()){ getWindow().requestFeature(Window.FEATURE_NO_TITLE); show(); setContentView(R.layout.hanzi_medal_dialoglayout); setCanceledOnTouchOutside(true); setCancelable(false); } } public void show(Activity activity, String medalName){ initDialog(activity); if(null != activitySRF&&null!=activitySRF.get()&&!activitySRF.get().isFinishing()){ TextView medalTxt = (TextView) findViewById(R.id.medalTxtId); if (!TextUtils.isEmpty(medalName)) { medalTxt.setText("恭喜您获得“" + medalName + "”勋章"); } new Handler(activitySRF.get().getMainLooper()).postDelayed(new Runnable() { @Override public void run() { if(null != activitySRF&&null!=activitySRF.get()&&!activitySRF.get().isFinishing()){ dismiss(); } } }, 3000); } }
### 通过SharedPreferences实现进程间数据共享 之前为了解决应用的内存压力,在同一个应用中使用了多进程,但在程序自测的过程中发现不同进程之间的SharedPreferences数据不能共享,但应用内很多数据都是通过SharedPreferences来保存的,如果改成其它多进程通信的方式改动比较大。通过查看源码发现,在API Level>=11即Android 3.0可以通过Context.MODE_MULTI_PROCESS属性来实现SharedPreferences多进程共享,具体使用方式如下: public class PreferencesUtils { public static String PREFERENCE_NAME = "SharedPreferencesDemo"; private PreferencesUtils(){ } public static boolean putString(Context context, String key, String value) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_MULTI_PROCESS); SharedPreferences.Editor editor = settings.edit(); editor.putString(key, value); return editor.commit(); } public static String getString(Context context, String key, String defaultValue) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_MULTI_PROCESS); return settings.getString(key, defaultValue); } } SharedPreferences时间进程间数据共享会导致的问题 本来以为通过MODE_MULTI_PROCESS属性使用SharedPreferences就可以解决不同进程之间不能共享数据的问题了,但SQA总是反馈一些随机但出现频率比较大的bug,比如在使用过程中没有清除程序数据的前提下,会出现欢迎界面和操作指引,这是通过保存在SharedPreferences的标志来判断用户是否是第一次启动程序的,分析发现保存在SharedPreferences中的数据丢失了,但代码中并没有去清除这些数据,所以推测可能是不同进程同一时间对SharedPreferences操作导致的,经验证确实如此,去掉多进程就不会再出现这个问题了。 解决方案 由于进程间是不能内存共享的,每个进程操作的SharedPreferences都是一个单独的实例,上述的问题并不能通过锁来解决,这导致了多进程间通过SharedPreferences来共享数据是不安全的,这个问题只能通过多进程间其它的通信方式或者是在确保不会同时操作SharedPreferences数据的前提下使用SharedPreferences来解决。 参考资料 Use SharedPreferences on multi-process mode
测试应用的启动时间 adb shell am start -W packagename/activity,eg:adb shell am start -W com.tencent.mm/.ui.LauncherUI,显示的结果中,thisTime和totalTime的含义分别为: thisTime: just current activity launched time totalTime:the activity you started may be on the bottom of activity stack. So it refers to the total time from activity searching to current activity launched. inal long thisTime = curTime - displayStartTime; final long totalTime = stack.mLaunchStartTime != 0? (curTime - stack.mLaunchStartTime) : thisTime; 实时显示程序的内存消耗 讯飞Android应用性能测试工具:iTest Android Studio-Android Monitor-Memory/CPU GPU通过观测程序运行过程中的内存状态可以粗略地检测到哪些界面存在内存泄漏、哪些地方存在内存抖动(内存抖动时可能触发GC,导致程序出现卡顿的现象)、优化效果等。 FPS查看工具 FpsService,一个实时查看帧率的工具,需要集成到代码中才能使用。 内存泄漏查询工具 leakcanary,这个需要集成到代码中才能正常使用,Github上也有Eclipse的版本。当在操作程序的过程中有内存泄漏时会弹出内存泄漏详细的通知信息,在使用这个工具的时候程序会存在卡顿的现象,因为这个工具就是通过触发系统GC来检测哪些对象没有释放确认是否有内存泄漏的,java并没有严格意义的内存泄漏,只是某些对象持有的时间太长导致了系统的内存不能够立即释放,导致运存不足。关于Leakcanry的参考资料可以看看:LeakCanary 中文使用说明、LeakCanary: 让内存泄露无所遁形 静态代码质量检测工具 Android Studio—>Analyze—>Inspect Code通过静态代码质量检测工具可以删掉工程中无用的资源文件、发现潜在的内存泄漏问题、明显的代码问题、简化代码等等。 检测应用耗时工具 StrictMode 性能测试移动端工具 讯飞Android应用性能测试工具:iTest 腾讯开发的GT Android 5.0原生系统设置中的开发者模式,里面内置了一系列的性能测试工具,可以在程序运行的过程中测试各界面显示的效率、布局的性能问题、内存问题、ANR等问题。 还没有使用过的性能测试工具 APT Emmagee 性能优化的参考资料 一个只关注安卓性能优化以及最佳实践的Blog Android Performance Patterns中文版 Android Performance Patterns Best Practices for Performance 行者无疆
在做手写的过程中,使用双缓冲可以提高手写的效率,具体做法是将笔迹画在Bitmap上,在onDraw方法中显示Bitmap,而不是直接在onDraw方法中用canvas画。在采用这种机制做手写功能时,可以通过如下方式清除Bitmap上的笔迹内容: canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
之前写过一篇博客汉字转拼音开源工具包Jpinyin介绍,介绍过JPinyin的使用,因为它实在是太方便了,在项目一直用它,但是最近在做项目的时候,发现使用了JPinyin的工程,在IDE中编译的APK能正常使用,但是APK被加密后再安装,使用JPinyin时会报汉字转拼音时读取数据失败,下面记录一下问题的解决过程。 加密后导致使用Jpinyin报异常的原因肯定是加密导致了Jpinyin中的某些内容变化导致(解密后数据不再是未加密前的数据了),为了跟踪这个问题,下载JPinyin的源码发现汉字转拼音是使用了三个“数据库”文件,分别是: chinese.db mutil_pinyin.db pinyin.db 起初我以为是数据被加密了,加密APK的时候相当于这些数据被再次加密导致了JPinyin不能正常使用,但是JPinyin中的代码是这样读取这三个文件的,读取文件的类为:PinyinResource.java package com.github.stuxuhai.jpinyin; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * 资源文件加载类 * * @author stuxuhai (dczxxuhai@gmail.com) * @version 1.0 */ public class PinyinResource { private static Properties getResource(String resourceName) { InputStream is = PinyinResource.class.getResourceAsStream(resourceName); Properties props = new Properties(); try { props.load(is); } catch (IOException e) { throw new RuntimeException(e); } finally { try { is.close(); } catch (IOException e) { throw new RuntimeException(e); } } return props; } protected static Properties getPinyinTable() { String resourceName = "/data/pinyin.db"; return getResource(resourceName); } protected static Properties getMutilPintinTable() { String resourceName = "/data/mutil_pinyin.db"; return getResource(resourceName); } protected static Properties getChineseTable() { String resourceName = "/data/chinese.db"; return getResource(resourceName); } } 文件读取过程中根本没有数据库的解密过程,代码中将db文件当做“.properties”文件处理了,遂在网上查询”.properties”为何物:JAVA操作properties文件,直接把这三个db文件的后缀改成”.properties”,然后打开的内容是这样的: 于是可以确定这三个文件的原始格式为”.properties”,只是作者将其改为”.db”文件了,于是尝试通过将db格式改为.properties,来解决加密APK导致JPinyin不能正常使用的问题,修改后的代码是这样的(这个工程是在Android项目中被使用的,所以我将这三个.properties文件放在了assets文件夹下): /** * 资源文件加载类 * * @author stuxuhai (dczxxuhai@gmail.com) * @version 1.0 */ public class PinyinResource { private static Properties getResource(Context context, String resourceName) { InputStream is = null; Properties props = null; try { is = context.getAssets().open(resourceName); props = new Properties(); props.load(is); } catch (IOException e) { throw new RuntimeException(e); } finally { try { if(null != is){ is.close(); } } catch (IOException e) { throw new RuntimeException(e); } } return props; } protected static Properties getPinyinTable(Context context) { String resourceName = "pinyin.properties"; return getResource(context, resourceName); } protected static Properties getMutilPintinTable(Context context) { String resourceName = "mutil_pinyin.properties"; return getResource(context, resourceName); } protected static Properties getChineseTable(Context context) { String resourceName = "chinese.properties"; return getResource(context, resourceName); } } JPinyin的其它几个类也需要修改,加上context参数,修改后重新打包JPinyin.jar,在IDE中运行没问题,加密后使用也没有问题。将properties文件改成db文件导致加密后不能正常使用的问题我还没有跟踪到具体原因,但解决这个问题的过程和方法值得记录一下,遇到这种问题最好的办法是查看源码,大胆猜测并尝试,比在不看源码的情况下去猜测要靠谱得多。
您可以订阅此RSS以获取更多信息