卡顿优化总结

本文详细探讨了系统卡顿的根源——丢帧,涉及View绘制过程、Vsync信号的作用、Choreographer监控机制,以及布局和耗时优化、内存影响及优化方法,包括垃圾回收策略和内存泄漏预防。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.系统卡顿的根本原因

系统卡顿的根本原因是因为丢帧导致的,即本该在一个Vsync周期内显示的画面帧,由于种种原因并没有即时显示到屏上,而是过了好几个Vsync信号周期后才显示出来。
出现这种情况大体分为两个原因:
1.1页面太复杂,导致View绘制的时候耗时太多,超过了16.6ms
1.2主线程存在耗时的任务,导致在Vsync信号开始之后,没有及时开始执行绘制的任务

2.View的绘制过程

首先在performLaunchActivity的时候会创建PhoneWindow,然后在setContentView的时候创建DecorView,并加载XML布局文件到DecorView的contentParent这个父布局上,在onResume的时候WindowManagerGlobal在addView方法中创建ViewRootImpl,然后执行ViewRootImpl的setView方法,然后ViewRootImpl会执行requestLayout方法。requestLayout方法是View绘制的入口方法,我们经常调用的view的invalidate或者requestLayout方法,都会执行到ViewRootImpl的requestLayout方法。
requestLayout方法会执行到scheduleTraversals方法,在scheduleTraversals里面,会添加同步屏障消息,以便及时响应界面绘制的异步消息。然后会给Choreographer注册一个TraversalRunnable回调,并让Choreographer去做Vsync垂直同步信号的监听,在Choreographer内部监听到Vsync垂直同步信号之后,会回调到TraversalRunnable的回调接口,然后移除同步屏障消息,执行performTraversals,这里才会真正开始执行测量、布局、绘制的流程。其中绘制过程是ActivityThread(CPU) + RenderThread(GPU)一起完成,然后把绘制好的buffer通过queuebuffer添加到缓冲队列,交给SurfaceFlinger。SurfaceFlinger在收到缓冲buffer,并监听到Vsync信号后,开始调用HWC或者OpenGL ES做合成处理,然后把合成的缓冲帧通过HWC给到显示驱动做显示。

3.vsync垂直同步信号

Vsync信号是由显示设备发出来的,经过HWC传递给SurfaceFlinger,SurfaceFlinger会把信号传给自己内部处理,同时还会传给各个应用进程的Choreographer。大部门显示设备的刷新频率是60HZ,也就是每个信号的间隔为16.6ms,刷新率和Vsync的频率是一致的。Vsync信号会触发View的测量、布局、绘制;也会触发SurfaceFlinger的合成缓冲帧。Vsync机制加上triple buffer缓存,可以最大程度保证屏幕的刷新率和软件绘制的帧率保持一致,尽量保证了系统的流畅性。

4.Choreographer监控丢帧情况

使用Choregrapher的postFrameCallback,去注册监听每一帧的开始刷新的时间,重而发现丢帧的情况。监听接口回调的时间单位是纳秒,也就是1/1000000ms。

// Application.java
public void onCreate() {
    super.onCreate();
    Choreographer.getInstance().postFrameCallback(new FPSFrameCallback(System.nanoTime()));
}

public class FPSFrameCallback implements Choreographer.FrameCallback {

    private static final String TAG = "FPS_TEST";
    private long mLastFrameTimeNanos = 0;
    private long mFrameIntervalNanos;

    public FPSFrameCallback(long lastFrameTimeNanos) {
        mLastFrameTimeNanos = lastFrameTimeNanos;
        mFrameIntervalNanos = (long)(1000000000 / 60.0);
    }

    
    public void doFrame(long frameTimeNanos) {
        // 初始化时间
        if (mLastFrameTimeNanos == 0) {
            mLastFrameTimeNanos = frameTimeNanos;
        }
        final long jitterNanos = frameTimeNanos - mLastFrameTimeNanos;
        if (jitterNanos >= mFrameIntervalNanos) {
            final long skippedFrames = jitterNanos / mFrameIntervalNanos;
            if(skippedFrames > 30){
                // 丢帧30以上打印日志
                Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                        + "The application may be doing too much work on its main thread.");
            }
        }
        mLastFrameTimeNanos = frameTimeNanos;
        // 注册下一帧回调
        Choreographer.getInstance().postFrameCallback(this);
    }
}

实际上在Choreographer内部,也同样是有这样一套判断是否掉帧的代码。

5.布局优化

5.1减少过度绘制
5.2减少布局层级
5.3使用include和merge标签
5.4使用viewStub懒加载View

6.耗时优化

6.1使用IdelHandler处理非紧急任务
6.2使用AsyncLayoutInflater去加载复杂的布局
6.3使用线程池去处理耗时任务

7.内存影响和优化

每次GC的时候,需要做一次GCRoot引用链的可达标记,这会挂起进程,暂停所有线程,在ART虚拟机中,这个时间大概是3ms。因此如果内存不足,会引起频繁的GC,那么就会导致主线程绘制受影响,从而导致丢帧卡顿。

7.1内存分析方式
7.1.1查看系统日志

在系统logcat日志齐全的情况下,可以查看是否是因为低内存导致的问题,如下会打印lowmemorykiller相关的tag:
在这里插入图片描述
或者看频繁存在GC的打印信息:
在这里插入图片描述

7.1.2 adb命令生成hprof文件分析内存状态

.hprof是Java堆内存的一个快照,内部记录了当时堆内存的状态,包括所有存活的对象和它们的引用关系。
.hprof文件可以用adb命令生成:adb dumpheap 包名 生成文件位置
在生成hprof文件的时候,最好是在卡顿前和卡顿后各采集一次,做对比。
然后使用Android Studio的Profiler打开这个.hprof文件

7.1.3直接使用Android Studio的Profiler

直接在Android Studio的Profiler选择MEMORY,然后选择Capture heap dump,然后选择Record开始采集:在这里插入图片描述
然后就会得到如下的堆栈分析界面:
在这里插入图片描述
其中的Leaks表示的是存在内存泄漏的点,点击reference然后jump to source可以跳转到泄露点代码的位置。
直接跳转到泄露点的位置这种情况,是一种比较理想的情况,实际排查内存泄露的时候,可能会更复杂一点。包括是否是完全的复现的场景,或者不会直接显示leaks内存泄露点。

7.2垃圾判断算法

java虚拟机判断垃圾的方法,是从一系列GCRoot开始做引用链的可达搜索,不在引用链上的对象就会被判断为垃圾。
GCRoot包括:虚拟机栈引用的对象;本地native方法引用的对象;方法区中静态变量引用的对象;存活线程中存在的对象。

7.3几种垃圾回收算法

标记清除算法:先做可达性的标记算法,把GCRoot没有直接或者间接引用的对象标记为垃圾对象;然后把垃圾对象直接清除。
优点是算法简单,不需要移动存活的对象;缺点是容易产生内存碎片,导致频繁的GC。
复制算法:把内存分成两块,只使用其中一块内存。垃圾回收的时候,把当前内存块中存活的对象直接复制到另外一块空闲的内存,然后清除掉当前的内存块,调整两个内存块的角色,完成垃圾回收。
优点是不会存在碎片内存;缺点是内存使用缩小了一半,对象高存活的时候,会导致大量频繁复制。
标记压缩算法:首先从GCRoot做可达性标记,然后把存活的对象压缩到内存的一端,然后清楚边界外的所有空间。
优点是避免了内存碎片的产生,有不需要两块内存空间,性价比高;缺点是压缩内存,需要移动存活的对象,存在一定的效率降低。

7.4分代回收策略

在ART虚拟机中采用内存分代的策略,把堆内存划分为新生代和老年代。
新生代:
新创建的对象默认放入新生代的内存区域,新生代内存的对象存活率低,回收率高,一次GC平均回收70%~90%的对象,所以在新生代中的垃圾回收算法使用的是复制算法。
新生代中的内存区域,还可以被细分成一个Eden区和两个Survive区,比例是8:1:1。Eden用来存在新创建的对象,GC的时候会把Eden中存活的对象和Survive区中的对象一起拷贝到另一个空闲的Survive区中。
老年代:
对象在多次GC后仍然没有被回收(15次),那就会移入老年代内存。老年代内存的大小一般会比新生代内存更大,内部的对象存活时间长,存活率也高,因此采用的垃圾回收算法是标记-压缩算法。
当新创建的对象很大,且新生代的内存不足的情况下,对象会被直接放入老年代。

7.5.优化内存的方法和实践
7.5.1防止内存泄漏

6.5.1.1避免使用非静态的内部类,非静态的内部类会持有外部类的引用,导致外部类不能被回收。
典型的就是在Activity或者Fragment中使用匿名内部类的方式实现Handler的实例。由于Handler持有Activity的引用,在Message延迟等原因没有执行的情况下,Activity就不能被回收,存在风险。
还有就是在Activity中去注册广播、系统回调之后,没有在ondestroy的时候去注销监听,也是会引起内存泄露的。
但是Activity的View设置匿名函数的监听并不会导致内存泄漏,是因为在Activity的ondestroy的时候会主动让View去释放掉监听的引用。
6.5.1.2静态变量的引用
在单例模式中,我们会创建一个静态的变量去持有这个实例。当实例中引入的Context是Activity的时候,会导致Activity被一直强引用,导致内存泄漏。这种情况,可以使用Application Context的去做为单例模式中的Context参数。

7.5.2使用软引用和弱引用

在恰当的时候使用软引用和弱引用,可以降低内存的占用,降低GC的频率。

7.5.3避免在ondraw中创建对象

在自定义View的时候,避免在onDraw中去创建对象,或者去加载图片。因为onDraw会频繁调用,频繁创建对象或者加载资源,会容易造成内存的抖动。

7.5.4对图片的优化处理

图片是很占用内存的,内存占用 = width * height *像素点占用字节,如果是RGB_8888类型,占用4字节,如果RGB_4444则是占用2字节。因此一张800 x 600像素的图片,加载到内存 = 1080 * 720 * 4 = 3037KB = 2.96M的内存。
现在对图片的处理,很多时候会用到Gilde框架处理,确实也挺强大,但是很多时候我们还是需要自己去处理图片问题,特别是图片的缓存和内存的管理。
我们可以使用Android SDK提供的LruCache去构建一个图片缓存容器,它可以用图片的名称或者绝对路径做key,然后图片自己做value来缓存。当图片的缓存达到上限的时候,就移除掉最久没有使用的图片,释放内存。

<think>好的,我现在需要帮用户解决Access数据库卡顿的问题。首先,我得回忆一下常见的Access性能问题以及优化方法。用户提到的是“卡顿”,可能涉及查询速度慢、界面响应迟缓或者数据处理效率低等情况。我需要系统地分析可能的原因,然后给出相应的优化建议。 首先,数据库设计方面。表结构是否合理?如果字段过多或者设计不规范,比如没有正确设置主键、索引,或者存在大量冗余数据,都会导致查询变慢。可能需要建议用户检查表结构,拆分大表,规范字段类型,比如避免使用文本类型存储数字或日期,这样可以减少存储空间和提高查询速度。 接下来是索引的问题。Access中的索引对查询性能影响很大。如果经常用于搜索、排序或连接的字段没有索引,查询可能会变慢。需要提醒用户为这些字段添加索引,但也要注意索引不宜过多,否则会影响写入性能。此外,主键自动带索引,所以确保主键设置正确也很重要。 然后是查询优化。用户可能编写了复杂的查询,尤其是嵌套查询或多表连接,这些如果没有优化的话,执行时间会很长。可能需要建议他们分步执行查询,将复杂查询拆分为多个步骤,使用临时表存储中间结果。同时,避免在查询条件中使用函数或计算,这样会导致索引失效。比如,使用WHERE Year(DateField) = 2023这样的条件不如直接使用DateField BETWEEN #2023-01-01# AND #2023-12-31#效率高。 碎片整理和压缩数据库也是关键。长时间使用的数据库会产生碎片,导致文件变大,性能下降。Access自带的压缩和修复功能可以解决这个问题,应该建议用户定期执行这个操作。另外,关闭数据库时是否压缩可能也是一个选项,需要提醒他们在选项里设置。 前端和后端分离。如果多个用户同时访问同一个数据库,尤其是前端界面和后端数据在一起的话,网络延迟和争用可能导致卡顿。建议将数据表拆分到后端数据库,前端只保留表单、查询等界面元素,这样能减少网络传输的数据量,提高响应速度。 临时表和内存优化。大量临时数据存储在磁盘上会影响性能,可以建议用户尽可能使用内存中的数据结构,比如VBA数组来处理中间数据,减少对磁盘的频繁读写。同时,及时关闭不再使用的记录集,释放资源。 VBA代码优化。如果用户使用了VBA,循环处理数据或者频繁的数据库操作可能导致性能问题。需要检查代码是否有冗余操作,比如在循环内执行查询,应该改为批量操作。使用事务处理来批量提交更改,减少磁盘I/O次数,也能提升性能。 硬件和网络方面。如果数据库文件存放在网络驱动器上,带宽或延迟问题可能导致卡顿。建议将文件移到本地SSD,或者优化网络环境。同时,确保计算机有足够的内存,避免因内存不足导致的频繁交换。 最后,升级到其他数据库系统。如果数据量或并发量超过Access的处理能力,可能需要考虑迁移到更强大的系统如SQL Server,但这对用户来说可能是个大步骤,需要根据实际情况判断。 总结一下,我需要把这些点组织成清晰的步骤,从设计优化、索引、查询调整到硬件建议,逐步帮助用户排查和解决问题。同时,注意用中文,结构清晰,避免使用专业术语过多,让用户容易理解。可能还需要提醒一些具体的操作步骤,比如如何压缩数据库,如何设置索引等,确保建议的可行性。</think>以下是Access数据库卡顿优化的分步解决方案,按优先级和操作复杂度排序: --- ### **一、数据库设计优化** 1. **拆分超大表** - 单表字段数建议不超过30个,若字段过多可拆分为多个关联表。 - 使用主键(如自增ID)和索引关联表,避免冗余数据。 2. **规范字段类型** - 数值用`数字`类型,日期用`日期/时间`类型,避免用文本类型存储。 - 文本字段根据实际长度设置`字段大小`(如邮编固定6位可设为`文本(6)`)。 3. **启用关系完整性** - 在「数据库工具」→「关系」中设置外键约束,勾选**级联更新/删除**。 --- ### **二、索引优化** 1. **添加必要索引** - 对高频查询的`WHERE`、`JOIN`、`ORDER BY`字段添加索引。 - 操作步骤:设计视图 → 选择字段 → 「索引」属性设为「是(允许重复)」。 2. **避免过度索引** - 索引总数不超过表字段的30%,写操作频繁的字段谨慎添加。 --- ### **三、查询性能优化** 1. **简化复杂查询** - 将嵌套查询拆分为多个步骤,用临时表(如`SELECT INTO`)存储中间结果。 - 示例优化前: ```sql SELECT * FROM TableA WHERE ID IN (SELECT ID FROM TableB WHERE Status=1) ``` 优化后: ```sql SELECT TableA.* INTO TempTable FROM TableA INNER JOIN TableB ON TableA.ID = TableB.ID WHERE TableB.Status=1; ``` 2. **避免函数计算** - 错误示例:`WHERE Format(DateField, "yyyy") = "2023"` - 正确示例:`WHERE DateField BETWEEN #2023/01/01# AND #2023/12/31#` --- ### **四、定期维护数据库** 1. **压缩修复数据库** - 操作路径:「文件」→「信息」→「压缩和修复数据库」。 - 建议每周执行一次,可设置关闭时自动压缩(选项→当前数据库→关闭时压缩)。 2. **清理历史数据** - 使用删除查询后立即执行压缩操作,释放存储空间。 --- ### **五、前端-后端分离(多用户场景)** 1. **拆分数据库** - 后端:仅存放数据表,存放于服务器共享目录。 - 前端:存放查询、表单、报表,分发给各用户。 - 工具:「数据库工具」→「Access数据库」→「拆分数据库」。 2. **链接表优化** - 前端链接表设置为「缓存表结构」,避免频繁刷新元数据。 --- ### **六、VBA代码优化** 1. **批量操作替代循环** - 错误示例: ```vba For Each Record In Records Rs.Edit Rs!Field = Value Rs.Update Next ``` - 正确示例: ```vba CurrentDb.Execute "UPDATE Table SET Field = Value WHERE Condition" ``` 2. **事务处理** - 批量写入时用事务提交: ```vba DBEngine.BeginTrans '...批量操作... DBEngine.CommitTrans ``` --- ### **七、硬件/环境优化** 1. **本地化存储** - 单机版数据库建议存放于SSD硬盘,网络版建议服务器用千兆网卡。 2. **关闭无关程序** - 禁用杀毒软件对`.accdb`文件的实时扫描(添加白名单)。 --- ### **八、终极方案:迁移到高级数据库** 若数据量超过1GB或并发用户>10人,建议: 1. 迁移到SQL Server Express(免费版支持10GB数据)。 2. 使用Access作为前端,通过ODBC链接SQL Server表。 --- 通过以上步骤逐步排查优化,可显著改善Access卡顿问题。建议从设计优化和索引调整入手,再逐步实施其他措施。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值