应用程序开发:如何优化响应时间?
响应时间的长短会影响用户对某个功能、某个应用程序、甚至某个系统的使用。毕竟,只要有选择,没有人会愿意使用落后的应用程序或速度慢的手机。
作为一名开发者,即使我们可能只关注堆的业务,没有时间和机会去优化我们程序的响应时间,但这些内容对于我们个人的技术成长来说是必不可少的。不说大事,这部分也是采访中经常探讨的内容。如果你知道这一点,你就不会受苦。
那么让我们长话短说,快速了解如何优化应用程序的响应时间。
1。基本原理
在算法中,我们经常从时间复杂度
和空间复杂度
两个维度来衡量算法的好坏。
很多时候我们无法达到时间复杂度
和空间复杂度
两者都是最好的,我们只能在“时间”和“空间”之间妥协作为最优方案。同样,如果我们追求终极的“时间”,我们可能就不得不牺牲一些“空间”。这就是用“空间”代替“时间”的解决方案。
这就是响应时间优化的核心:空间->时间(用空间代替时间)
那我们该怎么办呢?以下是我总结的四个基本原则:
- 1。缓存优先级:读缓存和读缓存。
- 2。尽量减少新的创建:如果可以重复使用,就不要创建新的。
- 3。尽量减少任务:尽量不要做尽可能多的事情。
- 4。具体问题具体分析:具体问题本身分析。如果可以的话,提前做需要做的事情,不需要做的事情推迟。
2。优化措施
也许我上面提到的本质和基本原理对于大多数人来说都很容易理解,但知道它们并不意味着你知道如何优化。这就像高中学习数学一样。即使他们告诉你一堆公式,你也不一定能够解决相关的应用问题。这时候,“合适的问题”就非常重要了。
同样,即使你了解应用响应时间优化的一些核心和原理,当你真正面对某些优化问题时,你也可能会感到尴尬。
接下来我从 业务通常由任务流组成。业务/任务的合理碎片化可以有效提高响应速度。 对作业和任务进行分类的正确方法是首先拆分作业,将其划分为子任务,然后根据需要整合子任务。 (1)划分不合理的业务流程。 (2) 整合工作流程。 1.串行 -> 并行 使用范围: 2。同步 -> 异步的应用领域: 与线程优先级类似,当系统资源不足时,优先级较高的线程会先执行。 首先我们需要对应用程序中所有需要优化的作业及其子任务进行优先级排序,然后按优先级顺序调度和执行它们。 那么如何保证任务按优先级执行呢? 1。对于线程,我们可以直接设置它的优先级值。 (但一般情况下,我们不能直接使用线程,所以可以忽略) 延迟执行是暂时停止执行一些不必要的、不重要的或耗时的任务,等待资源充足或稍后需要使用。 常见的延迟执行包括以下几种: 如果你想在空闲的时候做更多的任务,当然可以这样写: 对于一些很少使用或不重要的数据、图片、控件等资源,可以按需加载。 1。 kotlin中延迟数据加载 2。图片源延迟加载 3。缓慢加载控件 分段加载通常用于大数据加载,包括加载大图片、长视频等多媒体资源。无论在哪里使用,都可以加载它。您不必等待所有内容加载完毕后用户就可以使用它。 1。大图片的分段加载:对于大图片,我们可以按照一定的大小,一张一张的分割成小图块,然后设置预加载预览的范围,用户预览的范围就是我们的。只需上传那里即可。 (与地图加载类似) 2.长视频分段加载:长视频可分时间片,可设置加载缓存。这样,用户在浏览长视频时,可以快速打开并下载。 3。大文件或长网页视图的分段加载:一些阅读器应用程序经常遇到大文件和长网页视图的加载。这里也可以用同样的方式来划分。 分段加载常与预加载结合使用。对于一些需要较长时间加载的内容,我们可能会延长加载时间,以减少用户感知的加载时间。 预加载的本质就是提前加载,所以这个提前加载的时机非常关键和重要。因为如果预加载时间太晚,几乎没有效果;但如果预加载时间太早,会抢占其他模块的资源,造成资源紧张。 那么什么时候可以开始预载,预载时间是多少呢?下面我举一些简单的例子。 1。当用户行动时。如果用户点击第 2 章,我们就会开始预加载下一章和上一章;当用户滚动到第 3 页时,我们预加载第 4 页;当用户向下滚动到第 5 页时,我们预加载第 4 页。 2。当应用程序空闲时。比如前面提到的 3。漫长的等待。对于一些常见的耗时操作,我们最初可以并行执行一些预取操作,以提高时间利用率。例如,创建活动非常耗时。我们可以在startActivity之前开始预加载数据。这样,在创建activity之后,就可以加载数据并用于直接渲染。例如,一些带有开屏广告的应用程序可能会在广告开始时同步预加载一些数据源。 不同的数据结构有不同的使用场景。通过选择正确的数据结构,可以事半功倍。 1。 ArrayList和LinkedList: 3.设置:确保每一项都必须是唯一的。 4.TreeSet和TreeMap:有序集合,保证存储的元素是有序的,比HashSet和HashMap慢。 可以看出,无论空间利用率如何,HashMap 性能都很好。 但是由于初始化大小和扩展因子影响我们使用时的性能,所以我们尽量根据实际需要设置合理的初始化大小:避免设置太小导致扩展容量和设置时产生容量消耗较大的设置并导致空间损失。 由于HashMap的默认扩展因子是0.75,如果你实际使用的数字是8,那么你的初始大小设置为16;如果你实际使用的数字是60,那么你的初始大小设置为128。 对于一些不经常变化的数据源,我们可以缓存它们。这样,下次我们需要使用它们时,我们可以直接读取缓存,大大减少了加载和渲染的时间。 一般意义上的缓存按照读取时间从快到慢可以分为内存缓存、磁盘缓存和网络缓存。 从某种意义上来说,内存缓存、磁盘缓存和网络缓存是可以相互转换的。一般来说,我们会使用 具体可以参考滚动框架和RecyclerView的实现原理。 锁是处理并发的重要手段。但如果锁被滥用,很可能会导致执行效率的下降,更严重的是,可能会造成死锁等不可逆的场景。 当我们要应对高并发场景时,同步调用尤其要考虑由于加锁带来的性能损失: 那么具体应该做什么呢?下面我简单说几个例子。 1。使用乐观锁代替悲观锁,使用轻型锁代替重型锁。 采用机制 需要注意的是, 在Java中,JDK为我们提供了一些原子类,这些类默认通过 2。最小化同步范围并避免直接使用 3。针对不同的使用场景,使用不同类型的锁。 内存优化的本质是避免内存抖动。不合理的内存分配、内存泄漏以及对象的频繁创建和销毁都会引起内存抖动,最终导致系统频繁GC。 频繁的GC肯定会导致系统性能的下降。更严重的情况下,可能会导致页面冻结,导致用户体验不佳。那么我们应该从哪里开始优化呢? 当我们创建线程时,需要向系统请求资源并分配内存空间。这是一笔很大的成本,所以我们在正常的开发过程中并不直接管理它。线程,而是选择使用线程池来运行任务。因此,线程优化的本质是优化线程池。 使用线程池最大的问题是,如果池设置不正确,很容易被滥用并导致内存溢出问题。通常,一个应用程序有多个线程组。不同的函数、不同的模块、甚至不同的第三方库都会有自己的线程池。所以大家都用自己的,很难协调统一资源,不能在同一个地方工作。去做。 那么我们应该如何优化线程池呢? 1。建立主线程池+辅线程池的组合线程池,由线程池管理器协调管理。主线程池负责较高优先级的任务,而辅助线程池负责低优先级的任务以及被主线程池拒绝和降级的任务。 此处执行的任务必须优先考虑任务。任务的优先级调度是通过 2。使用Hook收集应用程序中使用 3。设置任何提供接口的第三方库,通过其开放接口将线程池设置为线程池管理器。如果没有可用的设置界面,请考虑更改库或工具来替换线程池的使用。 IO优化的核心是减少IO数量。 1。优化网络要求。 2。磁盘IO优化 还是这句话胜于见百遍,试一次胜于见一百次。写了这么多,还是希望大家在平时的开发过程中多关注一些与优化应用响应时间相关的技巧,这样我们才能开发出流畅、流畅的应用。执行任务
,加载资源
,数据结构 、
线程/IO 和
页面渲染。这些五个角度给我优化建议。 2.1 任务
IdleHandler
。 2.1.1 业务/任务概述
2.1.2 任务转换
异步线程+
同步锁来执行。 2.1.3 优先级任务
2、对于线程池,我们可以从代码层按照优先级顺序将任务添加到线程池中。注意,这里的线程池是优先阻塞的,像使用PriorityBlockingQueue实现的PriorityThreadPoolExecutor优先线程池。
3. 使用框架执行第三方任务。这里推荐我的开源XTask供大家参考。2.1.4 延迟执行
IdleHandler
,具体用法如下: Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
// 执行你的任务
return false;
}
});
public class DelayTaskQueue {
private final Queue<Runnable> mDelayTasks = new LinkedList<>();
private final MessageQueue.IdleHandler mIdleHandler = () -> {
if (mDelayTasks.size() > 0) {
Runnable task = mDelayTasks.poll();
if (task != null) {
task.run();
}
}
// mDelayTasks非空时返回ture表示下次继续执行,为空时返回false系统会移除该IdleHandler不再执行
return !mDelayTasks.isEmpty();
};
public DelayTaskQueue addTask(Runnable task) {
mDelayTasks.add(task);
return this;
}
public void start() {
Looper.myQueue().addIdleHandler(mIdleHandler);
}
}
2.2 加载资源
2.2.1 延迟加载
lazy
:修改val变量,并在程序第一次使用这个变量(或对象)时对其进行初始化。 private Map getSystemSettings() {
if (mSettingMap == null) {
mSettingMap = initSystemSettings();
}
return mSettingMap;
}
2.2.2 分段加载
2.2.3 预加载
IdleHandler
。或者在onUserInteraction
中监控用户的操作。如果一段时间内不起作用,则视为不活动。 2.3 数据结构
2.3.1 数据结构优化
2.3.2 数据缓存
网络缓存
->磁盘缓存
->内存缓存
来提高读取速度。 2.3.3 锁优化
CAS
,全称是Compare And Swap,意思是先比较,再交换。这意味着每次执行或更改变量时,我们都会比较新旧值,如果存在偏移则更新它们。这类似于一些无锁数据库,其中每个数据库操作都将带有唯一的版本号。每次修改数据库时,都会比较数据库记录和操作请求的版本号。如果版本号是最新版本号,则更改,否则丢弃。 CAS
必须使用volatile
来读取最新的共享变量值才能达到【比较替换】的效果,因为 具有挥发性。 将提供可变的可见性。
CAS机制
实现,例如AtomicInteger
、Ato mic参考
等synced
。即使使用它,也尽量使用同步块而不是同步方法。使用JDK提供的几个同步工具:CountDownLatch、CyclicBarrier、ConcurrentHashMap。2.3.4 内存优化
2.4 线程/IO
2.4.1 线程优化
PriorityBlockingQueue
队列执行的。以下是主、副线程池设置,仅供参考: newThread
方法的地方并进行更改,以便它们由线程池管理器协调和管理。 2.4.2 优化IO
3.推荐工具
终于
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。