最近在学习张绍文老师的《Android 开发高手课》。课后作业可不是一般的难,最近几天抽空练习了一下,结合老师给的步骤与完成的同学经验,完成了前五课的内容。

本系列其他内容:

Android 开发高手课 课后练习(6 ~ 8,12,17,19)

Android 开发高手课 课后练习(22,27,ASM)

Android 开发高手课 温故知新篇

整理总结了一下,分享出来。希望可以帮到一同学习的同学(当然希望大家尽量靠自己解决问题)。

Chapter01

例子里集成了Breakpad 来获取发生 native crash 时候的系统信息和线程堆栈信息。通过一个简单的 Native 崩溃捕获过程,完成 minidump 文件的生成和解析,在实践中加深对 Breakpad 工作机制的认识。

直接运行项目,按照README.md的步骤操作就行。

中间有个问题,老师提供的minidump_stackwalker 工具在macOS 10.14以上无法成功执行,因为没有libstdc++.6.dylib库,所以我就下载Breakpad源码重新编译了一遍。

使用 minidump_stackwalker 工具来根据minidump 文件生成堆栈跟踪log,得到的crashLog.txt文件如下:

Operating system: Android0.0.0 Linux 4.9.112-perf-gb92eddd #1 SMP PREEMPT Tue Jan 1 21:35:06 CST 2019 aarch64
CPU: arm64  // 注意点18 CPUsGPU: UNKNOWNCrash reason:  SIGSEGV /SEGV_MAPERR
Crash address: 0x0
Process uptime: not availableThread 0 (crashed)0  libcrash-lib.so + 0x600 // 注意点2x0 = 0x00000078e0ce8460    x1 = 0x0000007fd4000314x2 = 0x0000007fd40003b0    x3 = 0x00000078e0237134x4 = 0x0000007fd40005d0    x5 = 0x00000078dca14200x6 = 0x0000007fd4000160    x7 = 0x00000078c8987e18x8 = 0x0000000000000000    x9 = 0x0000000000000001x10 = 0x0000000000430000   x11 = 0x00000078e05ef688x12 = 0x00000079664ab050   x13 = 0x0ad046ab5a65bfdfx14 = 0x000000796650c000   x15 = 0xffffffffffffffffx16 = 0x00000078c83defe8   x17 = 0x00000078c83ce5ecx18 = 0x0000000000000001   x19 = 0x00000078e0c14c00x20 = 0x0000000000000000   x21 = 0x00000078e0c14c00x22 = 0x0000007fd40005e0   x23 = 0x00000078c89fa661x24 = 0x0000000000000004   x25 = 0x00000079666cc5e0x26 = 0x00000078e0c14ca0   x27 = 0x0000000000000001x28 = 0x0000007fd4000310    fp = 0x0000007fd40002e0lr = 0x00000078c83ce624    sp = 0x0000007fd40002c0pc = 0x00000078c83ce600Found by: given as instruction pointer in context1  libcrash-lib.so + 0x620fp = 0x0000007fd4000310    lr = 0x00000078e051c7e4sp = 0x0000007fd40002f0    pc = 0x00000078c83ce624Found by: previous frame's frame pointer2  libart.so + 0x55f7e0fp = 0x130c0cf800000001    lr = 0x00000079666cc5e0sp = 0x0000007fd4000320    pc = 0x00000078e051c7e4Found by: previous frame's frame pointer
......

下来是符号解析,可以使用 ndk 中提供的addr2line来根据地址进行一个符号反解的过程,该工具在 $NDK_HOME/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-addr2line
注意:此处要注意一下平台,如果是 arm64位的 so,解析是需要使用 aarch64-linux-android-4.9下的工具链。

因为我的是arm64位的 so。所以使用aarch64-linux-android-4.9libcrash-lib.soapp/build/intermediates/cmake/debug/obj/arm64-v8a下,0x600为错误位置符号。

aarch64-linux-android-addr2line -f -C -e libcrash-lib.so 0x600

输出结果如下

Crash()
/Users/weilu/Downloads/Chapter01-master/sample/.externalNativeBuild/cmake/debug/arm64-v8a/../../../../src/main/cpp/crash.cpp:10

可以看到输出结果与下图错误位置一致(第十行)。

最后,我将编译的minidump_stackwalker文件,以及上述用到dmplibcrash-lib.socrashLog.txt文件上传到了csdn,有兴趣的下载尝试一下。

Chapter02

该例子主要演示了如何通过关闭FinalizerWatchdogDaemon来减少TimeoutException的触发

在我的上一篇博客:安卓开发中遇到的奇奇怪怪的问题(三)中有说明,就不重复赘述了。

Chapter03

项目使用了 inline hook 来拦截内存对象分配时候的 RecordAllocation 函数,通过拦截该接口可以快速获取到当时分配对象的类名和分配的内存大小。
在初始化的时候我们设置了一个分配对象数量的最大值,如果从 start 开始对象分配数量超过最大值就会触发内存 dump,然后清空 alloc 对象列表,重新计算。该功能和 Android Studio 里的 Allocation Tracker 类似,只不过可以在代码级别更细粒度的进行控制。可以精确到方法级别。

项目直接跑起来后,点击开始记录,然后点击5次生成1000对象按钮。生成对象代码如下:

 for (int i = 0; i < 1000; i++) {Message msg = new Message();msg.what = i;}

因为代码从点击开始记录开始,触发到5000的数据就 dump 到文件中,点击5次后就会在sdcard/crashDump下生成一个时间戳命名的文件。项目根目录下调用命令:

 java -jar tools/DumpPrinter-1.0.jar dump文件路径 > dump_log.txt

然后就可以在 dump_log.txt中看到解析出来的数据:

Found 5000 records:
....
tid=4509 android.graphics.drawable.RippleForeground (112 bytes)android.graphics.drawable.RippleDrawable.tryRippleEnter (RippleDrawable.java:569)android.graphics.drawable.RippleDrawable.setRippleActive (RippleDrawable.java:276)android.graphics.drawable.RippleDrawable.onStateChange (RippleDrawable.java:266)android.graphics.drawable.Drawable.setState (Drawable.java:778)android.view.View.drawableStateChanged (View.java:21137)android.widget.TextView.drawableStateChanged (TextView.java:5289)android.support.v7.widget.AppCompatButton.drawableStateChanged (AppCompatButton.java:155)android.view.View.refreshDrawableState (View.java:21214)android.view.View.setPressed (View.java:10583)android.view.View.setPressed (View.java:10561)android.view.View.onTouchEvent (View.java:13865)android.widget.TextView.onTouchEvent (TextView.java:10070)android.view.View.dispatchTouchEvent (View.java:12533)android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:3032)android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:2662)android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:3032)
tid=4515 int[] (104 bytes)
tid=4509 android.os.BaseLooper$MessageMonitorInfo (88 bytes)android.os.Message.<init> (Message.java:123)com.dodola.alloctrack.MainActivity$4.onClick (MainActivity.java:70)android.view.View.performClick (View.java:6614)android.view.View.performClickInternal (View.java:6591)android.view.View.access$3100 (View.java:786)android.view.View$PerformClick.run (View.java:25948)android.os.Handler.handleCallback (Handler.java:873)android.os.Handler.dispatchMessage (Handler.java:99)android.os.Looper.loop (Looper.java:201)android.app.ActivityThread.main (ActivityThread.java:6806)java.lang.reflect.Method.invoke (Native method)com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:547)com.android.internal.os.ZygoteInit.main (ZygoteInit.java:873)
......

我们用Android Profiler查找一个Message对象对比一下,一模一样:

简单看一下HOOK代码:

void hookFunc() {LOGI("start hookFunc");void *handle = ndk_dlopen("libart.so", RTLD_LAZY | RTLD_GLOBAL);if (!handle) {LOGE("libart.so open fail");return;}void *hookRecordAllocation26 = ndk_dlsym(handle,"_ZN3art2gc20AllocRecordObjectMap16RecordAllocationEPNS_6ThreadEPNS_6ObjPtrINS_6mirror6ObjectEEEj");void *hookRecordAllocation24 = ndk_dlsym(handle,"_ZN3art2gc20AllocRecordObjectMap16RecordAllocationEPNS_6ThreadEPPNS_6mirror6ObjectEj");void *hookRecordAllocation23 = ndk_dlsym(handle,"_ZN3art3Dbg16RecordAllocationEPNS_6ThreadEPNS_6mirror5ClassEj");void *hookRecordAllocation22 = ndk_dlsym(handle,"_ZN3art3Dbg16RecordAllocationEPNS_6mirror5ClassEj");if (hookRecordAllocation26 != nullptr) {LOGI("Finish get symbol26");MSHookFunction(hookRecordAllocation26, (void *) &newArtRecordAllocation26,(void **) &oldArtRecordAllocation26);} else if (hookRecordAllocation24 != nullptr) {LOGI("Finish get symbol24");MSHookFunction(hookRecordAllocation26, (void *) &newArtRecordAllocation26,(void **) &oldArtRecordAllocation26);} else if (hookRecordAllocation23 != NULL) {LOGI("Finish get symbol23");MSHookFunction(hookRecordAllocation23, (void *) &newArtRecordAllocation23,(void **) &oldArtRecordAllocation23);} else {LOGI("Finish get symbol22");if (hookRecordAllocation22 == NULL) {LOGI("error find hookRecordAllocation22");return;} else {MSHookFunction(hookRecordAllocation22, (void *) &newArtRecordAllocation22,(void **) &oldArtRecordAllocation22);}}dlclose(handle);
}

使用了 inline hook 方案 Substrate来拦截内存对象分配时候libart.soRecordAllocation函数。首先如果我们要 hook 一个函数,需要知道这个函数的地址。我们也看到了代码中这个地址判断了四种不同系统。这里有一个网页版的解析工具可以快速获取。下面以8.0为例。

我在8.0的源码中找到了对应的方法:

7.0方法就明显不同:

我也同时参看了9.0的代码,发现没有变化,所以我的测试机是9.0的也没有问题。

Hook新内存对象分配处理代码:

static bool newArtRecordAllocationDoing24(Class *type, size_t byte_count) {allocObjectCount++;//根据 class 获取类名char *typeName = GetDescriptor(type, &a);//达到 maxif (allocObjectCount > setAllocRecordMax) {CMyLock lock(g_Lock);//此处需要 loc 因为对象分配的时候不知道在哪个线程,不 lock 会导致重复 dumpallocObjectCount = 0;// dump alloc 里的对象转换成 byte 数据jbyteArray allocData = getARTAllocationData();// 将alloc数据写入文件SaveAllocationData saveData{allocData};saveARTAllocationData(saveData);resetARTAllocRecord();LOGI("===========CLEAR ALLOC MAPS=============");lock.Unlock();}return true;
}

Chapter04

通过分析内存文件hprof快速判断内存中是否存在重复的图片,并且将这些重复图片的PNG、堆栈等信息输出。

1.首先是获取我们需要分析的hprof文件,我们加载两张相同的图片:

 Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.mipmap.test);Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.mipmap.test);imageView1.setImageBitmap(bitmap1);imageView2.setImageBitmap(bitmap2);

生成hprof文件

 // 手动触发GCRuntime.getRuntime().gc();System.runFinalization();Debug.dumpHprofData(file.getAbsolutePath());

2.下来就是利用haha库进行文件分析的核心代码:

// 打开hprof文件
final HeapSnapshot heapSnapshot = new HeapSnapshot(hprofFile);
// 获得snapshot
final Snapshot snapshot = heapSnapshot.getSnapshot();
// 获得Bitmap Class
final ClassObj bitmapClass = snapshot.findClass("android.graphics.Bitmap");
// 获得heap, 只需要分析app和default heap即可
Collection<Heap> heaps = snapshot.getHeaps();for (Heap heap : heaps) {// 只需要分析app和default heap即可if (!heap.getName().equals("app") && !heap.getName().equals("default")) {continue;}for (ClassObj clazz : bitmapClasses) {//从heap中获得所有的Bitmap实例List<Instance> bitmapInstances = clazz.getHeapInstances(heap.getId());//从Bitmap实例中获得buffer数组,宽高信息等。ArrayInstance buffer = HahaHelper.fieldValue(((ClassInstance) bitmapInstance).getValues(), "mBuffer");int bitmapHeight = fieldValue(bitmapInstance, "mHeight");int bitmapWidth = fieldValue(bitmapInstance, "mWidth");// 引用链信息while (bitmapInstance.getNextInstanceToGcRoot() != null) {print(instance.getNextInstanceToGcRoot());instance = instance.getNextInstanceToGcRoot();}// 根据hashcode来进行重复判断}
}

最终的输出结果:

我们用Studio打开hprof文件对比一下:

可以看到信息是一摸一样的。对于更优处理引用链的信息,可以参看的leakcanary源码的实现。

我已经将上面的代码打成jar包,可以直接调用:

//调用方法:
java -jar tools/DuplicatedBitmapAnalyzer-1.0.jar hprof文件路径

详细的代码我提交到了Github,供大家参考。

Chapter05

尝试模仿ProcessCpuTracker.java拿到一段时间内各个线程的耗时占比

 usage: CPU usage 5000ms(from 23:23:33.000 to 23:23:38.000):System TOTAL: 2.1% user + 16% kernel + 9.2% iowait + 0.2% irq + 0.1% softirq + 72% idleCPU Core: 8Load Average: 8.74 / 7.74 / 7.36Process:com.sample.app 50% 23468/com.sample.app(S): 11% user + 38% kernel faults:4965Threads:43% 23493/singleThread(R): 6.5% user + 36% kernel faults:30943.2% 23485/RenderThread(S): 2.1% user + 1% kernel faults:3290.3% 23468/.sample.app(S): 0.3% user + 0% kernel faults:60.3% 23479/HeapTaskDaemon(S): 0.3% user + 0% kernel faults:982...

因为了解linux不多,所以看这个有点懵逼。好在课代表孙鹏飞同学解答了相关问题,看懂了上面信息。同时学习到了一些linux知识。

 private void testIO() {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {File f = new File(getFilesDir(), "aee.txt");FileOutputStream fos = new FileOutputStream(f);byte[] data = new byte[1024 * 4 * 3000];// 此处分配一个 12mb 大小的 byte 数组for (int i = 0; i < 30; i++) {// 由于 IO cache 机制的原因所以此处写入多次 cache,触发 dirty writeback 到磁盘中Arrays.fill(data, (byte) i);// 当执行到此处的时候产生 minor fault,并且产生 User cpu useagefos.write(data);}fos.flush();fos.close();}});thread.setName("SingleThread");thread.start();}

上述代码就是导致的问题罪魁祸首,这种密集io操作集中在SingleThread线程中处理,导致发生了3094次faults,36% kernel。完全没有很好利用到8核CPU。

最后,通过检测CPU的使用率,可以更好的避免卡顿现象,防止ANR的发生。


前前后后用了两三天的时间,远远没有当初想的顺利,感觉身体被掏空。中间爬了不少坑,虽然没有太深入实现代码,但是中间的体验过程也是收获不小。总不能因为难就放弃了,先做到力所能及的部分,让自己动起来!

参考

  • 练习Sample跑起来 | 热点问题答疑第1期

  • 练习Sample跑起来 | 热点问题答疑第2期

Android 开发高手课 课后练习(1 ~ 5)相关推荐

  1. Android 开发高手课 温故知新篇

    首先推荐大家先阅读<Android 开发高手课>和我之前的三篇练习: Android 开发高手课 课后练习(1 ~ 5) Android 开发高手课 课后练习(6 ~ 8,12,17,19 ...

  2. Android开发高手课笔记--如何打造高质量应用?

    前言 学习<Android开发高手课>也有一段时间了,确实对技术的提高很有帮助,可以开拓自己的视野.想利用这一段时间好好整理下学习笔记,以便回顾.由于版权问题,后续笔记不再公开,感兴趣可扫 ...

  3. 学习Android开发高手课

    在极客时间推出<Android开发高手课>的时候就开始跟着学了,但是发现课程里的东西很难,很多知识点都不会,跟着跟着就没再继续下去,直到有一天看到这门课程全部已经结束了,心中无限感慨.自己 ...

  4. 《Android开发高手课》学习笔记

    最近在学习张绍文老师的<Android开发高手课>课程,学习到了很多的干货,特别是在处理问题的策略和知识的广度方面给了我很多的启发,对未来的学习也提供了方向. 目前,技术的发展有两个趋势. ...

  5. 张绍文android开发高手课读书笔记1

    本系列博文 基于是前微信高级工程师张绍文专栏 <Android开发高手课>的读书笔记. 文章所写内容是本人读完的感悟,需要原文的朋友请自行购买. Android的开发前景如何 移动互联网发 ...

  6. Android开发高手课笔记 - 01 崩溃优化(上):关于“崩溃”那点事

    Android 的两种崩溃 Java 崩溃就是在 Java 代码中,出现了未捕获的异常,导致程序异常退出 Native 崩溃一般都是因为在 Native 代码中访问非法地址,也可能是地址对齐出了问题, ...

  7. android开发高手课百度云盘,Android开发高手课NOTE

    内存优化 卡顿的原因 频繁 GC 造成卡顿.物理内存不足时系统会触发 low memory killer 机制,系统负载过高是造成卡顿的俩个原因. 除了频繁 GC 造成卡顿之外,物理内存不足时系统会触 ...

  8. android 优化启动事件,张绍文android开发高手课读书笔记4-启动优化篇

    启动优化篇 在说如何对启动时间进行优化之前,我们得先知道从用户点击应用图标开始,一共经历了那些阶段. 主要阶段如下图. image T0-T1阶段 这个阶段是手机系统解析点击事件,效应快慢跟手机性能有 ...

  9. android开发 解析 b5,张绍文android开发高手课读书笔记4-启动优化篇

    启动优化篇 在说如何对启动时间进行优化之前,我们得先知道从用户点击应用图标开始,一共经历了那些阶段. 主要阶段如下图. image T0-T1阶段 这个阶段是手机系统解析点击事件,效应快慢跟手机性能有 ...

最新文章

  1. 第十二届蓝桥杯大赛软件赛省赛 C/C++ 大学B组
  2. 科大星云诗社动态20210317
  3. matlab控工大作业,哈工大现代控制理论matlab极点配置作业
  4. 2017中国新型智慧城市创新50强在京发布
  5. (只需挨个复制粘贴命令即可部署)在Centos7下搭建文件服务器(VSFTPD)
  6. [sicily]【1001】 会议安排(田忌赛马类贪心)
  7. 侵犯软件著作权罪量刑标准
  8. gp3688写频线制作_摩托罗拉GP3688写频软件
  9. 回归(regression)——统计学习方法
  10. zabbix 添加 ROS 软路由监控 WinBox设置
  11. ajax跨域请求jsonp
  12. abb机器人工具坐标系设定方式_设定abb机器人工具坐标的方法
  13. 计算机职业素养论文1500字,职业素养论文1500字 [职业素养教育论文]
  14. Python:字符宽度相同的字体(等宽字体)
  15. HD AUDIO For XP SP3 声卡修正补丁下载
  16. 前端表白Html+css+js,表白源码,520和七夕告白,雪花爱心记录
  17. re.match()到底会返回什么?
  18. arcgis自动配置符号说明
  19. 九大背包问题专题--有依赖的背包问题(树形Dp结合)
  20. 前端小白初识CMD(dos窗口命令)

热门文章

  1. 『cocos2d-x』diamond hunter宝石猎手
  2. 【bitset乱搞】BZOJ3687 简单题
  3. 电商用户标签体系建设基础步骤
  4. BootStrap响应式项目实战之世界杯网页设计
  5. 计算机异常断电后无法启动,电脑突然断电后开不了机怎么办【解决方法】
  6. 演算法 - 分治法(Divide-and-Conquer)
  7. 有道云笔记的markdown编辑器如何通过mathtype来写公式
  8. python 随手记
  9. 网页背景动态线条 鼠标吸附动态线条效果的实现
  10. ESXi服务器CPU睿频检测