首先推荐大家先阅读《Android 开发高手课》和我之前的三篇练习:
Android 开发高手课 课后练习(1 ~ 5)
Android 开发高手课 课后练习(6 ~ 8,12,17,19)
Android 开发高手课 课后练习(22,27,ASM)
最近二刷了《Android 开发高手课》,对于老师提到的一些案例,自己实践了一下。分享给学习此专栏的大家:
这个是在第二课崩溃优化(下)中提到的一个问题。
当然,Google在Android 8.0修复了这个问题,其实就是捕获了一下异常:
try { mWM.addView(mView, mParams); trySendAccessibilityEvent(); } catch (WindowManager.BadTokenException e) { /* ignore */ } 复制代码
所以如果要避免这个问题,我们就要找到HOOK点。7.0 Toast源码里面有一个变量叫 mTN,它的类型为 handler,我们只需要代理它就可以实现捕获。
简单实现代码如下:
private void hook(Toast toast) { try { Field tn = Toast.class.getField("mTN"); tn.setAccessible(true); Field handler = tn.getType().getField("mHandler"); handler.setAccessible(true); Field callback = handler.getClass().getField("mCallback"); callback.setAccessible(true); // 替换 callback.set(handler, new NewCallback((Handler) handler.get(tn.get(toast)))); } catch (Exception e) { e.printStackTrace(); } } public class NewCallback implements Handler.Callback { private final Handler mHandler; public NewCallback(final Handler handler) { this.mHandler = handler; } @Override public boolean handleMessage(final Message msg) { try { this.mHandler.handleMessage(msg); } catch (final RuntimeException e) {} return true; } } 复制代码
在前一阵didi开源的 booster
中也有对此问题的 修复 ,看了后我觉得考虑的更加完善。有兴趣的可以看看。
当然了, Toast
的小问题还有不少,相关的开源修复方案也很多,具体可以参考这篇文章。
这个是在第八课启动优化(下)中提到的一个优化启动速度的方法。主要使用redex工具来实现,对redex用法不熟悉的,可以看我之前的文章。
adb shell ps | grep YOUR_APP_PACKAGE | awk '{print $2}' 复制代码
// 生成 adb shell am dumpheap YOUR_PID /data/local/tmp/SOMEDUMP.hprof // 获取 adb pull /data/local/tmp/SOMEDUMP.hprof YOUR_DIR_HERE 复制代码
python redex/tools/hprof/dump_classes_from_hprof.py --hprof SOMEDUMP.hprof > list_of_classes.txt 复制代码
我在这里遇到了些问题,首先这个python脚本只支持python2,同时需要安装 pip、enum、enum34。否则会提示类似 pip command not found
这样的错误。
pip是python的包管理工具,在Python2.7的安装包中,easy_install.py是默认安装的,而pip需要我们手动安装
安装方法如下:
sudo easy_install pip sudo pip install enum sudo pip install enum34 复制代码
还有这个脚本 不支持8.0以上 的设备生成的 DUMP.hprof
文件。
如果想获取8.0设备的类加载顺序,可以参考老师文中复写 ClassLoader
的方案。
以上完成后我们就获取到了一个 list_of_classes.txt
文件,大致格式如下:
com/bumptech/glide/load/resource/bitmap/BitmapResource.class java/lang/Integer.class android/graphics/Bitmap.class libcore/util/NativeAllocationRegistry.class java/lang/ref/FinalizerReference$Sentinel.class java/lang/ref/FinalizerReference.class libcore/util/NativeAllocationRegistry$CleanerThunk.class sun/misc/Cleaner.class libcore/util/NativeAllocationRegistry$CleanerRunner.class android/graphics/Canvas.class android/graphics/Paint.class android/graphics/BitmapShader.class android/graphics/RectF.class java/util/TreeMap$TreeMapEntry.class okhttp3/Request$Builder.class okhttp3/Headers$Builder.class java/util/ArrayList.class okhttp3/HttpUrl$Builder.class java/lang/String.class java/lang/StringBuffer.class android/icu/impl/ReplaceableUCharacterIterator.class android/icu/text/ReplaceableString.class okhttp3/HttpUrl.class java/util/Collections$UnmodifiableRandomAccessList.class okio/Buffer.class java/lang/StringBuilder.class java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1.class java/util/HashMap$EntryIterator.class java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry.class okhttp3/Request.class okhttp3/Headers.class com/bumptech/glide/load/resource/bitmap/LazyBitmapDrawableResource.class ... 复制代码
redex --sign -s test.jks -a key0 -p 111111 -c interdex.config -P proguard-rules.pro -o app_1.apk app.apk 复制代码
其中 interdex.config
文件配置如下:
{ "redex" : { "passes" : [ "InterDexPass" ] }, "coldstart_classes" : "list_of_classes.txt" } 复制代码
我们可以使用 010 Editor
来查看我们的前后对比。
观察第二张图可以看到,就是我们 list_of_classes.txt
文件中的顺序。(Interdex Pass将忽略它在apk中找不到相应的类)
其实,Google在Android 8.0的ART优化中也有引用一个叫dexlayout的来实现类和方法的重排,大家可以了解一下。
在第十三课存储优化(中)中,老师提到文件遍历在 API 26 之后建议使用 FileVisitor
替代 ListFiles
,因为文件遍历的耗时跟文件夹下的文件数量有关。老师文中说道:“曾经我们出现过一次 bug 导致某个文件夹下面有几万个文件,在遍历这个文件夹时,用户手机直接重启了。”
一般的文件夹删除方法:
public static void deleteDir(String path) { File dir = new File(path); if (dir == null || !dir.exists() || !dir.isDirectory()){ return; } for (File file : dir.listFiles()) { if (file.isFile()){ file.delete(); }else if (file.isDirectory()){ deleteDir(path); } } dir.delete(); } 复制代码
就是利用 dir.listFiles()
方法递归删除子文件。FileVisitor是Java7的新特性之一,在Android 8.0开始支持。所以完善后的删除文件方法如下:
public static void deleteDir(String path) throws IOException { File dir = new File(path); if (dir == null || !dir.exists() || !dir.isDirectory()){ return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { Files.walkFileTree(Paths.get(path), new SimpleFileVisitor<Path>(){ @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); //表示继续遍历 return FileVisitResult.CONTINUE; } /** * 访问某个path失败时调用 */ @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { //如果目录的迭代完成而没有错误,有时也会返回null if (exc == null) { Files.delete(file); return FileVisitResult.CONTINUE; } else { throw exc; } } }); }else { for (File file : dir.listFiles()) { if (file.isFile()){ file.delete(); }else if (file.isDirectory()){ deleteDir(path); } } } dir.delete(); } 复制代码
第一次听到 PrecomputedText
就是在二十一课UI优化(下)中,它可以异步进行 measure 和 layout。我也写了一篇博客,有兴趣可以点击查看。
Litho 如我前面提到的 PrecomputedText 一样,把 measure 和 layout 都放到了后台线程,只留下了必须要在主线程完成的 draw,这大大降低了 U线程的负载。
具体的原理我就不介绍了,大家可以参看美团技术团队前一阵分享的文章: 基本功 | Litho的使用及原理剖析 。我主要说说我的使用过程。
我用 Litho
的 RecyclerCollectionComponent
实现了和我在 PrecomputedText
博客中相同的例子。代码很简单,我就不贴出来了,有兴趣的可以查看 Github 。我们直接看看效果:
帧数效果来说略逊色 PrecomputedText
,但是 Litho
支持的组件更多,不单单是 TextView
,还有 Image
、 EditView
等。并且它还能实现界面扁平化、占用更少的内存的优点。据说给美团App带来了不错的性能提升,官方文档也很详细,所以还是值得大家尝试的。
好了,暂时就分享这么多了。如果对你有启发帮助,希望可以 点赞支持 !