android基础知识Android性能优化

Android 性能篇 - 内存优化二(精华篇)

2019-09-25  本文已影响0人  trycatchx

本篇文章已授权微信公众号 guolin_blog(郭霖)独家发布

一、内存的划分

二、java 内存优化

三、native 内存优化

四、graphics 内存优化

五、stack 内存优化

六、code 内存优化

七、other 内存优化

一、内存的划分

分类的标准 procrank dumpsys meminfo android studio profile JMM
划分区域 VSS/RSS/PSS/USS Native/Dalvik/Cursor/Ashmem.. java/native/graphics/stack/code/other 方法区/java堆/java栈/native栈/程序计数器
1、procrank 是一个 adb 的 root 指令,可以查询内存的划分:
procrank

那么最值得关注的是 PSSUSS,我们可以用 dumpsys meminfo 来查询(无需 root 权限)

2、dumpsys meminfo 查询 pss 划分
dumpsys meminfo

重点字段解读:

通过上面图片可得 launcher app 占用的内存是 250M,大部分内存在 Native Heapcodegraphics,那如何分析和解决,我们下面讲。

3、android studio profile 是 ide 提供出来的分类:
android studio profile
4、JMM 分类

java 栈、native 栈、程序计数器是线程私有
java 堆、方法区是线程共有的。

二、java 内存优化

Java 内存优化 内存泄漏 内存抖动 大内存对象使用
发生的场景 单例、匿名内部类、接口忘记释放 ... String拼接、循环内重复生成对象 ... HashMap、ArrayList ...

详细的理论可参考这篇文章

1、Java 检查泄漏 - LeakCanary 使用
1.1、 LeakCanary 结果分析

LeakCanary 可以检查 Activity Fragment View 界面的泄漏问题。通过接入 LeakCanary
接入库地址) 跑上 monkey 接着静等 java 内存泄漏的出现:

泄漏检查

通过上图可以知道 SearchActivityHistorySource.mContext 持有,HistorySource 是一个单例,然后最顶层的 Thread.contextClassLoader 就是 GC root(注意:静态变量不是 GC root),Thread.contextClassLoaderPathClassLoader 类,只要把 SearchActivitycontext 换成 Application 那就解决了。

1.2、Android 中 GC root 有哪些:

GC root 更多详情

1.3、LeakCanary 的核心原理:

小结:

那么LeakCanary 只能解决界面上的泄漏,其他内存上的优化是做不到的,譬如:线程池的泄漏,内存的抖动,大对象的滥用.. 那么就需要更为强大的工具 MAT

2、内存检测工具 MAT

MAT 是分析内存文件 hprof 的工具。(MAT 工具地址

2.1 、抓取步骤

跑几分钟 monkey 后,退回应用主界面,手动多次点击GC 按钮,把可回收的回收掉,为了剔除脏数据。通过 Android StudioProfile 把 内存文件 hprofdump 下来。

抓取步骤
2.2 、分析内存:

完成以上步骤之后的结果图

hprof 1566982559(1).png 1566988199(1).png 1566988599(1).jpg 1566989713(1).jpg

MAT 官方使用指导


小结

可先用LeakCanary 跑出明显的内存泄漏,再用MAT 检查整个应用的内存分布状况,去优化该优化的 Java 堆内存。

三、native 内存优化

native 内存优化 malloc_debug heapsnap DDMS
root权限 需要 需要 不需要
环境 python jni 需要使用sdk18 的 tools/ddms.bat(sdk 18之后就被剔除了)
1、malloc_debug 步骤
//查询所有内存
adb shell setprop wrap.packagename '"LIBC_DEBUG_MALLOC_OPTIONS=backtrace logwrapper"'

//查询内存泄漏
adb shell setprop wrap.packagename '"LIBC_DEBUG_MALLOC_OPTIONS=leak_track logwrapper"'

1567132646(1).jpg image.png image.png image.png
2、使用 python 分析
2.1、搭建环境
2.2、修改 python 代码
resByte = subprocess.check_output(["G:/AndroidNDK/android-ndk-r17/toolchains/aarch64-linux-android-4.9/prebuilt/windows-x86_64/bin/aarch64-linux-android-objdump", "-w", "-j", ".text", "-h", sofile])
p = subprocess.Popen(["G:/AndroidNDK/android-ndk-r17/toolchains/aarch64-linux-android-4.9/prebuilt/windows-x86_64/bin/aarch64-linux-android-addr2line", "-C", "-j", ".text", "-e", sofile, "-f"], stdout=subprocess.PIPE, stdin=subprocess.PIPE)

替换def __init__(self):函数中的部分代码,把下面代码:

if len(extra_args) != 1:
      print(self._usage)
      sys.exit(1)

替换为:

self.symboldir = "C:/Users/chaojiong.zhang/Documents/AndroidStudio/DeviceExplorer/xiaomi-mi_8-4b429b4"
extra_args.append("dump.txt")
def main():
sys.stdout = open("test.txt", "w")

 //...
sys.stdout.close()
3、malloc_debug 内存文件分析
3.1、字段解读
3.2、内存信息分析一
10285756  58.29%  99.95%       49     eac0b276 /system/lib/libhwui.so android::Bitmap::allocateHeapBitmap(SkBitmap*)

可以看得出来 allocateHeapBitmap方法占用了,10M 左右的内存,占总 native 内存 58.29%,占父帧 99.95% (意思是:A-> BA方法调用B方法,A方法总共占用了 10M,其中9.9M 是在B方法中申请的,那么 %PARENT 就是 99%),调用了49 次,动作发生在 libhwui.so 中的 android::Bitmap::allocateHeapBitmap方法。下面是 allocateHeapBitmap 被调用的流程:

BitmapFactory.decodeResource -> BitmapFactory.nativeDecodeStream ->BitmapFactory.cpp 中 nativeDecodeStream() -> doDecode() -> SkBitmap.tryAllocPixels() -> ... -> android::Bitmap::allocateHeapBitmap()
Bitmap.createBitmap -> nativeCreate() -> Bitmap.cpp 中的 nativeCreate() -> GraphicsJNI.cpp zhong de allocateJavaPixelRef() -> ... -> android::Bitmap::allocateHeapBitmap()

也就是说java层的 bitmap 创建都会跑到 allocateHeapBitmap 这个函数。那么上面这个占用了 10MallocateHeapBitmap,究竟是 java 层哪个类调用下来的,这个目前是无解(包括最近华为的方舟环境平台 DevEco 也不行),只能在 java 层去全盘查询了,哪些图片使用了较多的内存。

3.3、内存信息分析二
image.png
小结

native 内存目前无法很清晰的定位到对应的java 层代码,无解。只能看个大概,然后有目的性去排查某个类,或者某个模块。

四、graphics 内存优化

若应用没有自己接入 OpenGL/ GL surfaces/ GL textures开源库,来绘制图形,可不必理会。毕竟已经超出 android应用工程师的范围了。

五、stack 内存优化

1、解决栈溢出
1.1、死循环问题
1.2、递归问题
1.3、Intenet 问题
  1. 一般通过 static 持有需要传递的对象解决。
  2. 把跳转的页面写成 fragment ,数据可以不需要传递也可获取
  3. 通过EventBus RxBus(原理都是通过全局单例来传递)
  4. 通过 ObjectCache 把对象转成json 串,保存到本地,获取时候序列化为对象。
2、解决重复生成局部变量
2.1、避免在循环内重复生成局部变量:
    private void memoryShake() {
        ArrayList<Integer> shakes = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            Integer shake = new Integer(i);
            shakes.add(shake);
        }
    }
    
    private void memoryShake1() {
        ArrayList<Integer> shakes = new ArrayList<>();
        Integer shake;
        for (int i = 0; i < 100; i++) {
            shake = new Integer(i);
            shakes.add(shake);
        }
    }

memoryShake() 会在 循环内生成 100shake 局部变量 + 100 个局部变量的引用,
memoryShake1()会在 循环内生成 100shake 局部变量 + 1 个局部变量的引用,一个对象引用在 64bit 的环境是 8byte100*8 = 800 byte = 0.8KB

2.2、String 使用问题

循环内字符的拼接不要使用 + 符号,(使用 + 符号,编译成字节码后,循环内会生成StringBuilder 对象去拼接)。

正确应该使用StringBuffer (线程安全)或者 StringBuilder(线程不安全)。

六、code 内存优化

code 内存消耗主要是: so 库,dex ,ttf
以上三种文件都是要加载到运行内存才能被解析运行,所以它们的体积要算进自身的应用内存中。

七、other 内存优化

目前不清楚这部分是哪部分内存,无从下手,不过一般 other 的内存占用比例都是比较小,可不必理会。

上一篇 下一篇

猜你喜欢

热点阅读