您当前的位置:首页 > 手机百科 > 安卓百科

深入分析Android“卡顿掉帧”问题

时间:2022-07-15 11:18:12  来源:  作者:愿天堂没有代码

前言

深入分析Android“卡顿掉帧”问题

 

全球手机市场中,Android/ target=_blank class=infotextkey>安卓和IOS一直占着主流市场,iphone手机给人的感觉就是流畅,而安卓手机却一直是卡顿的代名词

其实,安卓机刚上手时还是速度飞快的,并且基于它开放性的原则,受到多数用户的喜爱,但通病是:运行一段时间后,反应变慢、容易卡顿,这也是iOS用户不肯换安卓最主要的原因

Android出现使用不流畅,卡顿的主要原因

深入分析Android“卡顿掉帧”问题

 

CPU使用率过高

● 手机固件有缺陷,导致CPU使用率始终过高,这时您刷一个稳定点的ROM就好了

● 开启了过多的程序;这时您可以使用进程管理程序清理一下后台进程

● 某个程序由于设计不当或者不兼容导致占用大量CPU资源,这时您可以使用手机安全卫士体检里的运行监测(只勾选这个)查看当前所有正在运行程序的CPU占用,找到消耗资源特别多的,结束或者卸载它

● 您执行的某一个操作可能导致CPU过高(有时候也可能是查看CPU占用这个操作)

● CPU使用率查看软件不准确,这时您可以用多个软件查看

系统内存使用率过高

系统内存分为物理内存和虚拟内存

● 物理内存就是系统硬件提供的内存大小,是真正的内存;在linux下还有一个虚拟内存的概念,虚拟内存就是为了满足物理内存的不足而提出的策略,它是利用磁盘空间虚拟出的一块逻辑内存,用作虚拟内存的磁盘空间被称为 交换空间(Swap Space)

● linux的内存管理采取的是分页存取机制,为了保证物理内存能得到充分的利用,内核会在适当的时候将物理内存中不经常使用的数据块自动交换到虚拟内存中,而将经常使用的信息保留到物理内存。而进行这种交换所遵循的依据是“LRU”算法(Least Recently Used,最近最少使用算法)

● 无论是使用桌面操作系统还是移动操作系统,很多人都喜欢随时关注内存,一旦发现内存使用率过高就难受,忍不住的要杀进程以释放内存。这种习惯很大程度上都是源自windows系统,当然这在Windows下也确实没错。然而很多人在使用Linux系统时仍然有这个习惯,甚至到了Android系统下,也改不掉(尤其是Android手机刚出现的几年),Clean Master等各种清理软件铺天盖地。毫不客气的说,Windows毒害了不少人!当然,这也不能怪Windows,毕竟Windows的普及率太高了,而大部分普通用户(甚至一些计算机相关人员)又不了解Windows和Linux在内存管理方面的差别

屏幕刷新丢帧

刷新频率

● 屏幕的刷新频率(Refresh Rate),就是一秒内屏幕刷新的次数

● 我们知道,在某一个时刻,将图像数据涂到屏幕上我们就能直观地看到一幅静态的画面,但这显然不能满足用户需求。我们需要看到的是屏幕上的动画——即不断切换的连续衔接的画面。在动画术语中,每一张这样的衔接画面被称作帧。也就是说,为了看到动画,我们需要以恒定的速度取到连续的帧,并将帧涂到屏幕上

如上,要显示屏幕动画,我们要设置两个时机

● 时机一:生成帧,产生了新的画面(帧),将其填充到 FrameBuffer 中,这个过程由 CPU(计算绘制需求)和 GPU(完成数据绘制)完成

● 时机二:显示帧,显示屏显示完一帧图像之后,等待一个固定的间隔后,从 FrameBuffer 中取下一帧图像并显示,这个过程由 GPU 完成

对于设备而言,其屏幕的刷新频率 就相当于显示帧的时机和速度,可以看做是额定不变的(而生成帧速度对应我们通常说的帧率)

刷新频率这个参数是手机出厂就决定的,取决于硬件的固定参数;目前大多数设备的刷新率是 60Hz,也就是一秒刷新60次,所以每次屏幕刷新的过程占用时间就是16ms(1000/60)左右,这个是固定参数,运行过程中,不会发生改变

UI线程阻塞

当一个应用程序启动之后,android系统会为这个应用创建一个主线程;这个线程非常重要,它负责渲染视图,分发事件到响应监听器并执行,对界面进行轮询监听;因此,一般也叫做“UI线程”

● android系统不会给应用程序的多个元素组件,建立多个线程来执行;一个视图Activity中的多个view组件运行在同一个UI线程中;因此,多个view组件的监听器的执行可能会相互影响

● 例如:当在UI线程中执行耗时操作,比如访问网络,访问数据库等,则会导致UI线程阻塞;当UI线程阻塞,则屏幕就会出现卡死情况;这样用户体验非常差;当线程阻塞超过5秒以后,android系统有可能进行干预,弹出对话框询问是否关闭应用程序

Android 绘制UI方式

把图形直接绘制到画布上(Canvas对象),这种方法可以通过独立的线程来管理surfaceView对象,并由独立线程来管理绘制过

● Android中的图形系统采用 Client/Server 架构。Server (即SurfaceFlinger)主要由 C++ 代码编写而成。Client 端代码分为两部分,一部分是由 JAVA 提供的供应用程序使用的 API,另一部分则是用 C++ 写成的底层实现

● 每个应用可能有一个或多个surface(含surface的情况下),surfaceFlinger是本地服务,用于管理surface的创建、销毁、zorder合成。View及其子类(如TextView, Button)要画在surface上。每个surface创建一个Canvas对象 (但属性时常改变),用来管理view在surface上的绘图操作,如画点画线。每个canvas对象对应一个bitmap,存储画在surface上的内容。当然这里还有个Layer的概念,在后面创建surface流程中我们再介绍

surface 创建

SurfaceControl surfaceControl = winAnimator.createSurfaceLocked();
  if (surfaceControl != null)
   {
 outSurface.copyFrom(surfaceControl);
 if (SHOW_TRANSACTIONS) Slog.i(TAG,
  "  OUT SURFACE " + outSurface + ": copied");
  }
else {
 // For some reason there isn't a surface.  Clear the
 // caller's object so they see the same state.
outSurface.release();
 }

Surface的绘制

在Android系统刷新过程中ViewRoot会调用performTraversals方法并依次调用performMeasure、performLayout、performDraw。在performDraw中会区分是否支持硬件加速,如果支持直接通过OPENGL做硬件加速绘制,如果不支持则走软件绘制。因为我们在独立线程绘制过程中一般走的是软件绘制。这里对软件绘制的方法做介绍以掌握如何在独立线程中绘制UI

   private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff,
   boolean scalingRequired, Rect dirty) {
 
   // Draw with software renderer.
   Canvas canvas;
   try {
   int left = dirty.left;
   int top = dirty.top;
   int right = dirty.right;
   int bottom = dirty.bottom;
 
   canvas = mSurface.lockCanvas(dirty);
 
   // The dirty rectangle can be modified by Surface.lockCanvas()
   //noinspection ConstantConditions
    if (left != dirty.left || top != dirty.top || right != dirty.right ||
    bottom != dirty.bottom) {
   attachInfo.mIgnoreDirtyState = true;
     }
 
    // TODO: Do this in native
   canvas.setDensity(mDensity);
    } catch (Surface.OutOfResourcesException e) {
   handleOutOfResourcesException(e);
    return false;
   } catch (IllegalArgumentException e) {
    Log.e(TAG, "Could not lock surface", e);
    // Don't assume this is due to out of memory, it could be
   // something else, and if it is something else then we could
   // kill stuff (or ourself) for no reason.
mLayoutRequested = true;    // ask wm for a new surface next time.
   return false;
   }
 
    try {
   if (DEBUG_ORIENTATION || DEBUG_DRAW) {
     Log.v(TAG, "Surface " + surface + " drawing to bitmap w="
     + canvas.getWidth() + ", h=" + canvas.getHeight());
     //canvas.drawARGB(255, 255, 0, 0);
    }
 
   // If this bitmap's format includes an alpha channel, we
   // need to clear it before drawing so that the child will
   // properly re-composite its drawing on a transparent
   // background. This automatically respects the clip/dirty region
    // or
    // If we are Applying an offset, we need to clear the area
   // where the offset doesn't appear to avoid having garbage
   // left in the blank areas.
    if (!canvas.isOpaque() || yoff != 0) {
   canvas.drawColor(0, PorterDuff.Mode.CLEAR);
     }
 
    dirty.setEmpty();
       mIsAnimating = false;
       attachInfo.mDrawingTime = SystemClock.uptimeMillis();
        mView.mPrivateFlags |= View.PFLAG_DRAWN;
 
   if (DEBUG_DRAW) {
    Context cxt = mView.getContext();
  Log.i(TAG, "Drawing: package:" + cxt.getPackageName() +
    ", metrics=" + cxt.getResources().getDisplayMetrics() +
     ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
    }
    
ry {
    canvas.translate(0, -yoff);
   if (mTranslator != null) {
  mTranslator.translateCanvas(canvas);
   }
   canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
  attachInfo.mSetIgnoreDirtyState = false;
 
   mView.draw(canvas);
 
   drawAccessibilityFocusedDrawableIfNeeded(canvas);
    } finally {
   if (!attachInfo.mSetIgnoreDirtyState) {
   // Only clear the flag if it was not set during the mView.draw() call
 attachInfo.mIgnoreDirtyState = false;
 }
 }
 } finally {
 try {
surface.unlockCanvasAndPost(canvas);
 } catch (IllegalArgumentException e) {
 Log.e(TAG, "Could not unlock surface", e);
  mLayoutRequested = true;    // ask wm for a new surface next time.
  //noinspection ReturnInsideFinallyBlock
  return false;
 }
 
 if (LOCAL_LOGV) {
Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost");
 }
 }
return true;
  }

其中关键就是canvas = mSurface.lockCanvas(dirty) 与 surface.unlockC anvasAndPost(canvas);先lockCanvas,绘制UI,最后通过unlockCanvasAndPost通知surfaceFlinger先做zorder组合显示。

lockCanvas(dirty) 就是通过JNI调用nativeLockCanvas返回一个Canvas下面看nativeLockCanvas的实现。

sttic void nativeLockCanvas(JNIEnv* env, jclass clazz, jint nativeObject, jobject canvasObj, jobject dirtyRectObj) { sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));

if (!isSurfaceValid(surface)) {
    doThrowIAE(env);
    return;
}
 
// get dirty region
Region dirtyRegion;
if (dirtyRectObj) {
    Rect dirty;
    dirty.left = env->GetIntField(dirtyRectObj, gRectClassInfo.left);
    dirty.top = env->GetIntField(dirtyRectObj, gRectClassInfo.top);
    dirty.right = env->GetIntField(dirtyRectObj, gRectClassInfo.right);
    dirty.bottom = env->GetIntField(dirtyRectObj, gRectClassInfo.bottom);
    if (!dirty.isEmpty()) {
        dirtyRegion.set(dirty);
    }
} else {
    dirtyRegion.set(Rect(0x3FFF, 0x3FFF));
}
 
ANativeWindow_Buffer outBuffer;
Rect dirtyBounds(dirtyRegion.getBounds());
status_t err = surface->lock(&outBuffer, &dirtyBounds);
dirtyRegion.set(dirtyBounds);
if (err < 0) {
    const char* const exception = (err == NO_MEMORY) ?
            OutOfResourcesException :
            "java/lang/IllegalArgumentException";
    jniThrowException(env, exception, NULL);
    return;
}
 
// Associate a SkCanvas object to this surface
env->SetIntField(canvasObj, gCanvasClassInfo.mSurfaceFormat, outBuffer.format);
 
SkBitmap bitmap;
ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
bitmap.setConfig(convertPixelFormat(outBuffer.format), outBuffer.width, outBuffer.height, bpr);
if (outBuffer.format == PIXEL_FORMAT_RGBX_8888) {
    bitmap.setIsOpaque(true);
}
if (outBuffer.width > 0 && outBuffer.height > 0) {
    bitmap.setPixels(outBuffer.bits);
} else {
    // be safe with an empty bitmap.
    bitmap.setPixels(NULL);
}
 
SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (bitmap));
swapCanvasPtr(env, canvasObj, nativeCanvas);
 
SkRegion clipReg;
if (dirtyRegion.isRect()) { // very common case
    const Rect b(dirtyRegion.getBounds());
    clipReg.setRect(b.left, b.top, b.right, b.bottom);
} else {
    size_t count;
    Rect const* r = dirtyRegion.getArray(&count);
    while (count) {
        clipReg.op(r->left, r->top, r->right, r->bottom, SkRegion::kUnion_Op);
        r++, count--;
    }
}
 
nativeCanvas->clipRegion(clipReg);
 
if (dirtyRectObj) {
    const Rect& bounds(dirtyRegion.getBounds());
    env->SetIntField(dirtyRectObj, gRectClassInfo.left, bounds.left);
    env->SetIntField(dirtyRectObj, gRectClassInfo.top, bounds.top);
    env->SetIntField(dirtyRectObj, gRectClassInfo.right, bounds.right);
    env->SetIntField(dirtyRectObj, gRectClassInfo.bottom, bounds.bottom);
}
}

在JNI层实现的就是通过surface获取到Layer中的buffer,并生成一个skiabitmap, Android 2D软件绘图使用skia作为核心引擎,这个bitmap的存储空间为Layer buffer。绘制的UI就是写入到这个buffer中,绘制好后通过 unlockCanvasAndPost通知surfaceflinger输出显示。如果是独立线程绘制UI,那么流程与上描述基本一致。但需要注意的是如果独立线程绘制的话,surface可通过surfaceView来获取

private void draw() {  
try {  
 //  步骤1  
canvas = sfh.lockCanvas(); // 得到一个canvas实例    
//  步骤2  
canvas.drawColor(Color.WHITE);// 刷屏                   
canvas.drawText("test", 100, 100, pAInt);// 画文字文本  
} catch (Exception ex) {  
} finally {   
 // 步骤3  
if (canvas != null)  
 sfh.unlockCanvasAndPost(canvas); // 将画好的画布提交  
}  
}  

总结

Android 原生系统是一个不断进化的过程 , 每个版本都会解决非常多的性能问题 , 同时也会引进一些问题 ; 到了手机厂商这里 , 由于硬件差异和软件定制 , 会在系统中加入大量的自己的代码 , 这无疑也会影响系统的性能 . 同样由于 Android 的开放 , App 的质量和行为也影响着整机的用户体验.

本篇主要列出了自身的实现问题导致的流畅性问题 , Android 最大的问题就是质量良莠不齐 , 不同于 App Store 这样的强力管理市场 , Android App 不仅可以在 google Play 上面进行安装 , 也可以在其他的软件市场上面安装 , 甚至可以下载安装包自行安装 , 可以说上架的门槛非常低 , 那么质量就只能由 开发者自己来把握了

有需要文中代码的同学,可以顺手给我点赞评论支持一下

获取方式:私信我发送“进阶”

技术是无止境的,你需要对自己提交的每一行代码、使用的每一个工具负责,不断挖掘其底层原理,才能使自己的技术升华到更高的层面

Android 架构师之路还很漫长,与君共勉

PS:有问题欢迎指正,可以在评论区留下你的建议和感受;

欢迎大家点赞评论,觉得内容可以的话,可以转发分享一下



Tags:Android   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Android 15 有望引入应用隔离功能,进一步提升系统安全性
IT之家 4 月 17 日消息,即将推出的 Android 15 系统可能引入一项全新功能:应用隔离。这一功能将更好地保护用户免受行为异常应用的侵害。Android 系统一直拥有强大的安全防护...【详细内容】
2024-04-17  Search: Android  点击:(8)  评论:(0)  加入收藏
Android 15 有望带来 NFC 无线充电支持,小型设备充电更方便
IT之家 4 月 16 日消息,说到无线充电,大多数人可能首先会想到 Qi 标准。Qi 无线充电已经存在很长一段时间了,并且广泛应用于我们日常使用的许多智能设备中。然而,许多小型电子设...【详细内容】
2024-04-16  Search: Android  点击:(5)  评论:(0)  加入收藏
Android Emulator黑屏怎么办 Android模拟器黑屏解决方法
Android Emulator黑屏问题困扰了非常多的玩家,Android Emulator作为一款安卓模拟器,可以让你在电脑上运行和浏览安卓应用程序,但是程序本身不是很稳定,很容易会出现黑屏,启动不了...【详细内容】
2024-03-04  Search: Android  点击:(46)  评论:(0)  加入收藏
Android 谷歌三件套:解锁谷歌生态!
大家是不是遇到这个情况?当我们需要下载一些国外的游戏或者软件的时候,需要在手机里面安装Google Play商店,然后通过Google Play商店下载国外软件!为了帮助大家使用上各种好用的...【详细内容】
2024-01-02  Search: Android  点击:(117)  评论:(0)  加入收藏
Android开发中常见的Hook技术有哪些?
Hook技术介绍Hook技术是一种在软件开发中常见的技术,它允许开发者在特定的事件发生时插入自定义的代码逻辑。常见的应用场景包括在函数调用前后执行特定的操作,或者在特定的事...【详细内容】
2023-12-25  Search: Android  点击:(92)  评论:(0)  加入收藏
在Android应用开发中使用NFC功能
NFC介绍NFC是指“近场通讯”(Near Field Communication),它是一种短距离无线通信技术,允许设备在非接触或极短距离内进行通信。NFC通常用于移动支付、门禁系统、智能标签和其他...【详细内容】
2023-12-22  Search: Android  点击:(104)  评论:(0)  加入收藏
关于Android图像Bitmap类,你要知道的一切
Bitmap介绍Bitmap是一种图像文件格式,它由像素阵列组成,每个像素都有自己的颜色信息。在计算机图形学中,Bitmap图像可以被描述为一个二维的矩阵,其中每个元素代表一个像素的颜色...【详细内容】
2023-12-19  Search: Android  点击:(102)  评论:(0)  加入收藏
Android开发中如何进行单元测试?
单元测试介绍单元测试是软件开发中的一种测试方法,用于验证代码中的最小可测试单元(通常是函数或方法)是否按预期工作。单元测试通常由开发人员编写,旨在隔离和测试代码的特定部...【详细内容】
2023-12-11  Search: Android  点击:(170)  评论:(0)  加入收藏
我的手机我做主,如何为Android手机应用换图标?
作为一名Android用户,你是否曾经为自己的手机桌面感到单调而乏味?虽然Android系统的桌面定制性已经非常强大,但有时候我们还是希望能够在细节上做出一些改变,尤其是对于那些每天...【详细内容】
2023-12-10  Search: Android  点击:(62)  评论:(0)  加入收藏
了解Android系统架构中的HAL硬件抽象层
在Android系统中,HAL的存在使得不同厂商的硬件可以统一被上层的应用程序调用,从而提高了系统的兼容性和可移植性。HAL还可以帮助开发者更方便地开发应用程序,因为他们不需要为...【详细内容】
2023-12-06  Search: Android  点击:(214)  评论:(0)  加入收藏
▌简易百科推荐
安卓手机内存满了,如何清理内存?
随着安卓手机的普及,越来越多的人开始使用安卓系统。然而,由于安卓系统的开放性和可定制性,许多用户可能会在手机上安装各种各样的应用程序,导致手机内存被占用,从而影响手机的运...【详细内容】
2024-01-11      Tags:清理内存   点击:(98)  评论:(0)  加入收藏
如何释放安卓应用内存:清理优化指南
清理安卓应用的内存占用可以提高设备性能并释放空间。以下是详细的步骤和方法:关闭不必要的应用: 进入设备的“最近使用的应用”或按下多任务切换按钮(通常是右下角的方形按钮)...【详细内容】
2024-01-11  智盼游    Tags:安卓   点击:(53)  评论:(0)  加入收藏
安卓系统更新迭代与Root需求的变化
随着安卓系统的不断更新迭代,我们见证了整个生态从混乱到规范的过程。回溯过去,安卓生态处于一个“妖魔横行”的时代,各种弹窗广告、自动下载、自动启动等问题层出不穷。为了解...【详细内容】
2024-01-03  科技办公达人莱说    Tags:安卓系统   点击:(51)  评论:(0)  加入收藏
Android 谷歌三件套:解锁谷歌生态!
大家是不是遇到这个情况?当我们需要下载一些国外的游戏或者软件的时候,需要在手机里面安装Google Play商店,然后通过Google Play商店下载国外软件!为了帮助大家使用上各种好用的...【详细内容】
2024-01-02  MatrixWave  今日头条  Tags:Android   点击:(117)  评论:(0)  加入收藏
安卓手机上面有哪些好用的录音软件?
安卓手机上面有哪些好用的录音软件?在日常的生活中,很少需要通过录音记录,在录音时,尽量选择一个安静的环境进行录音,并将手机放在靠近声源的位置,以获得更好的录音效果。录制人声...【详细内容】
2023-12-29  美食得爱好者    Tags:录音软件   点击:(81)  评论:(0)  加入收藏
安卓手机的照片怎么传到苹果手机?试试这3个方法!
原本一直使用安卓手机,但今天新买了一部苹果手机,需要将安卓手机中的照片全部转移到新的苹果手机里,不过她对该如何操作感到困惑。购置新手机是一件令人兴奋的事情,而将旧手机的...【详细内容】
2023-12-29  艾尚去旅行    Tags:安卓手机   点击:(54)  评论:(0)  加入收藏
搞定流畅性过后,安卓旗舰手机游戏体验还要什么?
手机游戏可以改善体验的地方数不胜数,在骁龙移动平台原生支持的“超分、超帧”等技术支持下,未来手机必将以更低的成本,呈现出更出色的游戏体验。曾几何时,手机性能的展示被局限...【详细内容】
2023-12-20    PChome  Tags:安卓   点击:(47)  评论:(0)  加入收藏
我的手机我做主,如何为Android手机应用换图标?
作为一名Android用户,你是否曾经为自己的手机桌面感到单调而乏味?虽然Android系统的桌面定制性已经非常强大,但有时候我们还是希望能够在细节上做出一些改变,尤其是对于那些每天...【详细内容】
2023-12-10  王炸无人机飞手    Tags:Android   点击:(62)  评论:(0)  加入收藏
安卓手机低电量模式有什么影响,性能与续航的权衡
在现代社会,手机已经成为我们日常生活中不可或缺的一部分。然而,随着手机功能的不断增加,电池的续航时间逐渐成为一个令人关注的问题。为了解决这一问题,安卓手机提供了一种称为...【详细内容】
2023-11-11  PC小教程    Tags:安卓手机   点击:(111)  评论:(0)  加入收藏
谷歌安卓 14 减少后台活动,可提高性能、延长续航
IT之家 10 月 25 日消息,在最新一期 #TheAndroidShow 活动,谷歌的安卓工程副总裁 Dave Burke 表示,安卓 14 系统更改了处理器和内存的工作负载平衡机制,不仅可以提升设备性能,而...【详细内容】
2023-10-25    IT之家  Tags:安卓   点击:(116)  评论:(0)  加入收藏
站内最新
站内热门
站内头条