投资建设一个网站多少钱,网站页面设计具体步骤,多语言网站建设 技术,哪个网站做视频收益高响应时间#xff0c;它是用来衡量系统运行效率的一个重要指标。评价一个应用的响应时间#xff0c;可以从用户感知和系统性能这两个角度来考量。 响应时间的长短#xff0c;可能影响用户对某个功能、某个应用、乃至某个系统的使用。毕竟如果有选择#xff0c;没有哪个人会愿… 响应时间它是用来衡量系统运行效率的一个重要指标。评价一个应用的响应时间可以从用户感知和系统性能这两个角度来考量。 响应时间的长短可能影响用户对某个功能、某个应用、乃至某个系统的使用。毕竟如果有选择没有哪个人会愿意去使用卡顿的应用运行慢的手机。
作为一名开发者虽然我们平时可能只关注于堆业务根本就没有时间或者机会去优化我们程序的响应时间但是这些内容对我们个人的技术成长是至关重要的。大的不说这部分也是面试中经常考察的内容知道了也不至于吃亏。
那么接下来我们就长话短说赶紧来瞧瞧到底如何来优化我们应用的响应时间。
1. 核心原则 在算法中我们经常会从时间复杂度和空间复杂度这两个纬度来衡量算法的优劣。 很多时候我们无法做到时间复杂度和空间复杂度两者都最佳只能在时间和空间中取折中的最优解。同样的如果我们追求最极致的时间最佳就可能需要牺牲一部分的空间这就是拿空间换时间的解法。
即响应时间优化的核心空间 - 时间 用空间换时间
那么我们应该怎么做呢下面是我归纳总结出来的四项基本原则
1.缓存优先能读缓存读缓存。2.减少新建能复用绝不新建。3.减少任务能不做的尽量不做。4.具体问题具体分析针对具体事务本身进行分析必须做的能提前做就提前做不必须做的延后做。
2. 优化措施
可能我上面说的这些核心和基本原则对绝大多数人来说都非常好理解但是知道了这些并不代表你懂得如何进行优化。 这就好比你高中学数学即便告诉了你一堆的公式但真要让你来一道相关的应用题你还真不一定能解得出来这个时候例题就很关键了。
同样的即便你知道了一些关于应用响应时间优化的核心和原则后当你真正面临具体的优化问题时你可能也会手足无措。
所以接下来我就从任务执行、资源加载、数据结构、线程/IO和页面渲染这五个角度来给出我的优化建议。
2.1 任务执行
1.业务/任务梳理对业务进行拆分对任务进行整合。2.任务转换串行 - 并行, 同步 - 异步。3.执行顺序按优先级调整。4.延迟执行、空闲执行如:IdleHandler。
2.1.1 业务/任务梳理 业务往往是由一个个任务流组合而成。合理的业务/任务粒度可以有效提高响应的速度。 对业务和任务的梳理正确的方式是先进行业务的拆分将业务拆分为一个个子任务再根据需要对子任务进行整合。
1对不合理的业务流进行拆分。
对业务进行拆分拆分出主要必要业务和次要非必要业务。分别对主要业务和次要业务进行优先级评估业务执行按优先级从高到底依次执行。
2对任务流进行整合。
多个相关的串行任务可以整合为统一的业务整体。多个不相关的串行任务可以整合为一个并行的业务。
2.1.2 任务转换
1.串行 - 并行的适用范围
多个不相关的串行任务。多个任务弱相关且耗时但是耗时接近。例如某个页面你需要调用多个模块的接口查询数据进行展示。
2.同步 - 异步的适用范围
非必要重要性不高且耗时的任务。耗时且关联性不大的任务。耗时且存在一定相关性的任务。使用异步线程 同步锁的方式执行。
2.1.3 任务优先级 类似线程中的优先级Priority当系统资源紧张的时候优先执行优先级高的线程。 首先我们要对应用内所有需要优化的业务以及其子任务的优先级进行定义然后按优先级顺序进行排列和执行。
那么如何才能保证任务被按优先级进行执行呢
1.对于线程我们可以直接设置其Priority值。但是一般我们不能直接使用线程所有这个可以忽略 2.对于线程池我们可以从代码层将任务按优先级顺序加入到线程池中。注意这里的线程池最好是阻塞式的例如使用PriorityBlockingQueue实现的优先级线程池 PriorityThreadPoolExecutor。 3.使用第三方的任务执行框架这里推荐我开源的 XTask 供大家参考。
2.1.4 延迟执行 延迟执行是将一些不必要、重要性不高或者高耗时的任务暂停执行等后面资源充足或者要使用时才执行。 常见的延迟执行有以下几种
延迟某个特定的时间执行。例如某应用启动后每隔2分钟同步一下用户状态。待某个特定的任务执行完成之后再执行。例如导航应用定位获取成功后再执行目的地推荐获取的任务。直接不执行等相关业务用到的时候再执行。空闲执行等待页面都完全渲染完毕之后再执行。例如使用IdleHandler具体使用如下
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {Overridepublic boolean queueIdle() {// 执行你的任务return false;}
});当然如果你想在空闲的时候执行多个任务你也可以这样写
public class DelayTaskQueue {private final QueueRunnable 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 资源加载
1.懒加载2.分段加载部分加载3.预加载数据、布局页面等
2.2.1 懒加载 对于一些不常用或者不重要的数据、图片、控件以及其他一些资源我们可以在用到时再进行加载。 1.数据懒加载
kotlin中的lazy标签修饰val变量程序第一次使用到这个变量(或者对象)时再初始化。Map、List和SharedPreferences等大数据的延迟初始化。
private Map getSystemSettings() {if (mSettingMap null) {mSettingMap initSystemSettings();}return mSettingMap;
}2.图片资源懒加载
对于不常用的图片可以使用云端图片的资源url来替代。对于非程序预置的图片本地图片文件或者云端图片用到时再加载。
3.控件懒加载
使用ViewStub进行布局的延迟加载。使用ViewPager2Fragment进行Fragment的懒加载。使用RecyclerView替代ListView。
2.2.2 分段加载
分段加载常见应用于大数据的加载这里包括大图和长视频等多媒体资源的加载。做到用到哪加载到哪完全不必要等全部加载完才给用户使用。
1.大图的分段加载对于大图我们可以将其按一定尺寸进行切分分割成一块一块的小瓦片然后设定一个预览预加载范围用户预览到哪里我们就加载到哪里。就类似地图的加载
2.长视频的分段加载对于长视频我们可以将其按时间片进行拆分并设置一个加载缓存池。这样用户浏览一个长视频时就可以快速打开加载。
3.大文件或者长WebView的分段加载对于一些阅读类的app经常会遇到大文件和长WebView的加载这里我们也可以同理对其进行拆分处理。
2.2.3 预加载 分段加载常和预加载一起组合使用。对于一些加载非常耗时的内容我们可以将加载时机提前从而减小用户感知的加载时间。 预加载的本质是提前加载这样这个提前加载的时机就非常的关键和重要。因为预加载时机如果太晚几乎看不出效果但是如果预加载的时机过早有可能抢占其他模块资源造成资源紧张。
那么我们何时可以触发预加载预加载的时机是什么呢下面我举几个简单的例子。
1.用户操作时。如果用户点击了第2章我们就开始预加载下一章和上一章用户上滑到了第3页我们预加载第4页用户下滑到第5页我们预加载第4页.
2.应用空闲时。例如之前说的IdleHandler。或者在onUserInteraction中监听用户的操作一段时间没有操作即视为空闲。
3.耗时等待时。对于一些常见的耗时操作我们可以在其开始时并行进行一些预加载操作从而提高时间的利用率。例如Activity的创建比较耗时我们可以在startActivity前就开始预加载数据这样Activity创建完之后有可能数据就已经加载好了直接可以拿来渲染。例如一些有开屏广告的app可以在广告开始时同步进行一些数据资源的预加载。
2.3 数据结构
1.数据结构优化空间大小、读取速度、复用性、扩展性。2.数据缓存内存缓存、磁盘缓存、网络缓存分段缓存。这里可以参考glide.3.锁优化减少过度锁避免死锁悲观锁/乐观锁。4.内存优化避免内存抖动频繁GC尤其关注bitmap
2.3.1 数据结构优化 不同的数据结构有不同的使用场景选择适合的数据结构能够事半功倍。 1.ArrayList和LinkedList
ArrayList底层数据结构是数组查询快、增删慢。LinkedList底层数据结构是链表查询慢、增删快。
2.HashMap和SparseArray
HashMap底层数据结构是数组和链表或红黑树的组合结合了ArrayList和LinkedList的优点查询快、增删也快。但是扩容很耗性能且空间利用率不高(75%)浪费内存。SparseArray底层数据结构是双数组一个数组存key一个数组存value。使用二分法查询进行优化在数据量小一百条以下的情况下速度和HashMap相当但是空间利用率大大提升。ArrayMap底层数据结构是双数组一个数组存key的hash值一个数组存value。设计与SparseArray类似在数据量小的情况下可完全替代HashMap。
3.Set: 保证每个元素都必须是唯一的。
4.TreeSet和TreeMap有序的集合保证存放的元素是排过序的速度慢于HashSet和HashMap。
可以看到在不考虑空间利用率的情况下HashMap的性能是不错的。
但是由于存在初始化大小和扩展因子对其性能有所影响我们在使用时尽量根据实际需要设置合理的初始化大小避免设置小了扩容带来性能消耗设置大了造成空间浪费。
因为HashMap的默认扩容因子是0.75如果你实际使用的数量是8那你初始化大小就设置16如果你实际使用的数量是60那你初始化大小就设置128。
2.3.2 数据缓存 对于一些变化不是很频繁的数据资源我们可以将其缓存下来。这样我们下次需要使用它们的时候就可以直接读取缓存这样极大地减少了加载和渲染所需要的时间。 一般意义上的缓存按读取的时间由快到慢我们可分为内存缓存、磁盘缓存、网络缓存。
内存缓存就是存储在内存中我们可以直接读取使用。而如果从界面渲染的角度我们又可以将内存缓存分为Active(活跃/正在显示)缓存和InActive(非活跃/不可显示)缓存。磁盘缓存就是存储在磁盘文件中每次读取都需要将磁盘文件内容读取到内存中方可使用。网络缓存就是存储在远端服务器中每次读取需要我们进行一次网络请求。一般来说我们也可以将一次网络缓存请求到的数据缓存到磁盘中将网络缓存转化为磁盘缓存通过减少网络请求来提升读取速度。
某种意义上来说内存缓存、磁盘缓存和网络缓存它们又是可以相互转化的一般来说我们会将网络缓存-磁盘缓存-内存缓存进行使用从而提升读取速度。
具体我们可以参考glide框架和RecyclerView的实现原理。
2.3.3 锁优化 锁是我们解决并发的重要手段但是如果滥用锁的话很可能造成执行效率下降更严重的可能造成死锁等无法挽回的场景。 当我们需要处理高并发的场景时同步调用尤其需要考量锁的性能损耗
能用无锁数据结构就不要用锁。缩小锁的范围。能锁区块就不要锁住方法体;能用对象锁就不要用类锁。
那么我们具体应该怎么做呢下面我简单讲几个例子。
1.使用乐观锁代替悲观锁轻量级锁代替重量级锁。
利用CAS机制, 全称是Compare And Swap即先比较然后再替换。就是每次执行或者修改某个变量时我们都会将新旧值进行比较如果发生偏移了就更新。这就好比在一些无锁的数据库中每次的数据库操作都会携带一个唯一的版本号每次进行数据库修改的时候都会对比一下数据库记录和操作请求的版本号如果版本号是最新的版本号则进行修改否则丢弃。
需要注意的是CAS必须借助volatile才能读取到共享变量的最新值来实现【比较并交换】的效果因为volatile会保证变量的可见性。
在Java中JDK给我们默认提供了一些CAS机制实现的原子类如AtomicInteger、AtomicReference等。
2.缩小同步范围避免直接使用synchronized即使使用也要尽量使用同步块而不是同步方法。多使用JDK提供给我们的同步工具CountDownLatchCyclicBarrierConcurrentHashMap。
3.针对不同使用场景使用不同类型的锁。
针对并发读多写少的我们可以使用读写锁多个读锁不互斥读锁与写锁互斥ReentrantReadWriteLockCopyOnWriteArrayListCopyOnWriteArraySet。针对某一个并发操作通常由某一特定线程执行时可尝试使用偏向锁偏向于第一个获得它的线程。针对存在大量并发资源竞争的场景推荐使用重量级锁synchronized。
2.3.4 内存优化 内存优化的核心是避免内存抖动。不合理的内存分配、内存泄漏、对象的频繁创建和销毁都会导致内存发生抖动最终导致系统的频繁GC。 频繁的GC必定会导致系统运行效率的下降严重的可能会导致页面卡顿造成不好的用户体验。那么我们应该着手从哪些地方进行优化呢
解决应用的内存泄漏问题。这里我们可以使用LeakCanary 或者 Android Profile 等工具来检查我们查询可能存在的内存泄漏。平时编码应当注意避免内存泄漏。如避免全局静态变量和常量、单例持有资源对象ActivityFragmentView等资源使用完立即释放或者recycle回收等。避免创建大内存对象频繁创建和释放对象尤其是在循环体内频繁创建的对象需要考虑复用或者使用缓存。加载图片可以适当降低图片质量小图标尽量使用SVG大图/复杂的图片考虑使用webp。尽量使用图片加载框架如glide这些框架都会帮我们进行加载优化。避免大量bitmap的绘制。避免在自定义View的onMeasure、onLayout和onDraw中创建对象。使用SpareArray、ArrayMap替代HashMap。避免进行大量的字符串操作特别是序列化和反序列化。不要使用(加号)进行字符串拼接。使用线程池可设置适当的最大线程池数执行线程任务避免大量Thread的创建及泄漏。
2.4 线程/IO
1.线程优化统一、优先级调度、任务特性2.IO优化网络IO和磁盘IO核心是减少IO次数 网络请求合并请求链路优化请求体优化系列化和反序列化优化请求复用等。磁盘文件随机读写、SharePreference读写等例如对于读多写少的可使用内存缓存 3.log优化循环中的log打印不必要的log打印log等级
2.4.1 线程优化 当我们创建一个线程时需要向系统申请资源分配内存空间这是一笔不小的开销所以我们平时开发的过程中都不会直接操作线程而是选择使用线程池来执行任务。所以线程优化的本质是对线程池的优化。 线程池使用的最大问题就在于如果线程池设置不对的话很容易被人滥用引发内存溢出的问题。而且通常一个应用会有多个线程池不同功能、不同模块乃至是不同三方库都会有自己的线程池这样大家各用各的就很难做到资源的协调统一劲不往一处使。
那么我们应该如何进行线程池优化呢
1.建立主线程池副线程池的组合线程池由线程池管理者统一协调管理。主线程池负责优先级较高的任务副线程池负责优先级不高以及被主线程池拒绝降级下来的任务。
这里执行的任务都需要设置优先级任务优先级的调度通过PriorityBlockingQueue队列实现以下是主副线程池的设置仅供参考
主线程池核心线程数和最大线程数2nn为CPU核心数60s keepTimePriorityBlockingQueue128。副线程池核心线程数和最大线程数nn为CPU核心数60s keepTimePriorityBlockingQueue64。
2.使用Hook的方式收集应用内所以使用newThread方法的地方改为由线程池管理者统一协调管理。
3.将所有提供了设置线程池接口的第三方库通过其开放的接口设置为线程池管理者管理。没有提供设置接口的考虑替换库或者插桩的方式替换线程池的使用。
2.4.2 IO优化 IO优化的核心是减少IO次数。 1.网络请求优化。
避免不必要的网络请求。对于那些非必要执行的网络请求可以延时请求或者使用缓存。对于需要进行多次串行网络请求的接口进行优化整合控制好请求接口的粒度。比如后台有获取用户信息的接口、获取用户推荐信息的接口、获取用户账户信息的接口。这三个接口都是必要的接口且存在先后关系。如果依次进行三次请求那么时间基本上都花在网络传输上尤其是在网络不稳定的情况下耗时尤为明显。但如果将这三个接口整合为获取用户的启动初始化信息这样数据在网络中传输的时间就会大大节省同时也能提高接口的稳定性。
2.磁盘IO优化
避免不必要的磁盘IO操作。这里的磁盘IO包括文件读写、数据库sqlite读写和SharePreference等。对于数据加载选择合适的数据结构。可以选择支持随机读写、延时解析的数据存储结构以替代SharePreference。避免程序执行出现大量的序列化和反序列化会造成大量的对象创建。
2.5 页面渲染
下面是我简单列举的几点加快页面渲染的方法相信大家或多或少都用过这里我就不详细阐述了
1.降低布局层级、减少嵌套、避免过度渲染背景(mergeConstraintLayout)2.页面复用(include)3.页面懒加载4.布局延迟加载(ViewStub)5.inflate优化布局预加载异步加载动态new控件/X2C6.动画优化注意动画的执行耗时和内存占用不可见时暂停动画可见时再恢复动画7.自定义view优化减少onDraw、onLayout、onMeasure的对象创建和执行耗时8.bitmap和canvas优化bitmap大小、质量、压缩、复用canvas复用clipRecttranslate9.RecycleView优化减少刷新次数缓存复用