最后
针对Android程序员,我这边给大家整理了一些资料,包括不限于高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!
往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、混合式开发(ReactNative+Weex)全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
python systrace.py -t 10 -o /Users/xxx/trace.html -a com.xx.xxx
其中:-t
10是指跟踪10秒,-o
表示把文件输出到指定目录下,-a
是指定应用包名。
输入完这行命令后,可以看到开始跟踪的提示。看到 Starting tracing
后,手动打开我们的应用。
等到运行结束后打开输出的trace.html
除了以上外,我们也可以通过TraceCompact.beginSection
来指定关注的时间段
更多关于Systrace
使用的实例,读者可以参阅:
systrace使用
知乎 Android 客户端启动优化 - Retrofit 代理-Systrace
小结
1.TraceView
可以用来定位具体耗时的方法
2.TraceView
运行时开销严重,导致整体性能变慢,可能会带偏优化方向
3.Systrace
开销小,可以直观反映Cpu
使用率,便于查找运行环境等外因(锁,GC
)等引起的问题
4.TraceView
与Systrace
都可以埋点,指定关心的区域
常规优化手段
1.Theme切换
Theme
设置可以说是启动优化的一个必备手段了
启动Activity
的windowBackground
主题属性预先设置一个启动图片(layer-list
实现),在启动后,在Activity
的onCreate()
方法中的super.onCreate()
前再setTheme(R.style.AppTheme)
。
优点
1.使用简单。
2.避免了启动白屏和点击启动图标不响应的情况。
缺点
治标不治本,表面上产生一种快的感觉。
2.异步方案
我们通常会在Application
的onCreate
中初始化很多任务,比如第三方库初始化,而且是串行的
这些初始化任务的耗时通常还不小,所以一个优化思路就是并行的初始化
这样就将初始化耗时从加法变成了求最大值
核心思路:子线程分担主线程任务,并行减少时间
常规异步方案的问题
1.代码不够优雅
假如我们有 100 个初始化任务,那我们就需要提交 100 次任务。
2.无法限制在 onCreate
中完成
有的第三方库的初始化任务需要在 Application
的 onCreate
方法中执行完成,虽然可以用 CountDownLatch
实现等待,但是还是有点繁琐。
3.无法实现存在依赖关系
有的初始化任务之间存在依赖关系,比如极光推送需要设备ID
,而 initDeviceId()
这个方法也是一个初始化任务。
异步启动器方案
上面介绍了常规异步方案的几个问题,我们可以通过启动器来解决
启动器的核心思想是充分利用多核 CPU
,自动梳理任务顺序。
1.第一步是我们要对代码进行任务化,任务化是一个简称,比如把启动逻辑抽象成一个任务。
2.第二步是根据所有任务的依赖关系排序生成一个有向无环图,这个图是自动生成的,也就是对所有任务进行排序。
比如我们有个任务 A
和任务 B
,任务 B
执行前需要任务 A
执行完,这样才能拿到特定的数据,比如上面提到的 initDeviceId
。
3.第三步是多线程根据排序后的优先级依次执行,比如我们现在有三个任务 A
、B
、C
。 假如任务 B
依赖于任务 A
,这时候生成的有向无环图就是 ACB
,A
和 C
可以提前执行,B
一定要排在 A
之后执行。
启动器的大致流程如上所示,我们下面介绍几种开源的启动器方案,供读者参考
JetPack App Startup
1.App Startup
这个库提供了一个组件,可以在应用程序启动的时候初始化。
2.开发人员可以使用这个组件精简启动序列和显式地设置初始化的顺序。
3.我们不需要为每个组件定义单独的 ContentProvider
,App Startup
允许您定义的所有组件化共享一个内容提供者。
这样可以极大地减少高应用程序的启动时间,但是App Startup
只是支持将多个ContentProvider
合并到一个ContentProvider
中,并指定一定依赖顺序
它的推出的目的,是管理第三方库使用ContentProvider
过多,导致启动速度变慢的问题
不支持异步与异步任务管理,所以并不符合我们的要求
阿里-alpha
Alpha
是一个基于PERT
图构建的Android
异步启动框架,它简单,高效,功能完善。 在应用启动的时候,我们通常会有很多工作需要做,为了提高启动速度,我们会尽可能让这些工作并发进行。但这些工作之间可能存在前后依赖的关系,所以我们又需要想办法保证他们执行顺序的正确性。Alpha
就是为此而设计的,使用者只需定义好自己的task
,并描述它依赖的task
,将它添加到Project
中。框架会自动并发有序地执行这些task
,并将执行的结果抛出来。 由于Android
应用支持多进程,所以Alpha
支持为不同进程配置不同的启动模式。
alpha
已经基本满足我们的使用,不过它不支持任务是否需要等待,同时它的代码比较旧,感觉已经很久不维护了,所以最后决定使用AnchorTask
框架
AnchorTask
AnchorTask与Alpha类似
1.支持多任务并发执行
2.支持任务间依赖与拓扑排序
3.支持任务监听与耗时统计
4.支持指定任务优先级
5.支持指定是否在主线程运行与是否等待
最主要的一点在于AnchorTask
文档比较强大,从数据结构到拓扑排序,到设计到详细的说清楚了,有一系列文章,这也是我最后决定使用它的原因
AnchorTask by 程序员徐公
简单使用如下,可以通过链式调用灵活的配置任务与依赖:
val project = AnchorProject.Builder().setContext(context).setLogLevel(LogUtils.LogLevel.DEBUG)
.setAnchorTaskCreator(ApplicationAnchorTaskCreator())
.addTask(TASK_NAME_ZERO)
.addTask(TASK_NAME_ONE)
.addTask(TASK_NAME_TWO)
.addTask(TASK_NAME_THREE).afterTask(
TASK_NAME_ZERO,
TASK_NAME_ONE
)
.addTask(TASK_NAME_FOUR).afterTask(
TASK_NAME_ONE,
TASK_NAME_TWO
)
.addTask(TASK_NAME_FIVE).afterTask(
TASK_NAME_THREE,
TASK_NAME_FOUR
)
.setThreadPoolExecutor(TaskExecutorManager.instance.cpuThreadPoolExecutor)
.build()
project.start().await()
延迟初始化方案
常规方案
有些任务我们需要延迟加载,常规方法是通过Handler.postDelayed
方法发送一个延迟消息,比如延迟到 100 毫秒后执行。
常规方案的问题
这种方法有以下几个问题
1.时机不便控制,无法确定一个合适的延迟时间
2.代码不够优雅,维护成本高,如果有多个任务,需要添加多次
3.可能造成主线程卡顿,假如把任务延迟 200 毫秒后执行,而 200 后用户还在滑动列表,那还是会发生卡顿。
更优方案
核心思想:对延迟任务进行分批初始化 利用IdleHandler
在当前消息队列空闲时执行的特性,实现一个延迟启动器
IdleHandler
在返回true
时会继续监听,返回false
结束监听
因此在任务全部完成后返回false
即可,实现如下:
public class DelayInitDispatcher {
private Queue mDelayTasks = new LinkedList<>();
private MessageQueue.IdleHandler mIdleHandler = new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
if(mDelayTasks.size()>0){
Task task = mDelayTasks.poll();
new DispatchRunnable(task).run();
}
return !mDelayTasks.isEmpty();
}
};
public DelayInitDispatcher addTask(Task task){
mDelayTasks.add(task);
return this;
}
public void start(){
Looper.myQueue().addIdleHandler(mIdleHandler);
}
}
//调用
DelayInitDispatcher delayInitDispatcher = new DelayInitDispatcher();
delayInitDispatcher.addTask(new DelayInitTaskA())
.addTask(new DelayInitTaskB())
.start();
极致懒加载与提前加载
首页极致懒加载
我们的首页通常有多个tab
,而当我们启动时,只需要初始化一个tab
即可
我们通常会利用ViewPager
来实现简单的懒加载,比如只有当Fragment
可见时才去进行网络请示
这样有一定效果,但是View
的inflate
,measure
,layout
也需要一定时间
更加极致的懒加载方案如下:
1.首屏加载时,只往ViewPager
中塞入默认要展示的tab
,剩余的tab
用空的占位Fragment
代替
2.占位Fragment
中只有一个空白的FrameLayout
3.当占位Fragment
可见时,将真正要展示的Fragment
添加到空白FrameLayout
,进行真正的初始化
通过这种方案,可以做到在启动时,只inflate
,measure
,layout
首页Fragment
的View
,其他Tab
只有可见时才会填充
如果你的Layout
比较复杂的话,通过这种方式可以较大的改善启动性能
布局预加载
官方提供了一个类,可以来进行异步的inflate
,但是有两个缺点:
1.每次都要现场new
一个出来
2.异步加载的view
只能通过callback
回调才能获得,使用不方便(死穴)
3.如果在Activity
中进行初始化,通过callback
回调时,并没有减少加载时间,仍然需要等待
由于以上问题,一个思考方向就是,能不能提前在子线程inflate
布局,然后在Activity
中通过id
取出来
核心思想如下
1.初始化时在子线程中inflate
布局,存储在缓存中
2.Activity
初始化时,先从缓存结果里面拿 View
,拿到了view
直接返回
3.没拿到view
,但是子线程在inflate
中,等待返回
4.如果还没开始inflate
,由UI
线程进行inflate
这种方案的优点:
可以大大减少 View
创建的时间,使用这种方案之后,获取 View
的时候基本在 10ms 之内的。
缺点
1.由于 View
是提前创建的,并且会存在在一个 map
,需要根据自己的业务场景将 View
从 map
中移除,不然会发生内存泄露
2.View
如果缓存起来,记得在合适的时候重置 view
的状态,不然有时候会发生奇奇怪怪的现象。
总得来说,优缺点都很明显,读者可根据实际情况(主要是项目中inflate
的时间长不长,改用提前加载后收益明不明显?),根据实际情况决定是否使用
具体实现可参阅:神奇的的预加载(预加载View,而不是data)
总结
本文主要总结了启动优化的方向,与精准测量启动时间的方式
重点讲解了几种可以实用的启动优化方案:
1.异步启动器加快初始化速度
2.延迟加载器减少卡顿,代码更加优雅
最后
愿你有一天,真爱自己,善待自己。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
你有一天,真爱自己,善待自己。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!